init
This commit is contained in:
0
backend-django/backend/__init__.py
Normal file
0
backend-django/backend/__init__.py
Normal file
380
backend-django/backend/admin.py
Normal file
380
backend-django/backend/admin.py
Normal file
@@ -0,0 +1,380 @@
|
||||
"""
|
||||
La classe Admin est la représentation d’un modèle dans l’interface d’administration
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.admin import helpers
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||
from django.db.models import (ManyToManyField, ManyToManyRel, ManyToOneRel,
|
||||
OneToOneRel)
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
|
||||
from backend.form import GroupeFeForm, PcpFeGroupeForm, TauxArmementForm
|
||||
|
||||
from .models import (FMOB, Administre, Calcul, Competence, CustomUser, Decision,
|
||||
Filiere, FormationEmploi, GroupeFe, Marque, MarquesGroupe,
|
||||
Notation, PcpFeGroupe, Poste, PreferencesListe,
|
||||
SousVivier, SousVivierAssociation, ZoneGeographique,
|
||||
RefGest, RefOrg, RefSvFil, FichiersExporte)
|
||||
|
||||
|
||||
def is_not_many_to(field):
|
||||
""" Vérifie qu'il ne s'agit ni d'un champ many-to-one ni d'un champ many-to-many """
|
||||
return not isinstance(field, ManyToManyField) and not isinstance(field, ManyToManyRel) and (not isinstance(field, ManyToOneRel) or isinstance(field, OneToOneRel))
|
||||
|
||||
def default_list_display(modelType):
|
||||
""" Retourne une liste de champs pour l'attribut list_display """
|
||||
pk = modelType._meta.pk.name
|
||||
fields = [field.name for field in modelType._meta.get_fields() if field.name != pk and is_not_many_to(field)]
|
||||
fields.insert(0, pk)
|
||||
return fields
|
||||
|
||||
|
||||
class AdministreAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section des administrés dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs des administrés qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(Administre)
|
||||
|
||||
|
||||
class CalculAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section des administrés dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs des administrés qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(Calcul)
|
||||
|
||||
|
||||
class DecisionAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section des décisions dans la page d'admin du projet."""
|
||||
|
||||
list_display = default_list_display(Decision)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.list_display_links = None
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
|
||||
class NotationAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section notation dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs de notation qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(Notation)
|
||||
|
||||
|
||||
class PosteAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section des postes dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs des postes qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(Poste)
|
||||
|
||||
class ZoneGeographiqueAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section des postes dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs des postes qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(ZoneGeographique)
|
||||
|
||||
|
||||
class PreferencesListeAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section de preferences liste dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs de preferences liste qui doivent être affichés dans cette section dans la page admin
|
||||
"""
|
||||
list_display = default_list_display(PreferencesListe)
|
||||
|
||||
|
||||
class FMOBAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section de FMOB dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs de FMOB qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(FMOB)
|
||||
|
||||
|
||||
class MarqueAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section de Marque dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs de la talbe Marque qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(Marque)
|
||||
|
||||
|
||||
class FiliereAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section de Filiere dans la page d'admin du projet.
|
||||
list_display va spécifier les champs f_code et domaine de la talbe Filiere qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(Filiere)
|
||||
|
||||
|
||||
class SousVivierAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section de sous vivier dans la page d'admin du projet.
|
||||
list_display va spécifier les champs sv_id, gestionnaire et sv_libelle de la talbe sous vivier qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(SousVivier)
|
||||
|
||||
|
||||
class RefGestAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section de référentiels gestionnaires dans la page d'admin du projet.
|
||||
list_display va spécifier les champs de la talbe référentiel gestionnaire qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(RefGest)
|
||||
|
||||
|
||||
class RefOrgAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section de référentiels organiques dans la page d'admin du projet.
|
||||
list_display va spécifier les champs de la talbe referentiel organique qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(RefOrg)
|
||||
|
||||
|
||||
class RefSvFilAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section de référentiels sous-vivier filiere dans la page d'admin du projet.
|
||||
list_display va spécifier les champs de la talbe referentiel sous-vivier filiere qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(RefSvFil)
|
||||
|
||||
|
||||
class FichiersExporteAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section des fichiers exportés dans la page d'admin du projet.
|
||||
list_display va spécifier les champs id, nom_fichier et date_export de la talbe des fichiers exportés qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(FichiersExporte)
|
||||
|
||||
|
||||
# class SousVivierAssociationAdmin(admin.ModelAdmin):
|
||||
# """Cette classe est dédiée à la section de SousVivierAssociation dans la page d'admin du projet.
|
||||
# list_display va spécifier tous les champs de la talbe SousVivierAssociation qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
# """
|
||||
# list_display = default_list_display(SousVivierAssociation)
|
||||
|
||||
|
||||
class MarquesGroupeAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section de MarquesGroupe dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs de la talbe MarquesGroupe qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(MarquesGroupe)
|
||||
|
||||
|
||||
class CustomUserCreationForm(UserCreationForm):
|
||||
"""Cette classe est dédiée à la section de creation d'un utilisateur dans la page d'admin du projet.
|
||||
|
||||
"""
|
||||
class Meta(UserCreationForm.Meta):
|
||||
model = CustomUser
|
||||
fields = UserCreationForm.Meta.fields + ('id', 'grade')
|
||||
|
||||
|
||||
class CustomUserChangeForm(UserChangeForm):
|
||||
"""Cette classe est dédiée à la section de changement d'un utilisateur dans la page d'admin du projet.
|
||||
|
||||
"""
|
||||
|
||||
class Meta(UserChangeForm.Meta):
|
||||
model = CustomUser
|
||||
fields = UserCreationForm.Meta.fields + ('id', 'grade')
|
||||
|
||||
|
||||
class CustomUserAdmin(BaseUserAdmin):
|
||||
"""Cette classe est dédiée à la section des utilisateurs dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs de la talbe CustomUser qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
model = CustomUser
|
||||
form = CustomUserChangeForm
|
||||
add_form = CustomUserCreationForm
|
||||
list_display = BaseUserAdmin.list_display + ('id', 'grade')
|
||||
fieldsets = BaseUserAdmin.fieldsets + (
|
||||
('Gestionnaire', {
|
||||
'fields': ('id', 'grade'),
|
||||
}),
|
||||
)
|
||||
add_fieldsets = BaseUserAdmin.add_fieldsets + (
|
||||
('Gestionnaire', {
|
||||
'fields': ('id', 'grade'),
|
||||
}),
|
||||
)
|
||||
# list_display = [field.name for field in CustomUser._meta.get_fields() if not isinstance(field, ManyToManyRel)
|
||||
# and not field.name in ['logentry', 'sousvivier']]
|
||||
|
||||
|
||||
class CompetenceAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section des competences dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs de la talbe Competence qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = default_list_display(Competence)
|
||||
|
||||
|
||||
class FormationEmploiAdmin(admin.ModelAdmin):
|
||||
"""Cette classe est dédiée à la section Formation Emploi dans la page d'admin du projet.
|
||||
list_display va spécifier tous les champs de la talbe FE qui doivent être affichés dans cette section dans la page admin.
|
||||
|
||||
"""
|
||||
list_display = ['fe_code', 'fe_libelle', 'fe_garnison_lieu', 'fe_code_postal', 'groupe_fe', 'fe_taux_armement_cible_mdr',
|
||||
'fe_taux_armement_cible_off', 'fe_taux_armement_cible_soff', 'get_mere', 'zone_defense']
|
||||
list_filter = ('groupe_fe__groupe_fe_nom', 'fe_garnison_lieu')
|
||||
actions = ['modification_taux_armement_mdr', 'modification_taux_armement_off', 'modification_taux_armement_soff', 'attribution_groupe']
|
||||
search_fields = ['fe_code']
|
||||
|
||||
@admin.display(description='FE mère')
|
||||
def get_mere(self, obj):
|
||||
return obj.mere.pk if obj.mere else None
|
||||
|
||||
# Fonctions d'actions supplémentaires
|
||||
@admin.action(description="Changer le taux d'armement pour la catégorie MDR")
|
||||
def modification_taux_armement_mdr(self, request, queryset):
|
||||
"""Fonction dédiée à la modification du taux d'armement mdr dans la section Formation Emploi de la page d'administration.
|
||||
|
||||
"""
|
||||
if 'apply' in request.POST:
|
||||
taux = int(request.POST['taux_armement_cible'])
|
||||
for fe_id in queryset.values_list('fe_code'):
|
||||
fe = FormationEmploi.objects.get(fe_code=fe_id[0])
|
||||
fe.fe_taux_armement_cible_mdr = taux
|
||||
new_nb_reevalue_mdr = np.ceil(0.01 * taux * fe.fe_nb_poste_reo_mdr)
|
||||
new_nb_vacant_mdr = new_nb_reevalue_mdr - fe.fe_nb_poste_occupe_mdr
|
||||
fe.fe_nb_poste_reevalue_mdr = new_nb_reevalue_mdr
|
||||
fe.fe_nb_poste_vacant_mdr = new_nb_vacant_mdr
|
||||
fe.save(update_fields=['fe_taux_armement_cible_mdr', 'fe_nb_poste_reevalue_mdr', 'fe_nb_poste_vacant_mdr'])
|
||||
messages.success(request, '{0} FEs ont été mises à jour'.format(queryset.count()))
|
||||
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
form = TauxArmementForm(initial={'_selected_action': queryset.values_list('fe_code', flat=True)})
|
||||
|
||||
return render(request,
|
||||
'admin/taux_cible_mdr.html',
|
||||
{'form': form, 'formations_emplois': queryset, 'do_action': 'modification_taux_armement_mdr', 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME})
|
||||
|
||||
modification_taux_armement_mdr.short_description = "Modification du taux d'armement cible pour la categorie MDR"
|
||||
|
||||
@admin.action(description="Changer le taux d'armement pour la catégorie OFF")
|
||||
def modification_taux_armement_off(self, request, queryset):
|
||||
"""Fonction dédiée à la modification du taux d'armement off dans la section Formation Emploi de la page d'administration.
|
||||
|
||||
"""
|
||||
if 'apply' in request.POST:
|
||||
taux = int(request.POST['taux_armement_cible'])
|
||||
for fe_id in queryset.values_list('fe_code'):
|
||||
fe = FormationEmploi.objects.get(fe_code=fe_id[0])
|
||||
fe.fe_taux_armement_cible_off = taux
|
||||
new_nb_reevalue_off = np.ceil(0.01 * taux * fe.fe_nb_poste_reo_off)
|
||||
new_nb_vacant_off = new_nb_reevalue_off - fe.fe_nb_poste_occupe_off
|
||||
fe.fe_nb_poste_reevalue_off = new_nb_reevalue_off
|
||||
fe.fe_nb_poste_vacant_off = new_nb_vacant_off
|
||||
fe.save(update_fields=['fe_taux_armement_cible_off', 'fe_nb_poste_reevalue_off', 'fe_nb_poste_vacant_off'])
|
||||
messages.success(request, '{0} FEs ont été mises à jour'.format(queryset.count()))
|
||||
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
form = TauxArmementForm(initial={'_selected_action': queryset.values_list('fe_code', flat=True)})
|
||||
|
||||
return render(request,
|
||||
'admin/taux_cible_off.html',
|
||||
{'form': form, 'formations_emplois': queryset, 'do_action': 'modification_taux_armement_off', 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME})
|
||||
|
||||
modification_taux_armement_off.short_description = "Modification du taux d'armement cible pour la categorie OFF"
|
||||
|
||||
@admin.action(description="Changer le taux d'armement pour la catégorie SOFF")
|
||||
def modification_taux_armement_soff(self, request, queryset):
|
||||
"""Fonction dédiée à la modification du taux d'armement soff dans la section Formation Emploi de la page d'administration.
|
||||
|
||||
"""
|
||||
if 'apply' in request.POST:
|
||||
taux = int(request.POST['taux_armement_cible'])
|
||||
for fe_id in queryset.values_list('fe_code'):
|
||||
fe = FormationEmploi.objects.get(fe_code=fe_id[0])
|
||||
fe.fe_taux_armement_cible_soff = taux
|
||||
new_nb_reevalue_soff = np.ceil(0.01 * taux * fe.fe_nb_poste_reo_soff)
|
||||
new_nb_vacant_soff = new_nb_reevalue_soff - fe.fe_nb_poste_occupe_soff
|
||||
fe.fe_nb_poste_reevalue_soff = new_nb_reevalue_soff
|
||||
fe.fe_nb_poste_vacant_soff = new_nb_vacant_soff
|
||||
fe.save(update_fields=['fe_taux_armement_cible_soff', 'fe_nb_poste_reevalue_soff', 'fe_nb_poste_vacant_soff'])
|
||||
messages.success(request, '{0} FEs ont été mises à jour'.format(queryset.count()))
|
||||
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
form = TauxArmementForm(initial={'_selected_action': queryset.values_list('fe_code', flat=True)})
|
||||
|
||||
return render(request,
|
||||
'admin/taux_cible_soff.html',
|
||||
{'form': form, 'formations_emplois': queryset, 'do_action': 'modification_taux_armement_soff', 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME})
|
||||
|
||||
modification_taux_armement_soff.short_description = "Modification du taux d'armement cible pour la categorie SOFF"
|
||||
|
||||
@admin.action(description='Attribuer les FE à un groupe')
|
||||
def attribution_groupe(modeladmin, request, queryset):
|
||||
"""Fonction dédiée à l'attribution du group pour la formation emploi dans la section Formation Emploi de la page d'administration.
|
||||
|
||||
"""
|
||||
if 'apply' in request.POST:
|
||||
nom = request.POST['nom_groupe_fe']
|
||||
try:
|
||||
groupe_fe = GroupeFe.objects.get(groupe_fe_nom=nom)
|
||||
except:
|
||||
groupe_fe = GroupeFe(groupe_fe_nom=nom)
|
||||
groupe_fe.save()
|
||||
|
||||
fe = FormationEmploi.objects.filter(fe_code__in=queryset.values_list('fe_code'))
|
||||
fe.update(groupe_fe_id=groupe_fe.pk)
|
||||
messages.success(request, '{0} FEs ont été mises à jour'.format(queryset.count()))
|
||||
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
form = GroupeFeForm(initial={'_selected_action': queryset.values_list('fe_code', flat=True)})
|
||||
|
||||
return render(request,
|
||||
'admin/groupe_fe.html',
|
||||
{'form': form, 'formations_emplois': queryset, 'do_action': 'attribution_groupe', 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME})
|
||||
|
||||
attribution_groupe.short_description = "Attribution des FE à un groupe"
|
||||
|
||||
|
||||
|
||||
# class PcpFeGroupeAdmin(admin.ModelAdmin):
|
||||
# """Cette classe est dédiée à l'association des groupes de FE aux gestionnaires PCP dans la page d'admin du projet.
|
||||
|
||||
# """
|
||||
# list_display = default_list_display(PcpFeGroupe)
|
||||
# form = PcpFeGroupeForm
|
||||
|
||||
|
||||
# Register your models here.
|
||||
|
||||
admin.site.register(Administre, AdministreAdmin)
|
||||
admin.site.register(Calcul, CalculAdmin)
|
||||
admin.site.register(Decision, DecisionAdmin)
|
||||
admin.site.register(FichiersExporte, FichiersExporteAdmin)
|
||||
admin.site.register(Poste, PosteAdmin)
|
||||
admin.site.register(SousVivier, SousVivierAdmin)
|
||||
admin.site.register(RefGest, RefGestAdmin)
|
||||
admin.site.register(RefOrg, RefOrgAdmin)
|
||||
admin.site.register(RefSvFil, RefSvFilAdmin)
|
||||
admin.site.register(Notation, NotationAdmin)
|
||||
admin.site.register(Filiere, FiliereAdmin)
|
||||
admin.site.register(FormationEmploi, FormationEmploiAdmin)
|
||||
# admin.site.register(SousVivierAssociation, SousVivierAssociationAdmin)
|
||||
admin.site.register(FMOB, FMOBAdmin)
|
||||
admin.site.register(Marque, MarqueAdmin)
|
||||
admin.site.register(MarquesGroupe, MarquesGroupeAdmin)
|
||||
admin.site.register(CustomUser, CustomUserAdmin)
|
||||
admin.site.register(Competence, CompetenceAdmin)
|
||||
# admin.site.register(PcpFeGroupe, PcpFeGroupeAdmin)
|
||||
admin.site.register(ZoneGeographique, ZoneGeographiqueAdmin)
|
||||
13
backend-django/backend/apps.py
Normal file
13
backend-django/backend/apps.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Ce fichier est créé pour inclure toute configuration de l'application.
|
||||
"""
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BackendConfig(AppConfig):
|
||||
"""Cette classe permet de configurer les variables du backend.
|
||||
|
||||
"""
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'backend'
|
||||
7
backend-django/backend/constants.py
Normal file
7
backend-django/backend/constants.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Ce fichier contient les constantes utilisées dans le backend.
|
||||
"""
|
||||
CATEGORIE_MDR = "MDR"
|
||||
CATEGORIE_SOFF = "SOFF"
|
||||
CATEGORIE_OFF = "OFF"
|
||||
CATEGORIE_BY_NF = {'1A': 'MDR', '1B': 'MDR', '1C': 'MDR', '2.': 'SOFF', '3A': 'SOFF', '3B': 'SOFF', '3B NFS': 'SOFF', '4.': 'OFF',
|
||||
'5A': 'OFF', '5B': 'OFF', '5C': 'OFF', '6A': 'OGX', '6B': 'OGX'}
|
||||
414
backend-django/backend/filters.py
Normal file
414
backend-django/backend/filters.py
Normal file
@@ -0,0 +1,414 @@
|
||||
"""
|
||||
Ce fichier contient des fonctions qui permet aux utilisateurs de filtrer un ensemble de questions sur la base des champs d'un modèle.
|
||||
"""
|
||||
|
||||
from django.db.models import Q
|
||||
from django_filters import rest_framework as filters
|
||||
from requests import request
|
||||
from rest_framework.filters import OrderingFilter
|
||||
|
||||
|
||||
from .models import Administre
|
||||
from .models import AvisPosteChoices as AvisPoste
|
||||
from .models import StatutPamChoices as StatutPam
|
||||
|
||||
from .models import (Decision, FormationEmploi, Poste, RefGest, RefOrg,
|
||||
RefSvFil, SpecifiqueChoices, Administres_Pams,
|
||||
Postes_Pams)
|
||||
from .utils.decorators import class_logger
|
||||
from .utils.logging import get_logger
|
||||
from .utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||
get_adm_filter_by_lvl4_codes_fil,
|
||||
get_adm_filter_by_lvl4_codes_future_pcp,
|
||||
get_adm_filter_by_lvl4_codes_pcp,
|
||||
get_lvl4_org_codes_by_any_code,
|
||||
get_poste_filter_by_lvl4_codes_fil,
|
||||
get_poste_filter_by_lvl4_codes_pcp,
|
||||
get_profile_summary, get_queryset_org_by_user,
|
||||
is_truthy)
|
||||
|
||||
# paramètre de requête pour restreindre par formation(s) d'emploi(s)
|
||||
REQ_PARAM_FE = 'formation_emploi'
|
||||
|
||||
# paramètre de requête pour restreindre à un seul profil
|
||||
REQ_PARAM_ONLY_PROFILE = 'profile'
|
||||
|
||||
# [poste] paramètre de requête pour restreindre par p_specifique
|
||||
REQ_PARAM_POSTE_SPECIFIQUE = 'p_specifique'
|
||||
|
||||
# paramètre de requête pour restreindre par sous-vivier
|
||||
REQ_PARAM_SOUS_VIVIER = 'sous_vivier'
|
||||
|
||||
@class_logger
|
||||
class AdministreFilter(filters.FilterSet):
|
||||
"""
|
||||
Cette classe contient tous les filtres qui peuvent être effectués sur la table des administrés.
|
||||
"""
|
||||
aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
|
||||
formation_emploi__in = filters.CharFilter(method='formation_emploi_filter')
|
||||
sous_vivier__in = filters.CharFilter(method='sous_vivier_filter')
|
||||
pam__in = filters.CharFilter(method='pam_filter')
|
||||
|
||||
class Meta:
|
||||
model = Administre
|
||||
fields = {
|
||||
Administre.Cols.REL_SOUS_VIVIER: ['exact'],
|
||||
}
|
||||
|
||||
def aide_a_la_decision_filter(self, queryset, name, value):
|
||||
statut = [StatutPam.A_ETUDIER, StatutPam.A_MUTER]
|
||||
return queryset.filter(Q(**{f'{Administre.Cols.REL_PAM_INTER}__a_statut_pam_annee__in' : statut})
|
||||
& Q(**{f'{Administre.Cols.REL_PAM_INTER}__{Administres_Pams.Cols.REL_DECISION}__de_decision__isnull' :True})
|
||||
& Q(**{f'{Administre.Cols.REL_PAM_INTER}__pam_id__exact' : value}))
|
||||
|
||||
def formation_emploi_filter(self, queryset, name, value):
|
||||
list_values = value.split(',')
|
||||
return queryset.filter(Q(**{f'{Administre.Cols.REL_FORMATION_EMPLOI}__fe_code__in': list_values}) \
|
||||
| Q(**{f'{Administre.Cols.REL_PAM_INTER}__{Administres_Pams.Cols.REL_DECISION}__{Decision.Cols.REL_POSTE}__{Poste.Cols.REL_FORMATION_EMPLOI}__fe_code__in': list_values}))
|
||||
|
||||
|
||||
def pam_filter(self, queryset, name, value):
|
||||
return queryset.filter(Q(**{f'{Administre.Cols.M2M_PAM}__pam_id__icontains': value}))
|
||||
|
||||
|
||||
@property
|
||||
def qs(self):
|
||||
qs = super().qs
|
||||
|
||||
|
||||
summary = get_profile_summary(self.request.user)
|
||||
|
||||
profiles = summary.profiles.get(KEY_READ, ())
|
||||
if not profiles:
|
||||
return qs.none()
|
||||
|
||||
only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)
|
||||
|
||||
org_code = summary.org_code
|
||||
is_fil = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
|
||||
is_pcp = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
|
||||
is_bvt = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
|
||||
if not org_code or (not is_fil and not is_pcp and not is_bvt):
|
||||
return qs.none()
|
||||
|
||||
codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
|
||||
adm_filter = None
|
||||
if is_fil:
|
||||
adm_filter = get_adm_filter_by_lvl4_codes_fil(codes_lvl4)
|
||||
if is_pcp:
|
||||
pcp_filter = get_adm_filter_by_lvl4_codes_pcp(codes_lvl4) \
|
||||
| get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)
|
||||
adm_filter = adm_filter | pcp_filter if adm_filter else pcp_filter
|
||||
if is_bvt:
|
||||
bvt_filter = Q(**{f'{Administre.Cols.REL_SOUS_VIVIER}': 'BVT'})
|
||||
adm_filter = adm_filter | bvt_filter if adm_filter else bvt_filter
|
||||
|
||||
return qs.filter(adm_filter)
|
||||
|
||||
|
||||
@class_logger
|
||||
class AdministrePAMFilter(filters.FilterSet):
|
||||
"""
|
||||
Cette classe contient tous les filtres qui peuvent être effectués sur la table des administrés.
|
||||
"""
|
||||
aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
|
||||
formation_emploi__in = filters.CharFilter(method='formation_emploi_filter')
|
||||
pam__in = filters.CharFilter(method='pam_filter')
|
||||
sous_vivier = filters.CharFilter(method='sous_vivier_filter')
|
||||
adm__in = filters.CharFilter(method='administre_filter')
|
||||
|
||||
class Meta:
|
||||
model = Administres_Pams
|
||||
fields = {
|
||||
}
|
||||
|
||||
def aide_a_la_decision_filter(self, queryset, name, value):
|
||||
statut = [StatutPam.A_ETUDIER, StatutPam.A_MUTER]
|
||||
return queryset.filter(Q(a_statut_pam_annee__in = statut)
|
||||
& Q(**{f'{Administres_Pams.Cols.REL_DECISION}__de_decision__isnull' :True}))
|
||||
|
||||
def administre_filter(self,queryset, name, value):
|
||||
values_list = value.split(',')
|
||||
if values_list:
|
||||
return queryset.filter(id__in=values_list)
|
||||
|
||||
def formation_emploi_filter(self, queryset, name, value):
|
||||
list_values = value.split(',')
|
||||
return queryset.filter(Q(**{f'{Administres_Pams.Cols.REL_ADMINISTRE}__{Administre.Cols.REL_FORMATION_EMPLOI}__fe_mere_credo__in': list_values}) \
|
||||
| Q(**{f'{Administres_Pams.Cols.REL_DECISION}__{Decision.Cols.REL_POSTE}__{Poste.Cols.REL_FORMATION_EMPLOI}__fe_mere_credo__in': list_values}))
|
||||
|
||||
def pam_filter(self, queryset, name, value):
|
||||
return queryset.filter(pam_id = value)
|
||||
|
||||
def sous_vivier_filter(self, queryset, name, value):
|
||||
list_values = value.split('-')
|
||||
return queryset.filter(administre__sous_vivier__in = list_values)
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def qs(self):
|
||||
qs = super().qs
|
||||
summary = get_profile_summary(self.request.user)
|
||||
|
||||
profiles = summary.profiles.get(KEY_READ, ())
|
||||
if not profiles:
|
||||
return qs.none()
|
||||
|
||||
only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)
|
||||
|
||||
org_code = summary.org_code
|
||||
is_fil = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
|
||||
is_pcp = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
|
||||
is_bvt = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
|
||||
if not org_code or (not is_fil and not is_pcp and not is_bvt):
|
||||
return qs.none()
|
||||
|
||||
codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
|
||||
adm_filter = None
|
||||
if is_fil:
|
||||
adm_filter = get_adm_filter_by_lvl4_codes_fil(codes_lvl4)
|
||||
if is_pcp:
|
||||
pcp_filter = get_adm_filter_by_lvl4_codes_pcp(codes_lvl4) \
|
||||
| get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)
|
||||
adm_filter = adm_filter | pcp_filter if adm_filter else pcp_filter
|
||||
if is_bvt:
|
||||
bvt_filter = Q(**{f'{Administres_Pams.Cols.REL_ADMINISTRE}__{Administre.Cols.REL_SOUS_VIVIER}': 'BVT'})
|
||||
adm_filter = adm_filter | bvt_filter if adm_filter else bvt_filter
|
||||
|
||||
return qs.filter(adm_filter)
|
||||
|
||||
class DecisionFilter(filters.FilterSet):
|
||||
"""
|
||||
Cette classe contient tous les filtres qui peuvent être effectués sur la table des decisions.
|
||||
"""
|
||||
class Meta:
|
||||
model = Decision
|
||||
fields = ['administre_id', 'poste_id']
|
||||
|
||||
|
||||
@class_logger
|
||||
class PosteFilter(filters.FilterSet):
|
||||
"""
|
||||
Cette classe contient tous les filtres qui peuvent être effectués sur la table des postes.
|
||||
"""
|
||||
aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
|
||||
pam__in = filters.CharFilter(method='pam_filter')
|
||||
|
||||
class Meta:
|
||||
model = Poste
|
||||
|
||||
fields = [
|
||||
'p_specifique',
|
||||
'p_itd_affecte',
|
||||
Poste.Cols.REL_FORMATION_EMPLOI
|
||||
]
|
||||
|
||||
def aide_a_la_decision_filter(self, queryset, name, value):
|
||||
statut = [AvisPoste.P1, AvisPoste.P2, AvisPoste.P3, AvisPoste.P4]
|
||||
return queryset.filter(Q(p_avis__in=statut) & Q(poste__decisions__isnull=True) & Q(poste__p_pam_id__exact = value))
|
||||
|
||||
def pam_filter(self, queryset, name, value):
|
||||
values_list = value.split(',')
|
||||
return queryset.filter(Q(**{f'{Poste.Cols.M2M_PAM}__pam_id__in': values_list}))
|
||||
|
||||
@property
|
||||
def qs(self):
|
||||
qs = super().qs
|
||||
|
||||
summary = get_profile_summary(self.request.user)
|
||||
|
||||
profiles = summary.profiles.get(KEY_READ, ())
|
||||
if not profiles:
|
||||
return qs.none()
|
||||
|
||||
only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)
|
||||
|
||||
org_code = summary.org_code
|
||||
is_fil = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
|
||||
is_pcp = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
|
||||
is_bvt = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
|
||||
is_itd = Profiles.ITD in profiles and (not only_profile or only_profile == Profiles.ITD)
|
||||
if not org_code or (not is_fil and not is_pcp and not is_bvt and not is_itd):
|
||||
return qs.none()
|
||||
|
||||
codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
|
||||
|
||||
poste_filter = None
|
||||
if is_fil:
|
||||
poste_filter = get_poste_filter_by_lvl4_codes_fil(codes_lvl4) \
|
||||
| Q(p_specifique=SpecifiqueChoices.ITD)
|
||||
if is_pcp:
|
||||
pcp_filter = get_poste_filter_by_lvl4_codes_pcp(codes_lvl4) \
|
||||
| Q(p_specifique=SpecifiqueChoices.ITD)
|
||||
poste_filter = poste_filter | pcp_filter if poste_filter else pcp_filter
|
||||
if is_itd:
|
||||
itd_filter = Q(p_specifique=SpecifiqueChoices.ITD)
|
||||
poste_filter = poste_filter | itd_filter if poste_filter else itd_filter
|
||||
if is_bvt:
|
||||
bvt_filter = Q(**{f'{Poste.Cols.M2M_SOUS_VIVIERS}': 'BVT'})
|
||||
poste_filter = poste_filter | bvt_filter if poste_filter else bvt_filter
|
||||
|
||||
return qs.filter(poste_filter)
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls):
|
||||
"""
|
||||
Cette fonction renvoie toutes les valeurs de recherche dans
|
||||
les champs qui peuvent être filtrés. Les champs qui peuvent être filtrés sont spécifiés dans la classe Meta variable fields.
|
||||
"""
|
||||
fields = super().get_fields()
|
||||
for field_name in fields.copy():
|
||||
lookup_list = cls.Meta.model._meta.get_field(field_name).get_lookups().keys()
|
||||
fields[field_name] = lookup_list
|
||||
return fields
|
||||
|
||||
|
||||
|
||||
@class_logger
|
||||
class PostePAMFilter(filters.FilterSet):
|
||||
"""
|
||||
Cette classe contient tous les filtres qui peuvent être effectués sur la table des postes.
|
||||
"""
|
||||
aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
|
||||
pam__in = filters.CharFilter(method='pam_filter')
|
||||
p_specifique = filters.CharFilter(method='p_specifique_filter')
|
||||
p_itd_affecte = filters.CharFilter(method='p_itd_affecte_filter')
|
||||
formation_emploi__in = filters.CharFilter(method='formation_emploi_filter')
|
||||
# sous_vivier = filters.CharFilter(method='sous_vivier_filter')
|
||||
poste__in = filters.CharFilter(method='poste_filter')
|
||||
|
||||
class Meta:
|
||||
model = Postes_Pams
|
||||
|
||||
fields = [
|
||||
]
|
||||
|
||||
def aide_a_la_decision_filter(self, queryset, name, value):
|
||||
statut = [AvisPoste.P1, AvisPoste.P2, AvisPoste.P3, AvisPoste.P4]
|
||||
return queryset.filter(Q(p_avis_pam__in=statut) & Q(**{f'{Postes_Pams.Cols.O2M_DECISION}__de_decision__isnull' :True}) & Q(p_pam = value))
|
||||
|
||||
def poste_filter(self,queryset, name, value):
|
||||
values_list = value.split(',')
|
||||
if values_list:
|
||||
return queryset.filter(id__in=values_list)
|
||||
|
||||
def pam_filter(self, queryset, name, value):
|
||||
values_list = value.split(',')
|
||||
return queryset.filter(Q(p_pam_id__in = values_list)).distinct('poste_id')
|
||||
|
||||
def p_specifique_filter(self, queryset, name, value):
|
||||
return queryset.filter(Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_specifique': value}))
|
||||
|
||||
def p_itd_affecte_filter(self, queryset, name, value):
|
||||
return queryset.filter(Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_itd_affecte': value}))
|
||||
|
||||
def formation_emploi_filter(self, queryset, name, value):
|
||||
list_values = value.split(',')
|
||||
return queryset.filter(Q(**{f'{Postes_Pams.Cols.REL_POSTE}__formation_emploi__fe_mere_credo__in': list_values}))
|
||||
|
||||
@property
|
||||
def qs(self):
|
||||
qs = super().qs
|
||||
|
||||
summary = get_profile_summary(self.request.user)
|
||||
|
||||
profiles = summary.profiles.get(KEY_READ, ())
|
||||
if not profiles:
|
||||
return qs.none()
|
||||
|
||||
only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)
|
||||
|
||||
org_code = summary.org_code
|
||||
is_fil = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
|
||||
is_pcp = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
|
||||
is_bvt = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
|
||||
is_itd = Profiles.ITD in profiles and (not only_profile or only_profile == Profiles.ITD)
|
||||
if not org_code or (not is_fil and not is_pcp and not is_bvt and not is_itd):
|
||||
return qs.none()
|
||||
|
||||
codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
|
||||
|
||||
|
||||
itd_filter_add = Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_specifique': SpecifiqueChoices.ITD})
|
||||
poste_filter = None
|
||||
if is_fil:
|
||||
poste_filter = get_poste_filter_by_lvl4_codes_fil(codes_lvl4) \
|
||||
| itd_filter_add
|
||||
if is_pcp:
|
||||
list_a_pcp_ok = Administres_Pams.objects.filter(
|
||||
get_adm_filter_by_lvl4_codes_pcp(codes_lvl4)
|
||||
| get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)
|
||||
).values_list('administre_id', flat=True)
|
||||
|
||||
pcp_filter = get_poste_filter_by_lvl4_codes_pcp(codes_lvl4) \
|
||||
| itd_filter_add \
|
||||
| Q(**{f'{Postes_Pams.Cols.O2M_DECISION}__{Decision.Cols.REL_ADMINISTRE}__in': list_a_pcp_ok})
|
||||
poste_filter = poste_filter | pcp_filter if poste_filter else pcp_filter
|
||||
|
||||
if is_itd:
|
||||
itd_filter = itd_filter_add
|
||||
poste_filter = poste_filter | itd_filter if poste_filter else itd_filter
|
||||
|
||||
if is_bvt:
|
||||
bvt_filter = Q(**{f'{Postes_Pams.Cols.REL_POSTE}__{Poste.Cols.M2M_SOUS_VIVIERS}': 'BVT'})
|
||||
poste_filter = poste_filter | bvt_filter if poste_filter else bvt_filter
|
||||
|
||||
return qs.filter(poste_filter).distinct()
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls):
|
||||
"""
|
||||
Cette fonction renvoie toutes les valeurs de recherche dans
|
||||
les champs qui peuvent être filtrés. Les champs qui peuvent être filtrés sont spécifiés dans la classe Meta variable fields.
|
||||
"""
|
||||
fields = super().get_fields()
|
||||
for field_name in fields.copy():
|
||||
lookup_list = cls.Meta.model._meta.get_field(field_name).get_lookups().keys()
|
||||
fields[field_name] = lookup_list
|
||||
return fields
|
||||
|
||||
|
||||
class RelatedOrderingFilter(OrderingFilter):
|
||||
_max_related_depth = 3
|
||||
|
||||
@staticmethod
|
||||
def _get_verbose_name(field, non_verbose_name):
|
||||
return field.verbose_name if hasattr(field, 'verbose_name') else non_verbose_name.replace('_', ' ')
|
||||
|
||||
def _retrieve_all_related_fields(self, fields, model, depth=0):
|
||||
valid_fields = []
|
||||
if depth > self._max_related_depth:
|
||||
return valid_fields
|
||||
for field in fields:
|
||||
if field.related_model and field.related_model != model:
|
||||
rel_fields = self._retrieve_all_related_fields(
|
||||
field.related_model._meta.get_fields(),
|
||||
field.related_model,
|
||||
depth + 1)
|
||||
for rel_field in rel_fields:
|
||||
valid_fields.append((
|
||||
f'{field.name}__{rel_field[0]}',
|
||||
self._get_verbose_name(field, rel_field[1])
|
||||
))
|
||||
else:
|
||||
valid_fields.append((
|
||||
field.name,
|
||||
self._get_verbose_name(field, field.name),
|
||||
))
|
||||
return valid_fields
|
||||
|
||||
def get_valid_fields(self, queryset, view, context=None):
|
||||
valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
|
||||
if not valid_fields == '__all_related__':
|
||||
if not context:
|
||||
context = {}
|
||||
valid_fields = super().get_valid_fields(queryset, view, context)
|
||||
else:
|
||||
valid_fields = [
|
||||
*self._retrieve_all_related_fields(queryset.model._meta.get_fields(), queryset.model),
|
||||
*[(key, key.title().split('__')) for key in queryset.query.annotations]
|
||||
]
|
||||
return valid_fields
|
||||
20
backend-django/backend/form.py
Normal file
20
backend-django/backend/form.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from backend.models import PcpFeGroupe
|
||||
from django import forms
|
||||
from django.contrib.admin.helpers import ActionForm
|
||||
|
||||
class TauxArmementForm(ActionForm):
|
||||
"""Cette classe définit le formulaire du taux d'armement
|
||||
"""
|
||||
taux_armement_cible = forms.IntegerField(widget=forms.NumberInput, help_text="Taux d'armement cible pour la catégorie")
|
||||
|
||||
class GroupeFeForm(ActionForm):
|
||||
"""Cette classe définit le formulaire du GroupeFe
|
||||
"""
|
||||
nom_groupe_fe = forms.CharField(widget=forms.TextInput, help_text="Nom du groupe de formations d'emploi")
|
||||
|
||||
class PcpFeGroupeForm(forms.ModelForm):
|
||||
"""Cette classe définit le formulaire du PcpFeGroupe
|
||||
"""
|
||||
class Meta:
|
||||
model = PcpFeGroupe
|
||||
fields = ['pcp_fe_groupe', 'pcp_fe_categorie', 'gestionnaire']
|
||||
0
backend-django/backend/migrations/__init__.py
Normal file
0
backend-django/backend/migrations/__init__.py
Normal file
660
backend-django/backend/models.py
Normal file
660
backend-django/backend/models.py
Normal file
@@ -0,0 +1,660 @@
|
||||
"""
|
||||
Ce dossier contient tous les modèles de la base de données d'OGURE
|
||||
"""
|
||||
# Import
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||
|
||||
"""
|
||||
Script de création des diffferentes tables de la base
|
||||
OGURE NG utilisation de l'ORM de Django pour réaliser cette étape
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Modéle des Sous viviers de militaires et poste
|
||||
class SousVivier(models.Model):
|
||||
"""Modèle des Sous viviers de militaires et poste
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'sv_id'
|
||||
LIBELLE = 'sv_libelle'
|
||||
REL_GESTIONNAIRE = 'gestionnaire'
|
||||
|
||||
sv_id = models.CharField(max_length=100, primary_key=True)
|
||||
gestionnaire = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
sv_libelle = models.CharField(max_length=100)
|
||||
|
||||
|
||||
# Modèle des Domaines
|
||||
class Domaine(models.Model):
|
||||
"""Modèle des Domaines
|
||||
"""
|
||||
d_code = models.CharField(primary_key=True, max_length=100)
|
||||
d_libelle = models.CharField(max_length=100, null=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.d_code,
|
||||
"code": self.d_code,
|
||||
"libelle": self.d_code,
|
||||
}
|
||||
|
||||
|
||||
# Modèle des Filières
|
||||
class Filiere(models.Model):
|
||||
"""Modèle des Filières
|
||||
"""
|
||||
f_code = models.CharField(primary_key=True, max_length=100)
|
||||
domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True)
|
||||
f_libelle_court = models.CharField(max_length=100, null=True)
|
||||
f_libelle_long = models.CharField(max_length=100, null=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.f_code,
|
||||
"domaineId": self.domaine_id,
|
||||
"code": self.f_code,
|
||||
"libelle": self.f_code,
|
||||
"libelleCourt": self.f_code,
|
||||
}
|
||||
|
||||
|
||||
# Modèle de l'association des sous-viviers à la catégorie et la filière
|
||||
class SousVivierAssociation(models.Model):
|
||||
"""Modèle de l'association des sous-viviers à la catégorie et la filière
|
||||
"""
|
||||
CATEGORIE_CHOICES = [('MDR', 'MDR'), ('SOFF', 'SOFF'), ('OFF', 'OFF'), ('OGX', 'OGX')]
|
||||
|
||||
sva_id = models.IntegerField(primary_key=True)
|
||||
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True)
|
||||
filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True)
|
||||
sva_categorie = models.CharField(max_length=5, choices=CATEGORIE_CHOICES)
|
||||
sva_arme = models.CharField(max_length=20, null=True)
|
||||
|
||||
|
||||
# Modèle des Grades
|
||||
# FIXME le code grade et libellé sont inversés
|
||||
class Grade(models.Model):
|
||||
"""Modèle des Grades
|
||||
"""
|
||||
gr_code = models.CharField(max_length=100, primary_key=True)
|
||||
gr_categorie = models.CharField(max_length=100, null=True)
|
||||
gr_ordre = models.IntegerField(null=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.gr_code,
|
||||
"code": self.gr_code,
|
||||
# TODO corriger ci-dessous : catégorie (ex : "Sous-officiers"), libellé long (ex : "SERGENT CHEF") + ordre
|
||||
"categorie": self.gr_categorie,
|
||||
"ordre": self.gr_ordre
|
||||
}
|
||||
|
||||
|
||||
# Modèle des Garnison
|
||||
class Garnison(models.Model):
|
||||
"""Modèle des Garnison
|
||||
"""
|
||||
gar_id = models.CharField(max_length=100, primary_key=True)
|
||||
gar_lieu = models.CharField(max_length=100, verbose_name="Garnison")
|
||||
gar_code_postal = models.CharField(max_length=100, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.gar_lieu
|
||||
|
||||
|
||||
# Modèle de groupe FE
|
||||
class GroupeFe(models.Model):
|
||||
"""Modèle de groupe FE
|
||||
"""
|
||||
groupe_fe_nom = models.CharField(primary_key=True, max_length=100, verbose_name='Groupe de FE')
|
||||
|
||||
def __str__(self):
|
||||
return self.groupe_fe_nom
|
||||
|
||||
|
||||
# Modèle des Formation d'emplois
|
||||
class FormationEmploi(models.Model):
|
||||
"""Modèle des Formation d'emplois
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'fe_code'
|
||||
ZONE_DEFENSE = 'zone_defense'
|
||||
REL_GARNISON = 'garnison'
|
||||
REL_MERE = 'mere'
|
||||
|
||||
fe_code = models.CharField(primary_key=True, max_length=100)
|
||||
groupe_fe = models.ForeignKey(GroupeFe, blank=True, on_delete=models.SET_NULL, null=True)
|
||||
garnison = models.ForeignKey(Garnison, on_delete=models.CASCADE)
|
||||
fe_libelle = models.CharField(max_length=100)
|
||||
fe_taux_armement_cible_off = models.FloatField(null=True, verbose_name="TA cible OFF")
|
||||
fe_nb_poste_reo_off = models.IntegerField(null=True)
|
||||
fe_nb_poste_reevalue_off = models.IntegerField(null=True)
|
||||
fe_nb_poste_vacant_off = models.IntegerField(null=True)
|
||||
fe_nb_poste_occupe_off = models.IntegerField(null=True)
|
||||
fe_taux_armement_cible_soff = models.FloatField(null=True, verbose_name="TA cible SOFF")
|
||||
fe_nb_poste_reo_soff = models.IntegerField(null=True)
|
||||
fe_nb_poste_reevalue_soff = models.IntegerField(null=True)
|
||||
fe_nb_poste_vacant_soff = models.IntegerField(null=True)
|
||||
fe_nb_poste_occupe_soff = models.IntegerField(null=True)
|
||||
fe_taux_armement_cible_mdr = models.FloatField(null=True, verbose_name="TA cible MDR")
|
||||
fe_nb_poste_reo_mdr = models.IntegerField(null=True)
|
||||
fe_nb_poste_reevalue_mdr = models.IntegerField(null=True)
|
||||
fe_nb_poste_vacant_mdr = models.IntegerField(null=True)
|
||||
fe_nb_poste_occupe_mdr = models.IntegerField(null=True)
|
||||
|
||||
zone_defense = models.CharField('zone de défense', db_column='fe_zone_defense', max_length=64, blank=True, null=True)
|
||||
mere = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='FE mère', db_column='fe_mere_id')
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.fe_code,
|
||||
"code": self.fe_code,
|
||||
"libelle": self.fe_libelle,
|
||||
"groupe_fe": self.groupe_fe_id
|
||||
}
|
||||
|
||||
|
||||
# Modèle des Groupes de marques
|
||||
class MarquesGroupe(models.Model):
|
||||
"""Modèle des Groupes de marques
|
||||
"""
|
||||
gm_id = models.IntegerField(primary_key=True)
|
||||
gm_type = models.CharField(max_length=100)
|
||||
gm_code = models.CharField(max_length=100)
|
||||
gm_libelle = models.CharField(max_length=100)
|
||||
gm_ordre = models.IntegerField(null=True)
|
||||
gm_selection_multiple = models.BooleanField(null=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.gm_id,
|
||||
"type": self.gm_type,
|
||||
"code": self.gm_code,
|
||||
"libelle": self.gm_libelle,
|
||||
"selectionMultiple": self.gm_selection_multiple,
|
||||
"ordre": self.gm_ordre
|
||||
}
|
||||
|
||||
|
||||
# Modèle des Marques
|
||||
class Marque(models.Model):
|
||||
"""Modèle des Marques
|
||||
"""
|
||||
mar_id = models.TextField(primary_key=True)
|
||||
groupe_marques = models.ForeignKey(MarquesGroupe, on_delete=models.CASCADE)
|
||||
mar_code = models.CharField(max_length=100)
|
||||
mar_libelle = models.CharField(max_length=100)
|
||||
mar_ordre = models.IntegerField(null=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.mar_id,
|
||||
"groupeMarquesId": self.groupe_marques_id,
|
||||
"code": self.mar_code,
|
||||
"libelle": self.mar_libelle,
|
||||
"ordre": self.mar_ordre
|
||||
}
|
||||
|
||||
|
||||
# Modèle des fonctions
|
||||
class Fonction(models.Model):
|
||||
"""Modèle des fonctions
|
||||
"""
|
||||
fon_id = models.CharField(max_length=100, primary_key=True)
|
||||
fon_libelle = models.CharField(max_length=100)
|
||||
|
||||
|
||||
# Modèle Compétences
|
||||
class Competence(models.Model):
|
||||
"""Modèle Compétences
|
||||
"""
|
||||
comp_id = models.CharField(primary_key=True, max_length=100)
|
||||
comp_libelle = models.CharField(null=True, max_length=100)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.comp_id,
|
||||
"libelle": self.comp_libelle,
|
||||
}
|
||||
|
||||
class SpecifiqueChoices(models.TextChoices):
|
||||
"""Choix pour les propositions de CIAT d'un poste"""
|
||||
SHM = 'SHM', 'SHM'
|
||||
ITD = 'ITD', 'ITD'
|
||||
PPE = 'PPE', 'PPE'
|
||||
|
||||
class PropositionsArmementChoices(models.TextChoices):
|
||||
"""Choix pour les propositions d'armement d'un poste"""
|
||||
|
||||
PROPOSE = 'PROPOSE', 'Propositions'
|
||||
VALIDE = 'VALIDE', 'Propositions validées'
|
||||
|
||||
# Modèle des Poste
|
||||
class Poste(models.Model):
|
||||
""" Modèle des Poste """
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'p_id'
|
||||
# relations many-to-many
|
||||
M2M_COMPETENCES = 'competences'
|
||||
# relations one-to-many
|
||||
O2M_DECISION = 'decisions'
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_FONCTION = 'fonction'
|
||||
REL_FORMATION_EMPLOI = 'formation_emploi'
|
||||
REL_SOUS_VIVIER = 'sous_vivier'
|
||||
|
||||
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||
CHOICES_NF = [('1A', '1A'), ('1B', '1B'), ('1C', '1C'), ('2.', '2.'), ('3A', '3A'), ('3B', '3B'),
|
||||
('3B NFS', '3B NFS'), ('4.', '4.'), ('5A', '5A'), ('5B', '5B'), ('5C', '5C'), ('6A', '6A'),
|
||||
('6B', '6B')]
|
||||
AVIS = [('P1', 'P1'), ('P2', 'P2'), ('P3', 'P3'), ('P4', 'P4'), ('P4', 'P4'),('P4', 'P4'),('Gele','Gele'),('Non etudie','Non etudie')]
|
||||
p_id = models.CharField(max_length=100, primary_key=True)
|
||||
fonction = models.ForeignKey(Fonction, on_delete=models.SET_NULL, null=True)
|
||||
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True)
|
||||
formation_emploi = models.ForeignKey(FormationEmploi, on_delete=models.SET_NULL, null=True)
|
||||
competences = models.ManyToManyField(Competence)
|
||||
p_domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True)
|
||||
p_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True)
|
||||
p_fonction = models.CharField(max_length=100, null=True)
|
||||
p_code_fonction = models.CharField(max_length=100, null=True)
|
||||
p_nf = models.CharField(max_length=100, null=True, choices=CHOICES_NF)
|
||||
p_categorie = models.CharField(max_length=100, null=True)
|
||||
p_dep = models.IntegerField(null=True)
|
||||
p_liste_id_marques = models.CharField(max_length=100, null=True, blank=True)
|
||||
p_eip = models.CharField(max_length=100)
|
||||
p_avis = models.CharField(max_length=100, default='Non etudie', choices=AVIS)
|
||||
p_notes_gestionnaire = models.TextField(null=True)
|
||||
p_notes_partagees = models.TextField(null=True)
|
||||
p_poids_competences = models.FloatField(null=True)
|
||||
p_poids_filiere = models.FloatField(null=True)
|
||||
p_poids_nf = models.FloatField(null=True)
|
||||
p_nb_p1 = models.IntegerField(null=True)
|
||||
p_nb_p2 = models.IntegerField(null=True)
|
||||
p_nb_p3 = models.IntegerField(null=True)
|
||||
p_nb_p4 = models.IntegerField(null=True)
|
||||
p_nb_non_etudie = models.IntegerField(null=True)
|
||||
p_nb_gele = models.IntegerField(null=True)
|
||||
p_nb_reo = models.IntegerField(null=True)
|
||||
p_nb_reevalue = models.IntegerField(null=True)
|
||||
p_nb_occupe = models.IntegerField(null=True)
|
||||
p_nb_vacant = models.IntegerField(null=True)
|
||||
p_nb_affectable = models.IntegerField(null=True)
|
||||
p_ciat = models.BooleanField('CIAT', default=False, null=True)
|
||||
p_specifique = models.CharField('Poste spécifique', max_length=250, choices=SpecifiqueChoices.choices, null=True, blank=True)
|
||||
propositions_armement = models.CharField("propositions d'armement", db_column='p_propositions_armement', max_length=10, choices=PropositionsArmementChoices.choices, null=True, blank=True)
|
||||
p_priorisation_pcp = models.TextField('Priorisation PCP', null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_p_specifique_valid',
|
||||
check=models.Q(p_specifique__in=SpecifiqueChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_propositions_armement_valid',
|
||||
check=models.Q(propositions_armement__in=PropositionsArmementChoices.values)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class StatutPamChoices(models.TextChoices):
|
||||
"""[Administre] choix pour le statut PAM"""
|
||||
|
||||
A_ETUDIER = 'A_ETUDIER', 'A étudier'
|
||||
A_ETUDIER_REC = 'A_ETUDIER_REC', 'A étudier REC'
|
||||
A_MAINTENIR = 'A_MAINTENIR', 'A maintenir'
|
||||
A_MUTER = 'A_MUTER', 'A muter'
|
||||
A_TRAITER = 'A_TRAITER', 'A traiter'
|
||||
NON_DISPONIBLE = 'NON_DISPONIBLE', 'Non disponible'
|
||||
NON_ETUDIE = 'NON_ETUDIE', 'Non étudié'
|
||||
PARTANT = 'PARTANT', 'Partant'
|
||||
|
||||
|
||||
class StatutFuturChoices(models.TextChoices):
|
||||
"""[Administre] choix pour le statut futur"""
|
||||
|
||||
CLDM = 'CLDM', 'CLDM'
|
||||
CP = 'CP', 'CP'
|
||||
DRJI = 'DRJI', 'DRJI'
|
||||
NRCT = 'NRCT', 'NRCT'
|
||||
DET = 'DET', 'DET'
|
||||
RECONV = 'RECONV', 'RECONV'
|
||||
|
||||
|
||||
# Modèle des administrés
|
||||
class Administre(models.Model):
|
||||
"""Modèle des administrés"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'a_id_sap'
|
||||
STATUT_CONCERTO = 'a_statut_concerto'
|
||||
STATUT_PAM = 'a_statut_pam'
|
||||
DATE_STATUT_CONCERTO = 'a_date_statut_concerto'
|
||||
# relations many-to-many
|
||||
M2M_COMPETENCES = 'a_liste_id_competences'
|
||||
# relations one-to-many
|
||||
O2M_FMOB = 'fmobs'
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_DECISION = 'decision'
|
||||
REL_FONCTION = 'fonction'
|
||||
REL_FORMATION_EMPLOI = 'formation_emploi'
|
||||
REL_GRADE = 'grade'
|
||||
REL_SOUS_VIVIER = 'sous_vivier'
|
||||
|
||||
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||
|
||||
CHOICES_NF = [('1A', '1A'), ('1B', '1B'), ('1C', '1C'), ('2.', '2.'), ('3A', '3A'), ('3B', '3B'),
|
||||
('3B NFS', '3B NFS'), ('4.', '4.'), ('5A', '5A'), ('5B', '5B'), ('5C', '5C'), ('6A', '6A'),
|
||||
('6B', '6B')]
|
||||
a_id_sap = models.IntegerField(primary_key=True)
|
||||
formation_emploi = models.ForeignKey(FormationEmploi, related_name="formation_emploi", on_delete=models.SET_NULL,
|
||||
null=True)
|
||||
fonction = models.ForeignKey(Fonction, on_delete=models.SET_NULL, null=True)
|
||||
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
grade = models.ForeignKey(Grade, on_delete=models.SET_NULL, null=True)
|
||||
a_grade_date_debut = models.CharField(max_length=100, null=True)
|
||||
a_liste_id_marques = models.CharField(max_length=100, null=True)
|
||||
a_liste_id_competences = models.ManyToManyField(Competence)
|
||||
a_nom = models.CharField(max_length=100)
|
||||
a_prenom = models.CharField(max_length=100)
|
||||
a_sexe = models.CharField(max_length=100)
|
||||
a_id_def = models.CharField(max_length=100, null=True)
|
||||
a_eip = models.CharField(max_length=100)
|
||||
a_fonction = models.CharField(max_length=100, null=True)
|
||||
a_code_fonction = models.CharField(max_length=100, null=True)
|
||||
a_domaine = models.ForeignKey(Domaine, related_name="domaine", on_delete=models.SET_NULL, null=True)
|
||||
a_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True)
|
||||
a_nf = models.CharField(max_length=100, null=True, choices=CHOICES_NF)
|
||||
a_domaine_poste = models.ForeignKey(Domaine, related_name="domaine_poste", on_delete=models.SET_NULL, null=True)
|
||||
a_filiere_poste = models.ForeignKey(Filiere, related_name="filiere_poste", on_delete=models.SET_NULL, null=True)
|
||||
a_nf_poste = models.CharField(max_length=100, null=True, choices=CHOICES_NF)
|
||||
a_categorie = models.CharField(max_length=5, null=True)
|
||||
a_domaine_futur = models.ForeignKey(Domaine, related_name="domaine_futur", on_delete=models.SET_NULL, null=True)
|
||||
a_filiere_futur = models.ForeignKey(Filiere, related_name="filiere_futur", on_delete=models.SET_NULL, null=True)
|
||||
a_nf_futur = models.CharField(max_length=100, null=True, choices=CHOICES_NF)
|
||||
a_bureau_gestion = models.CharField(max_length=100, null=True)
|
||||
a_date_entree_service = models.CharField(max_length=100, null=True)
|
||||
a_arme = models.CharField(max_length=100, null=True)
|
||||
a_rg_origine_recrutement = models.CharField(max_length=100, null=True)
|
||||
a_date_naissance = models.CharField(max_length=100, null=True)
|
||||
a_diplome_hl = models.CharField(max_length=100, null=True)
|
||||
a_dernier_diplome = models.CharField(max_length=100, null=True)
|
||||
a_credo_fe = models.CharField(max_length=100, null=True)
|
||||
a_date_arrivee_fe = models.CharField(max_length=100, null=True)
|
||||
a_pos_statuaire = models.CharField(max_length=100, null=True)
|
||||
a_date_pos_statuaire = models.CharField(max_length=100, null=True)
|
||||
a_interruption_service = models.CharField(max_length=100, null=True)
|
||||
a_situation_fam = models.CharField(max_length=100, null=True)
|
||||
a_nombre_enfants = models.IntegerField(null=True)
|
||||
a_date_rdc = models.DateField(null=True)
|
||||
a_date_dernier_acr = models.DateField(null=True)
|
||||
a_eis = models.CharField(max_length=100, null=True)
|
||||
a_sap_conjoint = models.IntegerField(null=True)
|
||||
a_flag_particulier = models.IntegerField(null=True)
|
||||
a_flag_pam = models.IntegerField(null=True)
|
||||
a_statut_pam = models.CharField('statut PAM', max_length=100, choices=StatutPamChoices.choices, null=True, blank=True)
|
||||
a_notes_gestionnaire = models.TextField(null=True)
|
||||
a_notes_partagees = models.TextField(null=True)
|
||||
a_eip_futur = models.CharField(max_length=100, null=True)
|
||||
a_affectation1 = models.CharField(max_length=100, null=True)
|
||||
a_affectation2 = models.CharField(max_length=100, null=True)
|
||||
a_affectation3 = models.CharField(max_length=100, null=True)
|
||||
a_liste_depts_souhaites = models.CharField(max_length=100, null=True)
|
||||
a_pls_gb_max = models.IntegerField(null=True)
|
||||
a_marqueur_pn = models.BooleanField(default=False)
|
||||
a_affectation4 = models.CharField(max_length=100, null=True)
|
||||
a_affectation5 = models.CharField(max_length=100, null=True)
|
||||
a_affectation6 = models.CharField(max_length=100, null=True)
|
||||
a_affectation7 = models.CharField(max_length=100, null=True)
|
||||
a_affectation8 = models.CharField(max_length=100, null=True)
|
||||
a_affectation9 = models.CharField(max_length=100, null=True)
|
||||
a_fonction1 = models.CharField(max_length=100, null=True)
|
||||
a_fonction2 = models.CharField(max_length=100, null=True)
|
||||
a_fonction3 = models.CharField(max_length=100, null=True)
|
||||
a_fonction4 = models.CharField(max_length=100, null=True)
|
||||
a_fonction5 = models.CharField(max_length=100, null=True)
|
||||
a_fonction6 = models.CharField(max_length=100, null=True)
|
||||
a_fonction7 = models.CharField(max_length=100, null=True)
|
||||
a_fonction8 = models.CharField(max_length=100, null=True)
|
||||
a_fonction9 = models.CharField(max_length=100, null=True)
|
||||
a_profession_conjoint = models.CharField(max_length=100, null=True)
|
||||
a_id_def_conjoint = models.CharField(max_length=100, null=True)
|
||||
a_diplome_1 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_1_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_1_note = models.FloatField(null=True)
|
||||
a_diplome_2 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_2_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_2_note = models.FloatField(null=True)
|
||||
a_diplome_3 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_3_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_3_note = models.FloatField(null=True)
|
||||
a_diplome_4 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_4_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_4_note = models.FloatField(null=True)
|
||||
a_diplome_5 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_5_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_5_note = models.FloatField(null=True)
|
||||
a_diplome_6 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_6_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_6_note = models.FloatField(null=True)
|
||||
a_diplome_7 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_7_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_7_note = models.FloatField(null=True)
|
||||
a_diplome_8 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_8_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_8_note = models.FloatField(null=True)
|
||||
a_diplome_9 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_9_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_9_note = models.FloatField(null=True)
|
||||
a_diplome_10 = models.CharField(max_length=100, null=True)
|
||||
a_diplome_10_date = models.DateField(max_length=100, null=True)
|
||||
a_diplome_10_note = models.FloatField(null=True)
|
||||
a_origine_recrutement = models.CharField(max_length=100, null=True)
|
||||
statut_futur = models.CharField('statut futur', db_column='a_statut_futur', max_length=8, choices=StatutFuturChoices.choices, null=True, blank=True)
|
||||
date_statut_futur = models.DateField('date du statut futur', db_column='a_date_statut_futur', null=True, blank=True)
|
||||
a_statut_concerto = models.CharField('statut CONCERTO', max_length=32, null=True, blank=True)
|
||||
a_date_statut_concerto = models.CharField('date du statut CONCERTO', max_length=32, null=True, blank=True)
|
||||
suivi_previsionnel_situation = models.CharField('suivi prévisionnel de la situation du militaire', db_column='a_suivi_previsionnel_situation', max_length=100, null=True, blank=True)
|
||||
date_suivi_previsionnel_situation = models.DateField('date du suivi prévisionnel de la situation du militaire', db_column='a_date_suivi_previsionnel_situation', null=True, blank=True)
|
||||
a_annee_previsible_mutation = models.PositiveIntegerField('année prévisible de mutation', null=True)
|
||||
a_fud = models.CharField('FUD de départ', max_length=32, null=True, blank=True)
|
||||
a_date_fud = models.CharField('date du FUD', max_length=32, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Administré"
|
||||
verbose_name_plural = "Administrés"
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_statut_pam_valid',
|
||||
check=models.Q(a_statut_pam__in=StatutPamChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_statut_futur_valid',
|
||||
check=models.Q(statut_futur__in=StatutFuturChoices.values)
|
||||
),
|
||||
]
|
||||
|
||||
# class CompetencesAdministre(models.Model):
|
||||
# """Modèle de table intermédiaire entre Administrés et Compétences
|
||||
# """
|
||||
# competence = models.ForeignKey(Competence, on_delete=models.CASCADE)
|
||||
# administre = models.ForeignKey(Administre, on_delete=models.CASCADE)
|
||||
# def __unicode__(self):
|
||||
|
||||
# return self.competence.comp_id + self.administre.a_id_sap
|
||||
|
||||
|
||||
class FMOB(models.Model):
|
||||
"""Modèle de FMOB
|
||||
"""
|
||||
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||
|
||||
fmob_id = models.CharField(max_length=100, primary_key=True)
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, related_name="fmobs",
|
||||
db_constraint=False)
|
||||
fmob_millesime = models.IntegerField(null=True)
|
||||
fmob_annulation_fmob = models.BooleanField(default=False)
|
||||
fmob_annulation_femp = models.BooleanField(default=False)
|
||||
fmob_sans_suite_militaire_fmob = models.BooleanField(default=False)
|
||||
fmob_sans_suite_militaire_femp = models.BooleanField(default=False)
|
||||
fmob_date_visa_militaire = models.DateField(max_length=100, null=True)
|
||||
fmob_depart_institution_soff = models.BooleanField(default=False)
|
||||
fmob_mobilite_bassin_externe = models.BooleanField(default=False)
|
||||
fmob_mobilite_bassin_interne = models.BooleanField(default=False)
|
||||
fmob_mobilite_centre_interet_adt = models.BooleanField(default=False)
|
||||
fmob_mobilite_dans_specialite = models.BooleanField(default=False)
|
||||
fmob_mobilite_hors_metropole = models.BooleanField(default=False)
|
||||
fmob_mobilite_recrutement_particulier_administre = models.BooleanField(default=False)
|
||||
fmob_motif_edition_la = models.CharField(max_length=100, null=True)
|
||||
fmob_motif_edition_ll = models.CharField(max_length=100, null=True)
|
||||
fmob_reception_drhat_fmob = models.BooleanField(default=False)
|
||||
fmob_reconnaissance_parcours_pro_administre = models.BooleanField(default=False)
|
||||
fmob_proposition_affectation_verrouille = models.CharField(max_length=100, null=True)
|
||||
fmob_reception_drhat_femp = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_interne = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_externe = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mutation_administre = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_centre_interet = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_specialite = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_hors_metropole = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_recrutement_particulier_admin = models.BooleanField(default=False)
|
||||
fmob_date_deb_fmob = models.CharField(max_length=100, null=True)
|
||||
fmob_date_fin_fmob = models.CharField(max_length=100, null=True)
|
||||
fmob_date_signature_admin_fmob = models.DateField(max_length=100, null=True)
|
||||
fmob_date_signature_admin_femp = models.DateField(max_length=100, null=True)
|
||||
fmob_date_signature_chef_de_corps = models.DateField(max_length=100, null=True)
|
||||
fmob_remarques_eventuelles_administres = models.TextField(null=True)
|
||||
fmob_avis_commandant_formation = models.TextField(null=True)
|
||||
fmob_fonction_1 = models.CharField(max_length=100, null=True)
|
||||
fmob_fonction_2 = models.CharField(max_length=100, null=True)
|
||||
fmob_fonction_3 = models.CharField(max_length=100, null=True)
|
||||
fmob_fonction_4 = models.CharField(max_length=100, null=True)
|
||||
fmob_fonction_5 = models.CharField(max_length=100, null=True)
|
||||
fmob_commune_1 = models.CharField(max_length=100, null=True)
|
||||
fmob_commune_2 = models.CharField(max_length=100, null=True)
|
||||
fmob_commune_3 = models.CharField(max_length=100, null=True)
|
||||
fmob_commune_4 = models.CharField(max_length=100, null=True)
|
||||
fmob_commune_5 = models.CharField(max_length=100, null=True)
|
||||
fmob_prio_1 = models.BooleanField(default=False)
|
||||
fmob_prio_2 = models.BooleanField(default=False)
|
||||
fmob_prio_3 = models.BooleanField(default=False)
|
||||
fmob_prio_4 = models.BooleanField(default=False)
|
||||
fmob_prio_5 = models.BooleanField(default=False)
|
||||
fmob_avis_mutabilite = models.CharField(max_length=100, null=True)
|
||||
fmob_obs = models.CharField(max_length=100, null=True)
|
||||
fmob_fe_future = models.CharField(max_length=100, null=True)
|
||||
|
||||
|
||||
# Modèle Liste de Préference
|
||||
class PreferencesListe(models.Model):
|
||||
"""Modèle Liste de Préference
|
||||
"""
|
||||
lp_id = models.IntegerField(primary_key=True)
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE)
|
||||
poste = models.ForeignKey(Poste, on_delete=models.CASCADE)
|
||||
lp_rang_poste = models.IntegerField()
|
||||
|
||||
|
||||
# Modèle pour le suivi des calculs
|
||||
class Calcul(models.Model):
|
||||
"""Modèle pour le suivi des calculs
|
||||
On y intègre également le statut du calcul
|
||||
"""
|
||||
class Statut(models.TextChoices):
|
||||
AUCUN = 'AUCUN', 'aucun'
|
||||
EN_ATTENTE = 'EN_ATTENTE', 'en attente'
|
||||
EN_COURS = 'EN_COURS', 'en cours'
|
||||
TERMINE = 'TERMINE', 'terminé'
|
||||
ERREUR = 'ERREUR', 'terminé en erreur'
|
||||
TERMINE_DE_FORCE = 'TERMINE_DE_FORCE', 'terminé de force'
|
||||
ARRETER = 'ARRETER'
|
||||
|
||||
sous_vivier = models.OneToOneField(SousVivier, primary_key=True, on_delete=models.CASCADE)
|
||||
ca_date_debut = models.DateTimeField(null=True, blank=True)
|
||||
ca_date_fin = models.DateTimeField(null=True, blank=True)
|
||||
ca_statut = models.CharField(max_length=100)
|
||||
ca_statut_pourcentage = models.FloatField(default='100')
|
||||
|
||||
|
||||
|
||||
# Modèle pour Le PAM
|
||||
class PAM(models.Model):
|
||||
"""Modèle pour Le PAM
|
||||
"""
|
||||
pam_id = models.IntegerField(primary_key=True)
|
||||
pam_date = models.CharField(max_length=100)
|
||||
pam_libelle = models.CharField(max_length=100)
|
||||
pam_statut = models.CharField(max_length=100)
|
||||
|
||||
|
||||
# Modèle pour les notations
|
||||
class Notation(models.Model):
|
||||
"""Modèle pour les notations
|
||||
"""
|
||||
no_id = models.AutoField(primary_key=True)
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE)
|
||||
poste = models.ForeignKey(Poste, on_delete=models.CASCADE)
|
||||
pam = models.ForeignKey(PAM, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
no_rang_administre = models.IntegerField(null=True)
|
||||
no_rang_poste = models.IntegerField(null=True)
|
||||
no_date_execution = models.DateTimeField(auto_now=True)
|
||||
no_score_administre = models.FloatField(null=True)
|
||||
no_score_poste = models.FloatField(null=True)
|
||||
no_flag_cple_ideal = models.BooleanField(default=False)
|
||||
|
||||
|
||||
# Modèle pour les décisions
|
||||
class Decision(models.Model):
|
||||
"""Modèle pour les décisions
|
||||
"""
|
||||
administre = models.OneToOneField(Administre, on_delete=models.CASCADE, primary_key=True)
|
||||
poste = models.ForeignKey(Poste, on_delete=models.CASCADE, related_name="decisions")
|
||||
pam = models.ForeignKey(PAM, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
de_decision = models.CharField(max_length=100)
|
||||
de_date_decision = models.DateTimeField(auto_now=True)
|
||||
de_notes_gestionnaire = models.TextField(null=True, blank=True)
|
||||
de_notes_partagees = models.TextField(null=True, blank=True)
|
||||
notation = models.ForeignKey(Notation, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
|
||||
# Modèle Utilisateur de l'application
|
||||
class CustomUser(AbstractUser):
|
||||
"""Modèle Utilisateur de l'application
|
||||
"""
|
||||
administre = models.ForeignKey(Administre, blank=True, on_delete=models.SET_NULL, null=True)
|
||||
grade = models.CharField(blank=True, null=True, max_length=100)
|
||||
|
||||
|
||||
# Modèle d'association des groupes de FE aux gestionnaires PCP
|
||||
class PcpFeGroupe(models.Model):
|
||||
"""Modèle d'association des groupes de FE aux gestionnaires PCP
|
||||
"""
|
||||
CATEGORIE_CHOICES = [('MDR', 'MDR'), ('OFF', 'OFF'), ('SOFF', 'SOFF'), ('OGX', 'OGX')]
|
||||
pcp_fe_id = models.AutoField(primary_key=True)
|
||||
pcp_fe_groupe = models.ForeignKey(GroupeFe, null=True, on_delete=models.CASCADE,
|
||||
verbose_name='Nom du groupe')
|
||||
pcp_fe_categorie = models.CharField(max_length=5, choices=CATEGORIE_CHOICES, null=True, verbose_name='Catégorie')
|
||||
gestionnaire = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, db_constraint=False)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.gestionnaire.first_name) + ' ' + str(self.gestionnaire.last_name.upper()) + ' / ' + self.pcp_fe_groupe.groupe_fe_nom + ' / ' + self.pcp_fe_categorie
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Lien gest PCP / groupe FE'
|
||||
verbose_name_plural = 'Liens gest PCP / groupe FE'
|
||||
18
backend-django/backend/models/__init__.py
Normal file
18
backend-django/backend/models/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from .administre import *
|
||||
from .calcul import *
|
||||
from .commun import *
|
||||
from .competence import *
|
||||
from .decision import *
|
||||
from .domaine import *
|
||||
from .fichier_exporte import *
|
||||
from .filiere import *
|
||||
from .fmob import *
|
||||
from .fonction import *
|
||||
from .formation_emploi import *
|
||||
from .garnison import *
|
||||
from .grade import *
|
||||
from .initial import *
|
||||
from .poste import *
|
||||
from .sous_vivier import *
|
||||
from .pam import *
|
||||
from .user import *
|
||||
282
backend-django/backend/models/administre.py
Normal file
282
backend-django/backend/models/administre.py
Normal file
@@ -0,0 +1,282 @@
|
||||
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||
from django.db import models
|
||||
|
||||
from .commun import NiveauFonctionnelChoices, SpecifiqueChoices
|
||||
from .competence import Competence
|
||||
from .domaine import Domaine
|
||||
from .filiere import Filiere
|
||||
from .fonction import Fonction
|
||||
from .formation_emploi import FormationEmploi
|
||||
from .grade import Grade
|
||||
from .sous_vivier import SousVivier
|
||||
from .pam import PAM
|
||||
class SuiviPrevisionnelMilitaire(models.TextChoices):
|
||||
"""
|
||||
[Administre] choix pour le gestionnaire PCP
|
||||
"""
|
||||
|
||||
MOB_EXT = 'MOB_EXT', 'Mobilité externe'
|
||||
MOB_INT = 'MOB_INT', 'Mobilité interne',
|
||||
NRCT = 'NRCT', 'NRCT',
|
||||
RDC = 'RDC', 'RDC',
|
||||
LMT = 'LMT', 'Limite de contrat',
|
||||
REC = 'REC', 'Reconversion',
|
||||
REC_OE = 'REC_OE', 'Recrutement OE',
|
||||
DFP = 'DFP', 'Détachement FP',
|
||||
|
||||
|
||||
class StatutFuturChoices(models.TextChoices):
|
||||
"""
|
||||
[Administre] choix pour le statut futur
|
||||
"""
|
||||
|
||||
CLDM = 'CLDM', 'CLDM'
|
||||
CP = 'CP', 'CP'
|
||||
DRJI = 'DRJI', 'DRJI'
|
||||
NRCT = 'NRCT', 'NRCT'
|
||||
DET = 'DET', 'DET'
|
||||
RECONV = 'RECONV', 'RECONV'
|
||||
|
||||
|
||||
class StatutPamChoices(models.TextChoices):
|
||||
"""
|
||||
[Administre] choix pour le statut PAM
|
||||
attributs supplémentaires :
|
||||
- calc_enabled : est-ce que ce statut permet le calcul ?
|
||||
- dec_enabled : est-ce que ce statut permet de créer une décision ?
|
||||
"""
|
||||
|
||||
# (valeur, calc_enabled, dec_enabled), libellé
|
||||
A_ETUDIER = ('A_ETUDIER', True, True), 'A étudier'
|
||||
A_ETUDIER_REC = ('A_ETUDIER_REC', True, True), 'A étudier REC'
|
||||
A_MAINTENIR = ('A_MAINTENIR', False, True), 'A maintenir'
|
||||
A_MUTER = ('A_MUTER', True, True), 'A muter'
|
||||
A_TRAITER = ('A_TRAITER', False, False), 'A traiter'
|
||||
NON_DISPONIBLE = ('NON_DISPONIBLE', False, False), 'Non disponible'
|
||||
NON_ETUDIE = ('NON_ETUDIE', False, False), 'Non étudié'
|
||||
PARTANT = ('PARTANT', False, False), 'Partant'
|
||||
|
||||
def __new__(cls, value):
|
||||
obj = str.__new__(cls, value[0])
|
||||
obj._value_ = value[0]
|
||||
obj.calc_enabled = value[1]
|
||||
obj.dec_enabled = value[2]
|
||||
return obj
|
||||
|
||||
|
||||
class Administre(models.Model):
|
||||
"""
|
||||
Modèle des administrés
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'a_id_sap'
|
||||
CATEGORIE = 'a_categorie'
|
||||
STATUT_CONCERTO = 'a_statut_concerto'
|
||||
STATUT_CONCERTO_FUTUR = 'a_statut_concerto_futur'
|
||||
DATE_STATUT_CONCERTO = 'a_date_statut_concerto'
|
||||
DATE_STATUT_CONCERTO_FUTUR = 'a_date_statut_concerto_futur'
|
||||
# relations many-to-many
|
||||
M2M_COMPETENCES = 'a_liste_id_competences'
|
||||
M2M_PAM = 'pam'
|
||||
# relations one-to-many
|
||||
O2M_FMOB = 'fmobs'
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_DOMAINE = 'a_domaine'
|
||||
REL_FILIERE = 'a_filiere'
|
||||
REL_DECISION = 'decision'
|
||||
REL_FONCTION = 'fonction'
|
||||
REL_FORMATION_EMPLOI = 'formation_emploi'
|
||||
REL_GRADE = 'grade'
|
||||
REL_SOUS_VIVIER = 'sous_vivier'
|
||||
STATUT_PAM = 'a_statut_pam'
|
||||
REL_PAM_INTER = 'administre'
|
||||
|
||||
|
||||
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||
|
||||
a_id_sap = models.IntegerField(primary_key=True)
|
||||
formation_emploi = models.ForeignKey(FormationEmploi, related_name="formation_emploi", on_delete=models.SET_NULL,
|
||||
null=True, blank=True)
|
||||
pam = models.ManyToManyField(PAM, through='Administres_Pams')
|
||||
fonction = models.ForeignKey(Fonction, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
grade = models.ForeignKey(Grade, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
a_grade_date_debut = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_liste_id_marques = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_liste_id_competences = models.ManyToManyField(Competence, blank=True)
|
||||
a_nom = models.CharField(max_length=100)
|
||||
a_prenom = models.CharField(max_length=100)
|
||||
a_sexe = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_id_def = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_eip = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_fonction = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_code_fonction = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True, blank=True, db_constraint=False)
|
||||
a_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True, blank=True, db_constraint=False)
|
||||
a_nf = models.CharField(max_length=100, null=True, blank=True, choices=NiveauFonctionnelChoices.choices)
|
||||
a_domaine_poste = models.ForeignKey(Domaine, related_name="domaine_poste", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
a_filiere_poste = models.ForeignKey(Filiere, related_name="filiere_poste", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
a_nf_poste = models.CharField(max_length=100, null=True, blank=True, choices=NiveauFonctionnelChoices.choices)
|
||||
a_categorie = models.CharField(max_length=5, null=True, blank=True)
|
||||
a_domaine_futur = models.ForeignKey(Domaine, related_name="domaine_futur", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
a_filiere_futur = models.ForeignKey(Filiere, related_name="filiere_futur", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
a_nf_futur = models.CharField(max_length=100, null=True, blank=True, choices=NiveauFonctionnelChoices.choices)
|
||||
a_domaine_gestion = models.CharField('Domaine de gestion (BVT)', max_length=100, null=True, blank=True)
|
||||
a_date_entree_service = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_arme = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_rg_origine_recrutement = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_naissance = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_diplome_hl = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_dernier_diplome = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_credo_fe = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_arrivee_fe = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_pos_statuaire = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_pos_statuaire = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_interruption_service = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_situation_fam = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_mariage = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_nombre_enfants = models.IntegerField(null=True, blank=True)
|
||||
a_enfants = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_rdc = models.DateField(null=True, blank=True)
|
||||
a_date_dernier_acr = models.DateField(null=True, blank=True)
|
||||
a_eis = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_sap_conjoint = models.IntegerField(null=True, blank=True)
|
||||
a_flag_particulier = models.IntegerField(null=True, blank=True)
|
||||
a_notes_partagees = models.TextField(null=True, blank=True)
|
||||
a_eip_fiche_detaille = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_eip_futur = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_liste_depts_souhaites = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_liste_zones_geographiques_shm = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_pls_gb_max = models.IntegerField(null=True, blank=True)
|
||||
a_marqueur_pn = models.BooleanField(default=False, null=True)
|
||||
a_fonction1 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_fonction2 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_fonction3 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_fonction4 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_fonction5 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_fonction6 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_fonction7 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_fonction8 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_fonction9 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_fonction1 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_fonction2 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_fonction3 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_fonction4 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_fonction5 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_fonction6 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_fonction7 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_fonction8 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_date_fonction9 = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_profession_conjoint = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_id_def_conjoint = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_notes_gestionnaire = models.TextField(null=True, blank=True)
|
||||
a_statut_pam = models.CharField('statut PAM', max_length=100,choices=StatutPamChoices.choices, null=True, blank=True)
|
||||
a_sexe_conjoint = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_lien_service = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_age_en_annees = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_origine_recrutement = models.CharField(max_length=100, null=True, blank=True)
|
||||
statut_futur = models.CharField('statut futur', db_column='a_statut_futur', max_length=8, choices=StatutFuturChoices.choices, null=True, blank=True)
|
||||
date_statut_futur = models.DateField('date du statut futur', db_column='a_date_statut_futur', null=True, blank=True)
|
||||
a_statut_concerto = models.CharField('statut CONCERTO', max_length=1000, null=True, blank=True)
|
||||
a_date_statut_concerto = models.CharField('date du statut CONCERTO', max_length=32, null=True, blank=True)
|
||||
a_statut_concerto_futur = models.CharField('statut CONCERTO futur', max_length=100, null=True, blank=True)
|
||||
a_date_statut_concerto_futur = models.CharField('date du statut CONCERTO futur', max_length=32, null=True, blank=True)
|
||||
suivi_previsionnel_situation = models.CharField('suivi prévisionnel de la situation du militaire', max_length=100, choices=SuiviPrevisionnelMilitaire.choices, null=True, blank=True)
|
||||
date_suivi_previsionnel_situation = models.DateField('date du suivi prévisionnel de la situation du militaire', db_column='a_date_suivi_previsionnel_situation', null=True, blank=True)
|
||||
a_annee_previsible_mutation = models.PositiveIntegerField('année prévisible de mutation', null=True, blank=True)
|
||||
a_fud = models.CharField('FUD de départ', max_length=32, null=True, blank=True)
|
||||
a_date_fud = models.CharField('date du FUD', max_length=32, null=True, blank=True)
|
||||
a_ciat = models.BooleanField('CIAT', default=False, null=True)
|
||||
a_specifique = models.CharField('PPE / SHM / ITD', max_length=250, choices=SpecifiqueChoices.choices, null=True, blank=True)
|
||||
a_date_affectation = models.DateField("Date d'affectation", null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Administré"
|
||||
verbose_name_plural = "Administrés"
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_a_nf_valid',
|
||||
check=models.Q(a_nf__in=NiveauFonctionnelChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_a_nf_poste_valid',
|
||||
check=models.Q(a_nf_poste__in=NiveauFonctionnelChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_a_nf_futur_valid',
|
||||
check=models.Q(a_nf_futur__in=NiveauFonctionnelChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_statut_pam_valid',
|
||||
check=models.Q(a_statut_pam__in=StatutPamChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_statut_futur_valid',
|
||||
check=models.Q(statut_futur__in=StatutFuturChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_a_specifique',
|
||||
check=models.Q(a_specifique__in=SpecifiqueChoices.values)
|
||||
)
|
||||
]
|
||||
|
||||
class Administres_Pams(models.Model):
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'id'
|
||||
REL_PAM = 'pam'
|
||||
REL_ADMINISTRE = 'administre'
|
||||
O2M_FMOB = 'fmobs'
|
||||
REL_DECISION = 'decision'
|
||||
STATUT_PAM = 'a_statut_pam_annee'
|
||||
|
||||
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||
|
||||
id = models.CharField(primary_key=True, max_length=100)
|
||||
pam = models.ForeignKey(PAM, on_delete = models.CASCADE, related_name='pam')
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, related_name=Administre.Cols.REL_PAM_INTER)
|
||||
a_statut_pam_annee = models.CharField('statut PAM', max_length=100,choices=StatutPamChoices.choices, null=True, blank=True)
|
||||
notes_pam = models.TextField(null=True, blank=True)
|
||||
a_ciat_pam = models.BooleanField('CIAT', default=False, null=True)
|
||||
a_specifique_pam = models.CharField('PPE / SHM / ITD', max_length=250, choices=SpecifiqueChoices.choices, null=True, blank=True)
|
||||
a_liste_depts_souhaites_pam = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_liste_zones_geographiques_shm_pam = models.CharField(max_length=100, null=True, blank=True)
|
||||
a_situationfuture_notes_fe = models.TextField(null=True, blank=True)
|
||||
|
||||
|
||||
|
||||
|
||||
class Affectation(models.Model):
|
||||
"""
|
||||
Modèle pour les affectations
|
||||
"""
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name='adm_affec', db_constraint=False)
|
||||
affect_libelle = models.CharField(max_length=100, null=True, blank=True)
|
||||
affect_date = models.CharField(max_length=100, null=True, blank=True)
|
||||
|
||||
|
||||
class Diplome(models.Model):
|
||||
"""
|
||||
Modèle pour les diplomes
|
||||
"""
|
||||
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name='adm_dip', db_constraint=False)
|
||||
diplome_libelle = models.CharField(max_length=100, null=True, blank=True)
|
||||
diplome_date = models.CharField(max_length=100, null=True, blank=True)
|
||||
diplome_note = models.CharField(max_length=100, null=True, blank=True)
|
||||
diplome_niveau = models.CharField(max_length=100, null=True, blank=True)
|
||||
|
||||
|
||||
class FUD(models.Model):
|
||||
"""
|
||||
Modèle pour les formulaires unitaires de demandes
|
||||
"""
|
||||
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name='adm_fud', db_constraint=False)
|
||||
fud_libelle = models.CharField(max_length=100, null=True, blank=True)
|
||||
fud_date_debut = models.CharField(max_length=100, null=True, blank=True)
|
||||
fud_date_fin = models.CharField(max_length=100, null=True, blank=True)
|
||||
44
backend-django/backend/models/calcul.py
Normal file
44
backend-django/backend/models/calcul.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
|
||||
from .pam import PAM
|
||||
|
||||
from .sous_vivier import SousVivier
|
||||
|
||||
|
||||
class StatutCalculChoices(models.TextChoices):
|
||||
AUCUN = 'AUCUN', 'aucun'
|
||||
EN_ATTENTE = 'EN_ATTENTE', 'en attente'
|
||||
EN_ATTENTE_ARRET = 'EN_ATTENTE_ARRET', "en attente d'arrêt"
|
||||
EN_COURS = 'EN_COURS', 'en cours'
|
||||
TERMINE = 'TERMINE', 'terminé'
|
||||
ERREUR_ADMINISTRE = 'ERREUR_ADMINISTRE','terminé en erreur administré',
|
||||
ERREUR_POSTE = 'ERREUR_POSTE', 'terminé en erreur poste',
|
||||
ERREUR = 'ERREUR', 'terminé en erreur'
|
||||
TERMINE_DE_FORCE = 'TERMINE_DE_FORCE', 'terminé de force'
|
||||
|
||||
|
||||
class Calcul(models.Model):
|
||||
"""
|
||||
Modèle pour le suivi des calculs
|
||||
On y intègre également le statut du calcul
|
||||
"""
|
||||
id = models.CharField(max_length=100, primary_key=True, default="")
|
||||
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.CASCADE, verbose_name="Sous-vivier")
|
||||
pam = models.ForeignKey(PAM, on_delete=models.CASCADE, null=True, blank=True)
|
||||
ca_date_debut = models.DateTimeField('Date de début', null=True, blank=True)
|
||||
ca_date_fin = models.DateTimeField('Date de fin', null=True, blank=True)
|
||||
ca_statut = models.CharField('Statut', max_length=40, choices=StatutCalculChoices.choices)
|
||||
ca_statut_pourcentage = models.FloatField('Avancement (%)',default=0)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_ca_statut_valid',
|
||||
check=models.Q(ca_statut__in=StatutCalculChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_ca_statut_pourcentage_valid',
|
||||
check=Q(ca_statut_pourcentage__gte=0.0) & Q(ca_statut_pourcentage__lte=100.0)
|
||||
),
|
||||
]
|
||||
31
backend-django/backend/models/commun.py
Normal file
31
backend-django/backend/models/commun.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class NiveauFonctionnelChoices(models.TextChoices):
|
||||
"""
|
||||
Choix pour les niveaux fonctionnels
|
||||
"""
|
||||
|
||||
N1A = '1A', '1A'
|
||||
N1B = '1B', '1B'
|
||||
N1C = '1C', '1C'
|
||||
N2 = '2.', '2.'
|
||||
N3A = '3A', '3A'
|
||||
N3B = '3B', '3B'
|
||||
N3B_NFS = '3B NFS', '3B NFS'
|
||||
N4 = '4.', '4.'
|
||||
N5A = '5A', '5A'
|
||||
N5B = '5B', '5B'
|
||||
N5C = '5C', '5C'
|
||||
N6A = '6A', '6A'
|
||||
N6B = '6B', '6B'
|
||||
|
||||
|
||||
class SpecifiqueChoices(models.TextChoices):
|
||||
"""
|
||||
Choix pour les propositions de CIAT
|
||||
"""
|
||||
|
||||
SHM = 'SHM', 'SHM'
|
||||
ITD = 'ITD', 'ITD'
|
||||
PPE = 'PPE', 'PPE'
|
||||
31
backend-django/backend/models/competence.py
Normal file
31
backend-django/backend/models/competence.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from django.db import models
|
||||
|
||||
from .domaine import Domaine
|
||||
from .filiere import Filiere
|
||||
|
||||
|
||||
class Competence(models.Model):
|
||||
"""Modèle Compétences"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'comp_id'
|
||||
CATEGORIE = 'comp_categorie'
|
||||
LIBELLE = 'comp_libelle'
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_DOMAINE = 'comp_domaine'
|
||||
REL_FILIERE = 'comp_filiere'
|
||||
|
||||
|
||||
comp_id = models.CharField('ID', primary_key=True, max_length=100)
|
||||
comp_libelle = models.CharField('libellé', null=True, blank=True, max_length=100)
|
||||
comp_domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='domaine')
|
||||
comp_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='filière')
|
||||
comp_categorie = models.CharField('catégorie', max_length=100, null=True, blank=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.comp_id,
|
||||
"libelle": self.comp_libelle,
|
||||
}
|
||||
113
backend-django/backend/models/decision.py
Normal file
113
backend-django/backend/models/decision.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from enum import Enum, auto
|
||||
|
||||
from django.db import models
|
||||
|
||||
from .administre import Administre, Administres_Pams
|
||||
from .initial import PAM, Notation
|
||||
from .poste import Poste, Postes_Pams
|
||||
|
||||
|
||||
class DecisionTree(Enum):
|
||||
""" enum pour les types d'arbres de décisions """
|
||||
|
||||
# métropole
|
||||
ME = auto()
|
||||
|
||||
# hors métropole
|
||||
HME = auto()
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class DecisionChoices(models.TextChoices):
|
||||
"""
|
||||
Choix pour les statuts de décisions
|
||||
attributs supplémentaires :
|
||||
- trees : Tuple[DecisionTree], permet de savoir à quel(s) arbre(s) appartient le statut
|
||||
"""
|
||||
|
||||
# arbre de décision en métropole
|
||||
PROPOSITION_FE = ('PROPOSITION_FE', DecisionTree.ME), 'Proposition de FE'
|
||||
DIALOGUE_EN_COURS = ('DIALOGUE_EN_COURS', DecisionTree.ME), 'Dialogue en cours'
|
||||
DIALOGUE_TERMINE = ('DIALOGUE_TERMINE', DecisionTree.ME), 'Dialogue terminé'
|
||||
DIALOGUE_INFRUCTUEUX = ('DIALOGUE_INFRUCTUEUX', DecisionTree.ME), 'Dialogue infructueux'
|
||||
FOREMP_EN_COURS = ('FOREMP_EN_COURS', DecisionTree.ME), 'FOREMP en cours'
|
||||
FOREMP_TERMINE = ('FOREMP_TERMINE', DecisionTree.ME), 'FOREMP terminé'
|
||||
PREPOSITIONNE = ('PREPOSITIONNE', DecisionTree.ME), 'Prépositionné'
|
||||
POSITIONNE = ('POSITIONNE', DecisionTree.ME), 'Positionné'
|
||||
OMIP_EN_COURS = ('OMIP_EN_COURS', DecisionTree.ME), 'OMIP en cours'
|
||||
OMIP_TERMINE = ('OMIP_TERMINE', DecisionTree.ME), 'OMIP terminé'
|
||||
ATTENTE_AVIONAGE = ('ATTENTE_AVIONAGE', DecisionTree.ME), "En attente d'avionage"
|
||||
OMI_EN_COURS = ('OMI_EN_COURS', DecisionTree.ME), 'OMI en cours'
|
||||
OMI_ACTIVE = ('OMI_ACTIVE', DecisionTree.ME), 'OMI terminé'
|
||||
OMI_ANNULE = ('OMI_ANNULE', DecisionTree.ME), 'OMI annulation'
|
||||
|
||||
# arbre de décision hors métropole
|
||||
HME_DIALOGUE_INITIE = ('HME_DIALOGUE_INITIE', DecisionTree.HME), 'Dialogue initié (HME)'
|
||||
HME_DIALOGUE_EN_COURS = ('HME_DIALOGUE_EN_COURS', DecisionTree.HME), 'Dialogue en cours (HME)'
|
||||
HME_DIALOGUE_INFRUCTUEUX = ('HME_DIALOGUE_INFRUCTUEUX', DecisionTree.HME), 'Dialogue infructueux (HME)'
|
||||
HME_DIALOGUE_TERMINE = ('HME_DIALOGUE_TERMINE', DecisionTree.HME), 'Dialogue terminé (HME)'
|
||||
HME_PROPOSITION_VIVIER = ('HME_PROPOSITION_VIVIER', DecisionTree.HME), 'Proposition vivier HME'
|
||||
HME_ETUDE_DESISTEMENT = ('HME_ETUDE_DESISTEMENT', DecisionTree.HME), 'Etude désistement (HME)'
|
||||
HME_DESISTEMENT = ('HME_DESISTEMENT', DecisionTree.HME), 'Désistement activé (HME)'
|
||||
HME_PREPOSITIONNE = ('HME_PREPOSITIONNE', DecisionTree.HME), 'Prépositionné (HME)'
|
||||
HME_VALIDATION_EXPERT = ('HME_VALIDATION_EXPERT', DecisionTree.HME), 'Validation HME (HME)'
|
||||
HME_REFUS_EXPERT = ('HME_REFUS_EXPERT', DecisionTree.HME), 'Refus HME (HME)'
|
||||
HME_POSITIONNE = ('HME_POSITIONNE', DecisionTree.HME), 'Positionné (HME)'
|
||||
HME_FOREMP_EN_COURS = ('HME_FOREMP_EN_COURS', DecisionTree.HME), 'FOREMP en cours (HME)'
|
||||
HME_FOREMP_TERMINE = ('HME_FOREMP_TERMINE', DecisionTree.HME), 'FOREMP terminé (HME)'
|
||||
HME_OMIP_EN_COURS = ('HME_OMIP_EN_COURS', DecisionTree.HME), 'OMIP en cours (HME)'
|
||||
HME_OMIP_TERMINE = ('HME_OMIP_TERMINE', DecisionTree.HME), 'OMIP terminé (HME)'
|
||||
HME_ATTENTE_AVIONAGE = ('HME_ATTENTE_AVIONAGE', DecisionTree.HME), "En attente d'avionage (HME)"
|
||||
HME_OMI_EN_COURS = ('HME_OMI_EN_COURS', DecisionTree.HME), 'OMI en cours (HME)'
|
||||
HME_OMI_ACTIVE = ('HME_OMI_ACTIVE', DecisionTree.HME), 'OMI terminé (HME)'
|
||||
HME_OMI_ANNULE = ('HME_OMI_ANNULE', DecisionTree.ME), 'OMI annulation'
|
||||
|
||||
REMIS_A_DISPOSITION = ('REMIS_A_DISPOSITION', (DecisionTree.ME, DecisionTree.HME)), 'Remis à disposition'
|
||||
|
||||
def __new__(cls, value):
|
||||
obj = str.__new__(cls, value[0])
|
||||
obj._value_ = value[0]
|
||||
_trees = value[1]
|
||||
obj.trees = _trees if isinstance(_trees, tuple) else (_trees,) if _trees else ()
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return "%s.%s" % (self.__class__.__name__, self._name_)
|
||||
|
||||
|
||||
# Modèle pour les décisions
|
||||
class Decision(models.Model):
|
||||
"""
|
||||
Modèle pour les décisions
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
STATUT = 'de_decision'
|
||||
DATE = 'de_date_decision'
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_ADMINISTRE = 'administre'
|
||||
REL_POSTE = 'poste'
|
||||
|
||||
administre_pam = models.OneToOneField(Administres_Pams, on_delete=models.CASCADE, related_name=Administres_Pams.Cols.REL_DECISION, primary_key=True, default="")
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, related_name=Administres_Pams.Cols.REL_DECISION)
|
||||
poste = models.ForeignKey(Poste, on_delete=models.CASCADE, related_name= Postes_Pams.Cols.O2M_DECISION)
|
||||
poste_pam = models.OneToOneField(Postes_Pams, on_delete=models.CASCADE, related_name= Postes_Pams.Cols.O2M_DECISION, default="")
|
||||
de_decision = models.CharField('Décision', max_length=50, choices=DecisionChoices.choices)
|
||||
de_date_decision = models.DateTimeField('Date de décision', auto_now=True)
|
||||
de_notes_gestionnaire = models.TextField(null=True, blank=True)
|
||||
de_notes_partagees = models.TextField(null=True, blank=True)
|
||||
notation = models.ForeignKey(Notation, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_de_decision_valid',
|
||||
check=models.Q(de_decision__in=DecisionChoices.values)
|
||||
)
|
||||
]
|
||||
verbose_name = 'Décision'
|
||||
verbose_name_plural = 'Décisions'
|
||||
21
backend-django/backend/models/domaine.py
Normal file
21
backend-django/backend/models/domaine.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Domaine(models.Model):
|
||||
"""
|
||||
Modèle des domaines
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'd_code'
|
||||
|
||||
d_code = models.CharField('code', primary_key=True, max_length=100)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
'id': self.d_code,
|
||||
'code': self.d_code,
|
||||
'libelle': self.d_code,
|
||||
}
|
||||
11
backend-django/backend/models/fichier_exporte.py
Normal file
11
backend-django/backend/models/fichier_exporte.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.db import models
|
||||
|
||||
class FichiersExporte(models.Model):
|
||||
"""Modèle de fichiers exportés"""
|
||||
|
||||
nom_fichier = models.CharField(null=True, blank=True, max_length=100)
|
||||
date_export = models.DateTimeField(null=True, blank=True, auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Fichier exporté'
|
||||
verbose_name_plural = 'Fichiers exportés'
|
||||
28
backend-django/backend/models/filiere.py
Normal file
28
backend-django/backend/models/filiere.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from django.db import models
|
||||
|
||||
from .domaine import Domaine
|
||||
|
||||
|
||||
class Filiere(models.Model):
|
||||
"""
|
||||
Modèle des filières
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'f_code'
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_DOMAINE = 'domaine'
|
||||
|
||||
|
||||
f_code = models.CharField('code', primary_key=True, max_length=100)
|
||||
domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
'id': self.f_code,
|
||||
'domaineId': self.domaine_id,
|
||||
'code': self.f_code,
|
||||
'libelle': self.f_code,
|
||||
}
|
||||
71
backend-django/backend/models/fmob.py
Normal file
71
backend-django/backend/models/fmob.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||
from django.db import models
|
||||
|
||||
from .administre import Administre, Administres_Pams
|
||||
|
||||
|
||||
class FMOB(models.Model):
|
||||
"""Modèle de FMOB
|
||||
"""
|
||||
CHOICES_STATUT = [('Annulé', 'Annulé'), ('Classé sans suite', 'Classé sans suite'), ('Non réceptionné', 'Non réceptionné'),
|
||||
('Réceptionné', 'Réceptionné')]
|
||||
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||
|
||||
fmob_id = models.CharField(max_length=100, primary_key=True)
|
||||
administre = models.OneToOneField(Administres_Pams, on_delete=models.CASCADE, null=True, blank=True, related_name='fmobs')
|
||||
#administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name=Administre.Cols.O2M_FMOB, db_constraint=False)
|
||||
fmob_millesime = models.IntegerField(null=True, blank=True)
|
||||
fmob_millesime_femp = models.IntegerField(null=True, blank=True)
|
||||
fmob_reception_drhat_fmob = models.BooleanField(default=False)
|
||||
fmob_annulation_fmob = models.BooleanField(default=False)
|
||||
fmob_annulation_femp = models.BooleanField(default=False)
|
||||
fmob_sans_suite_militaire_fmob = models.BooleanField(default=False)
|
||||
fmob_sans_suite_militaire_femp = models.BooleanField(default=False)
|
||||
fmob_date_visa_militaire = models.DateField(max_length=100, null=True, blank=True)
|
||||
fmob_depart_institution_soff = models.BooleanField(default=False)
|
||||
fmob_mobilite_bassin_externe = models.BooleanField(default=False)
|
||||
fmob_mobilite_bassin_interne = models.BooleanField(default=False)
|
||||
fmob_mobilite_centre_interet_adt = models.BooleanField(default=False)
|
||||
fmob_mobilite_dans_specialite = models.BooleanField(default=False)
|
||||
fmob_mobilite_hors_metropole = models.BooleanField(default=False)
|
||||
fmob_mobilite_recrutement_particulier_administre = models.BooleanField(default=False)
|
||||
fmob_motif_edition_la = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_motif_edition_ll = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_reception_drhat_fmob = models.BooleanField(default=False)
|
||||
fmob_reconnaissance_parcours_pro_administre = models.BooleanField(default=False)
|
||||
fmob_proposition_affectation_verrouille = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_reception_drhat_femp = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_interne = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_externe = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mutation_administre = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_centre_interet = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_specialite = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_hors_metropole = models.BooleanField(default=False)
|
||||
fmob_avis_cdc_mobilite_recrutement_particulier_admin = models.BooleanField(default=False)
|
||||
fmob_date_deb_fmob = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_date_fin_fmob = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_date_signature_admin_fmob = models.DateField(max_length=100, null=True, blank=True)
|
||||
fmob_date_signature_admin_femp = models.DateField(max_length=100, null=True, blank=True)
|
||||
fmob_date_signature_chef_de_corps = models.DateField(max_length=100, null=True, blank=True)
|
||||
fmob_remarques_eventuelles_administres = models.TextField(null=True, blank=True)
|
||||
fmob_avis_commandant_formation = models.TextField(null=True, blank=True)
|
||||
fmob_fonction_1 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_fonction_2 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_fonction_3 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_fonction_4 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_fonction_5 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_commune_1 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_commune_2 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_commune_3 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_commune_4 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_commune_5 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_prio_1 = models.BooleanField(default=False)
|
||||
fmob_prio_2 = models.BooleanField(default=False)
|
||||
fmob_prio_3 = models.BooleanField(default=False)
|
||||
fmob_prio_4 = models.BooleanField(default=False)
|
||||
fmob_prio_5 = models.BooleanField(default=False)
|
||||
fmob_avis_mutabilite = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_obs = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_fe_future = models.CharField(max_length=100, null=True, blank=True)
|
||||
fmob_statut = models.CharField(max_length=100, null=True, blank=True, choices=CHOICES_STATUT)
|
||||
fmob_commentaire_ac = models.TextField(null=True, blank=True)
|
||||
15
backend-django/backend/models/fonction.py
Normal file
15
backend-django/backend/models/fonction.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Fonction(models.Model):
|
||||
"""
|
||||
Modèle des fonctions
|
||||
"""
|
||||
|
||||
fon_id = models.CharField(max_length=100, primary_key=True)
|
||||
fon_libelle = models.CharField(max_length=100)
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.fon_id,
|
||||
"libelle": self.fon_libelle,
|
||||
}
|
||||
96
backend-django/backend/models/formation_emploi.py
Normal file
96
backend-django/backend/models/formation_emploi.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from django.db import models
|
||||
|
||||
from .garnison import Garnison
|
||||
from .user import CustomUser
|
||||
|
||||
|
||||
class GroupeFe(models.Model):
|
||||
"""
|
||||
Modèle de groupes FE
|
||||
"""
|
||||
groupe_fe_nom = models.CharField(primary_key=True, max_length=100, verbose_name='Groupe de FE')
|
||||
|
||||
def __str__(self):
|
||||
return self.groupe_fe_nom
|
||||
|
||||
|
||||
class PcpFeGroupe(models.Model):
|
||||
"""
|
||||
Modèle d'association des groupes de FE aux gestionnaires PCP
|
||||
"""
|
||||
|
||||
CATEGORIE_CHOICES = [('MDR', 'MDR'), ('OFF', 'OFF'), ('SOFF', 'SOFF'), ('OGX', 'OGX')]
|
||||
pcp_fe_id = models.AutoField(primary_key=True)
|
||||
pcp_fe_groupe = models.ForeignKey(GroupeFe, null=True, blank=True, on_delete=models.CASCADE,
|
||||
verbose_name='Nom du groupe')
|
||||
pcp_fe_categorie = models.CharField(max_length=5, choices=CATEGORIE_CHOICES, null=True, blank=True, verbose_name='Catégorie')
|
||||
gestionnaire = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, blank=True, db_constraint=False)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.gestionnaire.first_name) + ' ' + str(self.gestionnaire.last_name.upper()) + ' / ' + self.pcp_fe_groupe.groupe_fe_nom + ' / ' + self.pcp_fe_categorie
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Lien gest PCP / groupe FE'
|
||||
verbose_name_plural = 'Liens gest PCP / groupe FE'
|
||||
|
||||
|
||||
class FormationEmploi(models.Model):
|
||||
"""
|
||||
Modèle des formations d'emplois
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'fe_code'
|
||||
LIBELLE = 'fe_libelle'
|
||||
ZONE_DEFENSE = 'zone_defense'
|
||||
REL_MERE = 'mere'
|
||||
REL_POSTE = 'poste'
|
||||
M2M_GESTIONNAIRE = 'gestionnaires'
|
||||
|
||||
fe_code = models.CharField(primary_key=True, max_length=100)
|
||||
groupe_fe = models.ForeignKey(GroupeFe, blank=True, on_delete=models.SET_NULL, null=True)
|
||||
gestionnaires = models.ManyToManyField(CustomUser, related_name=CustomUser.Cols.M2M_FORMATION_EMPLOIS, blank=True)
|
||||
fe_code_postal = models.CharField(max_length=100, null=True, blank=True)
|
||||
fe_garnison_lieu = models.CharField(max_length=100, null=True, blank=True)
|
||||
fe_libelle = models.CharField(max_length=100, null=True, blank=True)
|
||||
fe_taux_armement_cible_off = models.FloatField(null=True, blank=True, verbose_name="TA cible OFF")
|
||||
fe_nb_poste_reo_off = models.IntegerField(null=True, blank=True)
|
||||
fe_nb_poste_reevalue_off = models.IntegerField(null=True, blank=True)
|
||||
fe_nb_poste_vacant_off = models.IntegerField(null=True, blank=True)
|
||||
fe_nb_poste_occupe_off = models.IntegerField(null=True, blank=True)
|
||||
fe_taux_armement_cible_soff = models.FloatField(null=True, blank=True, verbose_name="TA cible SOFF")
|
||||
fe_nb_poste_reo_soff = models.IntegerField(null=True, blank=True)
|
||||
fe_nb_poste_reevalue_soff = models.IntegerField(null=True, blank=True)
|
||||
fe_nb_poste_vacant_soff = models.IntegerField(null=True, blank=True)
|
||||
fe_nb_poste_occupe_soff = models.IntegerField(null=True, blank=True)
|
||||
fe_taux_armement_cible_mdr = models.FloatField(null=True, blank=True, verbose_name="TA cible MDR")
|
||||
fe_nb_poste_reo_mdr = models.IntegerField(null=True, blank=True)
|
||||
fe_nb_poste_reevalue_mdr = models.IntegerField(null=True, blank=True)
|
||||
fe_nb_poste_vacant_mdr = models.IntegerField(null=True, blank=True)
|
||||
fe_nb_poste_occupe_mdr = models.IntegerField(null=True, blank=True)
|
||||
fe_mere_credo = models.CharField('Code FE mère', max_length=100, null=True, blank=True)
|
||||
fe_mere_la = models.CharField('Libelle FE mère', max_length=100, null=True, blank=True)
|
||||
fe_fot = models.CharField(max_length=100, null=True, blank=True)
|
||||
fe_abo_fe = models.CharField(max_length=100, null=True, blank=True)
|
||||
fe_pilier_niv1 = models.CharField(max_length=100, null=True, blank=True)
|
||||
fe_code_niv_org4 = models.CharField('Code niveau d\'org 4', max_length=100, null=True, blank=True)
|
||||
fe_niv_org4 = models.CharField('Libelle niveau d\'org 4', max_length=100, null=True, blank=True)
|
||||
fe_code_niv_org4_mdr = models.CharField('Code niveau d\'org 4 (MDR)', max_length=100, null=True, blank=True)
|
||||
fe_niv_org4_mdr = models.CharField('Libelle niveau d\'org 4 (MDR)', max_length=100, null=True, blank=True)
|
||||
|
||||
zone_defense = models.CharField('zone de défense', db_column='fe_zone_defense', max_length=64, blank=True, null=True)
|
||||
mere = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='FE mère', db_column='fe_mere_id')
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.fe_code,
|
||||
"code": self.fe_code,
|
||||
"libelle": self.fe_libelle,
|
||||
"mere_code": self.fe_mere_credo,
|
||||
"mere_la": self.fe_mere_la,
|
||||
"groupe_fe": self.groupe_fe_id,
|
||||
"fe_garnison_lieu": self.fe_garnison_lieu,
|
||||
"zone_defense": self.zone_defense
|
||||
}
|
||||
21
backend-django/backend/models/garnison.py
Normal file
21
backend-django/backend/models/garnison.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Garnison(models.Model):
|
||||
"""
|
||||
Modèle des garnisons
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'gar_id'
|
||||
LIEU = 'gar_lieu'
|
||||
CODE_POSTAL = 'gar_code_postal'
|
||||
|
||||
gar_id = models.CharField(max_length=100, primary_key=True)
|
||||
gar_lieu = models.CharField(max_length=100, verbose_name="Garnison")
|
||||
gar_code_postal = models.CharField(max_length=100, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.gar_lieu
|
||||
21
backend-django/backend/models/grade.py
Normal file
21
backend-django/backend/models/grade.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
# FIXME le code grade et libellé sont inversés
|
||||
class Grade(models.Model):
|
||||
"""
|
||||
Modèle des grades
|
||||
"""
|
||||
|
||||
gr_code = models.CharField(max_length=100, primary_key=True)
|
||||
gr_categorie = models.CharField(max_length=100, null=True, blank=True)
|
||||
gr_ordre = models.IntegerField(null=True, blank=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.gr_code,
|
||||
"code": self.gr_code,
|
||||
# TODO corriger ci-dessous : catégorie (ex : "Sous-officiers"), libellé long (ex : "SERGENT CHEF") + ordre
|
||||
"categorie": self.gr_categorie,
|
||||
"ordre": self.gr_ordre
|
||||
}
|
||||
231
backend-django/backend/models/initial.py
Normal file
231
backend-django/backend/models/initial.py
Normal file
@@ -0,0 +1,231 @@
|
||||
"""
|
||||
Ce dossier contient tous les modèles de la base de données d'OGURE
|
||||
"""
|
||||
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .administre import Administre, Administres_Pams
|
||||
from .commun import NiveauFonctionnelChoices, SpecifiqueChoices
|
||||
from .competence import Competence
|
||||
from .domaine import Domaine
|
||||
from .filiere import Filiere
|
||||
from .garnison import Garnison
|
||||
from .grade import Grade
|
||||
from .poste import Poste, Postes_Pams
|
||||
from .sous_vivier import SousVivier
|
||||
from .pam import PAM
|
||||
|
||||
"""
|
||||
Script de création des diffferentes tables de la base
|
||||
OGURE NG utilisation de l'ORM de Django pour réaliser cette étape
|
||||
|
||||
"""
|
||||
|
||||
# TODO: supprimer ce modèle et le remplacer par le nouveau modèle des Sous-viviers
|
||||
# Modèle de l'association des sous-viviers à la catégorie et la filière
|
||||
class SousVivierAssociation(models.Model):
|
||||
"""Modèle de l'association des sous-viviers à la catégorie et la filière
|
||||
"""
|
||||
CATEGORIE_CHOICES = [('MDR', 'MDR'), ('SOFF', 'SOFF'), ('OFF', 'OFF'), ('OGX', 'OGX')]
|
||||
|
||||
sva_id = models.IntegerField(primary_key=True)
|
||||
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
sva_categorie = models.CharField(max_length=5, choices=CATEGORIE_CHOICES)
|
||||
sva_arme = models.CharField(max_length=20, null=True, blank=True)
|
||||
|
||||
|
||||
# Modèle des Groupes de marques
|
||||
class MarquesGroupe(models.Model):
|
||||
"""Modèle des Groupes de marques
|
||||
"""
|
||||
gm_id = models.IntegerField(primary_key=True)
|
||||
gm_type = models.CharField(max_length=100)
|
||||
gm_code = models.CharField(max_length=100)
|
||||
gm_libelle = models.CharField(max_length=100)
|
||||
gm_ordre = models.IntegerField(null=True, blank=True)
|
||||
gm_selection_multiple = models.BooleanField(null=True, blank=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.gm_id,
|
||||
"type": self.gm_type,
|
||||
"code": self.gm_code,
|
||||
"libelle": self.gm_libelle,
|
||||
"selectionMultiple": self.gm_selection_multiple,
|
||||
"ordre": self.gm_ordre
|
||||
}
|
||||
|
||||
|
||||
# Modèle des Marques
|
||||
class Marque(models.Model):
|
||||
"""Modèle des Marques
|
||||
"""
|
||||
mar_id = models.TextField(primary_key=True)
|
||||
groupe_marques = models.ForeignKey(MarquesGroupe, on_delete=models.CASCADE)
|
||||
mar_code = models.CharField(max_length=100)
|
||||
mar_libelle = models.CharField(max_length=100)
|
||||
mar_ordre = models.IntegerField(null=True, blank=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.mar_id,
|
||||
"groupeMarquesId": self.groupe_marques_id,
|
||||
"code": self.mar_code,
|
||||
"libelle": self.mar_libelle,
|
||||
"ordre": self.mar_ordre
|
||||
}
|
||||
|
||||
|
||||
class ZoneGeographique(models.Model):
|
||||
"""Modèle des Zones Geographiques
|
||||
"""
|
||||
zone_id = models.CharField(max_length=100, primary_key=True)
|
||||
zone_libelle = models.CharField(max_length=100)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.zone_id,
|
||||
"libelle": self.zone_libelle
|
||||
}
|
||||
|
||||
|
||||
# Modèle Liste de Préference
|
||||
class PreferencesListe(models.Model):
|
||||
"""Modèle Liste de Préference
|
||||
"""
|
||||
lp_id = models.IntegerField(primary_key=True)
|
||||
administre_pam = models.ForeignKey(Administres_Pams, on_delete=models.CASCADE, related_name= 'administre_pam_pref', default="")
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, related_name= 'administre_pref', default="")
|
||||
pam = models.ForeignKey(PAM, on_delete=models.CASCADE, default="")
|
||||
poste = models.ForeignKey(Poste, on_delete=models.CASCADE)
|
||||
lp_rang_poste = models.IntegerField()
|
||||
|
||||
|
||||
|
||||
|
||||
# Modèle pour les notations
|
||||
class Notation(models.Model):
|
||||
"""Modèle pour les notations
|
||||
"""
|
||||
no_id = models.AutoField(primary_key=True)
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE)
|
||||
poste = models.ForeignKey(Poste, on_delete=models.CASCADE)
|
||||
administre_pam = models.ForeignKey(Administres_Pams, on_delete=models.CASCADE, default="", related_name='administre_pam')
|
||||
poste_pam = models.ForeignKey(Postes_Pams, on_delete=models.CASCADE, default="", related_name='poste_pam')
|
||||
pam = models.ForeignKey(PAM, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
no_rang_administre = models.IntegerField(null=True, blank=True)
|
||||
no_rang_poste = models.IntegerField(null=True, blank=True)
|
||||
no_date_execution = models.DateTimeField(auto_now=True)
|
||||
no_score_administre = models.FloatField(null=True, blank=True)
|
||||
no_score_poste = models.FloatField(null=True, blank=True)
|
||||
no_flag_cple_ideal = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class Administre_Notation(models.Model):
|
||||
"""Modèle le lien entre Notation et Administre
|
||||
"""
|
||||
id = models.CharField(max_length=100, primary_key=True)
|
||||
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name='adm_not', db_constraint=False)
|
||||
no_annne_de_notation = models.CharField(max_length=100, null=True, blank=True)
|
||||
no_nr_ou_iris = models.CharField(max_length=100, null=True, blank=True)
|
||||
no_rac_ou_iris_cumule = models.CharField(max_length=100, null=True, blank=True)
|
||||
no_rf_qsr = models.CharField(max_length=100, null=True, blank=True)
|
||||
no_aptitude_emploie_sup = models.CharField(max_length=100, null=True, blank=True)
|
||||
no_potentiel_responsabilite_sup = models.CharField(max_length=100, null=True, blank=True)
|
||||
no_age_annees = models.CharField(max_length=100, null=True, blank=True)
|
||||
|
||||
|
||||
# Modèle du référentiel organique
|
||||
class RefOrg(models.Model):
|
||||
"""Modèle de référentiel organique"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'ref_org_code'
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_REF_GEST = 'ref_gest'
|
||||
|
||||
# TODO: ajouter la condition d'unicité des lignes (utiliser class Meta)
|
||||
ref_org_code = models.CharField('Code niveau d\'org', max_length=100, primary_key=True)
|
||||
ref_org_code_niv_org1 = models.CharField('Code niveau d\'org 1', max_length=100, null=True, blank=True)
|
||||
ref_org_lib_niv_org1 = models.CharField('Livelle niveau d\'org 1', max_length=100, null=True, blank=True)
|
||||
ref_org_code_niv_org2 = models.CharField('Code niveau d\'org 2', max_length=100, null=True, blank=True)
|
||||
ref_org_lib_niv_org2 = models.CharField('Libelle niveau d\'org 2', max_length=100, null=True, blank=True)
|
||||
ref_org_code_niv_org3 = models.CharField('Code niveau d\'org 3', max_length=100, null=True, blank=True)
|
||||
ref_org_lib_niv_org3 = models.CharField('Libelle niveau d\'org 3', max_length=100, null=True, blank=True)
|
||||
ref_org_code_niv_org4 = models.CharField('Code niveau d\'org 4', max_length=100, null=True, blank=True)
|
||||
ref_org_lib_niv_org4 = models.CharField('Libell niveau d\'org 4', max_length=100, null=True, blank=True)
|
||||
ref_org_niv_org = models.IntegerField('Niveau d\'org', null=True, blank=True)
|
||||
ref_org_ref_fe = models.BooleanField('Pameur BMOB', default=False, null=True)
|
||||
ref_org_ref_sv_fil = models.BooleanField('Gestionnaire BGCAT', default=False, null=True)
|
||||
ref_org_droit_lect = models.BooleanField('Droit de lecture', default=False, null=True)
|
||||
ref_org_droit_ecr = models.BooleanField("Droit d'écriture", default=False, null=True)
|
||||
ref_org_expert_hme = models.BooleanField('Expert HME', default=False, null=True)
|
||||
ref_org_bvt = models.BooleanField('Gestionnaire BVT', default=False, null=True)
|
||||
ref_org_itd = models.BooleanField('Gestionnaire ITD', default=False, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Référentiel organique'
|
||||
verbose_name_plural = 'Référentiels organiques'
|
||||
|
||||
|
||||
# Modèle du référentiel gestionnaire
|
||||
class RefGest(models.Model):
|
||||
"""Modèle de référentiel gestionnaire"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'ref_gest_sap'
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_ORG = 'ref_gest_org'
|
||||
|
||||
ref_gest_sap = models.IntegerField('Id SAP', primary_key=True)
|
||||
ref_gest_username = models.CharField('Nom d\'utilisateur', max_length=100, null=True, blank=True)
|
||||
ref_gest_email = models.CharField('Adresse mail', max_length=100, null=True, blank=True)
|
||||
ref_gest_first_name = models.CharField('Prénom', max_length=100, null=True, blank=True)
|
||||
ref_gest_last_name = models.CharField('Nom', max_length=100, null=True, blank=True)
|
||||
ref_gest_grade = models.CharField('Grade', max_length=100, null=True, blank=True)
|
||||
ref_gest_niv_org = models.CharField('Niveau d\'org', max_length=100, null=True, blank=True)
|
||||
ref_gest_org = models.ForeignKey(RefOrg, verbose_name='Référentiel organique', related_name=RefOrg.Cols.REL_REF_GEST, on_delete=models.CASCADE,
|
||||
db_column='ref_gest_org_id', null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Référentiel gestionnaire'
|
||||
verbose_name_plural = 'Référentiels gestionnaires'
|
||||
|
||||
|
||||
# Modèle du référentiel sous-vivier filière
|
||||
class RefSvFil(models.Model):
|
||||
"""Modèle de référentiel sous-vivier filière
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_SOUS_VIVIER = 'sous_vivier'
|
||||
|
||||
|
||||
ref_sv_fil_code = models.CharField('Code niveau d\'org', max_length=100, null=True, blank=True)
|
||||
ref_sv_fil_dom_gest = models.CharField('Domaine de gestion', max_length=100, null=True, blank=True)
|
||||
ref_sv_fil_dom = models.CharField('Domaine', max_length=100, null=True, blank=True)
|
||||
ref_sv_fil_fil = models.CharField('Filière', max_length=100, null=True, blank=True)
|
||||
ref_sv_fil_cat = models.CharField('Catégorie', max_length=100, null=True, blank=True)
|
||||
sous_vivier = models.ForeignKey(SousVivier, verbose_name='Sous-vivier', on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"ref_sv_fil_code": self.ref_sv_fil_code,
|
||||
"ref_sv_fil_dom_gest": self.ref_sv_fil_dom_gest,
|
||||
"ref_sv_fil_dom": self.ref_sv_fil_dom,
|
||||
"ref_sv_fil_fil": self.ref_sv_fil_fil,
|
||||
"ref_sv_fil_cat": self.ref_sv_fil_cat,
|
||||
}
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Référentiel sous-vivier/filière'
|
||||
verbose_name_plural = 'Référentiels sous-viviers/filières'
|
||||
18
backend-django/backend/models/pam.py
Normal file
18
backend-django/backend/models/pam.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
|
||||
class PAM(models.Model):
|
||||
"""Modèle pour Le PAM
|
||||
"""
|
||||
pam_id = models.CharField(primary_key=True,max_length=100, default=False)
|
||||
pam_date = models.CharField(max_length=100)
|
||||
pam_libelle = models.CharField(max_length=100)
|
||||
pam_statut = models.CharField(max_length=100)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"pam_id": self.pam_id,
|
||||
"pam_libelle": self.pam_libelle,
|
||||
"pam_statut": self.pam_statut,
|
||||
}
|
||||
189
backend-django/backend/models/poste.py
Normal file
189
backend-django/backend/models/poste.py
Normal file
@@ -0,0 +1,189 @@
|
||||
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||
from django.db import models
|
||||
|
||||
from .administre import Administre
|
||||
from .commun import NiveauFonctionnelChoices, SpecifiqueChoices
|
||||
from .competence import Competence
|
||||
from .domaine import Domaine
|
||||
from .filiere import Filiere
|
||||
from .fonction import Fonction
|
||||
from .formation_emploi import FormationEmploi
|
||||
from .sous_vivier import SousVivier
|
||||
from .pam import PAM
|
||||
|
||||
class AvisPosteChoices(models.TextChoices):
|
||||
"""
|
||||
[Poste] choix pour les avis
|
||||
attributs supplémentaires :
|
||||
- calc_enabled : est-ce que cet avis permet le calcul ?
|
||||
- dec_enabled : est-ce que cet avis permet de créer une décision ?
|
||||
"""
|
||||
|
||||
# (valeur, calc_enabled, dec_enabled), libellé
|
||||
P1 = ('P1', True, True), 'Priorité 1'
|
||||
P2 = ('P2', True, True), 'Priorité 2'
|
||||
P3 = ('P3', True, True), 'Priorité 3'
|
||||
P4 = ('P4', True, True), 'Priorité 4'
|
||||
GELE = ('GELE', False, False), 'Gelé'
|
||||
NON_ETUDIE = ('NON_ETUDIE', False, False), 'Non étudié'
|
||||
|
||||
def __new__(cls, value):
|
||||
obj = str.__new__(cls, value[0])
|
||||
obj._value_ = value[0]
|
||||
obj.calc_enabled = value[1]
|
||||
obj.dec_enabled = value[2]
|
||||
return obj
|
||||
|
||||
|
||||
class DirectCommissionneChoices(models.TextChoices):
|
||||
"""
|
||||
[Poste] choix pour direct/commissionné
|
||||
"""
|
||||
|
||||
DIRECT = 'DIRECT', 'Direct'
|
||||
COMMISSIONNE = 'COMMISSIONNE', 'Commissionné'
|
||||
|
||||
|
||||
class PropositionsArmementChoices(models.TextChoices):
|
||||
"""
|
||||
[Poste] choix pour les propositions d'armement
|
||||
"""
|
||||
|
||||
PROPOSE = 'PROPOSE', 'Propositions'
|
||||
VALIDE = 'VALIDE', 'Propositions validées'
|
||||
|
||||
|
||||
class Poste(models.Model):
|
||||
"""
|
||||
Modèle des postes
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'p_id'
|
||||
CATEGORIE = 'p_categorie'
|
||||
FONCTION = 'p_code_fonction'
|
||||
NIVEAU_FONCTIONNEL = 'p_nf'
|
||||
# relations many-to-many
|
||||
M2M_COMPETENCES = 'competences'
|
||||
M2M_SOUS_VIVIERS = 'sous_viviers'
|
||||
M2M_PAM = 'p_pam'
|
||||
# relations one-to-many
|
||||
O2M_DECISION = 'decisions'
|
||||
# relations one-to-one ou many-to-one
|
||||
REL_ADMINISTRE = 'p_administre'
|
||||
REL_DOMAINE = 'p_domaine'
|
||||
REL_FILIERE = 'p_filiere'
|
||||
REL_FONCTION = 'fonction'
|
||||
REL_FORMATION_EMPLOI = 'formation_emploi'
|
||||
|
||||
|
||||
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||
|
||||
p_id = models.CharField(max_length=100, primary_key=True)
|
||||
p_pam = models.ManyToManyField(PAM, through='Postes_Pams')
|
||||
p_annee = models.CharField(max_length=100, default=False)
|
||||
p_administre = models.ForeignKey(Administre, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
fonction = models.ForeignKey(Fonction, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
sous_viviers = models.ManyToManyField(SousVivier, related_name=SousVivier.Cols.M2M_POSTES, blank=True)
|
||||
formation_emploi = models.ForeignKey(FormationEmploi, on_delete=models.SET_NULL, related_name=FormationEmploi.Cols.REL_POSTE, null=True, blank=True)
|
||||
competences = models.ManyToManyField(Competence, blank=True)
|
||||
p_domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
p_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
p_fonction = models.CharField(max_length=100, null=True, blank=True)
|
||||
p_code_fonction = models.CharField(max_length=100, null=True, blank=True)
|
||||
p_nf = models.CharField(max_length=100, null=True, blank=True, choices=NiveauFonctionnelChoices.choices)
|
||||
p_categorie = models.CharField(max_length=100, null=True, blank=True)
|
||||
p_dep = models.CharField('Département', max_length=2, null=True, blank=True)
|
||||
p_liste_id_marques = models.CharField('Marques PAM en cours', max_length=100, null=True, blank=True)
|
||||
p_eip = models.CharField(max_length=100, null=True, blank=True)
|
||||
p_avis = models.CharField(max_length=100, choices=AvisPosteChoices.choices, null=True, default=AvisPosteChoices.NON_ETUDIE)
|
||||
p_avis_fe = models.CharField(max_length=100, choices=AvisPosteChoices.choices, null=True, default=AvisPosteChoices.NON_ETUDIE)
|
||||
p_notes_gestionnaire = models.TextField(null=True, blank=True)
|
||||
p_notes_partagees = models.TextField(null=True, blank=True)
|
||||
p_ciat = models.BooleanField('CIAT', default=False, null=True)
|
||||
p_specifique = models.CharField('PPE / SHM / ITD', max_length=250, choices=SpecifiqueChoices.choices, null=True, blank=True)
|
||||
p_direct_commissionne = models.CharField('Direct Commissionne', max_length=100, choices=DirectCommissionneChoices.choices, null=True, blank=True)
|
||||
propositions_armement = models.CharField("propositions d'armement", db_column='p_propositions_armement', max_length=10, choices=PropositionsArmementChoices.choices, null=True, blank=True)
|
||||
p_priorisation_pcp = models.TextField('Priorisation PCP', null=True, blank=True)
|
||||
p_nfs = models.CharField('Domaine de gestion (BVT)', max_length=100, null=True, blank=True)
|
||||
p_itd_cellule = models.CharField(max_length=100, null=True, blank=True)
|
||||
p_itd_affecte = models.BooleanField(null=True, blank=True)
|
||||
|
||||
|
||||
@property
|
||||
def p_poids_competences(self):
|
||||
"le poids des competences"
|
||||
if self.p_specifique:
|
||||
return 65 if self.competences.exists() else 0
|
||||
else:
|
||||
return 60 if self.competences.exists() else 0
|
||||
|
||||
@property
|
||||
def p_poids_filiere(self):
|
||||
"le poids des filieres"
|
||||
if self.p_specifique:
|
||||
return 0 if self.competences.exists() else 20
|
||||
else:
|
||||
return 10 if self.competences.exists() else 75
|
||||
|
||||
|
||||
@property
|
||||
def p_poids_nf(self):
|
||||
"le poids des niveaux fonctionnels"
|
||||
if self.p_specifique:
|
||||
return 35 if self.competences.exists() else 80
|
||||
else:
|
||||
return 30 if self.competences.exists() else 25
|
||||
|
||||
@property
|
||||
def p_poids_filiere_nf_competences(self):
|
||||
"le poids des filieres, nf et competences"
|
||||
if self.p_specifique:
|
||||
return (0, 35, 65) if self.competences.exists() else (20, 80, 0)
|
||||
else:
|
||||
return (10, 30, 60) if self.competences.exists() else (75, 25, 0)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_p_direct_commissionne_valid',
|
||||
check=models.Q(p_direct_commissionne__in=DirectCommissionneChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_p_nf_valid',
|
||||
check=models.Q(p_nf__in=NiveauFonctionnelChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_p_specifique_valid',
|
||||
check=models.Q(p_specifique__in=SpecifiqueChoices.values)
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name='%(app_label)s_%(class)s_propositions_armement_valid',
|
||||
check=models.Q(propositions_armement__in=PropositionsArmementChoices.values)
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class Postes_Pams(models.Model):
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'id'
|
||||
REL_PAM = 'p_pam'
|
||||
REL_POSTE = 'poste'
|
||||
O2M_DECISION = 'decisions'
|
||||
|
||||
|
||||
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||
|
||||
id = models.CharField(primary_key=True, max_length=100)
|
||||
p_pam = models.ForeignKey(PAM, on_delete = models.CASCADE, related_name='p_pam')
|
||||
poste = models.ForeignKey(Poste, on_delete=models.CASCADE, related_name='poste')
|
||||
p_avis_pam = models.CharField(max_length=100, choices=AvisPosteChoices.choices, null=True, default=AvisPosteChoices.NON_ETUDIE)
|
||||
p_avis_fe_pam = models.CharField(max_length=100, choices=AvisPosteChoices.choices, null=True, default=AvisPosteChoices.NON_ETUDIE)
|
||||
p_direct_commissionne_pam = models.CharField('Direct Commissionne', max_length=100, choices=DirectCommissionneChoices.choices, null=True, blank=True)
|
||||
p_notes_gestionnaire_pam = models.TextField(null=True, blank=True)
|
||||
p_priorisation_pcp_pam = models.TextField('Priorisation PCP', null=True, blank=True)
|
||||
info_reo = models.CharField(max_length=100, default="")
|
||||
39
backend-django/backend/models/sous_vivier.py
Normal file
39
backend-django/backend/models/sous_vivier.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from .user import CustomUser
|
||||
|
||||
|
||||
class SousVivier(models.Model):
|
||||
"""
|
||||
Modèle des sous-viviers de militaires et de postes
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
PK = 'sv_id'
|
||||
LIBELLE = 'sv_libelle'
|
||||
DOMAINE = 'sv_dom'
|
||||
FILIERE = 'sv_fil'
|
||||
CATEGORIE = 'sv_cat'
|
||||
# relations many-to-many
|
||||
M2M_GESTIONNAIRES = 'gestionnaires'
|
||||
M2M_POSTES = 'poste'
|
||||
|
||||
sv_id = models.CharField(max_length=100, primary_key=True)
|
||||
sv_libelle = models.CharField(max_length=100)
|
||||
gestionnaires = models.ManyToManyField(CustomUser, related_name=CustomUser.Cols.M2M_SOUS_VIVIERS, blank=True)
|
||||
sv_dom = models.CharField(max_length=100, null=True, blank=True)
|
||||
sv_fil = models.CharField(max_length=100, null=True, blank=True)
|
||||
sv_cat = models.CharField(max_length=100, null=True, blank=True)
|
||||
|
||||
def as_dict(self):
|
||||
mails = []
|
||||
for i in self.gestionnaires.all() :
|
||||
mails.append(i.email)
|
||||
return {
|
||||
"sv_id": self.sv_id,
|
||||
"sv_libelle": self.sv_libelle,
|
||||
"gestionnaires": mails,
|
||||
}
|
||||
36
backend-django/backend/models/user.py
Normal file
36
backend-django/backend/models/user.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.auth.validators import UnicodeUsernameValidator
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class CustomUser(AbstractUser):
|
||||
"""
|
||||
Modèle de l'utilisateur de l'application
|
||||
"""
|
||||
|
||||
class Cols():
|
||||
""" Constantes pour les noms de colonnes """
|
||||
|
||||
# relations one-to-many
|
||||
O2M_GROUPE_FE_PCP = 'pcpfegroupe_set'
|
||||
O2M_SOUS_VIVIER = 'sousvivier_set'
|
||||
M2M_SOUS_VIVIERS = 'sous_vivier'
|
||||
# relations many-to-many
|
||||
M2M_FORMATION_EMPLOIS = 'formation_emploi'
|
||||
|
||||
# id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
|
||||
id = models.IntegerField(auto_created=True, primary_key=True, verbose_name='Id SAP')
|
||||
password = models.CharField(max_length=128, verbose_name='password')
|
||||
last_login = models.DateTimeField(blank=True, null=True, verbose_name='last login')
|
||||
is_superuser = models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')
|
||||
username = models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[UnicodeUsernameValidator()], verbose_name='username')
|
||||
first_name = models.CharField(null=True, blank=True, max_length=150, verbose_name='first name')
|
||||
last_name = models.CharField(null=True, blank=True, max_length=150, verbose_name='last name')
|
||||
email = models.EmailField(null=True, blank=True, max_length=254, verbose_name='email address')
|
||||
is_staff = models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')
|
||||
is_active = models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')
|
||||
date_joined = models.DateTimeField(default=timezone.now, verbose_name='date joined')
|
||||
grade = models.CharField(blank=True, max_length=100, null=True)
|
||||
|
||||
REQUIRED_FIELDS = ['id']
|
||||
13
backend-django/backend/paginations.py
Normal file
13
backend-django/backend/paginations.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Ce fichier permet de modifier la façon dont les grands ensembles de résultats sont divisés en plusieurs pages de données
|
||||
"""
|
||||
|
||||
from rest_framework import pagination
|
||||
|
||||
|
||||
class HeavyDataPagination(pagination.PageNumberPagination):
|
||||
"""Cette classe modifie des aspects particuliers du style de pagination, elle remplace les classes de pagination et définit les attributs à modifier.
|
||||
"""
|
||||
page_size = 2
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 10000
|
||||
page_query_param = 'page'
|
||||
327
backend-django/backend/reporting.py
Normal file
327
backend-django/backend/reporting.py
Normal file
@@ -0,0 +1,327 @@
|
||||
"""Ce fichier contient les utilitaires du reporting"""
|
||||
# import des pré requis
|
||||
from ast import For
|
||||
import json
|
||||
from datetime import date
|
||||
from logging import getLevelName
|
||||
import time
|
||||
import pandas as pd
|
||||
from django.utils import timezone
|
||||
from django.db.models import Sum
|
||||
from .models import Administre, Domaine, FMOB, Fonction, FormationEmploi, Garnison, Grade, Poste, Notation, \
|
||||
PreferencesListe, Marque, MarquesGroupe, Filiere, Decision, SousVivier, SousVivierAssociation, \
|
||||
AvisPosteChoices as AvisPoste
|
||||
from .utils_insertion import insert_Notation
|
||||
import numpy as np
|
||||
|
||||
def reporting_taux_armement_pcp(fe_id, f_id, d_id, nf, categorie):
|
||||
"""
|
||||
Renvoie les indicateurs figurant dans la vue Taux Armement FE pour les PCP
|
||||
|
||||
:type fe_id: list
|
||||
:param fe_id: codes CREDO des FE sur lesquelles on filtre
|
||||
|
||||
:type f_id: list
|
||||
:param f_id: trigrammes des filières sur lesquelles on filtre
|
||||
|
||||
:type d_id: list
|
||||
:param d_id: trigrammes des domaines sur lesquels on filtre
|
||||
|
||||
:type nf: list
|
||||
:param nf: niveau fonctionnel sur lesquels on filtre
|
||||
|
||||
:type categorie: list
|
||||
:param categorie: catégorie sur lesquelles on filtre
|
||||
|
||||
|
||||
:return: - **nb_militaires_actuel** (*int*): Nombre de militaires présents dans la/les FE selon les filtres cochés.
|
||||
- **nb_postes_actuel** (*int*): Nombre de postes présents dans la/les FE selon les filtres cochés.
|
||||
- **ecart_actuel** (*int*): Différence entre le nombre de postes et de militaires présents actuellement.
|
||||
- **taux_armement_actuel** (*int*): Taux d'armement de la/les FE selon les filtres cochés.
|
||||
- **nb_militaires_entrants** (*int*): Nombre de militaires entrants dans la FE selon les filtres cochés.
|
||||
- **nb_militaires_sortants** (*int*): Nombre de militaires sortants de la FE selon les filtres cochés.
|
||||
- **nb_militaires_projete** (*int*): Nombre de militaires attendus dans la FE après les mobilités selon les filtres cochés.
|
||||
- **taux_armement_projete** (*int*): Taux d'armement projete selon les filtres cochés.
|
||||
- **taux_armement_cible** (*int*): Taux d'armement cible pour la FE choisie selon la catégorie choisie, None si plusieurs catégories ont été sélectionnées.
|
||||
|
||||
"""
|
||||
taux_armement_cible = None
|
||||
if len(fe_id) == 1 and len(categorie) == 1:
|
||||
fe = FormationEmploi.objects.get(fe_code__in=fe_id)
|
||||
if 'MDR' in categorie:
|
||||
taux_armement_cible = fe.fe_taux_armement_cible_mdr
|
||||
if 'OFF' in categorie:
|
||||
taux_armement_cible = fe.fe_taux_armement_cible_off
|
||||
if 'SOFF' in categorie:
|
||||
taux_armement_cible = fe.fe_taux_armement_cible_soff
|
||||
|
||||
nb_militaires_actuel = Administre.objects.filter(formation_emploi_id__in=fe_id, a_domaine__in=d_id, a_filiere__in=f_id, a_nf__in=nf, a_categorie__in=categorie).count() or 0
|
||||
nb_militaires_sortants = Administre.objects.filter(a_domaine_id__in=d_id, a_filiere_id__in=f_id, a_nf__in=nf, a_categorie__in=categorie, formation_emploi_id__in=fe_id, a_statut_pam__in=['A_MUTER', 'NON_DISPONIBLE', 'PARTANT']).count() or 0
|
||||
nb_militaires_entrants = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__formation_emploi_id__in=fe_id).count() or 0
|
||||
nb_militaires_projete = nb_militaires_actuel + nb_militaires_entrants - nb_militaires_sortants
|
||||
nb_postes_actuel = Poste.objects.filter(formation_emploi_id__in=fe_id, p_filiere__in=f_id, p_domaine__in=d_id, p_nf__in=nf, p_categorie__in=categorie).count() or 0
|
||||
if nb_postes_actuel == 0:
|
||||
nb_postes_actuel = 0
|
||||
taux_armement_actuel = 0
|
||||
taux_armement_projete = 0
|
||||
else:
|
||||
ecart_actuel = nb_postes_actuel - nb_militaires_actuel
|
||||
ecart_projete = nb_postes_actuel - nb_militaires_projete
|
||||
taux_armement_actuel = nb_militaires_actuel / nb_postes_actuel * 100
|
||||
taux_armement_projete = nb_militaires_projete / nb_postes_actuel * 100
|
||||
|
||||
ecart_actuel = nb_postes_actuel - nb_militaires_actuel
|
||||
ecart_projete = nb_postes_actuel - nb_militaires_projete
|
||||
|
||||
return nb_militaires_actuel, nb_postes_actuel, ecart_actuel, taux_armement_actuel, nb_militaires_entrants, nb_militaires_sortants, nb_militaires_projete, taux_armement_projete, taux_armement_cible
|
||||
|
||||
def reporting_taux_armement_gestionnaire(fe_id, f_id, d_id, nf, categorie, sv_id):
|
||||
"""
|
||||
Renvoie les indicateurs figurant dans la vue Taux Armement FE pour les gestionnaires
|
||||
|
||||
:type fe_id: list
|
||||
:param fe_id: codes CREDO des FE sur lesquelles on filtre
|
||||
|
||||
:type f_id: list
|
||||
:param f_id: trigrammes des filières sur lesquelles on filtre
|
||||
|
||||
:type d_id: list
|
||||
:param d_id: trigrammes des domaines sur lesquels on filtre
|
||||
|
||||
:type nf: list
|
||||
:param nf: niveau fonctionnel sur lesquels on filtre
|
||||
|
||||
:type sv_id: chaine de caractères
|
||||
:param categorie: sous-vivier du gestionnaire
|
||||
|
||||
:type categorie: list
|
||||
:param categorie: catégorie sur lesquelles on filtre
|
||||
|
||||
|
||||
:return: - **nb_militaires_actuel** (*int*): Nombre de militaires présents dans la/les FE selon les filtres cochés.
|
||||
- **nb_postes_actuel** (*int*): Nombre de postes présents dans la/les FE selon les filtres cochés.
|
||||
- **ecart_actuel** (*int*): Différence entre le nombre de postes et de militaires présents actuellement.
|
||||
- **taux_armement_actuel** (*int*): Taux d'armement de la/les FE selon les filtres cochés.
|
||||
- **nb_militaires_entrants** (*int*): Nombre de militaires entrants dans la FE selon les filtres cochés.
|
||||
- **nb_militaires_sortants** (*int*): Nombre de militaires sortants de la FE selon les filtres cochés.
|
||||
- **nb_militaires_projete** (*int*): Nombre de militaires attendus dans la FE après les mobilités selon les filtres cochés.
|
||||
- **taux_armement_projete** (*int*): Taux d'armement projete selon les filtres cochés.
|
||||
- **taux_armement_cible** (*int*): Taux d'armement cible pour la FE choisie selon la catégorie choisie, None si plusieurs catégories ont été sélectionnées.
|
||||
|
||||
|
||||
"""
|
||||
taux_armement_cible = None
|
||||
if len(fe_id) == 1 and len(categorie) == 1:
|
||||
fe = FormationEmploi.objects.get(fe_code__in=fe_id)
|
||||
if 'MDR' in categorie:
|
||||
taux_armement_cible = fe.fe_taux_armement_cible_mdr
|
||||
if 'OFF' in categorie:
|
||||
taux_armement_cible = fe.fe_taux_armement_cible_off
|
||||
if 'SOFF' in categorie:
|
||||
taux_armement_cible = fe.fe_taux_armement_cible_soff
|
||||
|
||||
nb_militaires_actuel = Administre.objects.filter(sous_vivier_id=sv_id, formation_emploi_id__in=fe_id, a_domaine__in=d_id, a_filiere__in=f_id, a_nf__in=nf, a_categorie__in=categorie).count() or 0
|
||||
nb_militaires_sortants = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_id__in=d_id, a_filiere_id__in=f_id, a_nf__in=nf, a_categorie__in=categorie, formation_emploi_id__in=fe_id, a_statut_pam__in=['A_MUTER', 'NON_DISPONIBLE', 'PARTANT']).count() or 0
|
||||
nb_militaires_entrants = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__formation_emploi_id__in=fe_id).count() or 0
|
||||
nb_militaires_projete = nb_militaires_actuel + nb_militaires_entrants - nb_militaires_sortants
|
||||
nb_postes_actuel = Poste.objects.filter(sous_viviers=sv_id, formation_emploi_id__in=fe_id, p_filiere__in=f_id, p_domaine__in=d_id, p_nf__in=nf, p_categorie__in=categorie).count() or 0
|
||||
if nb_postes_actuel == 0:
|
||||
nb_postes_actuel = 0
|
||||
taux_armement_actuel = 0
|
||||
taux_armement_projete = 0
|
||||
else:
|
||||
ecart_actuel = nb_postes_actuel - nb_militaires_actuel
|
||||
ecart_projete = nb_postes_actuel - nb_militaires_projete
|
||||
taux_armement_actuel = nb_militaires_actuel / nb_postes_actuel * 100
|
||||
taux_armement_projete = nb_militaires_projete / nb_postes_actuel * 100
|
||||
|
||||
ecart_actuel = nb_postes_actuel - nb_militaires_actuel
|
||||
ecart_projete = nb_postes_actuel - nb_militaires_projete
|
||||
|
||||
return nb_militaires_actuel, nb_postes_actuel, ecart_actuel, taux_armement_actuel, nb_militaires_entrants, nb_militaires_sortants, nb_militaires_projete, taux_armement_projete, taux_armement_cible
|
||||
|
||||
|
||||
def reporting_suivi_pam_admin(sv_id, f_id, d_id, nf, categorie):
|
||||
"""
|
||||
Renvoie les indicateurs figurant dans la vue Suivi PAM gestionnaire pour les administrés
|
||||
|
||||
:type f_id: list
|
||||
:param f_id: trigrammes des filières sur lesquelles on filtre
|
||||
|
||||
:type d_id: list
|
||||
:param d_id: trigrammes des domaines sur lesquels on filtre
|
||||
|
||||
:type nf: list
|
||||
:param nf: niveau fonctionnel sur lesquels on filtre
|
||||
|
||||
:type sv_id: chaine de caractères
|
||||
:param categorie: sous-vivier du gestionnaire
|
||||
|
||||
:type categorie: list
|
||||
:param categorie: catégorie sur lesquelles on filtre
|
||||
|
||||
|
||||
:return: - **nb_a_etudier** (*int*): Nombre de militaires à étudier selon les filtres cochés.
|
||||
- **nb_a_muter** (*int*): Nombre de militaires à muter selon les filtres cochés.
|
||||
- **nb_a_maintenir** (*int*): Nombre de militaires à maintenir selon les filtres cochés.
|
||||
- **nb_non_etudie_administres** (*int*): Nombre de militaires non étudiés selon les filtres cochés.
|
||||
- **nb_a_partant** (*int*): Nombre de militaires partant selon les filtres cochés.
|
||||
- **nb_a_non_dispo** (*int*): Nombre de militaires non disponibles selon les filtres cochés.
|
||||
- **nb_prepos_administres** (*int*): Nombre de militaires prépositionnés selon les filtres cochés.
|
||||
- **nb_pos_administres** (*int*): Nombre de militaires positionnés selon les filtres cochés.
|
||||
- **nb_omi_active_administres** (*int*): Nombre de militaires avec un OMI activé selon les filtres cochés.
|
||||
- **nb_omi_en_cours_administres** (*int*): Nombre de militaires avec un OMI en cours selon les filtres cochés.
|
||||
- **reste_a_realiser_administres** (*int*): Nombre de militaires à muter ou à étudier pour lesquels aucune décision n'a été prise.
|
||||
- **reste_a_realiser_a_etudier** (*int*): Nombre de militaires à étudier pour lesquels aucune décision n'a été prise.
|
||||
- **reste_a_realiser_a_muter** (*int*): Nombre de militaires à muter pour lesquels aucune décision n'a été prise.
|
||||
|
||||
|
||||
"""
|
||||
nb_militaires_fe = Administre.objects.filter(a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie).count()
|
||||
|
||||
a_etudier = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="A_ETUDIER")
|
||||
a_muter = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="A_MUTER")
|
||||
a_maintenir = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="A_MAINTENIR")
|
||||
non_etudie_administres = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="NON_ETUDIE")
|
||||
a_partant = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="PARTANT")
|
||||
a_non_dispo = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="NON_DISPONIBLE")
|
||||
nb_a_etudier = a_etudier.count() or 0
|
||||
nb_a_muter = a_muter.count() or 0
|
||||
nb_a_maintenir = a_maintenir.count() or 0
|
||||
nb_non_etudie_administres = non_etudie_administres.count() or 0
|
||||
nb_a_partant = a_partant.count() or 0
|
||||
nb_a_non_dispo = a_non_dispo.count() or 0
|
||||
nb_prepos_administres = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam__in=["A_ETUDIER", "A_MUTER"], de_decision='PREPOSITIONNE').count() or 0
|
||||
nb_pos_administres = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam__in=["A_ETUDIER", "A_MUTER"], de_decision='POSITIONNE').count() or 0
|
||||
nb_omi_active_administres = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam__in=["A_ETUDIER", "A_MUTER"], de_decision='OMI_ACTIVE').count() or 0
|
||||
nb_omi_en_cours_administres = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam__in=["A_ETUDIER", "A_MUTER"], de_decision='OMI_EN_COURS').count() or 0
|
||||
reste_a_realiser_a_etudier = nb_a_etudier - Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam="A_ETUDIER").count() or 0
|
||||
reste_a_realiser_a_muter = nb_a_muter - Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam="A_MUTER").count() or 0
|
||||
reste_a_realiser_administres = (nb_a_etudier + nb_a_muter) - (nb_prepos_administres + nb_pos_administres + nb_omi_en_cours_administres + nb_omi_active_administres)
|
||||
|
||||
return nb_a_etudier, nb_a_muter, nb_a_maintenir, nb_non_etudie_administres, nb_a_partant, nb_a_non_dispo, nb_prepos_administres, nb_pos_administres, nb_omi_active_administres, nb_omi_en_cours_administres, reste_a_realiser_administres, reste_a_realiser_a_etudier, reste_a_realiser_a_muter
|
||||
|
||||
|
||||
def reporting_suivi_pam_poste(sv_id, f_id, d_id, nf, categorie):
|
||||
"""
|
||||
Renvoie les indicateurs figurant dans la vue Suivi PAM gestionnaire pour les postes
|
||||
|
||||
:type f_id: list
|
||||
:param f_id: trigrammes des filières sur lesquelles on filtre
|
||||
|
||||
:type d_id: list
|
||||
:param d_id: trigrammes des domaines sur lesquels on filtre
|
||||
|
||||
:type nf: list
|
||||
:param nf: niveau fonctionnel sur lesquels on filtre
|
||||
|
||||
:type sv_id: chaine de caractères
|
||||
:param categorie: sous-vivier du gestionnaire
|
||||
|
||||
:type categorie: list
|
||||
:param categorie: catégorie sur lesquelles on filtre
|
||||
|
||||
:return: - **nb_p1** (*int*): Nombre de postes P1 selon les filtres cochés.
|
||||
- **nb_p2** (*int*): Nombre de postes P2 selon les filtres cochés.
|
||||
- **nb_p3** (*int*): Nombre de postes P3 selon les filtres cochés.
|
||||
- **nb_p4** (*int*): Nombre de postes P4 selon les filtres cochés.
|
||||
- **nb_gele** (*int*): Nombre de postes gelés selon les filtres cochés.
|
||||
- **nb_non_etudie_postes** (*int*): Nombre de postes non étudiés selon les filtres cochés.
|
||||
- **nb_prepos_postes** (*int*): Nombre de postes prépositionnés selon les filtres cochés.
|
||||
- **nb_pos_postes** (*int*): Nombre de postes positionnés selon les filtres cochés.
|
||||
- **nb_omi_active_postes** (*int*): Nombre de postes avec un OMI activé selon les filtres cochés.
|
||||
- **nb_omi_en_cours_postes** (*int*): Nombre de postes avec un OMI en cours selon les filtres cochés.
|
||||
- **reste_a_realiser_postes** (*int*): Nombre de postes de P1 à P4 pour lesquels aucune décision n'a été prise.
|
||||
- **reste_a_realiser_p1** (*int*): Nombre de postes P1 pour lesquels aucune décision n'a été prise.
|
||||
- **reste_a_realiser_p2** (*int*): Nombre de postes P2 pour lesquels aucune décision n'a été prise.
|
||||
- **reste_a_realiser_p3** (*int*): Nombre de postes P3 pour lesquels aucune décision n'a été prise.
|
||||
- **reste_a_realiser_p4** (*int*): Nombre de postes P4 pour lesquels aucune décision n'a été prise.
|
||||
|
||||
|
||||
|
||||
"""
|
||||
nb_p1 = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.P1).count() or 0
|
||||
nb_p2 = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.P2).count() or 0
|
||||
nb_p3 = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.P3).count() or 0
|
||||
nb_p4 = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.P4).count() or 0
|
||||
nb_gele = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.GELE).count() or 0
|
||||
nb_non_etudie_postes = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.NON_ETUDIE).count() or 0
|
||||
|
||||
|
||||
|
||||
nb_prepos_postes = Decision.objects.filter(poste__p_domaine__in=d_id, poste__p_filiere__in=f_id, poste__p_nf__in=nf, poste__p_categorie__in=categorie, poste__sous_viviers=sv_id, de_decision='PREPOSITIONNE').count() or 0
|
||||
nb_pos_postes = Decision.objects.filter(poste__p_domaine__in=d_id, poste__p_filiere__in=f_id, poste__p_nf__in=nf, poste__p_categorie__in=categorie, poste__sous_viviers=sv_id, de_decision='POSITIONNE').count() or 0
|
||||
nb_omi_active_postes = Decision.objects.filter(poste__p_domaine__in=d_id, poste__p_filiere__in=f_id, poste__p_nf__in=nf, poste__p_categorie__in=categorie, poste__sous_viviers=sv_id, de_decision='OMI_ACTIVE').count() or 0
|
||||
nb_omi_en_cours_postes = Decision.objects.filter(poste__p_domaine__in=d_id, poste__p_filiere__in=f_id, poste__p_nf__in=nf, poste__p_categorie__in=categorie, poste__sous_viviers=sv_id, de_decision='OMI_EN_COURS').count() or 0
|
||||
|
||||
reste_a_realiser_p1 = 0
|
||||
reste_a_realiser_p2 = 0
|
||||
reste_a_realiser_p3 = 0
|
||||
reste_a_realiser_p4 = 0
|
||||
reste_a_realiser_postes = (nb_p1 + nb_p2 + nb_p3 + nb_p4) - (nb_prepos_postes + nb_pos_postes + nb_omi_active_postes + nb_omi_en_cours_postes)
|
||||
if reste_a_realiser_postes > nb_p4:
|
||||
reste_a_realiser_p4 = nb_p4
|
||||
if reste_a_realiser_postes - nb_p4 > nb_p3:
|
||||
reste_a_realiser_p3 = nb_p3
|
||||
if reste_a_realiser_postes - nb_p4 - nb_p3 > nb_p2:
|
||||
reste_a_realiser_p2 = nb_p2
|
||||
if reste_a_realiser_postes - nb_p4 - nb_p3 - nb_p2 >= nb_p1:
|
||||
reste_a_realiser_p1 = nb_p1
|
||||
else:
|
||||
reste_a_realiser_p1 = reste_a_realiser_postes - nb_p4 - nb_p3 - nb_p2
|
||||
else:
|
||||
reste_a_realiser_p2 = reste_a_realiser_postes - nb_p4 - nb_p3
|
||||
reste_a_realiser_p1 = 0
|
||||
else:
|
||||
reste_a_realiser_p3 = reste_a_realiser_postes - nb_p4
|
||||
reste_a_realiser_p2 = 0
|
||||
reste_a_realiser_p1 = 0
|
||||
else:
|
||||
reste_a_realiser_p4 = nb_p4 - reste_a_realiser_postes
|
||||
reste_a_realiser_p2 = 0
|
||||
reste_a_realiser_p3 = 0
|
||||
reste_a_realiser_p4 = 0
|
||||
|
||||
|
||||
return nb_p1, nb_p2, nb_p3, nb_p4, nb_gele, nb_non_etudie_postes, reste_a_realiser_postes, reste_a_realiser_p1, reste_a_realiser_p2, reste_a_realiser_p3, reste_a_realiser_p4, nb_prepos_postes, nb_pos_postes, nb_omi_active_postes, nb_omi_en_cours_postes
|
||||
|
||||
# TODO : Supprimer cette fonction car non utilisée dans le code et utilise SousvivierAssociation
|
||||
def poste_vacant_vivier(sv_id, fe_id):
|
||||
"""
|
||||
Renvoie le nombre de postes vacants dans un sous-vivier pour une FE sélectionnée
|
||||
|
||||
:type sv_id: chaine de caractères
|
||||
:param sv_id: sous-vivier du gestionnaire
|
||||
|
||||
:type fe_id: chaine de caractères
|
||||
:param fe_id: FE sur laquelle le gestionnaire a filtré
|
||||
|
||||
:return: - **1** (*int*): Nombre de postes vacants pour la FE sélectionnée.
|
||||
|
||||
|
||||
|
||||
"""
|
||||
sva = SousVivierAssociation.objects.filter(sous_vivier_id=sv_id).first()
|
||||
categorie = sva.sva_categorie
|
||||
fe = FormationEmploi.objects.get(fe_code=fe_id)
|
||||
|
||||
if categorie == 'MDR':
|
||||
nb_poste_reevalue_fe = fe.fe_nb_poste_reevalue_mdr or 0
|
||||
nb_poste_vacant_fe = fe.fe_nb_poste_vacant_mdr or 0
|
||||
if categorie == 'OFF':
|
||||
nb_poste_reevalue_fe = fe.fe_nb_poste_reo_off or 0
|
||||
nb_poste_vacant_fe = fe.fe_nb_poste_vacant_off or 0
|
||||
if categorie == 'SOFF':
|
||||
nb_poste_reevalue_fe = fe.fe_nb_poste_reevalue_soff or 0
|
||||
nb_poste_vacant_fe = fe.fe_nb_poste_vacant_soff or 0
|
||||
|
||||
nb_poste_vivier_dans_fe = Poste.objects.filter(formation_emploi_id=fe_id, sous_viviers=sv_id).aggregate(Sum('p_id')) or 0
|
||||
if nb_poste_reevalue_fe == 0:
|
||||
return 0
|
||||
else:
|
||||
proportion = nb_poste_vivier_dans_fe / nb_poste_reevalue_fe
|
||||
nb_poste_vacant_vivier = proportion * nb_poste_vacant_fe
|
||||
if nb_poste_vacant_vivier > np.floor(nb_poste_vacant_vivier) + 0.5:
|
||||
return np.ceil(nb_poste_vacant_vivier)
|
||||
else:
|
||||
return np.floor(nb_poste_vacant_vivier)
|
||||
350
backend-django/backend/serializers.py
Normal file
350
backend-django/backend/serializers.py
Normal file
@@ -0,0 +1,350 @@
|
||||
"""Ce fichier contient des fonctions qui permettent de convertir des données complexes,
|
||||
telles que des querysets et des instances de modèle, en types de données Python qui peuvent ensuite
|
||||
être facilement rendus en JSON, XML ou d'autres types de contenu.
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
from .models import Competence, PcpFeGroupe, PreferencesListe, Poste, Notation, Administre, CustomUser, Grade, MarquesGroupe, \
|
||||
Garnison, \
|
||||
SousVivier, Domaine, Filiere, FMOB, Fonction, FormationEmploi, Marque, Decision
|
||||
|
||||
from . import constants
|
||||
|
||||
|
||||
class FileSVSerializer(serializers.Serializer):
|
||||
"""Cette classe ne sera utilisée que pour valider si le fichier SV est un vrai fichier.
|
||||
"""
|
||||
SV = serializers.FileField()
|
||||
|
||||
class Meta:
|
||||
fields = ['sv_file']
|
||||
|
||||
|
||||
class ScoringValidator(serializers.Serializer):
|
||||
""" pour interprêter et valider le contenu JSON """
|
||||
sous_vivier_id = serializers.CharField(max_length=100, required=True, allow_null=False, allow_blank=False)
|
||||
|
||||
class FileCompetenceSerializer(serializers.Serializer):
|
||||
"""Cette classe ne sera utilisée que pour valider si les fichiers competence_v1 et competence_v2 sont un vrais fichier.
|
||||
"""
|
||||
competence_v1 = serializers.FileField()
|
||||
competence_v2 = serializers.FileField()
|
||||
|
||||
class Meta:
|
||||
fields = ['competence_file_v1', 'competence_file_v2']
|
||||
|
||||
|
||||
class FileSerializer(serializers.Serializer):
|
||||
"""Cette classe ne sera utilisée que pour valider si les fichiers Donnees_BO_ADT, REO, ZPROPAF, FMOB, domaine_filiere, insee_mapin et diplomes sont des vrais fichiers.
|
||||
"""
|
||||
Donnees_BO_ADT = serializers.FileField(help_text="Fichier BO ADT 1.1")
|
||||
REO = serializers.FileField()
|
||||
ZPROPAF = serializers.FileField()
|
||||
FMOB = serializers.FileField()
|
||||
domaine_filiere = serializers.FileField()
|
||||
insee_maping = serializers.FileField()
|
||||
diplomes = serializers.FileField()
|
||||
|
||||
class Meta:
|
||||
fields = ['Donnees_BO_ADT', 'REO', 'ZPROPAF', 'FMOB', 'domaine_filiere', 'insee_maping', 'diplomes']
|
||||
|
||||
class AlimentationReferentielSerializer(serializers.Serializer):
|
||||
""" Valide que les données sont bien des fichiers. """
|
||||
|
||||
referentiel_fe = serializers.FileField(help_text="Référentiel FE")
|
||||
|
||||
class Meta:
|
||||
fields = ['referentiel_fe']
|
||||
|
||||
|
||||
class NotationSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets Notation en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Notation
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class GestionnaireSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets customusers en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
model = CustomUser
|
||||
fields = ['grade', 'first_name', 'last_name', 'email']
|
||||
|
||||
|
||||
class PreferencesListeSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets preferencesliste en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = PreferencesListe
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class FmobSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets Fmob en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = FMOB
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class SimpleDecisionSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets decision en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Decision
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class AdministreDecisionSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets decisions en type json contenant les informations mentionnées dans la variable fields.
|
||||
Et à l'ajout des variables poste_fe_code, poste_fe_libelle, poste_fe_garnison liés à la decision au json.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
poste_fe_code = serializers.CharField(source='poste.formation_emploi.fe_code', read_only=True)
|
||||
poste_fe_libelle = serializers.CharField(source='poste.formation_emploi.fe_libelle', read_only=True)
|
||||
poste_fe_garnison = serializers.CharField(source='poste.formation_emploi.garnison.gar_lieu', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Decision
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MarquesGroupeSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets marquegroupes en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = MarquesGroupe
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MarqueSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets marque en type json contenant les informations de marque et les champs de marquesgroupe liés à chaque marque.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
groupe_marques = MarquesGroupeSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Marque
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DomaineSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets domaines en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Domaine
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class FiliereSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets filières en type json contenant les informations de filière
|
||||
et les champs du domaine liés à chaque filiere.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
domaine = DomaineSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Filiere
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class GradeSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets grades en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Grade
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class GarnisonSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets garnisons en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Garnison
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class FormationEmploiSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets FormationEmploi en type json contenant les informations de FormationEmploi
|
||||
et le champs garnison lieu liés à chaque FormationEmploi.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
|
||||
class MereSerializer(serializers.ModelSerializer):
|
||||
"""Classe de représentation de la FE mère"""
|
||||
fe_code = serializers.ReadOnlyField()
|
||||
fe_libelle = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = FormationEmploi
|
||||
fields = ['fe_code', 'fe_libelle']
|
||||
|
||||
|
||||
garnison = serializers.CharField(source=f'{FormationEmploi.Cols.REL_GARNISON}.gar_lieu')
|
||||
mere = MereSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = FormationEmploi
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class FonctionSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets fonctions en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Fonction
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class SousVivierSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets SousVivier en type json contenant les informations du SousVivier
|
||||
et la/le gestionnaire responsable de chaque SousVivier.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
gestionnaire = GestionnaireSerializer()
|
||||
|
||||
class Meta:
|
||||
model = SousVivier
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class SousVivierAssociationSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la creation d'un json contenant les champs du SousVivier
|
||||
et les champs de filière liés à chaque SousVivier.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
filiere = FiliereSerializer()
|
||||
sous_vivier = SousVivierSerializer()
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
|
||||
class CompetenceSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets competences en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Competence
|
||||
fields = '__all__'
|
||||
|
||||
class PosteSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets postes en type json contenant les champs du poste
|
||||
et les champs de fonction, formation_emploi, sous_vivier et decisions liés à chaque poste. Cette classe va également ordonner le json par p_id et valider certaines variables.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
fonction = FonctionSerializer()
|
||||
formation_emploi = FormationEmploiSerializer()
|
||||
sous_vivier = SousVivierSerializer(allow_null=True)
|
||||
sous_vivier_id = serializers.IntegerField(write_only=True, allow_null=True)
|
||||
decisions = SimpleDecisionSerializer(many=True)
|
||||
p_nb_prepositionne = serializers.IntegerField(read_only=True)
|
||||
p_nb_positionne = serializers.IntegerField(read_only=True)
|
||||
p_nb_omi_en_cours = serializers.IntegerField(read_only=True)
|
||||
p_nb_omi_active = serializers.IntegerField(read_only=True)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Poste
|
||||
ordering = ['p_id']
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
|
||||
|
||||
class AdministreSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets administres en type json contenant les champs de l'administré
|
||||
et les champs de fonction, formation_emploi, sous_vivier, decisions, grade et Fmob liés à chaque administré. Cette classe va également ordonner le json par a_id_sap.
|
||||
Les variables qui seront affichées dans le json sont celles mentionnées dans la variable fields.
|
||||
"""
|
||||
a_id_sap = serializers.ReadOnlyField()
|
||||
formation_emploi = FormationEmploiSerializer(read_only=True)
|
||||
sous_vivier = SousVivierSerializer(read_only=True)
|
||||
sous_vivier_id = serializers.IntegerField(write_only=True, allow_null=True)
|
||||
fonction = FonctionSerializer(read_only=True)
|
||||
grade = GradeSerializer(read_only=True)
|
||||
decision = AdministreDecisionSerializer(read_only=True)
|
||||
fmobs = FmobSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Administre
|
||||
ordering = ['a_id_sap']
|
||||
fields = '__all__'
|
||||
|
||||
"""
|
||||
fields = ["a_id_sap",
|
||||
"formation_emploi",
|
||||
"formation_emploi_id",
|
||||
"fonction",
|
||||
"fonction_id",
|
||||
"sous_vivier",
|
||||
"sous_vivier_id",
|
||||
"grade",
|
||||
"decision",
|
||||
"fmobs",
|
||||
"a_liste_id_marques",
|
||||
"a_liste_id_competences",
|
||||
"a_nom",
|
||||
"a_prenom",
|
||||
"a_sexe",
|
||||
"a_fonction",
|
||||
"a_code_fonction",
|
||||
"a_domaine",
|
||||
"a_filiere",
|
||||
"a_nf",
|
||||
"a_categorie",
|
||||
"a_domaine_futur",
|
||||
"a_filiere_futur",
|
||||
"a_nf_futur",
|
||||
"a_bureau_gestion",
|
||||
"a_date_entree_service",
|
||||
"a_arme",
|
||||
"a_rg_origine_recrutement",
|
||||
"a_date_naissance",
|
||||
"a_liste_depts_souhaites",
|
||||
"a_interruption_service",
|
||||
"a_situation_fam",
|
||||
"a_date_rdc",
|
||||
"a_date_dernier_acr",
|
||||
"a_sap_conjoint",
|
||||
"a_statut_pam",
|
||||
"a_notes_gestionnaire",
|
||||
"a_notes_partagees"]
|
||||
read_only_fields = ("a_id_sap",)
|
||||
"""
|
||||
|
||||
|
||||
class DecisionSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets decisions en type json contenant les champs de decision
|
||||
et les champs de notation, poste et administre liés à chaque decision . Cette classe va également ordonner le json par de_date_decision.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
notation = NotationSerializer()
|
||||
poste = PosteSerializer()
|
||||
administre = AdministreSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Decision
|
||||
fields = '__all__'
|
||||
ordering = ['de_date_decision']
|
||||
|
||||
|
||||
|
||||
class PcpFeGroupeSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets PcpFeGroupe en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = PcpFeGroupe
|
||||
fields = '__all__'
|
||||
17
backend-django/backend/serializers/__init__.py
Normal file
17
backend-django/backend/serializers/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from .administre import *
|
||||
from .alimentation import *
|
||||
from .commun import *
|
||||
from .current_user import *
|
||||
from .decision import *
|
||||
from .domaine import *
|
||||
from .exportation_fichiers import *
|
||||
from .filiere import *
|
||||
from .fmob import *
|
||||
from .fonction import *
|
||||
from .formation_emploi import *
|
||||
from .grade import *
|
||||
from .initial import *
|
||||
from .notation import *
|
||||
from .pcp_fe_groupe import *
|
||||
from .sous_vivier import *
|
||||
from .suppression import *
|
||||
41
backend-django/backend/serializers/administre.py
Normal file
41
backend-django/backend/serializers/administre.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
from ..models import (Administre, Decision, DecisionChoices, DecisionTree,
|
||||
FormationEmploi, Poste, FMOB, Administres_Pams)
|
||||
from ..utils.attributes import safe_rgetattr
|
||||
from ..utils.decisions import KEY_CREATE, KEY_UPDATE, get_available_decisions
|
||||
from ..utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||
get_profiles_by_adm)
|
||||
from .commun import AssignmentState
|
||||
from .fmob import FmobSerializer
|
||||
from .fonction import FonctionSerializer
|
||||
from .formation_emploi import FormationEmploiSerializer
|
||||
from .grade import GradeSerializer
|
||||
from .sous_vivier import SousVivierSerializer
|
||||
|
||||
CTX_KEY_DECISIONS = 'decisions'
|
||||
CTX_KEY_PROFILES = 'profiles'
|
||||
|
||||
|
||||
class AdministreSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Cette classe sera responsable de la conversion des objets administres en type json contenant les champs de l'administré
|
||||
et les champs de fonction, formation_emploi, sous_vivier, decisions, grade et Fmob liés à chaque administré. Cette classe va également ordonner le json par a_id_sap.
|
||||
Les variables qui seront affichées dans le json sont celles mentionnées dans la variable fields.
|
||||
"""
|
||||
|
||||
|
||||
a_id_sap = serializers.ReadOnlyField()
|
||||
formation_emploi = FormationEmploiSerializer(read_only=True)
|
||||
sous_vivier = SousVivierSerializer(read_only=True)
|
||||
sous_vivier_id = serializers.IntegerField(write_only=True, allow_null=True)
|
||||
fonction = FonctionSerializer(read_only=True)
|
||||
grade = GradeSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Administre
|
||||
ordering = [Administre.Cols.PK]
|
||||
fields = '__all__'
|
||||
86
backend-django/backend/serializers/alimentation.py
Normal file
86
backend-django/backend/serializers/alimentation.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class FileSVSerializer(serializers.Serializer):
|
||||
"""Cette classe ne sera utilisée que pour valider si le fichier SV est un vrai fichier.
|
||||
"""
|
||||
SV = serializers.FileField()
|
||||
|
||||
class Meta:
|
||||
fields = ['sv_file']
|
||||
|
||||
|
||||
class ChargementCompetencesSerializer(serializers.Serializer):
|
||||
"""
|
||||
Cette classe ne sera utilisée que pour valider si les fichiers competence_v1 et competence_v2 sont un vrais fichier.
|
||||
"""
|
||||
ref_skills = serializers.FileField(label="Référentiel des compétences", help_text="Fichier 'Référenciel des compétences_corrigé v2.xlsx'", required = False)
|
||||
specific_skills = serializers.FileField(label="Compétences particulières", help_text="Fichier 'COMPETENCES PARTICULIERES.xlsx'", required = False)
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class AlimentationSerializer(serializers.Serializer):
|
||||
"""Cette classe ne sera utilisée que pour valider si les fichiers Donnees_BO_ADT, REO, Ref Gest, Ref Org, Ref Sv Fil, Ref FE, FMOB, domaine_filiere, insee_mapin et diplomes sont des vrais fichiers.
|
||||
"""
|
||||
|
||||
Donnees_BO_ADT = serializers.FileField(label="Données BO", help_text="Fichier 'Données BO_Anonymes.xlsx'", required=False)
|
||||
REO = serializers.FileField(label="REO", help_text="Fichier 'REO2022_OGURE_NG.xlsx'", required=False)
|
||||
REO_PAM_SUIVANT = serializers.FileField(label="REO A + 1",help_text="Fichier REO2023_OGURE_NG.xlsx", required=False)
|
||||
REO_OCV = serializers.FileField(label="Requêtes OCV", help_text="Fichier 'Copie requete ocv postes et mise à poste v2.xlsx'", required=False)
|
||||
referentiel_gestionnaire = serializers.FileField(label="Référentiel de gestionnaires", help_text="Fichier 'Référentiel gestionnaires DRHAT.xlsx'", required=False)
|
||||
referentiel_organique = serializers.FileField(label="Référentiel organique", help_text="Fichier 'Référentiel organique DRHAT.xlsx'", required=False)
|
||||
refeferentiel_sous_vivier_filiere = serializers.FileField(label="Référentiel de sous-viviers/filières", help_text="Fichier 'Référentiel sous-viviers filières_V3.xlsx'", required=False)
|
||||
referentiel_fe = serializers.FileField(label="Référentiel FE", help_text="Fichier 'Référentiel FE anonymisé_V2.xlsx'", required=False)
|
||||
FMOB = serializers.FileField(label="Formulaire de mobilité", help_text="Fichier 'FMOB_FEMP modifié.xlsx'", required=False)
|
||||
FMOB_PAM_SUIVANT = serializers.FileField(label="Formulaire de mobilité A + 1", help_text="Fichier 'FMOB PAM A1 (2023).xlsx", required=False)
|
||||
domaine_filiere = serializers.FileField(label="Domaines - filières", help_text="Fichier 'DOMAINES FILIERES DRHAT.xlsx'", required=False)
|
||||
insee_maping = serializers.FileField(label="INSEE", help_text="Fichier 'INSEE.xlsx'", required=False)
|
||||
diplomes = serializers.FileField(label="Diplômes", help_text="Fichier 'Diplomes 2021.xlsx'", required=False)
|
||||
FUD = serializers.FileField(label="FUD", help_text="Fichier 'FUD.xlsx'", required=False)
|
||||
ref_zones_geo = serializers.FileField(label="Référentiel des zones géographiques", help_text="Fichier 'Zones_Geographique.xlsx'", required=False)
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
|
||||
|
||||
class AlimentationReferentielSerializer(serializers.Serializer):
|
||||
""" Valide que les données sont bien des fichiers. """
|
||||
|
||||
referentiel_fe = serializers.FileField(label='Référentiel FE', help_text='Référentiel FE anonymisé_V2.xlsx')
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class AlimentationZoneGeographiqueSerializer(serializers.Serializer):
|
||||
""" Valide que les données sont bien des fichiers. """
|
||||
|
||||
ref_zones_geo = serializers.FileField(label="Référentiel des zones géographiques", help_text="Fichier 'Zones_Geographique.xlsx'", required=False)
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class AlimentationCommentairesSerializer(serializers.Serializer):
|
||||
""" Valide que les données sont bien des fichiers. """
|
||||
|
||||
commentaires = serializers.FileField(label='Commentaires', help_text='20220427_NP_DRHAT_SDG_BCCM_com-OGURE.xlsx', required=False)
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class AlimentationRefsDroitSerializer(serializers.Serializer):
|
||||
""" Valide que les données sont bien des fichiers. """
|
||||
|
||||
referentiel_gestionnaire = serializers.FileField(label="Référentiel de gestionnaires", help_text="Fichier 'Référentiel gestionnaires DRHAT.xlsx'", required=False)
|
||||
referentiel_organique = serializers.FileField(label="Référentiel organique", help_text="Fichier 'Référentiel organique DRHAT.xlsx'", required=False)
|
||||
refeferentiel_sous_vivier_filiere = serializers.FileField(label="Référentiel de sous-viviers/filières", help_text="Fichier 'Référentiel sous-viviers filières_V3.xlsx'", required=False)
|
||||
referentiel_fe = serializers.FileField(label="Référentiel FE", help_text="Fichier 'Référentiel FE anonymisé_V2.xlsx'", required=False)
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
29
backend-django/backend/serializers/commun.py
Normal file
29
backend-django/backend/serializers/commun.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from enum import Enum
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class AssignmentState(str, Enum):
|
||||
""" état d'affectation BMOB (lié au changement de BMOB) """
|
||||
|
||||
# reste dans la même FE
|
||||
RESTANT = 'RESTANT'
|
||||
|
||||
# entre dans une FE du périmètre du PCP
|
||||
ENTRANT = 'ENTRANT'
|
||||
|
||||
# sort d'une FE du périmètre du PCP
|
||||
SORTANT = 'SORTANT'
|
||||
|
||||
# ENTRANT + SORTANT (mais pas RESTANT car ce n'est pas la même FE)
|
||||
ENTRANT_SORTANT = 'ENTRANT_SORTANT'
|
||||
|
||||
def __repr__(self):
|
||||
return "%s.%s" % (self.__class__.__name__, self._name_)
|
||||
|
||||
|
||||
class ChoicesSerializer(serializers.Serializer):
|
||||
""" Représente un élément qui hérite de Choices sous forme {code, libelle} """
|
||||
|
||||
code = serializers.ReadOnlyField(source='value')
|
||||
libelle = serializers.ReadOnlyField(source='label')
|
||||
72
backend-django/backend/serializers/current_user.py
Normal file
72
backend-django/backend/serializers/current_user.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import CustomUser, SousVivier, FormationEmploi
|
||||
from ..utils.permissions import KEY_READ, KEY_WRITE
|
||||
from .pcp_fe_groupe import PcpFeGroupeSerializer
|
||||
|
||||
CTX_KEY_SOUS_VIVIERS_SUPER = 'sous_viviers_super'
|
||||
CTX_KEY_SOUS_VIVIERS = 'sous_viviers'
|
||||
CTX_KEY_FORMATION_EMPLOIS = 'formation_emplois'
|
||||
CTX_KEY_PCP_FE_GROUPE = 'fe_groupe'
|
||||
CTX_KEY_PROFILES = 'profiles'
|
||||
|
||||
|
||||
class _SousVivierUserSerializer(serializers.ModelSerializer):
|
||||
""" Classe de représentation d'un sous-vivier de l'utilisateur """
|
||||
|
||||
class Meta:
|
||||
model = SousVivier
|
||||
exclude = ('gestionnaires',)
|
||||
|
||||
def to_representation(self, instance):
|
||||
res = super().to_representation(instance)
|
||||
ids_super = self.context.get(CTX_KEY_SOUS_VIVIERS_SUPER)
|
||||
if ids_super and instance.pk in ids_super:
|
||||
res['admin'] = True
|
||||
return res
|
||||
|
||||
|
||||
class _FormationEmploiUserSerializer(serializers.ModelSerializer):
|
||||
""" Classe de représentation d'un sous-vivier de l'utilisateur """
|
||||
|
||||
class Meta:
|
||||
model = FormationEmploi
|
||||
fields = [FormationEmploi.Cols.PK, FormationEmploi.Cols.LIBELLE, 'fe_mere_credo', 'fe_mere_la', 'gestionnaire_id']
|
||||
|
||||
gestionnaire_id = serializers.ReadOnlyField()
|
||||
|
||||
def to_representation(self, instance):
|
||||
res = super().to_representation(instance)
|
||||
return res
|
||||
|
||||
|
||||
class UserInfoSerializer(serializers.ModelSerializer):
|
||||
""" Gère la forme publique de l'utilisateur connecté. """
|
||||
|
||||
class Meta:
|
||||
model = CustomUser
|
||||
fields = ['id', 'username', 'first_name', 'last_name', 'email', 'grade', 'administre']
|
||||
|
||||
administre = serializers.ReadOnlyField(source='administre_id')
|
||||
|
||||
def to_representation(self, instance):
|
||||
res = super().to_representation(instance)
|
||||
|
||||
svs_super = self.context.get(CTX_KEY_SOUS_VIVIERS_SUPER) or ()
|
||||
svs = self.context.get(CTX_KEY_SOUS_VIVIERS) or ()
|
||||
fes = self.context.get(CTX_KEY_FORMATION_EMPLOIS) or ()
|
||||
all_svs = set(list(svs) + list(svs_super))
|
||||
all_fes = set(list(fes))
|
||||
svs_ids_specific_super = {sv.pk for sv in svs_super if sv not in svs}
|
||||
|
||||
res['sous_viviers'] = _SousVivierUserSerializer(all_svs, many=True, context={
|
||||
CTX_KEY_SOUS_VIVIERS_SUPER: svs_ids_specific_super
|
||||
}).data
|
||||
|
||||
res['formation_emplois'] = _FormationEmploiUserSerializer(all_fes, many=True).data
|
||||
|
||||
res['fe_groupe'] = PcpFeGroupeSerializer(self.context.get(CTX_KEY_PCP_FE_GROUPE) or ()).data
|
||||
|
||||
profiles = self.context.get(CTX_KEY_PROFILES) or {}
|
||||
res['profils'] = {'lecture': profiles.get(KEY_READ) or (), 'ecriture': profiles.get(KEY_WRITE) or ()}
|
||||
return res
|
||||
35
backend-django/backend/serializers/decision.py
Normal file
35
backend-django/backend/serializers/decision.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import Decision, DecisionChoices
|
||||
from .administre import AdministreSerializer
|
||||
from .initial import PosteSerializer
|
||||
from .notation import NotationSerializer
|
||||
|
||||
|
||||
class DecisionSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets decisions en type json contenant les champs de decision
|
||||
et les champs de notation, poste et administre liés à chaque decision . Cette classe va également ordonner le json par de_date_decision.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
notation = NotationSerializer()
|
||||
# poste = PosteSerializer()
|
||||
# administre = AdministreSerializer()
|
||||
de_date_decision = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = Decision
|
||||
fields = '__all__'
|
||||
ordering = ['de_date_decision']
|
||||
|
||||
|
||||
class CreateDecisionSerializer(serializers.ModelSerializer):
|
||||
""" Valide les données pour une création de décision """
|
||||
|
||||
administre_id = serializers.IntegerField(write_only=True)
|
||||
poste_id = serializers.CharField(max_length=100, write_only=True)
|
||||
de_decision = serializers.ChoiceField(choices=DecisionChoices.choices)
|
||||
delete_former = serializers.BooleanField(write_only=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Decision
|
||||
fields = ['administre_id', 'poste_id', 'de_decision', 'delete_former']
|
||||
12
backend-django/backend/serializers/domaine.py
Normal file
12
backend-django/backend/serializers/domaine.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models.domaine import Domaine
|
||||
|
||||
|
||||
class DomaineSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets domaines en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Domaine
|
||||
fields = '__all__'
|
||||
11
backend-django/backend/serializers/exportation_fichiers.py
Normal file
11
backend-django/backend/serializers/exportation_fichiers.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from tabnanny import verbose
|
||||
from rest_framework import serializers
|
||||
|
||||
class ExportationSerializer(serializers.Serializer):
|
||||
"""Cette classe ne sera utilisée que pour valider si les fichiers Donnees_BO_ADT, REO, Ref Gest, Ref Org, Ref Sv Fil, Ref FE, FMOB, domaine_filiere, insee_mapin et diplomes sont des vrais fichiers.
|
||||
"""
|
||||
FICHIERS_CHOICES = (("1", "Données BO"), ("2", "REO"),)
|
||||
fichier_exporte = serializers.ChoiceField(label='Fichier à exporter', help_text='Selectionnez le fichier que vous souhaitez exporter', choices=FICHIERS_CHOICES)
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
16
backend-django/backend/serializers/filiere.py
Normal file
16
backend-django/backend/serializers/filiere.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models.filiere import Filiere
|
||||
from .domaine import DomaineSerializer
|
||||
|
||||
|
||||
class FiliereSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets filières en type json contenant les informations de filière
|
||||
et les champs du domaine liés à chaque filiere.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
domaine = DomaineSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Filiere
|
||||
fields = '__all__'
|
||||
12
backend-django/backend/serializers/fmob.py
Normal file
12
backend-django/backend/serializers/fmob.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import FMOB
|
||||
|
||||
|
||||
class FmobSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets Fmob en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = FMOB
|
||||
fields = '__all__'
|
||||
12
backend-django/backend/serializers/fonction.py
Normal file
12
backend-django/backend/serializers/fonction.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import Fonction
|
||||
|
||||
|
||||
class FonctionSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets fonctions en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Fonction
|
||||
fields = '__all__'
|
||||
25
backend-django/backend/serializers/formation_emploi.py
Normal file
25
backend-django/backend/serializers/formation_emploi.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from rest_framework import serializers
|
||||
from ..models import FormationEmploi, Garnison
|
||||
|
||||
|
||||
class FormationEmploiSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets FormationEmploi en type json contenant les informations de FormationEmploi
|
||||
et le champs garnison lieu liés à chaque FormationEmploi.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
|
||||
class MereSerializer(serializers.ModelSerializer):
|
||||
"""Classe de représentation de la FE mère"""
|
||||
fe_code = serializers.ReadOnlyField()
|
||||
fe_libelle = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = FormationEmploi
|
||||
fields = ['fe_code', 'fe_libelle']
|
||||
|
||||
|
||||
mere = MereSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = FormationEmploi
|
||||
exclude = ('gestionnaires',)
|
||||
12
backend-django/backend/serializers/grade.py
Normal file
12
backend-django/backend/serializers/grade.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import Grade
|
||||
|
||||
|
||||
class GradeSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets grades en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Grade
|
||||
fields = '__all__'
|
||||
259
backend-django/backend/serializers/initial.py
Normal file
259
backend-django/backend/serializers/initial.py
Normal file
@@ -0,0 +1,259 @@
|
||||
"""Ce fichier contient des fonctions qui permettent de convertir des données complexes,
|
||||
telles que des querysets et des instances de modèle, en types de données Python qui peuvent ensuite
|
||||
être facilement rendus en JSON, XML ou d'autres types de contenu.
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
from .. import constants
|
||||
from ..models import (Competence, Decision, Garnison, Marque, MarquesGroupe,
|
||||
Poste, PAM, Administres_Pams, FormationEmploi, Postes_Pams,
|
||||
DecisionChoices, DecisionTree, FMOB)
|
||||
|
||||
from .commun import AssignmentState
|
||||
from ..utils.attributes import safe_rgetattr
|
||||
from ..utils.decisions import KEY_CREATE, KEY_UPDATE, get_available_decisions
|
||||
from ..utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||
get_profiles_by_adm)
|
||||
from .commun import ChoicesSerializer
|
||||
from .filiere import FiliereSerializer
|
||||
from .fonction import FonctionSerializer
|
||||
from .formation_emploi import FormationEmploiSerializer
|
||||
from .sous_vivier import SousVivierSerializer
|
||||
from .fmob import FmobSerializer
|
||||
from .administre import AdministreSerializer
|
||||
|
||||
|
||||
CTX_KEY_DECISIONS = 'decisions'
|
||||
CTX_KEY_PROFILES = 'profiles'
|
||||
|
||||
|
||||
class ScoringValidator(serializers.Serializer):
|
||||
""" pour interprêter et valider le contenu JSON """
|
||||
sous_vivier_id = serializers.CharField(max_length=100, required=True, allow_null=False, allow_blank=False)
|
||||
pam_id = serializers.CharField(max_length=100, required=True, allow_null=False, allow_blank=False)
|
||||
|
||||
class ScoringSelectifValidator(serializers.Serializer):
|
||||
""" pour interprêter et valider le contenu JSON """
|
||||
sous_vivier_id = serializers.CharField(max_length=100, required=True, allow_null=False, allow_blank=False)
|
||||
administre_id = serializers.ListField()
|
||||
poste_id = serializers.ListField()
|
||||
pam_id = serializers.CharField(max_length=100)
|
||||
|
||||
|
||||
class SimpleDecisionPosteSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets decision en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
grade = serializers.ReadOnlyField(source='administre.grade.gr_code')
|
||||
nom = serializers.ReadOnlyField(source='administre.a_nom')
|
||||
prenom = serializers.ReadOnlyField(source='administre.a_prenom')
|
||||
|
||||
class Meta:
|
||||
model = Decision
|
||||
fields = '__all__'
|
||||
|
||||
class AdministresPamsSerializer(serializers.ModelSerializer):
|
||||
|
||||
class DecisionLocalSerializer(serializers.ModelSerializer):
|
||||
"""Classe de représentation locale d'une décision"""
|
||||
|
||||
class PosteLocalSerializer(serializers.Serializer):
|
||||
"""Classe de représentation locale d'un poste"""
|
||||
|
||||
class FormationEmploiLocalSerializer(FormationEmploiSerializer):
|
||||
"""Classe de représentation locale d'une FE"""
|
||||
|
||||
class Meta:
|
||||
model = FormationEmploi
|
||||
fields = ['fe_code', 'fe_libelle', 'fe_mere_credo', 'fe_mere_la', 'fe_garnison_lieu', 'mere']
|
||||
|
||||
p_id = serializers.ReadOnlyField()
|
||||
formation_emploi = FormationEmploiLocalSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Poste
|
||||
fields = '__all__'
|
||||
|
||||
poste = PosteLocalSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Decision
|
||||
fields = '__all__'
|
||||
|
||||
administre = AdministreSerializer()
|
||||
pam_id = serializers.ReadOnlyField(source='pam.pam_id')
|
||||
pam_date = serializers.ReadOnlyField(source='pam.pam_date')
|
||||
pam_libelle = serializers.ReadOnlyField(source='pam.pam_libelle')
|
||||
pam_statut = serializers.ReadOnlyField(source='pam.pam_statut')
|
||||
fmobs = FmobSerializer()
|
||||
decision = DecisionLocalSerializer()
|
||||
profils = serializers.SerializerMethodField()
|
||||
decisions_possibles = serializers.SerializerMethodField()
|
||||
etat_fe_bmob = serializers.SerializerMethodField()
|
||||
|
||||
def get_decisions_possibles(self, obj: Administres_Pams) -> Tuple[DecisionChoices]:
|
||||
decisions_by_adm = self.context.get(CTX_KEY_DECISIONS) or get_available_decisions((obj,), user=self.context['request'].user)
|
||||
decisions = decisions_by_adm.get(obj.id) or {}
|
||||
return {'creation': decisions.get(KEY_CREATE) or (), 'maj': decisions.get(KEY_UPDATE) or ()}
|
||||
|
||||
def get_profils(self, obj: Administres_Pams) -> Tuple[Profiles]:
|
||||
profiles_by_adm = self.context.get(CTX_KEY_PROFILES) or get_profiles_by_adm(self.context['request'].user, obj)
|
||||
profiles = profiles_by_adm.get(obj.id) or {}
|
||||
return {'lecture': profiles.get(KEY_READ) or (), 'ecriture': profiles.get(KEY_WRITE) or ()}
|
||||
|
||||
def get_etat_fe_bmob(self, obj: Administres_Pams) -> Optional[AssignmentState]:
|
||||
decision = safe_rgetattr(obj, f'{Administres_Pams.Cols.REL_DECISION}.{Decision.Cols.STATUT}')
|
||||
if not decision or DecisionTree.ME not in DecisionChoices(decision).trees:
|
||||
return None
|
||||
|
||||
profiles_by_adm = self.context.get(CTX_KEY_PROFILES) or get_profiles_by_adm(self.context['request'].user, obj)
|
||||
profiles = (profiles_by_adm.get(obj.id) or {}).get(KEY_READ) or ()
|
||||
if Profiles.PCP in profiles:
|
||||
return AssignmentState.RESTANT
|
||||
|
||||
current = Profiles.PCP_ACTUEL in profiles
|
||||
future = Profiles.PCP_FUTUR in profiles
|
||||
if current and future:
|
||||
return AssignmentState.ENTRANT_SORTANT
|
||||
if current:
|
||||
return AssignmentState.SORTANT
|
||||
if future:
|
||||
return AssignmentState.ENTRANT
|
||||
return None
|
||||
|
||||
def to_representation(self, instance):
|
||||
ret = super().to_representation(instance)
|
||||
|
||||
ret.update(ret.pop('administre'))
|
||||
ret['a_statut_pam'] = ret.pop('a_statut_pam_annee')
|
||||
|
||||
if 'fmobs' in ret and ret['fmobs']:
|
||||
ret['fmobs'] = [ret['fmobs']]
|
||||
|
||||
return ret
|
||||
class Meta:
|
||||
model = Administres_Pams
|
||||
fields = '__all__'
|
||||
|
||||
class PAMSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets decision en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = PAM
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MarquesGroupeSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets marquegroupes en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = MarquesGroupe
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MarqueSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets marque en type json contenant les informations de marque et les champs de marquesgroupe liés à chaque marque.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
groupe_marques = MarquesGroupeSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Marque
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class GarnisonSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets garnisons en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Garnison
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class SousVivierAssociationSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la creation d'un json contenant les champs du SousVivier
|
||||
et les champs de filière liés à chaque SousVivier.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
filiere = FiliereSerializer()
|
||||
sous_vivier = SousVivierSerializer()
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
|
||||
class CompetenceSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets competences en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Competence
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PosteSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets postes en type json contenant les champs du poste
|
||||
et les champs de fonction, formation_emploi, sous_vivier et decisions liés à chaque poste. Cette classe va également ordonner le json par p_id et valider certaines variables.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
p_id = serializers.ReadOnlyField()
|
||||
fonction = FonctionSerializer()
|
||||
formation_emploi = FormationEmploiSerializer()
|
||||
sous_viviers = SousVivierSerializer(many=True)
|
||||
p_nb_prepositionne = serializers.IntegerField(read_only=True)
|
||||
p_nb_positionne = serializers.IntegerField(read_only=True)
|
||||
p_nb_omi_en_cours = serializers.IntegerField(read_only=True)
|
||||
p_nb_omi_active = serializers.IntegerField(read_only=True)
|
||||
p_poids_competences = serializers.IntegerField(read_only=True)
|
||||
p_poids_filiere = serializers.IntegerField(read_only=True)
|
||||
p_poids_nf = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Poste
|
||||
ordering = ['p_id']
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PostesPamsSerializer(serializers.ModelSerializer):
|
||||
|
||||
pam_id = serializers.ReadOnlyField(source='p_pam.pam_id')
|
||||
pam_date = serializers.ReadOnlyField(source='p_pam.pam_date')
|
||||
pam_libelle = serializers.ReadOnlyField(source='p_pam.pam_libelle')
|
||||
pam_statut = serializers.ReadOnlyField(source='p_pam.pam_statut')
|
||||
decisions = SimpleDecisionPosteSerializer()
|
||||
poste = PosteSerializer()
|
||||
|
||||
|
||||
def to_representation(self, instance):
|
||||
ret = super().to_representation(instance)
|
||||
ret.update(ret.pop('poste'))
|
||||
|
||||
if ret['decisions']:
|
||||
ret['decisions'] = [ret['decisions']]
|
||||
else :
|
||||
ret['decisions'] = []
|
||||
|
||||
|
||||
|
||||
return ret
|
||||
|
||||
class Meta:
|
||||
model = Postes_Pams
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
|
||||
class RefStatutPamChoicesSerializer(ChoicesSerializer):
|
||||
""" Représente un élément StatutPamChoices dans les données de référence """
|
||||
|
||||
inclusAlgo = serializers.ReadOnlyField(source='calc_enabled')
|
||||
affectationManuellePossible = serializers.ReadOnlyField(source='dec_enabled')
|
||||
|
||||
|
||||
class RefAvisPosteChoicesSerializer(RefStatutPamChoicesSerializer):
|
||||
""" Représente un élément AvisPosteChoices dans les données de référence """
|
||||
11
backend-django/backend/serializers/notation.py
Normal file
11
backend-django/backend/serializers/notation.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from rest_framework import serializers
|
||||
from ..models import Notation
|
||||
|
||||
|
||||
class NotationSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets Notation en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = Notation
|
||||
fields = '__all__'
|
||||
12
backend-django/backend/serializers/pcp_fe_groupe.py
Normal file
12
backend-django/backend/serializers/pcp_fe_groupe.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import PcpFeGroupe
|
||||
|
||||
|
||||
class PcpFeGroupeSerializer(serializers.ModelSerializer):
|
||||
"""Cette classe sera responsable de la conversion des objets PcpFeGroupe en type json contenant les informations mentionnées dans la variable fields.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
class Meta:
|
||||
model = PcpFeGroupe
|
||||
fields = '__all__'
|
||||
15
backend-django/backend/serializers/sous_vivier.py
Normal file
15
backend-django/backend/serializers/sous_vivier.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import SousVivier
|
||||
|
||||
|
||||
class SousVivierSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Cette classe sera responsable de la conversion des objets SousVivier en type json contenant les informations du SousVivier
|
||||
et la/le gestionnaire responsable de chaque SousVivier.
|
||||
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = SousVivier
|
||||
exclude = (SousVivier.Cols.M2M_GESTIONNAIRES,)
|
||||
9
backend-django/backend/serializers/suppression.py
Normal file
9
backend-django/backend/serializers/suppression.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
class SuppressionAdministresSerializer(serializers.Serializer):
|
||||
""" Valide que les données sont bien des fichiers. """
|
||||
|
||||
administres = serializers.FileField(label='Administrés à supprimer', help_text="Fichier 'Militaires à supprimer d\'OGURE.xlsx'", required=False)
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
198
backend-django/backend/signals.py
Normal file
198
backend-django/backend/signals.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""
|
||||
Les signaux permettent à certains expéditeurs d’avertir un ensemble de destinataires qu’une action a eu lieu.
|
||||
Dans ce projet, les signaux ont été utilisés pour mettre à jour les compteurs des postes appartenant à la FE lorsque le taux d'armement change en fonction de la catégorie.
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from . import constants
|
||||
from .models import FormationEmploi, Poste, SousVivierAssociation, Administre
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
|
||||
|
||||
def update_taux_mdr(sender, instance, **kwargs):
|
||||
"""Met à jour les compteurs des postes appartenant à la FE dont on change le taux d'armement cible pour la catégorie MDR
|
||||
|
||||
:type instance: objet du modèle FormationEmploi
|
||||
:param instance: FE dont on change le taux d'armement cible pour la catégorie MDR
|
||||
|
||||
|
||||
"""
|
||||
|
||||
taux = instance.fe_taux_armement_cible_mdr
|
||||
fe_id = instance.fe_code
|
||||
|
||||
postes = Poste.objects.filter(formation_emploi_id=fe_id, p_categorie='MDR')
|
||||
if (len(postes) > 0) and (taux is not None):
|
||||
taux = taux * 0.01
|
||||
for poste in postes:
|
||||
new_nb_reevalue = poste.p_nb_reevalue * taux
|
||||
new_nb_vacant = new_nb_reevalue - poste.p_nb_occupe
|
||||
if new_nb_reevalue >= poste.p_nb_reevalue:
|
||||
new_nb_p4 = poste.p_nb_p4 + new_nb_vacant - poste.p_nb_vacant
|
||||
poste.p_nb_p4 = new_nb_p4
|
||||
|
||||
else:
|
||||
if new_nb_reevalue > poste.p_nb_occupe:
|
||||
new_nb_non_etudie = poste.p_nb_non_etudie + poste.p_nb_reevalue - new_nb_reevalue
|
||||
poste.p_nb_non_etudie = new_nb_non_etudie
|
||||
delta_vacant = new_nb_vacant - poste.p_nb_vacant
|
||||
if delta_vacant <= poste.p_nb_p4:
|
||||
new_poste_p4 = poste.p_nb_p4 - delta_vacant
|
||||
poste.p_nb_p4 = new_poste_p4
|
||||
else:
|
||||
delta_vacant -= poste.p_nb_p4
|
||||
poste.p_nb_p4 = 0
|
||||
if delta_vacant <= poste.p_nb_p3:
|
||||
new_poste_p3 = poste.p_nb_p3 - delta_vacant
|
||||
poste.p_nb_p3 = new_poste_p3
|
||||
else:
|
||||
delta_vacant -= poste.p_nb_p3
|
||||
poste.p_nb_p3 = 0
|
||||
if delta_vacant <= poste.p_nb_p2:
|
||||
new_poste_p2 = poste.p_nb_p2 - delta_vacant
|
||||
poste.p_nb_p2 = new_poste_p2
|
||||
else:
|
||||
delta_vacant -= poste.p_nb_p2
|
||||
poste.p_nb_p2 = 0
|
||||
new_nb_p1 = poste.p_nb_p1 - delta_vacant
|
||||
poste.p_nb_p1 = new_nb_p1
|
||||
else:
|
||||
poste.p_nb_p3 = 0
|
||||
poste.p_nb_p2 = 0
|
||||
poste.p_nb_p1 = 0
|
||||
poste.p_nb_p4 = 0
|
||||
|
||||
poste.p_nb_reevalue = new_nb_reevalue
|
||||
poste.p_nb_vacant = new_nb_vacant
|
||||
poste.save(
|
||||
update_fields=['p_nb_reevalue', 'p_nb_vacant', 'p_nb_non_etudie', 'p_nb_p4', 'p_nb_p3', 'p_nb_p2',
|
||||
'p_nb_p1'])
|
||||
|
||||
|
||||
post_save.connect(update_taux_mdr, sender=FormationEmploi)
|
||||
|
||||
|
||||
def update_taux_off(sender, instance, **kwargs):
|
||||
"""Met à jour les compteurs des postes appartenant à la FE dont on change le taux d'armement cible pour la catégorie OFF
|
||||
|
||||
:type instance: objet du modèle FormationEmploi
|
||||
:param instance: FE dont on change le taux d'armement cible pour la catégorie OFF
|
||||
|
||||
"""
|
||||
taux = instance.fe_taux_armement_cible_off
|
||||
fe_id = instance.fe_code
|
||||
|
||||
postes = Poste.objects.filter(formation_emploi_id=fe_id, p_categorie='OFF')
|
||||
if (len(postes) > 0) and (taux is not None):
|
||||
taux = taux * 0.01
|
||||
for poste in postes:
|
||||
new_nb_reevalue = poste.p_nb_reevalue * taux
|
||||
new_nb_vacant = new_nb_reevalue - poste.p_nb_occupe
|
||||
if new_nb_reevalue >= poste.p_nb_reevalue:
|
||||
new_nb_p4 = poste.p_nb_p4 + new_nb_vacant - poste.p_nb_vacant
|
||||
poste.p_nb_p4 = new_nb_p4
|
||||
|
||||
else:
|
||||
if new_nb_reevalue > poste.p_nb_occupe:
|
||||
new_nb_non_etudie = poste.p_nb_non_etudie + poste.p_nb_reevalue - new_nb_reevalue
|
||||
poste.p_nb_non_etudie = new_nb_non_etudie
|
||||
delta_vacant = new_nb_vacant - poste.p_nb_vacant
|
||||
if delta_vacant <= poste.p_nb_p4:
|
||||
new_poste_p4 = poste.p_nb_p4 - delta_vacant
|
||||
poste.p_nb_p4 = new_poste_p4
|
||||
else:
|
||||
delta_vacant -= poste.p_nb_p4
|
||||
poste.p_nb_p4 = 0
|
||||
if delta_vacant <= poste.p_nb_p3:
|
||||
new_poste_p3 = poste.p_nb_p3 - delta_vacant
|
||||
poste.p_nb_p3 = new_poste_p3
|
||||
else:
|
||||
delta_vacant -= poste.p_nb_p3
|
||||
poste.p_nb_p3 = 0
|
||||
if delta_vacant <= poste.p_nb_p2:
|
||||
new_poste_p2 = poste.p_nb_p2 - delta_vacant
|
||||
poste.p_nb_p2 = new_poste_p2
|
||||
else:
|
||||
delta_vacant -= poste.p_nb_p2
|
||||
poste.p_nb_p2 = 0
|
||||
new_nb_p1 = poste.p_nb_p1 - delta_vacant
|
||||
poste.p_nb_p1 = new_nb_p1
|
||||
else:
|
||||
poste.p_nb_p3 = 0
|
||||
poste.p_nb_p2 = 0
|
||||
poste.p_nb_p1 = 0
|
||||
poste.p_nb_p4 = 0
|
||||
|
||||
poste.p_nb_reevalue = new_nb_reevalue
|
||||
poste.p_nb_vacant = new_nb_vacant
|
||||
poste.save(
|
||||
update_fields=['p_nb_reevalue', 'p_nb_vacant', 'p_nb_non_etudie', 'p_nb_p4', 'p_nb_p3', 'p_nb_p2',
|
||||
'p_nb_p1'])
|
||||
|
||||
|
||||
post_save.connect(update_taux_off, sender=FormationEmploi)
|
||||
|
||||
|
||||
def update_taux_soff(sender, instance, **kwargs):
|
||||
"""Met à jour les compteurs des postes appartenant à la FE dont on change le taux d'armement cible pour la catégorie SOFF
|
||||
|
||||
:type instance: objet du modèle FormationEmploi
|
||||
:param instance: FE dont on change le taux d'armement cible pour la catégorie SOFF
|
||||
|
||||
"""
|
||||
taux = instance.fe_taux_armement_cible_soff
|
||||
fe_id = instance.fe_code
|
||||
|
||||
postes = Poste.objects.filter(formation_emploi_id=fe_id, p_categorie='SOFF')
|
||||
if (len(postes) > 0) and (taux is not None):
|
||||
taux = taux * 0.01
|
||||
|
||||
for poste in postes:
|
||||
new_nb_reevalue = np.ceil(poste.p_nb_reo * taux)
|
||||
if new_nb_reevalue != poste.p_nb_reevalue:
|
||||
if new_nb_reevalue > poste.p_nb_reevalue:
|
||||
new_nb_p4 = poste.p_nb_p4 + new_nb_reevalue - poste.p_nb_reevalue
|
||||
new_nb_non_etudie = (new_nb_reevalue - new_nb_p4) if (new_nb_reevalue - new_nb_p4) > 0 else 0
|
||||
|
||||
elif new_nb_reevalue > poste.p_nb_occupe:
|
||||
new_nb_p4 = new_nb_reevalue - poste.p_nb_occupe
|
||||
new_nb_non_etudie = new_nb_reevalue - new_nb_p4
|
||||
poste.p_nb_p3 = 0
|
||||
poste.p_nb_p2 = 0
|
||||
poste.p_nb_p1 = 0
|
||||
else:
|
||||
new_nb_p4 = 0
|
||||
new_nb_non_etudie = 0
|
||||
poste.p_nb_p3 = 0
|
||||
poste.p_nb_p2 = 0
|
||||
poste.p_nb_p1 = 0
|
||||
poste.p_nb_p4 = 0
|
||||
new_nb_reevalue = poste.p_nb_occupe
|
||||
|
||||
poste.p_nb_non_etudie = new_nb_non_etudie
|
||||
poste.p_nb_p4 = new_nb_p4
|
||||
poste.p_nb_reevalue = new_nb_reevalue
|
||||
poste.save(
|
||||
update_fields=['p_nb_reevalue', 'p_nb_non_etudie', 'p_nb_p4', 'p_nb_p3', 'p_nb_p2',
|
||||
'p_nb_p1'])
|
||||
|
||||
|
||||
post_save.connect(update_taux_soff, sender=FormationEmploi)
|
||||
|
||||
|
||||
def add_sva(sender, instance, **kwargs):
|
||||
"""Met à jour les sous-viviers des militaires ayant la même filière de la nouvelle instance de SousVivierAssociation créée
|
||||
|
||||
:param instance: La nouvelle instance de SousVivierAssociation créée
|
||||
:type instance: objet du modèle SousVivierAssociation
|
||||
|
||||
"""
|
||||
filiere_id = instance.filiere_id
|
||||
sva_categorie = instance.sva_categorie
|
||||
sous_vivier_id = instance.sous_vivier_id
|
||||
administres = Administre.objects.filter(a_filiere_id=filiere_id, a_categorie=sva_categorie)
|
||||
administres.update(sous_vivier_id=sous_vivier_id)
|
||||
|
||||
|
||||
post_save.connect(add_sva, sender=SousVivierAssociation)
|
||||
25
backend-django/backend/templates/admin/groupe_fe.html
Normal file
25
backend-django/backend/templates/admin/groupe_fe.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
|
||||
{% block content %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
Entrer le nom du groupe des formations d'emploi
|
||||
</p>
|
||||
<ul>
|
||||
{% for fe in formations_emplois %}
|
||||
<li>
|
||||
{{ fe.fe_code }} — {{ fe.fe_libelle }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% for obj in formations_emplois %}
|
||||
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
|
||||
{% endfor %}
|
||||
{# <input type="text" name="taux_armement_cli" value="POMME"/>#}
|
||||
{{ form.nom_groupe_fe }}
|
||||
<br/>
|
||||
<input type="hidden" name="action" value="{{ do_action }}"/>
|
||||
<input type="submit" name="apply" value="Modifier le nom du groupe"/>
|
||||
</form>
|
||||
{% endblock %}
|
||||
25
backend-django/backend/templates/admin/taux_cible_mdr.html
Normal file
25
backend-django/backend/templates/admin/taux_cible_mdr.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
|
||||
{% block content %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
Entrer le nouveau taux cible d'armement pour la catégorie MDR pour les formations-emplois sélectionnées
|
||||
</p>
|
||||
<ul>
|
||||
{% for fe in formations_emplois %}
|
||||
<li>
|
||||
{{ fe.fe_code }} — {{ fe.fe_libelle }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% for obj in formations_emplois %}
|
||||
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
|
||||
{% endfor %}
|
||||
{# <input type="text" name="taux_armement_cli" value="POMME"/>#}
|
||||
{{ form.taux_armement_cible }}
|
||||
<br/>
|
||||
<input type="hidden" name="action" value="{{ do_action }}"/>
|
||||
<input type="submit" name="apply" value="Modifier le taux cible"/>
|
||||
</form>
|
||||
{% endblock %}
|
||||
25
backend-django/backend/templates/admin/taux_cible_off.html
Normal file
25
backend-django/backend/templates/admin/taux_cible_off.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
|
||||
{% block content %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
Entrer le nouveau taux cible d'armement pour la catégorie OFF pour les formations-emplois sélectionnées
|
||||
</p>
|
||||
<ul>
|
||||
{% for fe in formations_emplois %}
|
||||
<li>
|
||||
{{ fe.fe_code }} — {{ fe.fe_libelle }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% for obj in formations_emplois %}
|
||||
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
|
||||
{% endfor %}
|
||||
{# <input type="text" name="taux_armement_cli" value="POMME"/>#}
|
||||
{{ form.taux_armement_cible }}
|
||||
<br/>
|
||||
<input type="hidden" name="action" value="{{ do_action }}"/>
|
||||
<input type="submit" name="apply" value="Modifier le taux cible"/>
|
||||
</form>
|
||||
{% endblock %}
|
||||
25
backend-django/backend/templates/admin/taux_cible_soff.html
Normal file
25
backend-django/backend/templates/admin/taux_cible_soff.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
|
||||
{% block content %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
Entrer le nouveau taux cible d'armement pour la catégorie SOFF pour les formations-emplois sélectionnées
|
||||
</p>
|
||||
<ul>
|
||||
{% for fe in formations_emplois %}
|
||||
<li>
|
||||
{{ fe.fe_code }} — {{ fe.fe_libelle }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% for obj in formations_emplois %}
|
||||
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
|
||||
{% endfor %}
|
||||
{# <input type="text" name="taux_armement_cli" value="POMME"/>#}
|
||||
{{ form.taux_armement_cible }}
|
||||
<br/>
|
||||
<input type="hidden" name="action" value="{{ do_action }}"/>
|
||||
<input type="submit" name="apply" value="Modifier le taux cible"/>
|
||||
</form>
|
||||
{% endblock %}
|
||||
137
backend-django/backend/tests.py
Normal file
137
backend-django/backend/tests.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Ce fichier n'est utilisé que pour les tests.
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from numpy.core.fromnumeric import shape
|
||||
import pandas as pd
|
||||
from .utils_scoring import encoding,notePonderee,notations_test
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
class InsertModelTest(TestCase):
|
||||
# Fonction pour la table Garnisons
|
||||
def test_scoring(self):
|
||||
"""Pour tester la fonction de scoring
|
||||
"""
|
||||
mut_test = notations_test()
|
||||
data_test = mut_test.to_dict('records')
|
||||
data_source = [{
|
||||
'mu_id': 0,
|
||||
'poste_id': 1,
|
||||
'administres_id_sap': 9984,
|
||||
'pam_id': 1,
|
||||
'mu_date_execution': '14/06/2021',
|
||||
'mu_note_militaire': 0.5,
|
||||
'mu_flag_cple_ideal': 0,
|
||||
'mu_decision': '',
|
||||
'mu_date_decision': '',
|
||||
'mu_notes_gestionnaire': '',
|
||||
'mu_notes_partagees': ''},
|
||||
{
|
||||
'mu_id': 0,
|
||||
'poste_id': 1,
|
||||
'administres_id_sap': 56754,
|
||||
'pam_id': 1,
|
||||
'mu_date_execution': '14/06/2021',
|
||||
'mu_note_militaire': 0.0,
|
||||
'mu_flag_cple_ideal': 0,
|
||||
'mu_decision': '',
|
||||
'mu_date_decision': '',
|
||||
'mu_notes_gestionnaire': '',
|
||||
'mu_notes_partagees': ''},
|
||||
{
|
||||
'mu_id': 0,
|
||||
'poste_id': 1,
|
||||
'administres_id_sap': 78905,
|
||||
'pam_id': 1,
|
||||
'mu_date_execution': '14/06/2021',
|
||||
'mu_note_militaire': 0.0,
|
||||
'mu_flag_cple_ideal': 0,
|
||||
'mu_decision': '',
|
||||
'mu_date_decision': '',
|
||||
'mu_notes_gestionnaire': '',
|
||||
'mu_notes_partagees': ''
|
||||
},
|
||||
{
|
||||
'mu_id': 0,
|
||||
'poste_id': 2,
|
||||
'administres_id_sap': 9984,
|
||||
'pam_id': 1,
|
||||
'mu_date_execution': '14/06/2021',
|
||||
'mu_note_militaire': 0.0,
|
||||
'mu_flag_cple_ideal': 0,
|
||||
'mu_decision': '',
|
||||
'mu_date_decision': '',
|
||||
'mu_notes_gestionnaire': '',
|
||||
'mu_notes_partagees': ''
|
||||
},
|
||||
{
|
||||
'mu_id': 0,
|
||||
'poste_id': 2,
|
||||
'administres_id_sap': 56754,
|
||||
'pam_id': 1,
|
||||
'mu_date_execution': '14/06/2021',
|
||||
'mu_note_militaire': 0.5,
|
||||
'mu_flag_cple_ideal': 0,
|
||||
'mu_decision': '',
|
||||
'mu_date_decision': '',
|
||||
'mu_notes_gestionnaire': '',
|
||||
'mu_notes_partagees': ''
|
||||
},
|
||||
{
|
||||
'mu_id': 0,
|
||||
'poste_id': 2,
|
||||
'administres_id_sap': 78905,
|
||||
'pam_id': 1,
|
||||
'mu_date_execution': '14/06/2021',
|
||||
'mu_note_militaire': 0.0,
|
||||
'mu_flag_cple_ideal': 0,
|
||||
'mu_decision': '',
|
||||
'mu_date_decision': '',
|
||||
'mu_notes_gestionnaire': '',
|
||||
'mu_notes_partagees': ''
|
||||
},
|
||||
{
|
||||
'mu_id': 0,
|
||||
'poste_id': 3,
|
||||
'administres_id_sap': 9984,
|
||||
'pam_id': 1,
|
||||
'mu_date_execution': '14/06/2021',
|
||||
'mu_note_militaire': 0.0,
|
||||
'mu_flag_cple_ideal': 0,
|
||||
'mu_decision': '',
|
||||
'mu_date_decision': '',
|
||||
'mu_notes_gestionnaire': '',
|
||||
'mu_notes_partagees': ''
|
||||
},
|
||||
{
|
||||
'mu_id': 0,
|
||||
'poste_id': 3,
|
||||
'administres_id_sap': 56754,
|
||||
'pam_id': 1,
|
||||
'mu_date_execution': '14/06/2021',
|
||||
'mu_note_militaire': 0.0,
|
||||
'mu_flag_cple_ideal': 0,
|
||||
'mu_decision': '',
|
||||
'mu_date_decision': '',
|
||||
'mu_notes_gestionnaire': '',
|
||||
'mu_notes_partagees': ''
|
||||
},
|
||||
{
|
||||
'mu_id': 0,
|
||||
'poste_id': 3,
|
||||
'administres_id_sap': 78905,
|
||||
'pam_id': 1,
|
||||
'mu_date_execution': '14/06/2021',
|
||||
'mu_note_militaire': 0.5,
|
||||
'mu_flag_cple_ideal': 0,
|
||||
'mu_decision': '',
|
||||
'mu_date_decision': '',
|
||||
'mu_notes_gestionnaire': '',
|
||||
'mu_notes_partagees': ''
|
||||
}]
|
||||
if data_source == data_test :
|
||||
test_value = True
|
||||
else :
|
||||
test_value = False
|
||||
self.assertIs(test_value,True)
|
||||
5
backend-django/backend/tests/__init__.py
Normal file
5
backend-django/backend/tests/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .initial import *
|
||||
from .models import *
|
||||
from .utils import *
|
||||
from .views import *
|
||||
from .droits import *
|
||||
1
backend-django/backend/tests/droits/__init__.py
Normal file
1
backend-django/backend/tests/droits/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .socle import *
|
||||
590
backend-django/backend/tests/droits/socle.py
Normal file
590
backend-django/backend/tests/droits/socle.py
Normal file
@@ -0,0 +1,590 @@
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient, APITestCase
|
||||
|
||||
from ...models import (Administre, CustomUser, Decision, DecisionChoices,
|
||||
Domaine, Filiere, FormationEmploi, Garnison, Poste,
|
||||
RefGest, RefOrg, RefSvFil, SousVivier)
|
||||
|
||||
|
||||
class TestSocleVerifDroits(APITestCase):
|
||||
""" Classe permettant de tester la vérification des droits de lecture et d'écriture d'un gestionnaire """
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# Création des gestionnaires
|
||||
cls.gest1 = CustomUser.objects.create(username='gest1', password='password', id=1)
|
||||
cls.gest2 = CustomUser.objects.create(username='gest2', password='password', id=2)
|
||||
cls.gest3 = CustomUser.objects.create(username='gest3', password='password', id=3)
|
||||
cls.gest4 = CustomUser.objects.create(username='gest4', password='password', id=4)
|
||||
cls.gest5 = CustomUser.objects.create(username='gest5', password='password', id=5)
|
||||
cls.gest6 = CustomUser.objects.create(username='gest6', password='password', id=6)
|
||||
cls.gest7 = CustomUser.objects.create(username='gest7', password='password', id=7)
|
||||
cls.gest8 = CustomUser.objects.create(username='gest8', password='password', id=8)
|
||||
cls.gest9 = CustomUser.objects.create(username='gest9', password='password', id=9)
|
||||
cls.gest10 = CustomUser.objects.create(username='gest10', password='password', id=10)
|
||||
|
||||
|
||||
# Création des objets du référentiel organique
|
||||
cls.ref_org30 = RefOrg.objects.create(
|
||||
ref_org_code='org30',
|
||||
ref_org_code_niv_org3='org30',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_itd=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
) # Référentiel organique avec les droits de lecture et d'écriture (attribué aux gestionnaires 1)
|
||||
|
||||
cls.ref_org40a = RefOrg.objects.create(
|
||||
ref_org_code='org40a',
|
||||
ref_org_code_niv_org3='org30',
|
||||
ref_org_code_niv_org4='org40a',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_itd=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
)
|
||||
|
||||
cls.ref_org40b = RefOrg.objects.create(
|
||||
ref_org_code='org40b',
|
||||
ref_org_code_niv_org3='org30',
|
||||
ref_org_code_niv_org4='org40b',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_itd=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
)
|
||||
|
||||
cls.ref_org41 = RefOrg.objects.create(
|
||||
ref_org_code='org41',
|
||||
ref_org_code_niv_org4='org41',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True
|
||||
) # Référentiel organique sans les droits de lecture et d'écriture (attribué aux gestionnaires 3)
|
||||
|
||||
cls.ref_org42 = RefOrg.objects.create(
|
||||
ref_org_code='org42',
|
||||
ref_org_code_niv_org4='org42',
|
||||
ref_org_droit_lect=True
|
||||
) # Référentiel organique avec les droits de lecture mais ni pcp ni fil ni itd (attribué aux gestionnaires 4)
|
||||
|
||||
cls.ref_org43 = RefOrg.objects.create(
|
||||
ref_org_code='org43',
|
||||
ref_org_code_niv_org4='org43',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
)
|
||||
|
||||
cls.ref_org44 = RefOrg.objects.create(
|
||||
ref_org_code='org44',
|
||||
ref_org_code_niv_org4='org44',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_droit_lect=True
|
||||
) # Référentiel organique sans les droits d'écriture (attribué aux gestionnaires 6)
|
||||
|
||||
cls.ref_org45 = RefOrg.objects.create(
|
||||
ref_org_code='org45',
|
||||
ref_org_code_niv_org4='org45',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
) # Référentiel organique sans FE ni FIL (attribué aux gestionnaires 7)
|
||||
|
||||
cls.ref_org36 = RefOrg.objects.create(
|
||||
ref_org_code='org36',
|
||||
ref_org_code_niv_org3='org36',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
) # Référentiel organique ne possedant aucune instances dans les ref FE et FIL (attribué aux gestionnaires 8)
|
||||
|
||||
cls.ref_org37 = RefOrg.objects.create(
|
||||
ref_org_code='org37',
|
||||
ref_org_code_niv_org3='org37',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
)
|
||||
|
||||
cls.ref_org47a = RefOrg.objects.create(
|
||||
ref_org_code='org47a',
|
||||
ref_org_code_niv_org3='org37',
|
||||
ref_org_code_niv_org4='org47a',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
)
|
||||
|
||||
cls.ref_org47b = RefOrg.objects.create(
|
||||
ref_org_code='org47b',
|
||||
ref_org_code_niv_org3='org37',
|
||||
ref_org_code_niv_org4='org47b',
|
||||
ref_org_ref_fe=True,
|
||||
ref_org_ref_sv_fil=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
)
|
||||
|
||||
cls.ref_org48 = RefOrg.objects.create(
|
||||
ref_org_code='org48',
|
||||
ref_org_code_niv_org4='org48',
|
||||
ref_org_itd=True,
|
||||
ref_org_droit_lect=True,
|
||||
ref_org_droit_ecr=True
|
||||
) # Référentiel organique avec tous les droits mais seulement pour un gestionnaire ITD (attribué au gestionnaire 10)
|
||||
|
||||
|
||||
# Création des objets du référentiel gestionnaire
|
||||
cls.ref_gest1 = RefGest.objects.create(ref_gest_sap=cls.gest1.id, ref_gest_org=cls.ref_org30)
|
||||
cls.ref_gest2 = RefGest.objects.create(ref_gest_sap=cls.gest2.id) # Gestionnaire ne possedant pas de cod_niv_org
|
||||
cls.ref_gest3 = RefGest.objects.create(ref_gest_sap=cls.gest3.id, ref_gest_org=cls.ref_org41)
|
||||
cls.ref_gest4 = RefGest.objects.create(ref_gest_sap=cls.gest4.id, ref_gest_org=cls.ref_org42)
|
||||
cls.ref_gest5 = RefGest.objects.create(ref_gest_sap=cls.gest5.id, ref_gest_org=cls.ref_org43)
|
||||
cls.ref_gest6 = RefGest.objects.create(ref_gest_sap=cls.gest6.id, ref_gest_org=cls.ref_org44)
|
||||
cls.ref_gest7 = RefGest.objects.create(ref_gest_sap=cls.gest7.id, ref_gest_org=cls.ref_org45)
|
||||
cls.ref_gest8 = RefGest.objects.create(ref_gest_sap=cls.gest8.id, ref_gest_org=cls.ref_org36)
|
||||
cls.ref_gest9 = RefGest.objects.create(ref_gest_sap=cls.gest9.id, ref_gest_org=cls.ref_org37)
|
||||
cls.ref_gest10 = RefGest.objects.create(ref_gest_sap=cls.gest10.id, ref_gest_org=cls.ref_org48)
|
||||
|
||||
# Création du référentiel FE et des DOM/FIL
|
||||
cls.gar1 = Garnison.objects.create(gar_id='Gar1', gar_lieu='lieu1')
|
||||
cls.fe1 = FormationEmploi.objects.create(fe_code='Fe1', fe_code_niv_org4='org40a', fe_garnison_lieu=cls.gar1.gar_lieu)
|
||||
cls.fe2 = FormationEmploi.objects.create(fe_code='Fe2', fe_code_niv_org4='org43', fe_garnison_lieu=cls.gar1.gar_lieu)
|
||||
cls.fe3 = FormationEmploi.objects.create(fe_code='Fe3', fe_code_niv_org4='org47a', fe_garnison_lieu=cls.gar1.gar_lieu)
|
||||
|
||||
cls.dom1 = Domaine.objects.create(d_code='Dom1')
|
||||
cls.fil1 = Filiere.objects.create(f_code='Fil1')
|
||||
|
||||
|
||||
# Création des administrés, postes et décisions
|
||||
cls.adm1 = Administre.objects.create(a_id_sap=1, formation_emploi=cls.fe1, a_domaine=cls.dom1, a_filiere=cls.fil1, a_categorie='Cat1', a_nom='nom', a_prenom='prénom', a_sexe='M', a_eip='eip1') # Administré du fe1
|
||||
cls.adm2 = Administre.objects.create(a_id_sap=2, formation_emploi=cls.fe1, a_domaine=cls.dom1, a_filiere=cls.fil1, a_categorie='Cat1', a_nom='nom', a_prenom='prénom', a_sexe='M', a_eip='eip2') # Idem
|
||||
cls.adm3 = Administre.objects.create(a_id_sap=3, formation_emploi=cls.fe2, a_domaine=cls.dom1, a_filiere=cls.fil1, a_categorie='Cat1', a_nom='nom', a_prenom='prénom', a_sexe='M', a_eip='eip3') # Administré du fe2
|
||||
cls.adm4 = Administre.objects.create(a_id_sap=4, formation_emploi=cls.fe2, a_domaine=cls.dom1, a_filiere=cls.fil1, a_categorie='Cat1', a_nom='nom', a_prenom='prénom', a_sexe='M', a_eip='eip4') # Idem
|
||||
|
||||
cls.poste1 = Poste.objects.create(p_id='Poste1', formation_emploi=cls.fe1, p_domaine=cls.dom1, p_filiere=cls.fil1, p_categorie='Cat1', p_eip='eip1')
|
||||
cls.poste2 = Poste.objects.create(p_id='Poste2', formation_emploi=cls.fe1, p_domaine=cls.dom1, p_filiere=cls.fil1, p_categorie='Cat1', p_eip='eip2')
|
||||
cls.poste3 = Poste.objects.create(p_id='Poste3', formation_emploi=cls.fe2, p_domaine=cls.dom1, p_filiere=cls.fil1, p_categorie='Cat1', p_eip='eip3')
|
||||
cls.poste4 = Poste.objects.create(p_id='Poste4', formation_emploi=cls.fe2, p_domaine=cls.dom1, p_filiere=cls.fil1, p_categorie='Cat1', p_eip='eip4')
|
||||
cls.poste5 = Poste.objects.create(p_id='Poste5', p_specifique='ITD', p_eip='eip5')
|
||||
|
||||
cls.dec1 = Decision.objects.create(administre=cls.adm1, poste=cls.poste1, de_decision=DecisionChoices.PROPOSITION_FE)
|
||||
cls.dec2 = Decision.objects.create(administre=cls.adm3, poste=cls.poste3, de_decision=DecisionChoices.PROPOSITION_FE)
|
||||
|
||||
# Création du référentiel sous-vivier filiere
|
||||
cls.ref_sv_fil1 = RefSvFil.objects.create(ref_sv_fil_code='org40b', ref_sv_fil_dom='Dom1', ref_sv_fil_fil='Fil1', ref_sv_fil_cat='Cat1')
|
||||
|
||||
|
||||
# Création des clients de test
|
||||
cls.c0 = APIClient() # Client de test qui ne sera pas authentifié
|
||||
cls.c1 = APIClient()
|
||||
cls.c2 = APIClient()
|
||||
cls.c3 = APIClient()
|
||||
cls.c4 = APIClient()
|
||||
cls.c5 = APIClient()
|
||||
cls.c6 = APIClient()
|
||||
cls.c7 = APIClient()
|
||||
cls.c8 = APIClient()
|
||||
cls.c9 = APIClient()
|
||||
cls.c10 = APIClient()
|
||||
|
||||
# Authentification des clients de test
|
||||
cls.c1.force_authenticate(cls.gest1)
|
||||
cls.c2.force_authenticate(cls.gest2)
|
||||
cls.c3.force_authenticate(cls.gest3)
|
||||
cls.c4.force_authenticate(cls.gest4)
|
||||
cls.c5.force_authenticate(cls.gest5)
|
||||
cls.c6.force_authenticate(cls.gest6)
|
||||
cls.c7.force_authenticate(cls.gest7)
|
||||
cls.c8.force_authenticate(cls.gest8)
|
||||
cls.c9.force_authenticate(cls.gest9)
|
||||
cls.c10.force_authenticate(cls.gest10)
|
||||
|
||||
cls.list_clients = [cls.c1, cls.c2, cls.c3, cls.c4, cls.c5, cls.c6, cls.c7, cls.c8, cls.c9, cls.c10]
|
||||
|
||||
# Création des données utilsées pour les requêtes
|
||||
cls.patch_datas = {
|
||||
'data_p1': {
|
||||
'p_id': 'Poste1',
|
||||
'p_eip': 'eip2'
|
||||
},
|
||||
'data_a1': {
|
||||
'a_id_sap': 1,
|
||||
'a_eip': 'eip2'
|
||||
},
|
||||
'data_d1': {
|
||||
'pk': cls.adm1.pk,
|
||||
'de_decision': DecisionChoices.POSITIONNE
|
||||
},
|
||||
'data_p3': {
|
||||
'p_id': 'Poste3',
|
||||
'p_eip': 'eip4'
|
||||
},
|
||||
'data_a3': {
|
||||
'a_id_sap': 3,
|
||||
'a_eip': 'eip4'
|
||||
},
|
||||
'data_d3': {
|
||||
'pk': cls.adm3.pk,
|
||||
'de_decision': DecisionChoices.POSITIONNE
|
||||
},
|
||||
'data_p5': {
|
||||
'p_id': 'Poste5',
|
||||
'p_eip': 'eip6'
|
||||
}
|
||||
}
|
||||
|
||||
cls.put_datas = {
|
||||
'data_p1': [
|
||||
{'p_id': 'Poste1',
|
||||
'p_eip': 'eip2'
|
||||
},
|
||||
{'p_id': 'Poste2',
|
||||
'p_eip': 'eip3'
|
||||
}
|
||||
],
|
||||
'data_a1': [
|
||||
{'a_id_sap': 1,
|
||||
'a_eip': 'eip2'
|
||||
},
|
||||
{'a_id_sap': 2,
|
||||
'a_eip': 'eip3'
|
||||
},
|
||||
],
|
||||
'data_p3': [
|
||||
{'p_id': 'Poste3',
|
||||
'p_eip': 'eip4'
|
||||
},
|
||||
{'p_id': 'Poste4',
|
||||
'p_eip': 'eip5'
|
||||
}
|
||||
],
|
||||
'data_a3': [
|
||||
{'a_id_sap': 3,
|
||||
'a_eip': 'eip4'
|
||||
},
|
||||
{'a_id_sap': 4,
|
||||
'a_eip': 'eip5'
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# Définition des liens sur lesquels les requêtes seront effectuées
|
||||
cls.urls = {
|
||||
'list_p': reverse('Poste-list'),
|
||||
'list_a': reverse('Administre-list'),
|
||||
'list_d': reverse('Decision-list'),
|
||||
'instance_p1': reverse('Poste-list') + 'Poste1/',
|
||||
'instance_a1': reverse('Administre-list') + '1/',
|
||||
'instance_d1': reverse('Decision-list') + '1/',
|
||||
'instance_p3': reverse('Poste-list') + 'Poste3/',
|
||||
'instance_a3': reverse('Administre-list') + '3/',
|
||||
'instance_d3': reverse('Decision-list') + '3/',
|
||||
'instance_p5': reverse('Poste-list') + 'Poste5/'
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super().tearDownClass()
|
||||
for client in cls.list_clients:
|
||||
client.logout()
|
||||
|
||||
|
||||
def test_get_not_authenticated(self):
|
||||
"""
|
||||
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'est pas authentifié
|
||||
"""
|
||||
# Le test passe mais il y a également interdiction car c5 n'a pas de 'code_niv_org'...
|
||||
get_resp_p = self.c0.get(path=self.urls['list_p'], follow=True)
|
||||
get_resp_a = self.c0.get(path=self.urls['list_a'], follow=True)
|
||||
get_resp_d = self.c0.get(path=self.urls['list_d'], follow=True)
|
||||
self.assertEqual(get_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(get_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(get_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_get_ok(self):
|
||||
"""
|
||||
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que :
|
||||
- Le gestionnaire est authentifié,
|
||||
- Qu'il a les droits de lecture
|
||||
"""
|
||||
get_resp_p = self.c1.get(path=self.urls['list_p'], follow=True)
|
||||
get_resp_a = self.c1.get(path=self.urls['list_a'], follow=True)
|
||||
get_resp_d = self.c1.get(path=self.urls['list_d'], follow=True)
|
||||
self.assertEqual(get_resp_p.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(get_resp_a.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(get_resp_d.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_get_no_code_niv_org(self):
|
||||
"""
|
||||
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'a pas d'attribut code_niv_org
|
||||
"""
|
||||
get_resp_p = self.c2.get(path=self.urls['list_p'], follow=True)
|
||||
get_resp_a = self.c2.get(path=self.urls['list_a'], follow=True)
|
||||
get_resp_d = self.c2.get(path=self.urls['list_d'], follow=True)
|
||||
self.assertEqual(get_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(get_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(get_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_get_no_reading_rights(self):
|
||||
"""
|
||||
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'a pas les droits de lecture
|
||||
"""
|
||||
get_resp_p = self.c3.get(path=self.urls['list_p'], follow=True)
|
||||
get_resp_a = self.c3.get(path=self.urls['list_a'], follow=True)
|
||||
get_resp_d = self.c3.get(path=self.urls['list_d'], follow=True)
|
||||
self.assertEqual(get_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(get_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(get_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_get_not_pcp_not_fil(self):
|
||||
"""
|
||||
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire est ni pcp ni filiere
|
||||
"""
|
||||
get_resp_p = self.c4.get(path=self.urls['list_p'], follow=True)
|
||||
get_resp_a = self.c4.get(path=self.urls['list_a'], follow=True)
|
||||
get_resp_d = self.c4.get(path=self.urls['list_d'], follow=True)
|
||||
self.assertEqual(get_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(get_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(get_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_put_ok_fe_fil(self):
|
||||
"""
|
||||
Teste la requete PUT sur des postes et des administrés sachant que :
|
||||
- Le gestionnaire est authentifié,
|
||||
- Qu'il a les droits de lecture et d'écriture,
|
||||
- Qu'il est PCP et FIL,
|
||||
- Qu'il est associé à deux code_niv_org4,
|
||||
- Les instances qu'il édite sont dans son FE et dans sa filiere
|
||||
"""
|
||||
# Pas de test put pour les décisions car la modification de plusieurs décisions n'est pas possible dans Ogure
|
||||
put_resp_p = self.c1.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||
put_resp_a = self.c1.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||
self.assertEqual(put_resp_p.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(put_resp_a.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_put_ok_fe(self):
|
||||
"""
|
||||
Teste la requete PUT sur des postes et des administrés sachant que :
|
||||
- Le gestionnaire est authentifié,
|
||||
- Qu'il a les droits de lecture et d'écriture,
|
||||
- Qu'il est PCP et FIL,
|
||||
- Qu'il est associé à un code_niv_org4,
|
||||
- Les instances qu'il édite sont dans son FE MAIS PAS dans sa FIL
|
||||
"""
|
||||
# Pas de test put pour les décisions car la modification de plusieurs décisions n'est pas possible dans Ogure
|
||||
put_resp_p = self.c5.put(path=self.urls['list_p'], data=self.put_datas['data_p3'], follow=True)
|
||||
put_resp_a = self.c5.put(path=self.urls['list_a'], data=self.put_datas['data_a3'], follow=True)
|
||||
self.assertEqual(put_resp_p.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(put_resp_a.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_put_no_writing_rights(self):
|
||||
"""
|
||||
Teste la requete PUT sur des postes et des administrés sachant qu'il n'a pas les droits d'écriture'
|
||||
"""
|
||||
put_resp_p = self.c6.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||
put_resp_a = self.c6.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||
self.assertEqual(put_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(put_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_put_no_fe_no_fil(self):
|
||||
"""
|
||||
Teste la requete PUT sur des postes et des administrés sachant qu'il n'a pas de FE ni de FIL associé'
|
||||
"""
|
||||
put_resp_p = self.c7.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||
put_resp_a = self.c7.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||
self.assertEqual(put_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(put_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_put_no_instances_ok(self):
|
||||
"""
|
||||
Teste la requete PUT sur des postes et des administrés sachant qu'il n'a pas d'instances dans son FE ou dans sa FIL
|
||||
"""
|
||||
put_resp_p = self.c8.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||
put_resp_a = self.c8.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||
self.assertEqual(put_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(put_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_put_not_ok(self):
|
||||
"""
|
||||
Teste la requete PUT sur des postes et des administrés sachant que les instances modifiées ne sont ni dans le FE ni dans la FIL du gestionnaire
|
||||
"""
|
||||
put_resp_p = self.c9.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||
put_resp_a = self.c9.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||
self.assertEqual(put_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(put_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_patch_no_code_niv_org(self):
|
||||
"""
|
||||
Teste la requete PATCH sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'a pas d'attribut code_niv_org
|
||||
"""
|
||||
patch_resp_p = self.c2.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||
patch_resp_a = self.c2.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||
patch_resp_d = self.c2.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_patch_no_reading_rights(self):
|
||||
"""
|
||||
Teste la requete PATCH sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'a pas les droits de lecture
|
||||
"""
|
||||
patch_resp_p = self.c3.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||
patch_resp_a = self.c3.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||
patch_resp_d = self.c3.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_patch_not_pcp_not_fil_not_itd(self):
|
||||
"""
|
||||
Teste la requete PATCH sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire est ni pcp ni filiere
|
||||
"""
|
||||
patch_resp_p = self.c4.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||
patch_resp_a = self.c4.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||
patch_resp_d = self.c4.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_patch_ok_fe_fil(self):
|
||||
"""
|
||||
Teste la requete PATCH sur un poste, un administré et une décision sachant que :
|
||||
- Le gestionnaire est authentifié,
|
||||
- Qu'il a les droits de lecture et d'écriture,
|
||||
- Qu'il est PCP et FIL,
|
||||
- Qu'il est associé à deux code_niv_org4,
|
||||
- Les instances qu'il édite sont dans son FE et dans sa filiere
|
||||
"""
|
||||
patch_resp_p = self.c1.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||
patch_resp_a = self.c1.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||
patch_resp_d = self.c1.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(patch_resp_a.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(patch_resp_d.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_patch_ok_fe(self):
|
||||
"""
|
||||
Teste la requete PATCH sur un poste, un administré et une décision sachant que :
|
||||
- Le gestionnaire est authentifié,
|
||||
- Qu'il a les droits de lecture et d'écriture,
|
||||
- Qu'il est PCP et FIL,
|
||||
- Qu'il est associé à un code_niv_org4,
|
||||
- Les instances qu'il édite sont dans son FE MAIS PAS dans sa FIL
|
||||
"""
|
||||
patch_resp_p = self.c5.patch(path=self.urls['instance_p3'], data=self.patch_datas['data_p3'], follow=True)
|
||||
patch_resp_a = self.c5.patch(path=self.urls['instance_a3'], data=self.patch_datas['data_a3'], follow=True)
|
||||
patch_resp_d = self.c5.patch(path=self.urls['instance_d3'], data=self.patch_datas['data_d3'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(patch_resp_a.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(patch_resp_d.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_patch_ok_itd(self):
|
||||
"""
|
||||
Teste la requete PATCH sur un poste sachant que :
|
||||
- Le gestionnaire est authentifié,
|
||||
- Qu'il a les droits de lecture et d'écriture,
|
||||
- Qu'il est ITD,
|
||||
- Qu'il est associé à un code_niv_org4,
|
||||
- Le poste édité est marqué ITD
|
||||
"""
|
||||
patch_resp_p = self.c10.patch(path=self.urls['instance_p5'], data=self.patch_datas['data_p5'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_patch_no_writing_rights(self):
|
||||
"""
|
||||
Teste la requete PATCH sur des postes et des administrés sachant qu'il n'a pas les droits d'écriture'
|
||||
"""
|
||||
patch_resp_p = self.c6.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||
patch_resp_a = self.c6.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||
patch_resp_d = self.c6.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_patch_no_niv_org(self):
|
||||
"""
|
||||
Teste la requete PATCH sur des postes et des administrés sachant qu'il n'a pas d'attribut niv_org'
|
||||
"""
|
||||
patch_resp_p = self.c7.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||
patch_resp_a = self.c7.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||
patch_resp_d = self.c7.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_patch_no_instances_ok(self):
|
||||
"""
|
||||
Teste la requete PATCH sur des postes et des administrés sachant qu'il n'a pas d'instances dans son FE ou dans sa FIL
|
||||
"""
|
||||
patch_resp_p = self.c8.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||
patch_resp_a = self.c8.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||
patch_resp_d = self.c8.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_patch_not_ok_fe_fil(self):
|
||||
"""
|
||||
Teste la requete PATCH sur un poste, un administré et une décision sachant que les instances modifiées ne sont ni dans le FE ni dans la FIL du gestionnaire
|
||||
"""
|
||||
patch_resp_p = self.c9.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||
patch_resp_a = self.c9.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||
patch_resp_d = self.c9.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_patch_not_ok_itd(self):
|
||||
"""
|
||||
Teste la requete PATCH sur un poste sachant que l'instance modifiée n'est pas marquée ITD
|
||||
"""
|
||||
patch_resp_p = self.c10.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_delete_ok(self):
|
||||
delete_resp_p = self.c1.delete(path=self.urls['instance_p1'], follow=True)
|
||||
delete_resp_a = self.c1.delete(path=self.urls['instance_a1'], follow=True)
|
||||
self.assertEqual(delete_resp_p.status_code, status.HTTP_204_NO_CONTENT)
|
||||
self.assertEqual(delete_resp_a.status_code, status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def test_delete_decision_ok(self):
|
||||
# On teste la suppression de décision séparément des autres suppressions sans quoi instance_d ferait référence à un administré supprimé
|
||||
delete_resp_d = self.c1.delete(path=self.urls['instance_d1'], follow=True)
|
||||
self.assertEqual(delete_resp_d.status_code, status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def test_delete_not_ok(self):
|
||||
# delete_resp_p = self.c9.delete(path=self.urls['instance_p1'], follow=True)
|
||||
# delete_resp_a = self.c9.delete(path=self.urls['instance_a1'], follow=True)
|
||||
delete_resp_d = self.c9.delete(path=self.urls['instance_d1'], follow=True)
|
||||
# self.assertEqual(delete_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
# self.assertEqual(delete_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(delete_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_head_ok(self):
|
||||
head_resp_p = self.c1.head(path=self.urls['list_p'], follow=True)
|
||||
head_resp_a = self.c1.head(path=self.urls['list_a'], follow=True)
|
||||
head_resp_d = self.c1.head(path=self.urls['list_d'], follow=True)
|
||||
self.assertEqual(head_resp_p.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(head_resp_a.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(head_resp_d.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_head_no_reading_rights(self):
|
||||
head_resp_p = self.c3.head(path=self.urls['list_p'], follow=True)
|
||||
head_resp_a = self.c3.head(path=self.urls['list_a'], follow=True)
|
||||
head_resp_d = self.c3.head(path=self.urls['list_d'], follow=True)
|
||||
self.assertEqual(head_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(head_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(head_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||
140
backend-django/backend/tests/initial.py
Normal file
140
backend-django/backend/tests/initial.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
Ce fichier n'est utilisé que pour les tests.
|
||||
"""
|
||||
# from ..utils_scoring import notations_test
|
||||
# from django.test import TestCase
|
||||
# from numpy import fliplr
|
||||
# from numpy.core.fromnumeric import shape
|
||||
# import pandas as pd
|
||||
# from .utils_scoring import encoding,notePonderee,notations_test
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
# class InsertModelTest(TestCase):
|
||||
# # Fonction pour la table Garnisons
|
||||
# def test_scoring(self):
|
||||
# """Pour tester la fonction de scoring
|
||||
# """
|
||||
# mut_test = notations_test()
|
||||
# data_test = mut_test.to_dict('records')
|
||||
# data_source = [{
|
||||
# 'mu_id': 0,
|
||||
# 'poste_id': 1,
|
||||
# 'administres_id_sap': 9984,
|
||||
# 'pam_id': 1,
|
||||
# 'mu_date_execution': '14/06/2021',
|
||||
# 'mu_note_militaire': 0.5,
|
||||
# 'mu_flag_cple_ideal': 0,
|
||||
# 'mu_decision': '',
|
||||
# 'mu_date_decision': '',
|
||||
# 'mu_notes_gestionnaire': '',
|
||||
# 'mu_notes_partagees': ''},
|
||||
# {
|
||||
# 'mu_id': 0,
|
||||
# 'poste_id': 1,
|
||||
# 'administres_id_sap': 56754,
|
||||
# 'pam_id': 1,
|
||||
# 'mu_date_execution': '14/06/2021',
|
||||
# 'mu_note_militaire': 0.0,
|
||||
# 'mu_flag_cple_ideal': 0,
|
||||
# 'mu_decision': '',
|
||||
# 'mu_date_decision': '',
|
||||
# 'mu_notes_gestionnaire': '',
|
||||
# 'mu_notes_partagees': ''},
|
||||
# {
|
||||
# 'mu_id': 0,
|
||||
# 'poste_id': 1,
|
||||
# 'administres_id_sap': 78905,
|
||||
# 'pam_id': 1,
|
||||
# 'mu_date_execution': '14/06/2021',
|
||||
# 'mu_note_militaire': 0.0,
|
||||
# 'mu_flag_cple_ideal': 0,
|
||||
# 'mu_decision': '',
|
||||
# 'mu_date_decision': '',
|
||||
# 'mu_notes_gestionnaire': '',
|
||||
# 'mu_notes_partagees': ''
|
||||
# },
|
||||
# {
|
||||
# 'mu_id': 0,
|
||||
# 'poste_id': 2,
|
||||
# 'administres_id_sap': 9984,
|
||||
# 'pam_id': 1,
|
||||
# 'mu_date_execution': '14/06/2021',
|
||||
# 'mu_note_militaire': 0.0,
|
||||
# 'mu_flag_cple_ideal': 0,
|
||||
# 'mu_decision': '',
|
||||
# 'mu_date_decision': '',
|
||||
# 'mu_notes_gestionnaire': '',
|
||||
# 'mu_notes_partagees': ''
|
||||
# },
|
||||
# {
|
||||
# 'mu_id': 0,
|
||||
# 'poste_id': 2,
|
||||
# 'administres_id_sap': 56754,
|
||||
# 'pam_id': 1,
|
||||
# 'mu_date_execution': '14/06/2021',
|
||||
# 'mu_note_militaire': 0.5,
|
||||
# 'mu_flag_cple_ideal': 0,
|
||||
# 'mu_decision': '',
|
||||
# 'mu_date_decision': '',
|
||||
# 'mu_notes_gestionnaire': '',
|
||||
# 'mu_notes_partagees': ''
|
||||
# },
|
||||
# {
|
||||
# 'mu_id': 0,
|
||||
# 'poste_id': 2,
|
||||
# 'administres_id_sap': 78905,
|
||||
# 'pam_id': 1,
|
||||
# 'mu_date_execution': '14/06/2021',
|
||||
# 'mu_note_militaire': 0.0,
|
||||
# 'mu_flag_cple_ideal': 0,
|
||||
# 'mu_decision': '',
|
||||
# 'mu_date_decision': '',
|
||||
# 'mu_notes_gestionnaire': '',
|
||||
# 'mu_notes_partagees': ''
|
||||
# },
|
||||
# {
|
||||
# 'mu_id': 0,
|
||||
# 'poste_id': 3,
|
||||
# 'administres_id_sap': 9984,
|
||||
# 'pam_id': 1,
|
||||
# 'mu_date_execution': '14/06/2021',
|
||||
# 'mu_note_militaire': 0.0,
|
||||
# 'mu_flag_cple_ideal': 0,
|
||||
# 'mu_decision': '',
|
||||
# 'mu_date_decision': '',
|
||||
# 'mu_notes_gestionnaire': '',
|
||||
# 'mu_notes_partagees': ''
|
||||
# },
|
||||
# {
|
||||
# 'mu_id': 0,
|
||||
# 'poste_id': 3,
|
||||
# 'administres_id_sap': 56754,
|
||||
# 'pam_id': 1,
|
||||
# 'mu_date_execution': '14/06/2021',
|
||||
# 'mu_note_militaire': 0.0,
|
||||
# 'mu_flag_cple_ideal': 0,
|
||||
# 'mu_decision': '',
|
||||
# 'mu_date_decision': '',
|
||||
# 'mu_notes_gestionnaire': '',
|
||||
# 'mu_notes_partagees': ''
|
||||
# },
|
||||
# {
|
||||
# 'mu_id': 0,
|
||||
# 'poste_id': 3,
|
||||
# 'administres_id_sap': 78905,
|
||||
# 'pam_id': 1,
|
||||
# 'mu_date_execution': '14/06/2021',
|
||||
# 'mu_note_militaire': 0.5,
|
||||
# 'mu_flag_cple_ideal': 0,
|
||||
# 'mu_decision': '',
|
||||
# 'mu_date_decision': '',
|
||||
# 'mu_notes_gestionnaire': '',
|
||||
# 'mu_notes_partagees': ''
|
||||
# }]
|
||||
# if data_source == data_test :
|
||||
# test_value = True
|
||||
# else :
|
||||
# test_value = False
|
||||
# self.assertIs(test_value,True)
|
||||
|
||||
3
backend-django/backend/tests/models/__init__.py
Normal file
3
backend-django/backend/tests/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .administre import *
|
||||
from .calcul import *
|
||||
from .poste import *
|
||||
39
backend-django/backend/tests/models/administre.py
Normal file
39
backend-django/backend/tests/models/administre.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from ...models import Administre, StatutPamChoices
|
||||
from django.db.utils import IntegrityError
|
||||
from django.test import TestCase
|
||||
|
||||
class AdministreTestCase(TestCase):
|
||||
|
||||
def test_attributes_statut_pam(self):
|
||||
""" vérifie que le comportement reste correct avec l'ajout de nouveaux attributs """
|
||||
|
||||
attr_calc = 'calc_enabled'
|
||||
attr_dec = 'dec_enabled'
|
||||
for choice in StatutPamChoices:
|
||||
self.assertIsInstance(choice.label, str, f"{choice} : le libellé n'a pas le bon type")
|
||||
self.assertIsInstance(choice.value, str, f"{choice} : la valeur n'a pas le bon type")
|
||||
self.assertEqual(choice.value, choice, f"{choice} : n'est pas égal à sa valeur")
|
||||
self.assertIsInstance(getattr(choice, attr_calc), bool, f"{choice} : l'attribut {attr_calc} n'a pas le bon type")
|
||||
self.assertIsInstance(getattr(choice, attr_dec), bool, f"{choice} : l'attribut {attr_dec} n'a pas le bon type")
|
||||
|
||||
def test_constraint_statut_pam(self):
|
||||
""" vérifie qu'il n'est pas possible de stocker n'importe quoi en base """
|
||||
|
||||
valides = StatutPamChoices.values
|
||||
invalide = 'autre'
|
||||
|
||||
def given():
|
||||
self.assertTrue(valides, 'il devrait exister des choix valides')
|
||||
self.assertNotIn(invalide, valides, 'le test nécessite une valeur invalide')
|
||||
given()
|
||||
|
||||
id_sap = 1
|
||||
|
||||
# valide : création OK
|
||||
for valide in valides:
|
||||
Administre.objects.create(pk=id_sap, a_statut_pam=valide)
|
||||
id_sap = id_sap + 1
|
||||
|
||||
# invalide : création KO
|
||||
with self.assertRaises(IntegrityError):
|
||||
Administre.objects.create(pk=id_sap, a_statut_pam=invalide)
|
||||
34
backend-django/backend/tests/models/calcul.py
Normal file
34
backend-django/backend/tests/models/calcul.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.db.utils import IntegrityError
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from ...models.calcul import Calcul, SousVivier
|
||||
from ...models.calcul import StatutCalculChoices as StatutCalcul
|
||||
|
||||
|
||||
class CalculTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
for i in range(1, 15):
|
||||
SousVivier.objects.create(pk=str(i))
|
||||
|
||||
def test_constraint_statut(self):
|
||||
valides = StatutCalcul.values
|
||||
invalide = 'autre'
|
||||
|
||||
def given():
|
||||
self.assertTrue(valides, 'il devrait exister des choix valides')
|
||||
self.assertNotIn(invalide, valides, 'le test nécessite une valeur invalide')
|
||||
given()
|
||||
|
||||
i = 1
|
||||
|
||||
# valide : création OK
|
||||
for valide in valides:
|
||||
Calcul.objects.create(pk=str(i), ca_date_debut=timezone.now(), ca_statut=valide, ca_statut_pourcentage=0)
|
||||
i = i + 1
|
||||
|
||||
# invalide : création KO
|
||||
with self.assertRaises(IntegrityError):
|
||||
Calcul.objects.create(pk=str(i), ca_date_debut=timezone.now(), ca_statut=invalide, ca_statut_pourcentage=0)
|
||||
34
backend-django/backend/tests/models/poste.py
Normal file
34
backend-django/backend/tests/models/poste.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from ...models import AvisPosteChoices as AvisPoste, Poste, PropositionsArmementChoices as PropositionsArmement
|
||||
from django.db.utils import IntegrityError
|
||||
from django.test import TestCase
|
||||
|
||||
class PosteTestCase(TestCase):
|
||||
|
||||
def test_attributes_avis(self):
|
||||
""" vérifie que le comportement reste correct avec l'ajout de nouveaux attributs """
|
||||
|
||||
attr_calc = 'calc_enabled'
|
||||
attr_dec = 'dec_enabled'
|
||||
for choice in AvisPoste:
|
||||
self.assertIsInstance(choice.label, str, f"{choice} : le libellé n'a pas le bon type")
|
||||
self.assertIsInstance(choice.value, str, f"{choice} : la valeur n'a pas le bon type")
|
||||
self.assertEqual(choice.value, choice, f"{choice} : n'est pas égal à sa valeur")
|
||||
self.assertIsInstance(getattr(choice, attr_calc), bool, f"{choice} : l'attribut {attr_calc} n'a pas le bon type")
|
||||
self.assertIsInstance(getattr(choice, attr_dec), bool, f"{choice} : l'attribut {attr_dec} n'a pas le bon type")
|
||||
|
||||
def test_constraint_propositions_armement(self):
|
||||
valides = PropositionsArmement.values
|
||||
invalide = 'autre'
|
||||
|
||||
def given():
|
||||
self.assertTrue(valides, 'il devrait exister des choix valides')
|
||||
self.assertNotIn(invalide, valides, 'le test nécessite une valeur invalide')
|
||||
given()
|
||||
|
||||
# valide : création OK
|
||||
for valide in valides:
|
||||
Poste.objects.create(pk=valide, propositions_armement=valide)
|
||||
|
||||
# invalide : création KO
|
||||
with self.assertRaises(IntegrityError):
|
||||
Poste.objects.create(pk=invalide, propositions_armement=invalide)
|
||||
75
backend-django/backend/tests/pam/__init__.py
Normal file
75
backend-django/backend/tests/pam/__init__.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
import pandas as pd
|
||||
from rest_framework.test import APIClient, APITestCase
|
||||
from ...utils_extraction import to_table_pam
|
||||
|
||||
from ...models import (Administre, CustomUser, Decision, DecisionChoices,
|
||||
Domaine, Filiere, FormationEmploi, Garnison, Poste,
|
||||
RefGest, RefOrg, RefSvFil, SousVivier, Administres_Pams, Postes_Pams, PAM)
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
|
||||
class PamTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.date = datetime.date(2023,1,1)
|
||||
cls.pam_2022 = PAM.objects.create(pam_id='2022',pam_date=datetime.date(2022,7,15), pam_libelle= "PAM de l'année 2022", pam_statut='PAM en cours')
|
||||
cls.pam_2023 = PAM.objects.create(pam_id='2023',pam_date=datetime.date(2022,7,15), pam_libelle= "PAM de l'année 2023", pam_statut='PAM A+1')
|
||||
|
||||
|
||||
def test_extraction_pam(self):
|
||||
"""
|
||||
Construit la table PAM qui servira à récuperer l'année du PAM et piloter l'affichage des données en fonction du bon pam pour chaque tables
|
||||
Cloture le PAM en le sauvegardant dans un fichier excel
|
||||
"""
|
||||
annee_pam = str(self.date.year)
|
||||
annee_pam_suivant = str(self.date.year + 1)
|
||||
|
||||
pam=pd.DataFrame(columns = ['pam_id','pam_date', 'pam_libelle','pam_statut'])
|
||||
pam_id = annee_pam
|
||||
pam_id_suivant = annee_pam_suivant
|
||||
pam_libelle = f"PAM de l'année {annee_pam}"
|
||||
pam_libelle_suivant = f"PAM de l'année {annee_pam_suivant}"
|
||||
pam_date = self.date
|
||||
pam_date_suivant = pam_date
|
||||
pam_statut = "PAM en cours"
|
||||
pam_statut_suivant = "PAM A+1"
|
||||
|
||||
sorg_id = "SORG"
|
||||
sorg_libelle = "SORG"
|
||||
sorg_statut = "SORG"
|
||||
|
||||
pam['pam_id'] = [pam_id,pam_id_suivant, sorg_id]
|
||||
pam['pam_date'] = [pam_date,pam_date_suivant, pam_date]
|
||||
pam['pam_libelle'] = [pam_libelle,pam_libelle_suivant,sorg_libelle]
|
||||
pam['pam_statut'] = [pam_statut,pam_statut_suivant,sorg_statut]
|
||||
|
||||
return pam
|
||||
|
||||
|
||||
def test_insertion_pam(self):
|
||||
self.liste_create = []
|
||||
self.liste_update = []
|
||||
self.update_header = ['pam_date','pam_libelle','pam_statut']
|
||||
self.df = self.test_extraction_pam()
|
||||
for i in range(self.df.shape[0]):
|
||||
self.pams = PAM.objects.filter(pam_id=self.df.at[i,'pam_id'])
|
||||
self.pam = PAM(pam_id=self.df.at[i, 'pam_id'],pam_date=self.df.at[i, 'pam_date'], pam_libelle=self.df.at[i, 'pam_libelle'],
|
||||
pam_statut=self.df.at[i, 'pam_statut'])
|
||||
|
||||
if self.pam.pam_id in self.pams.values_list('pam_id', flat = True):
|
||||
self.liste_update.append(self.pam)
|
||||
else:
|
||||
cloture=PAM.objects.filter(pam_statut="PAM en cours")
|
||||
cloture.update(pam_statut ="PAM clôturé")
|
||||
self.liste_create.append(self.pam)
|
||||
|
||||
if self.liste_create:
|
||||
PAM.objects.bulk_create(self.liste_create)
|
||||
if self.liste_update:
|
||||
PAM.objects.bulk_update(self.liste_update, fields=self.update_header)
|
||||
6
backend-django/backend/tests/utils/__init__.py
Normal file
6
backend-django/backend/tests/utils/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .alimentation import *
|
||||
from .decisions import *
|
||||
from .decorators import *
|
||||
from .functions import *
|
||||
from .logging import *
|
||||
from .predicates import *
|
||||
56
backend-django/backend/tests/utils/alimentation.py
Normal file
56
backend-django/backend/tests/utils/alimentation.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
from ...utils.alimentation import BOCols as Cols
|
||||
|
||||
|
||||
class BOColsTestCase(SimpleTestCase):
|
||||
""" tests pour BOCols """
|
||||
|
||||
def test_attributes(self):
|
||||
""" vérifie que les nouveaux attributs sont renseignés correctement """
|
||||
|
||||
for col in Cols:
|
||||
self.assertIsInstance(col.value, str, f"{col} : la valeur n'a pas le bon type")
|
||||
|
||||
def test_columns(self):
|
||||
""" vérifie les colonnes renvoyées """
|
||||
members = list(Cols)
|
||||
|
||||
col_names = [col.value for col in members]
|
||||
self.assertCountEqual(col_names, Cols.columns(), "les noms de colonnes (sans argument) ne sont pas les bons")
|
||||
|
||||
col = members[0]
|
||||
col_names = [col.value]
|
||||
self.assertCountEqual(col_names, Cols.columns(col), "les noms de colonnes (un argument) ne sont pas les bons")
|
||||
|
||||
col_list = members[0:2]
|
||||
col_names = [col.value for col in col_list]
|
||||
self.assertCountEqual(col_names, Cols.columns(*col_list), "les noms de colonnes (plusieurs arguments) ne sont pas les bons")
|
||||
|
||||
def test_col_mapping(self):
|
||||
""" vérifie les correspondances renvoyés """
|
||||
|
||||
members = list(Cols)
|
||||
std_mapping = {'a': 1, 'b': 'test'}
|
||||
enum_mapping = {members[0]: (2, 3), members[1]: members[1].name}
|
||||
mapping_before = {**enum_mapping, **std_mapping}
|
||||
mapping_after = Cols.col_mapping(mapping_before)
|
||||
self.assertTrue(all(k in mapping_after for k in std_mapping.keys()), "toutes les clés standard doivent faire partie du mapping final")
|
||||
self.assertTrue(all(v == mapping_after.get(k) for k, v in std_mapping.items()), "les valeurs de clés standard ne doivent pas changer")
|
||||
self.assertTrue(all(k.value in mapping_after for k in enum_mapping.keys()), f"tous les noms de colonnes de clés de type {Cols.__name__} doivent faire partie du mapping final")
|
||||
self.assertTrue(all(v == mapping_after.get(k.value) for k, v in enum_mapping.items()), f"les valeurs de clés de type {Cols.__name__} ne doivent pas changer")
|
||||
|
||||
def test_converters(self):
|
||||
""" vérifie les convertisseurs renvoyés """
|
||||
members = list(Cols)
|
||||
|
||||
col_names = [col.value for col in members if col.converter]
|
||||
self.assertCountEqual(col_names, Cols.converters().keys(), "les convertisseurs (sans argument) ne sont pas les bons")
|
||||
|
||||
col = members[0]
|
||||
col_names = [col.value]
|
||||
self.assertCountEqual(col_names, Cols.converters(col).keys(), "les convertisseurs (un argument) ne sont pas les bons")
|
||||
|
||||
col_list = members[0:2]
|
||||
col_names = [col.value for col in col_list]
|
||||
self.assertCountEqual(col_names, Cols.converters(*col_list).keys(), "les convertisseurs (plusieurs arguments) ne sont pas les bons")
|
||||
283
backend-django/backend/tests/utils/decisions.py
Normal file
283
backend-django/backend/tests/utils/decisions.py
Normal file
@@ -0,0 +1,283 @@
|
||||
from typing import Callable, List, Optional, Tuple, Union
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from ...models import Administre
|
||||
from ...models import AvisPosteChoices as AvisPoste
|
||||
from ...models import CustomUser, Decision, DecisionChoices, Poste, SousVivier
|
||||
from ...models import StatutPamChoices as StatutPam
|
||||
from ...utils.decisions import (DECISIONS, KEY_CHOICES, KEY_CREATE,
|
||||
KEY_PROFILES, KEY_UPDATE, ExtraDecisions,
|
||||
get_all_decisions, get_available_decisions)
|
||||
from ...utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||
get_profiles_by_adm)
|
||||
|
||||
|
||||
class DecisionsTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
""" vérifie qu'il n'existe aucun administré ou poste avant le test """
|
||||
|
||||
self.assertEqual(Administre.objects.exists(), False, "pas d'administré avant un test")
|
||||
self.assertEqual(Poste.objects.exists(), False, "pas de poste avant un test")
|
||||
|
||||
def tearDown(self):
|
||||
""" supprime tous les administrés, les postes, les décisions en fin de test """
|
||||
|
||||
Decision.objects.all().delete()
|
||||
Poste.objects.all().delete()
|
||||
Administre.objects.all().delete()
|
||||
|
||||
def __load_administres(self) -> List[Administre]:
|
||||
""" charge les administrés de la base à partir des IDs """
|
||||
|
||||
self.assertTrue(self.adm_ids, "il est nécessaire de renseigner des IDs d'administrés pour le chargement")
|
||||
return list(Administre.objects.filter(pk__in=self.adm_ids or ()))
|
||||
|
||||
def __setup_decisions(self, decision: Optional[Union[DecisionChoices, ExtraDecisions]]) -> None:
|
||||
""" supprime et recrée (si nécessaire) les postes et les décisions des administrés """
|
||||
|
||||
Decision.objects.all().delete()
|
||||
Poste.objects.all().delete()
|
||||
if decision and decision != ExtraDecisions.EMPTY:
|
||||
for adm_id in self.adm_ids:
|
||||
poste = Poste.objects.create(pk=str(adm_id), p_avis=AvisPoste.P1)
|
||||
Decision.objects.create(administre_id=adm_id, poste=poste, de_decision=decision)
|
||||
|
||||
def __do_for_profiles(self, action: Callable[[Profiles], None], profiles: Tuple[Profiles], decisions: Tuple[Union[DecisionChoices, ExtraDecisions]] = ()) -> None:
|
||||
"""
|
||||
exécute une action (un test) avec un sous-test :
|
||||
- soit pour chaque profil (utilise un mock)
|
||||
- soit pour chaque profil et chaque statut de décision (utilise un mock)
|
||||
implicite : self.adm_ids
|
||||
"""
|
||||
|
||||
def run_subTest(profile: Profiles, decision: Optional[Union[DecisionChoices, ExtraDecisions]] = None) -> None:
|
||||
with self.subTest(profile=profile.name, **({'decision': decision.name} if decision else {})):
|
||||
@mock.patch(
|
||||
f'{get_available_decisions.__module__}.{get_profiles_by_adm.__name__}',
|
||||
return_value={adm_id: {KEY_READ: (), KEY_WRITE: (profile,)} for adm_id in self.adm_ids}
|
||||
)
|
||||
def do_with_profile(mock):
|
||||
action(profile, decision)
|
||||
do_with_profile()
|
||||
|
||||
for p in profiles:
|
||||
if decisions:
|
||||
for decision in decisions:
|
||||
self.__setup_decisions(decision)
|
||||
run_subTest(p, decision)
|
||||
else:
|
||||
run_subTest(p)
|
||||
|
||||
def __assert_common(self, decisions_by_adm):
|
||||
"""
|
||||
assertions communes à propos des décisions renvoyées pour les administrés
|
||||
implicite : self.adm_ids
|
||||
"""
|
||||
|
||||
self.assertIsInstance(decisions_by_adm, dict, "le résultat n'est jamais None")
|
||||
for adm_id in self.adm_ids:
|
||||
self.assertTrue(adm_id in decisions_by_adm, "tous les administrés doivent être présents dans le résultat")
|
||||
|
||||
decisions = decisions_by_adm.get(adm_id)
|
||||
self.assertIsInstance(decisions, dict, "le format des décisions est un dictionnaire")
|
||||
for key in (KEY_CREATE, KEY_UPDATE):
|
||||
self.assertTrue(key in decisions, f"les décisions doivent contenir la clé {key}")
|
||||
self.assertIsInstance(decisions.get(key), tuple, f"la valeur de {key} des décisions n'a pas le type attendu")
|
||||
|
||||
def test_tree_attribute(self):
|
||||
""" vérifie que le comportement reste correct avec l'ajout de nouveaux attributs """
|
||||
|
||||
attr_trees = 'trees'
|
||||
for choice in DecisionChoices:
|
||||
self.assertIsInstance(choice.label, str, f"{choice} : le libellé n'a pas le bon type")
|
||||
self.assertIsInstance(choice.value, str, f"{choice} : la valeur n'a pas le bon type")
|
||||
self.assertEqual(choice.value, choice, f"{choice} : n'est pas égal à sa valeur")
|
||||
self.assertIsInstance(getattr(choice, attr_trees), tuple, f"{choice} : l'attribut {attr_trees} n'a pas le bon type")
|
||||
|
||||
def test_champ_decisions(self):
|
||||
""" vérifie la structure de DECISIONS """
|
||||
|
||||
to_test = DECISIONS
|
||||
|
||||
self.assertIsInstance(to_test, dict, "la structure n'a pas le type attendu")
|
||||
self.assertTrue(to_test, "la structure ne doit pas être vide")
|
||||
|
||||
for key, value in to_test.items():
|
||||
self.assertTrue(key is None or isinstance(key, DecisionChoices), f"la décision {key} n'a pas le type attendu")
|
||||
self.assertIsInstance(value, dict, f"la valeur pour la décision {key} n'a pas le type attendu")
|
||||
|
||||
profiles = value.get(KEY_PROFILES)
|
||||
self.assertTrue(profiles is None or isinstance(profiles, tuple), f"la valeur de {KEY_PROFILES} de la valeur pour la décision {key} n'a pas le type attendu")
|
||||
for p in profiles:
|
||||
self.assertIsInstance(p, Profiles, f"un élément de la clé {KEY_PROFILES} de la valeur pour la décision {key} n'a pas le type attendu")
|
||||
|
||||
choices = value.get(KEY_CHOICES)
|
||||
self.assertTrue(choices is None or isinstance(choices, tuple), f"la valeur de {KEY_CHOICES} de la valeur pour la décision {key} n'a pas le type attendu")
|
||||
for c in choices:
|
||||
self.assertIsInstance(c, (DecisionChoices, ExtraDecisions), f"un élément de la clé {KEY_CHOICES} de la valeur pour la décision {key} n'a pas le type attendu")
|
||||
|
||||
def test_get_all_decisions(self):
|
||||
""" vérifie les valeurs de get_all_decisions """
|
||||
|
||||
to_test = get_all_decisions()
|
||||
|
||||
self.assertIsInstance(to_test, tuple, "le résultat n'a pas le type attendu")
|
||||
self.assertTrue(to_test, "le résultat ne doit pas être vide")
|
||||
|
||||
for value in to_test:
|
||||
self.assertIsInstance(value, DecisionChoices, f"la valeur {value} n'a pas le type attendu")
|
||||
|
||||
def test_pam_wihout_choice(self):
|
||||
""" pour ces statuts PAM il n'y a aucun choix """
|
||||
|
||||
user = CustomUser()
|
||||
self.adm_ids = tuple(a.a_id_sap for a in (
|
||||
Administre.objects.create(pk=i + 1, a_statut_pam=status) for i, status in enumerate(StatutPam) if not status.dec_enabled
|
||||
))
|
||||
|
||||
def action(_p, _d):
|
||||
decisions_by_adm = get_available_decisions(self.__load_administres(), user=user)
|
||||
|
||||
self.__assert_common(decisions_by_adm)
|
||||
for adm_id in self.adm_ids:
|
||||
decisions = decisions_by_adm.get(adm_id)
|
||||
self.assertFalse(decisions.get(KEY_CREATE))
|
||||
self.assertFalse(decisions.get(KEY_UPDATE))
|
||||
|
||||
self.__do_for_profiles(action, profiles=tuple(Profiles))
|
||||
|
||||
def test_pam_with_choices(self):
|
||||
""" pour ces statuts PAM c'est l'arbre de décision qui est utilisé """
|
||||
|
||||
user = CustomUser()
|
||||
self.adm_ids = tuple(a.a_id_sap for a in (
|
||||
Administre.objects.create(pk=i + 1, a_statut_pam=status) for i, status in enumerate(StatutPam) if status.dec_enabled
|
||||
))
|
||||
D = DecisionChoices
|
||||
EX = ExtraDecisions
|
||||
|
||||
def action(p, d):
|
||||
decisions_by_adm = get_available_decisions(self.__load_administres(), user=user)
|
||||
|
||||
self.__assert_common(decisions_by_adm)
|
||||
for adm_id in self.adm_ids:
|
||||
decisions = decisions_by_adm.get(adm_id)
|
||||
|
||||
# create
|
||||
decisions_create = decisions.get(KEY_CREATE)
|
||||
if p == Profiles.FILIERE:
|
||||
self.assertCountEqual(decisions_create, (D.PROPOSITION_FE, D.HME_PROPOSITION_VIVIER))
|
||||
else:
|
||||
self.assertFalse(decisions_create)
|
||||
|
||||
# update
|
||||
decisions_update = decisions.get(KEY_UPDATE)
|
||||
if not d and p == Profiles.FILIERE:
|
||||
self.assertCountEqual(decisions_update, (D.PROPOSITION_FE, D.HME_PROPOSITION_VIVIER))
|
||||
|
||||
elif d == D.PROPOSITION_FE and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||
self.assertCountEqual(decisions_update, (D.DIALOGUE_EN_COURS, D.FOREMP_EN_COURS))
|
||||
|
||||
elif d == D.DIALOGUE_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||
self.assertCountEqual(decisions_update, (D.DIALOGUE_TERMINE, D.DIALOGUE_INFRUCTUEUX))
|
||||
|
||||
elif d == D.DIALOGUE_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||
self.assertCountEqual(decisions_update, (D.FOREMP_EN_COURS,))
|
||||
|
||||
elif d == D.DIALOGUE_INFRUCTUEUX and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||
self.assertCountEqual(decisions_update, (D.FOREMP_EN_COURS, D.REMIS_A_DISPOSITION))
|
||||
|
||||
elif d == D.FOREMP_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||
self.assertCountEqual(decisions_update, (D.FOREMP_TERMINE,))
|
||||
|
||||
elif d == D.FOREMP_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||
self.assertCountEqual(decisions_update, (D.PREPOSITIONNE, D.REMIS_A_DISPOSITION))
|
||||
|
||||
elif d == D.PREPOSITIONNE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.POSITIONNE, D.REMIS_A_DISPOSITION))
|
||||
|
||||
elif d == D.POSITIONNE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.OMIP_EN_COURS, D.OMI_EN_COURS, D.REMIS_A_DISPOSITION))
|
||||
|
||||
elif d == D.OMIP_EN_COURS and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.OMIP_TERMINE, D.REMIS_A_DISPOSITION))
|
||||
|
||||
elif d == D.OMIP_TERMINE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.ATTENTE_AVIONAGE, D.OMI_EN_COURS, D.REMIS_A_DISPOSITION))
|
||||
|
||||
elif d == D.ATTENTE_AVIONAGE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.OMI_EN_COURS,))
|
||||
|
||||
elif d == D.OMI_EN_COURS and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.OMI_ACTIVE, D.REMIS_A_DISPOSITION))
|
||||
|
||||
elif d == D.OMI_ACTIVE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.OMI_ANNULE,))
|
||||
|
||||
elif d == D.OMI_ANNULE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.REMIS_A_DISPOSITION,))
|
||||
|
||||
elif d == D.REMIS_A_DISPOSITION and p == Profiles.FILIERE:
|
||||
self.assertCountEqual(decisions_update, (EX.EMPTY,))
|
||||
|
||||
elif d == D.HME_PROPOSITION_VIVIER and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_DIALOGUE_INITIE,))
|
||||
|
||||
elif d == D.HME_ETUDE_DESISTEMENT and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_DESISTEMENT,))
|
||||
|
||||
elif d == D.HME_DESISTEMENT and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.REMIS_A_DISPOSITION,))
|
||||
|
||||
elif d == D.HME_DIALOGUE_INITIE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_DIALOGUE_EN_COURS,))
|
||||
|
||||
elif d == D.HME_DIALOGUE_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_DIALOGUE_TERMINE, D.HME_DIALOGUE_INFRUCTUEUX))
|
||||
|
||||
elif d == D.HME_DIALOGUE_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_PREPOSITIONNE,))
|
||||
|
||||
elif d == D.HME_DIALOGUE_INFRUCTUEUX and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_ETUDE_DESISTEMENT, D.HME_PREPOSITIONNE, D.REMIS_A_DISPOSITION))
|
||||
|
||||
elif d == D.HME_FOREMP_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_FOREMP_TERMINE, D.HME_OMI_EN_COURS))
|
||||
|
||||
elif d == D.HME_FOREMP_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_OMIP_EN_COURS, D.HME_OMI_EN_COURS))
|
||||
|
||||
elif d == D.HME_PREPOSITIONNE and p == Profiles.HME:
|
||||
self.assertCountEqual(decisions_update, (D.HME_VALIDATION_EXPERT, D.HME_REFUS_EXPERT))
|
||||
|
||||
elif d == D.HME_POSITIONNE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_OMIP_EN_COURS, D.HME_OMI_EN_COURS, D.HME_FOREMP_EN_COURS))
|
||||
|
||||
elif d == D.HME_VALIDATION_EXPERT and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_POSITIONNE,))
|
||||
|
||||
elif d == D.HME_REFUS_EXPERT and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_PREPOSITIONNE, D.REMIS_A_DISPOSITION))
|
||||
|
||||
elif d == D.HME_OMIP_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_OMIP_TERMINE,))
|
||||
|
||||
elif d == D.HME_OMIP_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_ATTENTE_AVIONAGE, D.HME_OMI_EN_COURS))
|
||||
|
||||
elif d == D.HME_ATTENTE_AVIONAGE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_OMI_EN_COURS,))
|
||||
|
||||
elif d == D.HME_OMI_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, (D.HME_OMI_ACTIVE,))
|
||||
|
||||
elif d == D.HME_OMI_ACTIVE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||
self.assertCountEqual(decisions_update, ())
|
||||
|
||||
else:
|
||||
self.assertFalse(decisions_update)
|
||||
|
||||
self.__do_for_profiles(action, profiles=tuple(Profiles), decisions=(EX.EMPTY, *get_all_decisions()))
|
||||
85
backend-django/backend/tests/utils/decorators.py
Normal file
85
backend-django/backend/tests/utils/decorators.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from .samples import MyClass
|
||||
from ...utils.decorators import class_logger, decorate_functions, CLASS_ATTR_LOGGER
|
||||
from ...utils.functions import find_class
|
||||
from ...utils.logging import get_logger_name
|
||||
from ...utils.predicates import func_name_is_in
|
||||
from django.test import SimpleTestCase
|
||||
from typing import Tuple
|
||||
import functools
|
||||
|
||||
|
||||
def double_result(func):
|
||||
""" décorateur pour doubler les résultats """
|
||||
@functools.wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
return 2 * func(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
|
||||
class DecoratorTest(SimpleTestCase):
|
||||
|
||||
def test_decorate_functions(self):
|
||||
num = 5
|
||||
a = 1
|
||||
b = 2
|
||||
res = a + b
|
||||
res_x2 = res * 2
|
||||
|
||||
def given():
|
||||
msg = 'le résultat initial est bien celui attendu'
|
||||
self.assertEqual(MyClass.class_met(a, b), res, msg)
|
||||
self.assertEqual(MyClass.static_met(a, b), res, msg)
|
||||
self.assertEqual(MyClass(num).std_met(a, b), res, msg)
|
||||
given()
|
||||
|
||||
# tout est décoré
|
||||
Type = decorate_functions(double_result, lambda func: True)(MyClass)
|
||||
self.assertEqual(Type.class_met(a, b), res_x2)
|
||||
self.assertEqual(Type.static_met(a, b), res_x2)
|
||||
self.assertEqual(Type(num).std_met(a, b), res_x2)
|
||||
|
||||
# une seule méthode est décorée (1ère)
|
||||
Type = decorate_functions(double_result, func_name_is_in(MyClass.class_met.__name__,))(MyClass)
|
||||
self.assertEqual(Type.class_met(a, b), res_x2)
|
||||
self.assertEqual(Type.static_met(a, b), res)
|
||||
self.assertEqual(Type(num).std_met(a, b), res)
|
||||
|
||||
# une seule méthode est décorée (2ème)
|
||||
Type = decorate_functions(double_result, func_name_is_in(MyClass.static_met.__name__,))(MyClass)
|
||||
self.assertEqual(Type.class_met(a, b), res)
|
||||
self.assertEqual(Type.static_met(a, b), res_x2)
|
||||
self.assertEqual(Type(num).std_met(a, b), res)
|
||||
|
||||
# une seule méthode est décorée (3ème)
|
||||
Type = decorate_functions(double_result, func_name_is_in(MyClass.std_met.__name__,))(MyClass)
|
||||
self.assertEqual(Type.class_met(a, b), res)
|
||||
self.assertEqual(Type.static_met(a, b), res)
|
||||
self.assertEqual(Type(num).std_met(a, b), res_x2)
|
||||
|
||||
# une seule méthode est décorée deux fois (3ème)
|
||||
Type = decorate_functions(double_result, func_name_is_in(MyClass.std_met.__name__,))(Type)
|
||||
self.assertEqual(Type.class_met(a, b), res)
|
||||
self.assertEqual(Type.static_met(a, b), res)
|
||||
self.assertEqual(Type(num).std_met(a, b), 2 * res_x2)
|
||||
|
||||
# pas de changement au niveau de la classe d'origine
|
||||
self.assertEqual(MyClass.class_met(a, b), res)
|
||||
self.assertEqual(MyClass.static_met(a, b), res)
|
||||
self.assertEqual(MyClass(num).std_met(a, b), res)
|
||||
|
||||
def test_class_logger(self):
|
||||
def given():
|
||||
self.assertEqual(hasattr(MyClass, CLASS_ATTR_LOGGER), False, "la classe initiale n'a pas de logger")
|
||||
given()
|
||||
|
||||
Type = class_logger(MyClass)
|
||||
self.assertEqual(hasattr(Type, CLASS_ATTR_LOGGER), True)
|
||||
self.assertEqual(getattr(Type, CLASS_ATTR_LOGGER).name, get_logger_name(MyClass))
|
||||
|
||||
# 2ème décoration
|
||||
Type = class_logger(Type)
|
||||
self.assertEqual(hasattr(Type, CLASS_ATTR_LOGGER), True)
|
||||
self.assertEqual(getattr(Type, CLASS_ATTR_LOGGER).name, get_logger_name(MyClass))
|
||||
|
||||
# pas de changement au niveau de la classe d'origine
|
||||
self.assertEqual(hasattr(MyClass, CLASS_ATTR_LOGGER), False)
|
||||
30
backend-django/backend/tests/utils/functions.py
Normal file
30
backend-django/backend/tests/utils/functions.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from .samples import my_func, MyClass
|
||||
from ...utils.functions import find_class
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
|
||||
class FunctionsTest(SimpleTestCase):
|
||||
|
||||
def test_find_class(self):
|
||||
MainType = MyClass
|
||||
main_type = MainType(11)
|
||||
SubType = MyClass.MySubClass
|
||||
sub_type = SubType(13)
|
||||
|
||||
def local_func(a: int, b: int):
|
||||
return a + b
|
||||
|
||||
# fonction native : None
|
||||
self.assertIsNone(find_class(str.join))
|
||||
|
||||
# fonctions : None
|
||||
self.assertIsNone(find_class(local_func))
|
||||
self.assertIsNone(find_class(my_func))
|
||||
|
||||
# méthodes
|
||||
self.assertEqual(find_class(MainType.class_met), MainType)
|
||||
self.assertEqual(find_class(MainType.static_met), MainType)
|
||||
self.assertEqual(find_class(main_type.std_met), MainType)
|
||||
self.assertEqual(find_class(SubType.sub_class_met), SubType)
|
||||
self.assertEqual(find_class(SubType.sub_static_met), SubType)
|
||||
self.assertEqual(find_class(sub_type.sub_std_met), SubType)
|
||||
104
backend-django/backend/tests/utils/logging.py
Normal file
104
backend-django/backend/tests/utils/logging.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import random
|
||||
import string
|
||||
from unittest import mock
|
||||
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
from ...utils.logging import (TAG_DATA_FEED, TAG_PERF, get_logger,
|
||||
get_logger_name)
|
||||
from .samples import MyClass, my_func
|
||||
|
||||
|
||||
class LoggingTest(SimpleTestCase):
|
||||
|
||||
def test_get_logger_name(self):
|
||||
module_name = __name__
|
||||
ext_module_name = my_func.__module__
|
||||
|
||||
MainType = MyClass
|
||||
main_type = MainType(11)
|
||||
main_type_logger = f'{ext_module_name}.{MainType.__qualname__}'
|
||||
SubType = MyClass.MySubClass
|
||||
sub_type = SubType(13)
|
||||
sub_type_logger = f'{ext_module_name}.{SubType.__qualname__}'
|
||||
|
||||
def local_func(a: int, b: int):
|
||||
return a + b
|
||||
|
||||
# None
|
||||
self.assertIsNone(get_logger_name())
|
||||
self.assertIsNone(get_logger_name(None))
|
||||
|
||||
# classes natives
|
||||
self.assertIsNone(get_logger_name(str))
|
||||
self.assertIsNone(get_logger_name(int))
|
||||
self.assertIsNone(get_logger_name(dict))
|
||||
self.assertIsNone(get_logger_name(list))
|
||||
self.assertIsNone(get_logger_name(tuple))
|
||||
|
||||
# instances de classes natives (mais pas str)
|
||||
self.assertIsNone(get_logger_name(1))
|
||||
self.assertIsNone(get_logger_name({'a': 'b'}))
|
||||
self.assertIsNone(get_logger_name(['c']))
|
||||
self.assertIsNone(get_logger_name(('d',)))
|
||||
|
||||
# fonctions natives
|
||||
self.assertIsNone(get_logger_name(str.join))
|
||||
|
||||
# str
|
||||
self.assertEqual(get_logger_name('test'), 'test')
|
||||
self.assertEqual(get_logger_name(''), None)
|
||||
|
||||
# classes
|
||||
self.assertEqual(get_logger_name(MainType), main_type_logger)
|
||||
self.assertEqual(get_logger_name(SubType), sub_type_logger)
|
||||
|
||||
# instances de classes
|
||||
self.assertEqual(get_logger_name(main_type), main_type_logger)
|
||||
self.assertEqual(get_logger_name(sub_type), sub_type_logger)
|
||||
|
||||
# fonctions et méthodes
|
||||
self.assertEqual(get_logger_name(local_func), module_name)
|
||||
self.assertEqual(get_logger_name(my_func), ext_module_name)
|
||||
self.assertEqual(get_logger_name(MainType.class_met), main_type_logger)
|
||||
self.assertEqual(get_logger_name(MainType.static_met), main_type_logger)
|
||||
self.assertEqual(get_logger_name(main_type.std_met), main_type_logger)
|
||||
self.assertEqual(get_logger_name(SubType.sub_class_met), sub_type_logger)
|
||||
self.assertEqual(get_logger_name(SubType.sub_static_met), sub_type_logger)
|
||||
self.assertEqual(get_logger_name(sub_type.sub_std_met), sub_type_logger)
|
||||
|
||||
def test_get_logger_without_tags(self):
|
||||
func_1 = MyClass.class_met
|
||||
func_2 = MyClass.static_met
|
||||
|
||||
def given():
|
||||
self.assertEqual(get_logger_name(func_1), get_logger_name(func_2), 'le nom de logger doit être le même')
|
||||
given()
|
||||
|
||||
msg = "l'instance doit être la même"
|
||||
logger_name = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
|
||||
self.assertIs(get_logger(logger_name), get_logger(logger_name), msg)
|
||||
self.assertIs(get_logger(func_1), get_logger(func_2), msg)
|
||||
|
||||
def test_get_logger_with_tags(self):
|
||||
func_1 = MyClass.class_met
|
||||
func_2 = MyClass.static_met
|
||||
|
||||
def given():
|
||||
self.assertEqual(get_logger_name(func_1), get_logger_name(func_2), 'le nom de logger doit être le même')
|
||||
given()
|
||||
|
||||
msg = "l'instance doit être la même"
|
||||
for tags in [TAG_PERF, TAG_DATA_FEED, ('1', '2'), ['3', '4'], set(['5', '6'])]:
|
||||
with self.subTest(tags=tags):
|
||||
logger_name = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
|
||||
self.assertIs(get_logger(logger_name, tags), get_logger(logger_name, tags), msg)
|
||||
self.assertIs(get_logger(func_1, tags), get_logger(func_2, tags), msg)
|
||||
|
||||
prev_logger = None
|
||||
logger_name = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
|
||||
for i, tags in enumerate([('1', '2', '3', '4'), ['3', '4', '1', '2'], set(['2', '1', '3', '4', '1'])]):
|
||||
curr_logger = get_logger(logger_name, tags)
|
||||
if i > 0:
|
||||
self.assertIs(curr_logger, prev_logger, msg)
|
||||
prev_logger = get_logger(logger_name, tags)
|
||||
127
backend-django/backend/tests/utils/predicates.py
Normal file
127
backend-django/backend/tests/utils/predicates.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from .samples import my_func, _my_private_func, MyClass
|
||||
from ...utils.predicates import func_class_name_is_in, func_class_name_is_not_in, func_name_is_in, func_name_is_not_in, func_is_public
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
|
||||
class PredicatesTest(SimpleTestCase):
|
||||
|
||||
def __test_func_class_name_is_in(self, negation: bool = False):
|
||||
""" teste func_class_name_is_in et func_class_name_is_not_in """
|
||||
|
||||
func = func_class_name_is_not_in if negation else func_class_name_is_in
|
||||
result = negation is not True
|
||||
|
||||
own_name = self.__class__.__name__
|
||||
MainType = MyClass
|
||||
main_type = MainType(11)
|
||||
main_type_name = MainType.__name__
|
||||
SubType = MyClass.MySubClass
|
||||
sub_type = SubType(13)
|
||||
sub_type_name = SubType.__name__
|
||||
|
||||
def local_func(a: int, b: int):
|
||||
return a + b
|
||||
|
||||
# fonction native : None
|
||||
self.assertEqual(func(str.__name__)(str.join), False)
|
||||
|
||||
# fonction locale : None
|
||||
self.assertEqual(func(own_name)(local_func), False)
|
||||
|
||||
# méthodes
|
||||
predicate = func(main_type_name)
|
||||
self.assertEqual(predicate(MainType.class_met), result)
|
||||
self.assertEqual(predicate(MainType.static_met), result)
|
||||
self.assertEqual(predicate(main_type.std_met), result)
|
||||
self.assertEqual(predicate(SubType.sub_class_met), not result)
|
||||
self.assertEqual(predicate(SubType.sub_static_met), not result)
|
||||
self.assertEqual(predicate(sub_type.sub_std_met), not result)
|
||||
|
||||
predicate = func(sub_type_name)
|
||||
self.assertEqual(predicate(MainType.class_met), not result)
|
||||
self.assertEqual(predicate(MainType.static_met), not result)
|
||||
self.assertEqual(predicate(main_type.std_met), not result)
|
||||
self.assertEqual(predicate(SubType.sub_class_met), result)
|
||||
self.assertEqual(predicate(SubType.sub_static_met), result)
|
||||
self.assertEqual(predicate(sub_type.sub_std_met), result)
|
||||
|
||||
def __test_func_name_is_in(self, negation: bool = False):
|
||||
""" teste func_name_is_in et func_name_is_not_in """
|
||||
|
||||
func = func_name_is_not_in if negation else func_name_is_in
|
||||
result = negation is not True
|
||||
|
||||
MainType = MyClass
|
||||
main_type = MainType(11)
|
||||
SubType = MyClass.MySubClass
|
||||
sub_type = SubType(13)
|
||||
|
||||
def local_func(a: int, b: int):
|
||||
return a + b
|
||||
|
||||
# fonction native
|
||||
self.assertEqual(func('join')(str.join), result)
|
||||
self.assertEqual(func('other', 'join')(str.join), result)
|
||||
|
||||
# autre fonction, fonction locale
|
||||
self.assertEqual(func('local_func')(local_func), result)
|
||||
self.assertEqual(func('other', 'local_func')(local_func), result)
|
||||
self.assertEqual(func('my_func')(my_func), result)
|
||||
self.assertEqual(func('other', 'my_func')(my_func), result)
|
||||
|
||||
|
||||
# méthodes
|
||||
predicate = func('class_met', 'std_met', 'sub_static_met')
|
||||
self.assertEqual(predicate(MainType.class_met), result)
|
||||
self.assertEqual(predicate(MainType.static_met), not result)
|
||||
self.assertEqual(predicate(main_type.std_met), result)
|
||||
self.assertEqual(predicate(SubType.sub_class_met), not result)
|
||||
self.assertEqual(predicate(SubType.sub_static_met), result)
|
||||
self.assertEqual(predicate(sub_type.sub_std_met), not result)
|
||||
|
||||
predicate = func('static_met', 'sub_class_met', 'sub_std_met')
|
||||
self.assertEqual(predicate(MainType.class_met), not result)
|
||||
self.assertEqual(predicate(MainType.static_met), result)
|
||||
self.assertEqual(predicate(main_type.std_met), not result)
|
||||
self.assertEqual(predicate(SubType.sub_class_met), result)
|
||||
self.assertEqual(predicate(SubType.sub_static_met), not result)
|
||||
self.assertEqual(predicate(sub_type.sub_std_met), result)
|
||||
|
||||
|
||||
def test_func_class_name_is_in(self):
|
||||
self.__test_func_class_name_is_in()
|
||||
|
||||
def test_func_class_name_is_not_in(self):
|
||||
self.__test_func_class_name_is_in(negation=True)
|
||||
|
||||
def test_func_name_is_in(self):
|
||||
self.__test_func_name_is_in()
|
||||
|
||||
def test_func_name_is_not_in(self):
|
||||
self.__test_func_name_is_in(negation=True)
|
||||
|
||||
def test_func_is_public(self):
|
||||
MainType = MyClass
|
||||
main_type = MainType(11)
|
||||
SubType = MyClass.MySubClass
|
||||
sub_type = SubType(13)
|
||||
|
||||
def local_func(a: int, b: int):
|
||||
return a + b
|
||||
|
||||
# fonction native
|
||||
self.assertEqual(func_is_public(str.join), True)
|
||||
|
||||
# autre fonction, fonction locale
|
||||
self.assertEqual(func_is_public(local_func), True)
|
||||
self.assertEqual(func_is_public(my_func), True)
|
||||
self.assertEqual(func_is_public(_my_private_func), False)
|
||||
|
||||
# méthodes
|
||||
self.assertEqual(func_is_public(MainType._protected_met), False)
|
||||
self.assertEqual(func_is_public(MainType.class_met), True)
|
||||
self.assertEqual(func_is_public(MainType.static_met), True)
|
||||
self.assertEqual(func_is_public(main_type.std_met), True)
|
||||
self.assertEqual(func_is_public(SubType.sub_class_met), True)
|
||||
self.assertEqual(func_is_public(SubType.sub_static_met), True)
|
||||
self.assertEqual(func_is_public(sub_type.sub_std_met), True)
|
||||
58
backend-django/backend/tests/utils/samples.py
Normal file
58
backend-django/backend/tests/utils/samples.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# quelques définitions pour les tests ==>
|
||||
|
||||
def my_func(a: int, b: int):
|
||||
""" fonction sans classe """
|
||||
return a + b
|
||||
|
||||
|
||||
def _my_private_func(a: int, b: int):
|
||||
""" fonction sans classe (non importée par défaut avec import *) """
|
||||
return a + b
|
||||
|
||||
|
||||
class MyClass():
|
||||
""" classe pour tests """
|
||||
num = None
|
||||
|
||||
def __init__(self, num):
|
||||
""" la classe est paramétrée """
|
||||
self.num = num
|
||||
|
||||
def _protected_met(self, a: int, b: int):
|
||||
""" méthode standard (protected par convention) """
|
||||
return a + b
|
||||
|
||||
class MySubClass():
|
||||
sub_num = None
|
||||
|
||||
def __init__(self, num):
|
||||
""" la classe est paramétrée """
|
||||
self.sub_num = num
|
||||
|
||||
@classmethod
|
||||
def sub_class_met(cls):
|
||||
""" méthode de classe """
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def sub_static_met():
|
||||
""" méthode statique """
|
||||
return True
|
||||
|
||||
def sub_std_met():
|
||||
""" méthode standard """
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def class_met(cls, a: int, b: int):
|
||||
""" méthode de classe """
|
||||
return a + b
|
||||
|
||||
@staticmethod
|
||||
def static_met(a: int, b: int):
|
||||
""" méthode statique """
|
||||
return a + b
|
||||
|
||||
def std_met(self, a: int, b: int):
|
||||
""" méthode standard """
|
||||
return a + b
|
||||
2
backend-django/backend/tests/views/__init__.py
Normal file
2
backend-django/backend/tests/views/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .decision import *
|
||||
from .notation import *
|
||||
2
backend-django/backend/tests/views/constants.py
Normal file
2
backend-django/backend/tests/views/constants.py
Normal file
@@ -0,0 +1,2 @@
|
||||
USERNAME = 'test'
|
||||
PASSWORD = 'test'
|
||||
147
backend-django/backend/tests/views/decision.py
Normal file
147
backend-django/backend/tests/views/decision.py
Normal file
@@ -0,0 +1,147 @@
|
||||
from unittest import mock
|
||||
|
||||
from backend.models import Administre, Decision, DecisionChoices, Poste
|
||||
from backend.models import StatutPamChoices as StatutPam
|
||||
from backend.utils.decisions import (KEY_CREATE, KEY_UPDATE,
|
||||
get_available_decisions)
|
||||
from backend.utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||
get_profiles_by_adm)
|
||||
from backend.views import DecisionView
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from .constants import PASSWORD, USERNAME
|
||||
from .test_utils import TestUtilsMixin, disable_gestionnaire_permission
|
||||
|
||||
PK_SV_1 = 'SV1'
|
||||
VIEW_TYPE = DecisionView
|
||||
|
||||
class DecisionViewTest(APITestCase, TestUtilsMixin):
|
||||
|
||||
basename = 'Decision'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
user = get_user_model().objects.create(id=1, username=USERNAME, is_superuser=True)
|
||||
user.set_password(PASSWORD)
|
||||
user.save()
|
||||
|
||||
def setUp(self):
|
||||
""" vérifie qu'il n'existe aucun administré ou poste avant le test """
|
||||
|
||||
self.assertEqual(Administre.objects.exists(), False, "pas d'administré avant un test")
|
||||
self.assertEqual(Poste.objects.exists(), False, "pas de poste avant un test")
|
||||
logged_in = self.client.login(username=USERNAME, password=PASSWORD)
|
||||
self.assertTrue(logged_in, "l'utilisateur devrait être connecté")
|
||||
|
||||
def tearDown(self):
|
||||
""" supprime tous les administrés, les postes, les décisions en fin de test """
|
||||
|
||||
self.client.logout()
|
||||
Decision.objects.all().delete()
|
||||
Poste.objects.all().delete()
|
||||
Administre.objects.all().delete()
|
||||
|
||||
def test_list_anonymous(self):
|
||||
""" vérifie que l'accès anonyme est interdit """
|
||||
|
||||
self.client.logout()
|
||||
url = self._api_url()
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
@disable_gestionnaire_permission(VIEW_TYPE)
|
||||
def test_list_authenticated(self):
|
||||
""" vérifie que l'utilisateur authentifié peut récupérer des décisions """
|
||||
|
||||
url = self._api_url()
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@disable_gestionnaire_permission(VIEW_TYPE)
|
||||
def test_crud(self):
|
||||
""" test de création, MAJ, suppression """
|
||||
try:
|
||||
statut_pam = next(x for x in StatutPam if x.dec_enabled)
|
||||
self.assertIsNotNone(statut_pam, 'il devrait exister un statut permettant les décisions')
|
||||
self.user = get_user_model().objects.first()
|
||||
self.adm = Administre.objects.create(pk=1, a_statut_pam=statut_pam)
|
||||
|
||||
@mock.patch(
|
||||
f'{get_available_decisions.__module__}.{get_profiles_by_adm.__name__}',
|
||||
return_value={self.adm.pk: {KEY_READ: (), KEY_WRITE: (Profiles.FILIERE, Profiles.PCP)}}
|
||||
)
|
||||
def do_with_mock_profiles(mock):
|
||||
user = self.user
|
||||
adm = self.adm
|
||||
decisions = get_available_decisions((adm,), user=user).get(adm.pk)
|
||||
self.assertTrue(decisions.get(KEY_CREATE), 'il devrait exister des décisions possibles (création)')
|
||||
|
||||
def create(poste_id, de_decision, adm_id=adm.pk, delete_former=None):
|
||||
return self.client.post(
|
||||
self._api_url(),
|
||||
{'administre_id': adm_id, 'poste_id': poste_id, 'de_decision': de_decision, **({'delete_former': delete_former} if isinstance(delete_former, bool) else {})}
|
||||
)
|
||||
|
||||
postes = (Poste.objects.create(pk='11'), Poste.objects.create(pk='13'))
|
||||
poste_1 = postes[0].pk
|
||||
poste_2 = postes[1].pk
|
||||
dec_status_1 = decisions.get(KEY_CREATE)[0]
|
||||
|
||||
# création initiale
|
||||
response = create(poste_1, dec_status_1)
|
||||
|
||||
qs_decision1 = Decision.objects.filter(pk=adm.pk, poste_id=poste_1, de_decision=dec_status_1)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(1, Decision.objects.count(), "il doit exister une seule décision")
|
||||
self.assertTrue(qs_decision1.exists(), "la décision n'a pas les bonnes données")
|
||||
|
||||
# création bis, pas possible sans forcer
|
||||
response = create(poste_2, dec_status_1)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(1, Decision.objects.count(), "il doit exister une seule décision")
|
||||
self.assertTrue(qs_decision1.exists(), "la première décision doit encore exister")
|
||||
|
||||
# création bis en forçant
|
||||
notes = 'notes'
|
||||
qs_decision1.update(de_notes_gestionnaire='notes')
|
||||
self.assertEqual(notes, qs_decision1.first().de_notes_gestionnaire, "les notes doivent être sauvegardées pour le prochain test")
|
||||
|
||||
response = create(poste_2, dec_status_1, delete_former=True)
|
||||
|
||||
qs_decision2 = Decision.objects.filter(pk=adm.pk, poste_id=poste_2, de_decision=dec_status_1)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(1, Decision.objects.count(), "il doit exister une seule décision")
|
||||
self.assertFalse(qs_decision1.exists(), "la première décision ne doit plus exister")
|
||||
self.assertTrue(qs_decision2.exists(), "la deuxième décision n'a pas les bonnes données")
|
||||
self.assertIsNone(qs_decision2.first().de_notes_gestionnaire, "il ne doit plus exister de notes dans la deuxième décision")
|
||||
|
||||
# MAJ
|
||||
adm = Administre.objects.filter(pk=adm.pk).first()
|
||||
decisions = get_available_decisions((adm,), user=user).get(adm.pk)
|
||||
self.assertTrue(decisions.get(KEY_UPDATE), 'il devrait exister des décisions possibles (MAJ) pour le prochain test')
|
||||
dec_status_2 = decisions.get(KEY_UPDATE)[0]
|
||||
|
||||
response = self.client.patch(self._api_url(adm.pk), data={'de_decision': dec_status_2})
|
||||
|
||||
qs_decision3 = Decision.objects.filter(pk=adm.pk, poste_id=poste_2, de_decision=dec_status_2)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(1, Decision.objects.count(), "il doit exister une seule décision")
|
||||
self.assertTrue(qs_decision3.exists(), "la deuxième décision doit changer de statut")
|
||||
|
||||
# suppression
|
||||
response = self.client.delete(self._api_url(adm.pk))
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
self.assertEqual(0, Decision.objects.count(), "il ne doit plus exister de décision")
|
||||
|
||||
do_with_mock_profiles()
|
||||
finally:
|
||||
self.user = None
|
||||
self.adm = None
|
||||
46
backend-django/backend/tests/views/notation.py
Normal file
46
backend-django/backend/tests/views/notation.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from backend.views import NotationView
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from .constants import PASSWORD, USERNAME
|
||||
from .test_utils import TestUtilsMixin, disable_gestionnaire_permission
|
||||
|
||||
VIEW_TYPE = NotationView
|
||||
|
||||
class NotationViewTest(APITestCase, TestUtilsMixin):
|
||||
|
||||
basename = 'Notation'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
user = get_user_model().objects.create(id=1, username=USERNAME)
|
||||
user.set_password(PASSWORD)
|
||||
user.save()
|
||||
|
||||
def setUp(self):
|
||||
logged_in = self.client.login(username=USERNAME, password=PASSWORD)
|
||||
self.assertTrue(logged_in, "l'utilisateur devrait être connecté")
|
||||
|
||||
def tearDown(self):
|
||||
self.client.logout()
|
||||
|
||||
def test_list_anonymous(self):
|
||||
""" vérifie que l'accès anonyme est interdit """
|
||||
|
||||
self.client.logout()
|
||||
url = self._api_url()
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
@disable_gestionnaire_permission(VIEW_TYPE)
|
||||
def test_list_authenticated(self):
|
||||
""" vérifie que l'utilisateur authentifié peut récupérer des décisions """
|
||||
|
||||
url = self._api_url()
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
44
backend-django/backend/tests/views/test_utils.py
Normal file
44
backend-django/backend/tests/views/test_utils.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from django.urls import reverse
|
||||
from typing import Any
|
||||
from unittest import mock
|
||||
|
||||
|
||||
def disable_gestionnaire_permission(type):
|
||||
"""
|
||||
Utilise "mock" pour ignorer le gestionnaire de permissions dans la vue.
|
||||
TODO trouver un mécanisme de mock plus précis
|
||||
|
||||
:param type: le type de la vue
|
||||
:type type: class:`ModelViewSet` par exemple
|
||||
|
||||
:return: résultat du mock
|
||||
:rtype: voir mock
|
||||
"""
|
||||
return mock.patch.object(type, 'permission_classes', [p for p in type.permission_classes if p.__name__ != 'GestionnairePermission'])
|
||||
|
||||
|
||||
def api_url(basename: str, pk: Any = None) -> str:
|
||||
"""
|
||||
renvoie l'URL selon qu'on a besoin d'un ID ou non
|
||||
nécessite l'utilisation de SimpleRouter ou DefaultRouter (voir urls.py)
|
||||
|
||||
:param basename: valeur de "basename"
|
||||
:type basename: str
|
||||
|
||||
:param pk: valeur qui permet de savoir s'il s'agit d'une URL "list" ou "detail" (voir https://www.django-rest-framework.org/api-guide/routers/#simplerouter)
|
||||
:type pk: Any
|
||||
|
||||
:return: URL
|
||||
:rtype: str
|
||||
"""
|
||||
return reverse(f'{basename}-list') if pk is None else reverse(f'{basename}-detail', args=[pk])
|
||||
|
||||
|
||||
class TestUtilsMixin:
|
||||
"""
|
||||
mixin à ajouter pour les tests
|
||||
"""
|
||||
|
||||
def _api_url(self, pk: Any = None) -> str:
|
||||
""" voir fonction "api_url", nécessite un champ "basename" pour remplir le paramètre """
|
||||
return api_url(self.basename, pk)
|
||||
48
backend-django/backend/urls.py
Normal file
48
backend-django/backend/urls.py
Normal file
@@ -0,0 +1,48 @@
|
||||
""" Ce fichier est pour organiser les URLs de l'application web. Le code de ce module est une correspondance entre expressions de chemins d’URL et fonctions Python.
|
||||
"""
|
||||
from django.urls import include, path
|
||||
from rest_framework import routers
|
||||
|
||||
from . import views
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'notations', views.NotationView, basename='Notation')
|
||||
router.register(r'decisions', views.DecisionView, basename='Decision')
|
||||
router.register(r'groupes_marques', views.MarquesGroupeView, basename='MarquesGroupe')
|
||||
router.register(r'marques', views.MarqueView, basename='Marque')
|
||||
router.register(r'domaines', views.DomaineView, basename='Domaine')
|
||||
router.register(r'filieres', views.FiliereView, basename='Filiere')
|
||||
router.register(r'administres', views.AdministreView, basename='Administre')
|
||||
router.register(r'administres_pams', views.AdministrePAMView, basename='Administre')
|
||||
router.register(r'postes', views.PosteView, basename='Poste')
|
||||
router.register(r'postes_pams', views.PostePAMView, basename='Poste')
|
||||
router.register(r'liste_preferences', views.ListesPreferencesView, basename='PreferencesListe')
|
||||
router.register(r'fmob', views.FmobView, basename='FMOB')
|
||||
router.register(r'formation_emploi', views.FormationEmploiView, basename='FormationEmploi')
|
||||
router.register(r'sous_vivier_association', views.SousVivierAssociationView, basename='SousVivierAssociation')
|
||||
router.register(r'fe_pcp', views.PcpFeGroupeView, basename='PcpFeGroupe')
|
||||
# Wire up our API using automatic URL routing.
|
||||
# Additionally, we include login URLs for the browsable API.
|
||||
urlpatterns = [
|
||||
path('alimentation/', views.AlimentationView.as_view()),
|
||||
path('nettoyage_pam/', views.NettoyagePamView.as_view()),
|
||||
path('chargement_pam/', views.AlimentationPamView.as_view()),
|
||||
path('alimentation_ref_fe/', views.AlimentationReferentielView.as_view()),
|
||||
path('alimentation_zones_geo/', views.AlimentationZoneGeographiqueView.as_view()),
|
||||
path('alimentation_ref_droits/', views.AlimentationReferentielsDroitView.as_view()),
|
||||
path('alimentation_commentaires/', views.AlimentationCommentairesView.as_view()),
|
||||
path('suppression_administres/', views.SuppressionAdministresView.as_view()),
|
||||
path('exportation_fichiers/', views.ExportationFichiersView.as_view()),
|
||||
path('scoring/', views.ScoringView.as_view()),
|
||||
path('arret/', views.ArretCalcul.as_view()),
|
||||
path('suppression/', views.SuppressionAdministresView.as_view()),
|
||||
path('references/', views.ReferencesView.as_view()),
|
||||
path('me/', views.CurrentUserView.as_view()),
|
||||
path('auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
path('chargement_sv/', views.ChargementSVView.as_view()),
|
||||
path('chargement_competences/', views.ChargementCompetenceView.as_view()),
|
||||
path('fiche_detaillee/', views.FicheDetailleeView.as_view()),
|
||||
path('reporting/', views.ReportingView.as_view()),
|
||||
path('', include(router.urls)),
|
||||
|
||||
]
|
||||
155
backend-django/backend/utils.py
Normal file
155
backend-django/backend/utils.py
Normal file
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
Ce module contient les Utilitaires du backend
|
||||
"""
|
||||
from datetime import datetime
|
||||
from random import randint
|
||||
import re
|
||||
from django.forms import model_to_dict
|
||||
|
||||
from backend import constants
|
||||
from backend.models import Administre, Poste, SousVivier, FormationEmploi
|
||||
|
||||
|
||||
def check_positive(valeur_nb):
|
||||
"""
|
||||
Vérifier si une valeur est positive et retourner 1, si elle est nulle ou négative retourner 0.
|
||||
|
||||
:type poste_nb: DataFrame
|
||||
:param poste_nb: ID du sous-vivier
|
||||
|
||||
|
||||
:return: - **valeur_nb_modifie** (*dataframe*): Dataframe contenant des valeurs 1 ou 0.
|
||||
"""
|
||||
valeur_nb_modifie = valeur_nb.apply(lambda x: 1 if x > 0 else 0)
|
||||
return valeur_nb_modifie
|
||||
|
||||
def cleanString(string):
|
||||
"""Cette fonction supprimera tous les caractères qui ne sont pas alphanumériques.
|
||||
|
||||
:type string: chaîne de caractères
|
||||
:param string: chaîne qui doit être nettoyée
|
||||
|
||||
:return: - **return** (*chaîne de caractères*): chaîne nettoyée.
|
||||
|
||||
"""
|
||||
# print(string)
|
||||
return ''.join([i for i in string if i.isalnum()])
|
||||
|
||||
|
||||
def intOrNone(value):
|
||||
"""Cette fonction renvoie l'entier de la valeur ou renvoie None.
|
||||
|
||||
:type value: float
|
||||
:param value: valeur a con
|
||||
|
||||
:return: - **res** (*int or None*): valeur à convertir.
|
||||
|
||||
"""
|
||||
try:
|
||||
res = int(float(value))
|
||||
except:
|
||||
res = None
|
||||
|
||||
return res
|
||||
|
||||
def impact_decisions(old_administre, administre, old_avis, avis, eip, fe_code, categorie):
|
||||
"""
|
||||
|
||||
:type administre: objet
|
||||
:param administre: instance du model Administre
|
||||
|
||||
:type old_avis: chaine de caractères
|
||||
:param old_avis: avis de l'administré avant la mise à jour
|
||||
|
||||
:type avis: chaine de caractère
|
||||
:param avis: nouvel avis de l'administré
|
||||
|
||||
:type eip: chaine de caractère
|
||||
:param eip: eip actuel du militaire
|
||||
|
||||
:type categorie: dataframe pandas
|
||||
:param categorie: Dataframe contenant les données pretraités à inserer
|
||||
|
||||
:return: - **list_error** (*liste*): liste des identifiants SAP pour lesquels il y a eu une erreur.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
list_error = []
|
||||
if (old_avis == 'NON_ETUDIE' or old_avis == 'A_MAINTENIR' or
|
||||
old_avis == 'A_ETUDIER') and (
|
||||
avis == 'A_MUTER' or avis == 'PARTANT' or avis == 'NON_DISPONIBLE'):
|
||||
poste_qs = Poste.objects.filter(p_eip__iexact=eip, formation_emploi_id=fe_code).exclude(
|
||||
p_nb_occupe=0)
|
||||
poste = poste_qs.first()
|
||||
print(fe_code)
|
||||
fe = FormationEmploi.objects.get(fe_code=fe_code)
|
||||
if categorie == constants.CATEGORIE_MDR:
|
||||
fe.fe_nb_poste_vacant_mdr = fe.fe_nb_poste_vacant_mdr + 1
|
||||
fe.fe_nb_poste_occupe_mdr = fe.fe_nb_poste_occupe_mdr - 1
|
||||
elif categorie == constants.CATEGORIE_SOFF:
|
||||
fe.fe_nb_poste_vacant_soff = fe.fe_nb_poste_vacant_soff + 1
|
||||
fe.fe_nb_poste_occupe_soff = fe.fe_nb_poste_occupe_soff - 1
|
||||
elif categorie == constants.CATEGORIE_OFF:
|
||||
fe.fe_nb_poste_vacant_off = fe.fe_nb_poste_vacant_off + 1
|
||||
fe.fe_nb_poste_occupe_off = fe.fe_nb_poste_occupe_off - 1
|
||||
|
||||
fe.save()
|
||||
|
||||
|
||||
if poste and poste.p_nb_non_etudie > 0:
|
||||
print(poste)
|
||||
new_nb_p4, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p4 + 1, poste.p_nb_non_etudie - 1, poste.p_nb_vacant + 1, poste.p_nb_occupe - 1
|
||||
poste_qs.update(p_nb_p4=new_nb_p4, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
|
||||
else:
|
||||
list_error.append(administre.a_id_sap)
|
||||
|
||||
if (old_avis == 'A_MUTER' or old_avis == 'PARTANT' or
|
||||
old_avis == 'NON_DISPONIBLE') and (
|
||||
avis == 'NON_ETUDIE' or avis == 'A_MAINTENIR' or avis == 'A_ETUDIER'):
|
||||
poste_qs = Poste.objects.filter(p_eip__iexact=eip, formation_emploi_id=fe_code).exclude(p_nb_occupe=0)
|
||||
poste = poste_qs.first()
|
||||
fe = FormationEmploi.objects.get(fe_code=fe_code)
|
||||
if categorie == constants.CATEGORIE_MDR:
|
||||
fe.fe_nb_poste_vacant_mdr = fe.fe_nb_poste_vacant_mdr - 1
|
||||
fe.fe_nb_poste_occupe_mdr = fe.fe_nb_poste_occupe_mdr + 1
|
||||
elif categorie == constants.CATEGORIE_SOFF:
|
||||
fe.fe_nb_poste_vacant_soff = fe.fe_nb_poste_vacant_soff - 1
|
||||
fe.fe_nb_poste_occupe_soff = fe.fe_nb_poste_occupe_soff + 1
|
||||
elif categorie == constants.CATEGORIE_OFF:
|
||||
fe.fe_nb_poste_vacant_off = fe.fe_nb_poste_vacant_off - 1
|
||||
fe.fe_nb_poste_occupe_off = fe.fe_nb_poste_occupe_off + 1
|
||||
|
||||
fe.save()
|
||||
print(model_to_dict(fe))
|
||||
|
||||
if poste and poste.p_nb_p4 > 0:
|
||||
print(poste)
|
||||
new_nb_p4, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p4 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||
poste_qs.update(p_nb_p4=new_nb_p4, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 > 0:
|
||||
new_nb_p3, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p3 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||
poste_qs.update(p_nb_p3=new_nb_p3, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 == 0 and poste.p_nb_p2 > 0:
|
||||
new_nb_p2, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p2 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||
poste_qs.update(p_nb_p2=new_nb_p2, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 == 0 and poste.p_nb_p2 == 0 and poste.p_nb_p1 > 0:
|
||||
new_nb_p1, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p1 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||
poste_qs.update(p_nb_p1=new_nb_p1, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
|
||||
else:
|
||||
list_error.append(administre.a_id_sap)
|
||||
|
||||
print(list_error)
|
||||
return list_error
|
||||
12
backend-django/backend/utils/__init__.py
Normal file
12
backend-django/backend/utils/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from .alimentation import *
|
||||
from .alimentation_decorators import *
|
||||
from .decisions import *
|
||||
from .decorators import *
|
||||
from .extraction import *
|
||||
from .functions import *
|
||||
from .initial import *
|
||||
from .insertion import *
|
||||
from .logging import *
|
||||
from .permissions import *
|
||||
from .predicates import *
|
||||
from .view_predicates import *
|
||||
259
backend-django/backend/utils/alimentation.py
Normal file
259
backend-django/backend/utils/alimentation.py
Normal file
@@ -0,0 +1,259 @@
|
||||
import datetime
|
||||
from enum import Enum, EnumMeta, unique
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def excel_date_converter(value) -> Union[str, float]:
|
||||
"""
|
||||
convertit la valeur en date (sans information de temps)
|
||||
note : type(np.nan) => <class 'float'>
|
||||
"""
|
||||
if isinstance(value, datetime.datetime):
|
||||
return value.date().isoformat()
|
||||
if isinstance(value, str):
|
||||
val = '-'.join(list(reversed(value.split('/'))))
|
||||
return datetime.datetime.strptime(val, "%Y-%m-%d").date().isoformat() if val else np.nan
|
||||
return str(value) if value else np.nan
|
||||
|
||||
|
||||
def excel_int64_converter(value) -> Union[int, float]:
|
||||
"""
|
||||
convertit la valeur en entier
|
||||
note : type(np.nan) => <class 'float'>
|
||||
"""
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
val = value.strip() if isinstance(value, str) else value
|
||||
return np.int64(val) if val else np.nan
|
||||
|
||||
|
||||
def excel_uint64_converter(value) -> Union[int, float]:
|
||||
"""
|
||||
convertit la valeur en entier non signé
|
||||
note : type(np.nan) => <class 'float'>
|
||||
"""
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
val = value.strip() if isinstance(value, str) else value
|
||||
return np.uint64(val) if val else np.nan
|
||||
|
||||
|
||||
class FileCols(str, Enum):
|
||||
""" Classe de base pour les colonnes d'un fichier """
|
||||
|
||||
converter: str
|
||||
|
||||
def __new__(metacls, value: str, converter=None):
|
||||
obj = str.__new__(metacls, value)
|
||||
obj._value_ = value
|
||||
obj.converter = converter
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
@classmethod
|
||||
def columns(cls, *args: 'FileCols') -> List[str]:
|
||||
"""
|
||||
Renvoie les noms de colonnes. Deux cas :
|
||||
- il n'y a pas d'argument : tous les noms de colonnes
|
||||
- il y a des arguments : uniquement leurs noms de colonnes
|
||||
|
||||
:param args: membres de l'enum
|
||||
:type args: class:`FileCols` (multiple)
|
||||
|
||||
:return: noms de colonnes
|
||||
:rtype: List[str]
|
||||
"""
|
||||
return [member.value for member in (args or cls)]
|
||||
|
||||
@classmethod
|
||||
def col_mapping(cls, mapping: Dict[Union[str, 'FileCols'], Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Renvoie un mapping de noms pour utiliser avec pandas.DataFrame.rename.
|
||||
Les clés du dictionnaire initial peut être des membres de l'enum, elles seront converties
|
||||
en noms de colonnes.
|
||||
|
||||
:param mapping: correspondances initiales
|
||||
:type mapping: Dict[Union[str, FileCols], Any]
|
||||
|
||||
:return: nouvelles correspondances
|
||||
:rtype: Dict[str, Any]
|
||||
"""
|
||||
return {k.value if isinstance(k, cls) else k: v for k, v in mapping.items()}
|
||||
|
||||
@classmethod
|
||||
def converters(cls, *args: 'FileCols') -> Dict[str, Any]:
|
||||
"""
|
||||
Renvoie un mapping de conversion pour Pandas. Deux cas :
|
||||
- il n'y a pas d'argument : mapping de toutes les colonnes qui ont un convertisseur explicite
|
||||
- il y a des arguments : mapping des colonnes fournies qui ont un convertisseur explicite
|
||||
|
||||
:return: mapping entre noms de colonnes et convertisseur
|
||||
:rtype: Dict[str, Any]
|
||||
"""
|
||||
return {member.value: member.converter for member in (args or cls) if member.converter is not None}
|
||||
|
||||
|
||||
class BOCols(FileCols):
|
||||
""" Colonnes du fichier BO """
|
||||
|
||||
AFFECTATION_1 = ('Affectation -1 L', str) # AV
|
||||
AFFECTATION_2 = ('Affectation -2 L', str) # AX
|
||||
AFFECTATION_3 = ('Affectation -3 L', str) # AZ
|
||||
AFFECTATION_4 = ('Affectation -4 L', str) # BB
|
||||
AFFECTATION_5 = ('Affectation -5 L', str) # BD
|
||||
AFFECTATION_6 = ('Affectation -6 L', str) # BF
|
||||
AFFECTATION_7 = ('Affectation -7 L', str) # BH
|
||||
AFFECTATION_8 = ('Affectation -8 L', str) # BJ
|
||||
AFFECTATION_9 = ('Affectation -9 L', str) # BL
|
||||
AGE_ANNEES = ('Age en années (au 31/12)', excel_uint64_converter) # CG
|
||||
ANNEE_NOTATION = ('Année notation A', excel_uint64_converter) # CL
|
||||
ANNEE_NOTATION_1 = ('Année notation A-1', excel_uint64_converter) # CR
|
||||
ANNEE_NOTATION_2 = ('Année notation A-2', excel_uint64_converter) # CX
|
||||
ANNEE_NOTATION_3 = ('Année notation A-3', excel_uint64_converter) # DD
|
||||
ANNEE_NOTATION_4 = ('Année notation A-4', excel_uint64_converter) # DJ
|
||||
ANNEE_NOTATION_5 = ('Année notation A-5', excel_uint64_converter) # DP
|
||||
APTITUDE_EMPLOI_SUP = ('Apt resp / Emp sup A', str) # CP
|
||||
APTITUDE_EMPLOI_SUP_1 = ('Apt resp / Emp sup A-1', str) # CV
|
||||
APTITUDE_EMPLOI_SUP_2 = ('Apt resp / Emp sup A-2', str) # DB
|
||||
APTITUDE_EMPLOI_SUP_3 = ('Apt resp / Emp sup A-3', str) # DH
|
||||
APTITUDE_EMPLOI_SUP_4 = ('Apt resp / Emp sup A-4', str) # DN
|
||||
APTITUDE_EMPLOI_SUP_5 = ('Apt resp / Emp sup A-5', str) # DT
|
||||
ARME = ('Arme', str) # L
|
||||
CREDO_FE = ('CREDO FE act', str) # X
|
||||
DATE_AFFECTATION_1 = ('Affectation -1 DD', excel_date_converter) # AU
|
||||
DATE_AFFECTATION_2 = ('Affectation -2 DD', excel_date_converter) # AW
|
||||
DATE_AFFECTATION_3 = ('Affectation -3 DD', excel_date_converter) # AY
|
||||
DATE_AFFECTATION_4 = ('Affectation -4 DD', excel_date_converter) # BA
|
||||
DATE_AFFECTATION_5 = ('Affectation -5 DD', excel_date_converter) # BC
|
||||
DATE_AFFECTATION_6 = ('Affectation -6 DD', excel_date_converter) # BE
|
||||
DATE_AFFECTATION_7 = ('Affectation -7 DD', excel_date_converter) # BG
|
||||
DATE_AFFECTATION_8 = ('Affectation -8 DD', excel_date_converter) # BI
|
||||
DATE_AFFECTATION_9 = ('Affectation -9 DD', excel_date_converter) # BK
|
||||
DATE_ARRIVEE_FE = ('Date arrivée FE', excel_date_converter) # AA
|
||||
DATE_DEBUT_GRADE = ('Grade act DD', excel_date_converter) # AH
|
||||
DATE_DERNIER_ACR = ('Dernière mutation ACR D', excel_date_converter) # AC
|
||||
DATE_ENTREE_SERVICE = ('Entrée en Service', excel_date_converter) # N
|
||||
DATE_FONCTION_1 = ('Fonction -1 DD', excel_date_converter) # BM
|
||||
DATE_FONCTION_2 = ('Fonction -2 DD', excel_date_converter) # BO
|
||||
DATE_FONCTION_3 = ('Fonction -3 DD', excel_date_converter) # BQ
|
||||
DATE_FONCTION_4 = ('Fonction -4 DD', excel_date_converter) # BS
|
||||
DATE_FONCTION_5 = ('Fonction -5 DD', excel_date_converter) # BU
|
||||
DATE_FONCTION_6 = ('Fonction -6 DD', excel_date_converter) # BW
|
||||
DATE_FONCTION_7 = ('Fonction -7 DD', excel_date_converter) # BY
|
||||
DATE_FONCTION_8 = ('Fonction -8 DD', excel_date_converter) # CA
|
||||
DATE_FONCTION_9 = ('Fonction -9 DD', excel_date_converter) # CC
|
||||
DATE_FUD = ('Date prise effet FUD départ', excel_date_converter) # DV
|
||||
DATE_LIEN_SERVICE = ('Lien au service DF', excel_date_converter) # EG
|
||||
DATE_NAISSANCE = ('Naissance', excel_date_converter) # R
|
||||
DATE_POSITION_STATUAIRE = ('Date Position statutaire', excel_date_converter) # AF
|
||||
DATE_RDC = ('Date Radiation des contrôles', excel_date_converter) # W
|
||||
DATE_STATUT_CONCERTO = ('Situation administrative act DD', excel_date_converter) # DX
|
||||
DATE_STATUT_CONCERTO_FUTUR = ('Date Position statu future', excel_date_converter) # DZ
|
||||
DATE_MARIAGE = ('Situation familiale (IT0002) DD', excel_date_converter) # CH
|
||||
DERNIER_DIPLOME = ('Dernier diplôme militaire LA', str) # V
|
||||
DIPLOME_PLUS_HAUT_NIVEAU = ('Diplôme militaire de plus haut niveau', str) # U
|
||||
DOMAINE = ('Domaine EIP act LA', str) # I
|
||||
DOMAINE_GESTION = ('Domaine de gestion act LA', str) # H
|
||||
DOMAINE_POSTE = ('Domaine emploi occupé act LA', str) # ED
|
||||
EIP = ('EIP act LA', str) # F
|
||||
EIS = ('EIS act LA', str) # G
|
||||
ENFANTS = ('Enfants', str) # EK
|
||||
FILIERE = ('Filière EIP act LA', str) # J
|
||||
FILIERE_POSTE = ('Nature de filière emploi occupé act LA', str) # EE
|
||||
FONCTION = ('Fonction act L', str) # T
|
||||
FONCTION_1 = ('Fonction -1 L', str) # BN
|
||||
FONCTION_2 = ('Fonction -2 L', str) # BP
|
||||
FONCTION_3 = ('Fonction -3 L', str) # BR
|
||||
FONCTION_4 = ('Fonction -4 L', str) # BT
|
||||
FONCTION_5 = ('Fonction -5 L', str) # BV
|
||||
FONCTION_6 = ('Fonction -6 L', str) # BX
|
||||
FONCTION_7 = ('Fonction -7 L', str) # BZ
|
||||
FONCTION_8 = ('Fonction -8 L', str) # CB
|
||||
FONCTION_9 = ('Fonction -9 L', str) # CD
|
||||
FUD = ('Type de FUD départ') # DW
|
||||
GARNISON = ('Garnison act', str) # Z
|
||||
GRADE = ('GRADE TA', str) # B
|
||||
ID_DEF = ('Identifiant défense', str) # E
|
||||
ID_DEF_CONJOINT = ('Identifiant défense du conjoint militaire', str) # AN
|
||||
ID_SAP = ('Matricule SAP', excel_int64_converter) # A
|
||||
ID_SAP_CONJOINT = ('Identifiant SAP du conjoint militaire', excel_int64_converter) # AM
|
||||
INTERRUPTION_SERVICE = ('Interruption de service', str) # AG
|
||||
MARQUEUR_PN = ('Marquant Personnel Navigant') # EH
|
||||
NF = ('Niveau fonctionnel EIP act', str) # K
|
||||
NF_POSTE = ('Niveau fonctionnel emploi occupé act C', str) # EF
|
||||
NOM = ('NOM', str) # C
|
||||
NOMBRE_ENFANTS = ("Nbr total d'enfants", excel_uint64_converter) # O
|
||||
NR_OU_IRIS = ('IRIS / RAC retenu A', excel_int64_converter) # CN
|
||||
NR_OU_IRIS_1 = ('IRIS / RAC retenu A-1', excel_int64_converter) # CT
|
||||
NR_OU_IRIS_2 = ('IRIS / RAC retenu A-2', excel_int64_converter) # CZ
|
||||
NR_OU_IRIS_3 = ('IRIS / RAC retenu A-3', excel_int64_converter) # DF
|
||||
NR_OU_IRIS_4 = ('IRIS / RAC retenu A-4', excel_int64_converter) # DL
|
||||
NR_OU_IRIS_5 = ('IRIS / RAC retenu A-5', excel_int64_converter) # DR
|
||||
ORIGINE_RECRUTEMENT = ('Origine recrutement LA', str) # Q
|
||||
PLS_GB_MAX = ('PLS GB Max') # EI
|
||||
POSITION_STATUTAIRE = ('Position statutaire act L', str) # AE
|
||||
POTENTIEL_RESPONSABILITE_SUP = ('Potentiel responsabilités catégorie sup A', str) # CQ
|
||||
POTENTIEL_RESPONSABILITE_SUP_1 = ('Potentiel responsabilités catégorie sup A-1', str) # CW
|
||||
POTENTIEL_RESPONSABILITE_SUP_2 = ('Potentiel responsabilités catégorie sup A-2', str) # DC
|
||||
POTENTIEL_RESPONSABILITE_SUP_3 = ('Potentiel responsabilités catégorie sup A-3', str) # DI
|
||||
POTENTIEL_RESPONSABILITE_SUP_4 = ('Potentiel responsabilités catégorie sup A-4', str) # DO
|
||||
POTENTIEL_RESPONSABILITE_SUP_5 = ('Potentiel responsabilités catégorie sup A-5', str) # DU
|
||||
PRENOM = ('Prénom', str) # D
|
||||
PROFESSION_CONJOINT = ('Profession conjoint Act L', str) # AL
|
||||
RAC_OU_IRIS_CUMULE = ('NR/NGC cumulé A', excel_int64_converter) # CM
|
||||
RAC_OU_IRIS_CUMULE_1 = ('NR/NGC cumulé A-1', excel_int64_converter) # CS
|
||||
RAC_OU_IRIS_CUMULE_2 = ('NR/NGC cumulé A-2', excel_int64_converter) # CY
|
||||
RAC_OU_IRIS_CUMULE_3 = ('NR/NGC cumulé A-3', excel_int64_converter) # DE
|
||||
RAC_OU_IRIS_CUMULE_4 = ('NR/NGC cumulé A-4', excel_int64_converter) # DK
|
||||
RAC_OU_IRIS_CUMULE_5 = ('NR/NGC cumulé A-5', excel_int64_converter) # DQ
|
||||
REGROUPEMENT_ORIGINE_RECRUTEMENT = ('Regroupement origine recrutement C', str) # M
|
||||
RF_QSR = ('QSR A', str) # CO
|
||||
RF_QSR_1 = ('QSR A-1', str) # CU
|
||||
RF_QSR_2 = ('QSR A-2', str) # DA
|
||||
RF_QSR_3 = ('QSR A-3', str) # DG
|
||||
RF_QSR_4 = ('QSR A-4', str) # DM
|
||||
RF_QSR_5 = ('QSR A-5', str) # DS
|
||||
SEXE = ('Sexe', str) # CJ
|
||||
SEXE_CONJOINT = ('Sexe du conjoint', str) # AT
|
||||
SITUATION_FAMILIALE = ('Situation familiale (IT0002) L', str) # CI
|
||||
STATUT_CONCERTO = ('Situation admi actuelle', str) # DY
|
||||
STATUT_CONCERTO_FUTUR = ('Position statutaire future', str) # EA
|
||||
|
||||
|
||||
class FmobCols(FileCols):
|
||||
""" Colonnes du fichier FMOB """
|
||||
|
||||
ID_SAP = ('Matricule SAP', excel_int64_converter) # A
|
||||
|
||||
|
||||
class InseeCols(FileCols):
|
||||
""" Colonnes du fichier INSEE """
|
||||
|
||||
CODE_INSEE = ('CODE INSEE', str) # D
|
||||
CODE_POSTAL = ('CODE POSTAL', str) # C
|
||||
CREDO_FE = ('CREDO FE act', str) # B
|
||||
ID_SAP = ('Matricule SAP', excel_int64_converter) # A
|
||||
|
||||
|
||||
class ReoCols(FileCols):
|
||||
""" Colonnes du fichier REO """
|
||||
|
||||
ANNEE_PROJET = ('Année Projet', str) # B
|
||||
CATEGORIE = ('Catégorie /EFF CELL', str) # P
|
||||
CODE_NF = ('NFEO', str) # W
|
||||
CODE_POSTAL = ('Code Postal /OB G', str) # J
|
||||
DOMAINE = ('DEO', str) # U
|
||||
DOMAINE_GESTION = ('Postes NFS', str) # AA
|
||||
EIP = ('MGS gestionnaire / EFF CELL', str) # T
|
||||
FONCTION_ID = ('ETR Code / EFF CELL', str) # Y
|
||||
FONCTION_LIBELLE = ('ETR Libellé / EFF CELL', str) # Z
|
||||
FORMATION_EMPLOI = ('Code CREDO Long OB', str) # F
|
||||
FILIERE = ('FEO', str) # V
|
||||
ID_POSTE = ('N° De Poste', str) # X
|
||||
|
||||
|
||||
17
backend-django/backend/utils/alimentation_decorators.py
Normal file
17
backend-django/backend/utils/alimentation_decorators.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from logging import Logger, LoggerAdapter
|
||||
from typing import Any, List, Set, Tuple, Union
|
||||
|
||||
from .logging import TAG_DATA_FEED, TAG_PERF, get_logger
|
||||
|
||||
|
||||
def get_data_logger(value: Any = None, tags: Union[str, List[str], Set[str], Tuple[str]] = None) -> Union[Logger, LoggerAdapter]:
|
||||
""" variante spécifique de 'get_logger' qui ajoute toujours le tag TAG_DATA_FEED """
|
||||
all_tags = TAG_DATA_FEED
|
||||
if tags:
|
||||
all_tags = [TAG_DATA_FEED, *tags] if isinstance(tags, (list, set, tuple)) else [TAG_DATA_FEED, tags]
|
||||
return get_logger(value, all_tags)
|
||||
|
||||
|
||||
def data_perf_logger_factory(func):
|
||||
""" fonction de création de logger pour la performance et l'alimentation, à utiliser avec 'execution_time' ou 'query_count' """
|
||||
return get_data_logger(func, TAG_PERF)
|
||||
32
backend-django/backend/utils/attributes.py
Normal file
32
backend-django/backend/utils/attributes.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import functools
|
||||
|
||||
|
||||
def rgetattr(obj, attr: str, safe: bool = False, *args, **kwargs):
|
||||
"""
|
||||
Version récursive de getattr pour utiliser une propriété chaînée même avec None au milieu.
|
||||
exemples :
|
||||
- rgetattr(obj, 'a.b.c')
|
||||
- rgetattr(obj, 'a.b.c', safe=True)
|
||||
|
||||
:param obj: cible
|
||||
:type obj: Any
|
||||
|
||||
:param attr: propriété qui peut être chaînée en séparant par '.'
|
||||
:type attr: str
|
||||
|
||||
:param safe: indique qu'il faut tester la présence de l'attribut avec hasattr (utile pour les relations entre modèles Django), defaults to False
|
||||
:type safe: bool
|
||||
|
||||
:return: valeur de l'attribut
|
||||
:rtype: Any
|
||||
"""
|
||||
def _getattr(obj, attr):
|
||||
return getattr(obj, attr, *args) if not safe or hasattr(obj, attr) else None
|
||||
return functools.reduce(_getattr, [obj] + attr.split('.'))
|
||||
|
||||
def safe_rgetattr(obj, attr: str, safe: bool = False, *args, **kwargs):
|
||||
"""
|
||||
version safe de rgetattr
|
||||
exemples : safe_rgetattr(obj, 'a.b.c')
|
||||
"""
|
||||
return rgetattr(obj, attr, safe=True, *args, **kwargs)
|
||||
266
backend-django/backend/utils/decisions.py
Normal file
266
backend-django/backend/utils/decisions.py
Normal file
@@ -0,0 +1,266 @@
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
|
||||
from ..models import Administre, CustomUser, Decision, DecisionChoices, Administres_Pams
|
||||
from ..models import StatutPamChoices as StatutPam
|
||||
from .logging import get_logger
|
||||
from .permissions import KEY_WRITE, Profiles, get_profiles_by_adm
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# clé d'une valeur de l'arbre de décision : choix disponibles
|
||||
KEY_CHOICES = 'choices'
|
||||
|
||||
# clé d'une valeur de l'arbre de décision : profils habilités à modifier le statut
|
||||
KEY_PROFILES = 'editable_by'
|
||||
|
||||
# clé d'un élément de "get_available_decisions" : pour une création
|
||||
KEY_CREATE = 'creation'
|
||||
|
||||
# clé d'un élément de "get_available_decisions" : pour une mise à jour
|
||||
KEY_UPDATE = 'update'
|
||||
|
||||
class ExtraDecisions(str, Enum):
|
||||
""" décisions en plus """
|
||||
|
||||
EMPTY = ''
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
def __init_decisions():
|
||||
D = DecisionChoices
|
||||
EX = ExtraDecisions
|
||||
P = Profiles
|
||||
return {
|
||||
# === commun ===
|
||||
|
||||
None: {
|
||||
KEY_PROFILES: (P.FILIERE, P.BVT),
|
||||
KEY_CHOICES: (D.PROPOSITION_FE, D.HME_PROPOSITION_VIVIER)
|
||||
},
|
||||
D.REMIS_A_DISPOSITION: {
|
||||
KEY_PROFILES: (P.FILIERE,),
|
||||
KEY_CHOICES: (EX.EMPTY,),
|
||||
},
|
||||
|
||||
# === en métropole ===
|
||||
|
||||
D.PROPOSITION_FE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL),
|
||||
KEY_CHOICES: (D.DIALOGUE_EN_COURS, D.FOREMP_EN_COURS)
|
||||
},
|
||||
D.DIALOGUE_EN_COURS: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||
KEY_CHOICES: (D.DIALOGUE_TERMINE, D.DIALOGUE_INFRUCTUEUX)
|
||||
},
|
||||
D.DIALOGUE_TERMINE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||
KEY_CHOICES: (D.FOREMP_EN_COURS,)
|
||||
},
|
||||
D.DIALOGUE_INFRUCTUEUX: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||
KEY_CHOICES: (D.FOREMP_EN_COURS, D.REMIS_A_DISPOSITION)
|
||||
},
|
||||
D.FOREMP_EN_COURS: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||
KEY_CHOICES: (D.FOREMP_TERMINE,)
|
||||
},
|
||||
D.FOREMP_TERMINE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||
KEY_CHOICES: (D.PREPOSITIONNE, D.REMIS_A_DISPOSITION)
|
||||
},
|
||||
D.PREPOSITIONNE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.POSITIONNE, D.REMIS_A_DISPOSITION)
|
||||
},
|
||||
D.POSITIONNE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.OMIP_EN_COURS, D.OMI_EN_COURS, D.REMIS_A_DISPOSITION)
|
||||
},
|
||||
D.OMIP_EN_COURS: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.OMIP_TERMINE, D.REMIS_A_DISPOSITION),
|
||||
},
|
||||
D.OMIP_TERMINE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.ATTENTE_AVIONAGE, D.OMI_EN_COURS, D.REMIS_A_DISPOSITION),
|
||||
},
|
||||
D.ATTENTE_AVIONAGE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.OMI_EN_COURS,),
|
||||
},
|
||||
D.OMI_EN_COURS: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.OMI_ACTIVE, D.REMIS_A_DISPOSITION),
|
||||
},
|
||||
D.OMI_ACTIVE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.OMI_ANNULE,),
|
||||
},
|
||||
D.OMI_ANNULE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.REMIS_A_DISPOSITION,),
|
||||
},
|
||||
|
||||
# === hors métropole (HME) ===
|
||||
|
||||
D.HME_PROPOSITION_VIVIER: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_DIALOGUE_INITIE,)
|
||||
},
|
||||
D.HME_ETUDE_DESISTEMENT: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_DESISTEMENT,)
|
||||
},
|
||||
D.HME_DESISTEMENT: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.REMIS_A_DISPOSITION,)
|
||||
},
|
||||
D.HME_DIALOGUE_INITIE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_DIALOGUE_EN_COURS,)
|
||||
},
|
||||
D.HME_DIALOGUE_EN_COURS: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_DIALOGUE_TERMINE, D.HME_DIALOGUE_INFRUCTUEUX)
|
||||
},
|
||||
D.HME_DIALOGUE_TERMINE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_PREPOSITIONNE,)
|
||||
},
|
||||
D.HME_DIALOGUE_INFRUCTUEUX: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_ETUDE_DESISTEMENT, D.HME_PREPOSITIONNE, D.REMIS_A_DISPOSITION)
|
||||
},
|
||||
D.HME_FOREMP_EN_COURS: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_FOREMP_TERMINE, D.HME_OMI_EN_COURS)
|
||||
},
|
||||
D.HME_FOREMP_TERMINE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_OMIP_EN_COURS, D.HME_OMI_EN_COURS)
|
||||
},
|
||||
D.HME_PREPOSITIONNE: {
|
||||
KEY_PROFILES: (P.HME,),
|
||||
KEY_CHOICES: (D.HME_VALIDATION_EXPERT, D.HME_REFUS_EXPERT)
|
||||
},
|
||||
D.HME_VALIDATION_EXPERT: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_POSITIONNE,)
|
||||
},
|
||||
D.HME_POSITIONNE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_OMIP_EN_COURS, D.HME_OMI_EN_COURS, D.HME_FOREMP_EN_COURS)
|
||||
},
|
||||
D.HME_REFUS_EXPERT: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_PREPOSITIONNE, D.REMIS_A_DISPOSITION),
|
||||
},
|
||||
D.HME_OMIP_EN_COURS: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_OMIP_TERMINE,),
|
||||
},
|
||||
D.HME_OMIP_TERMINE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_ATTENTE_AVIONAGE, D.HME_OMI_EN_COURS),
|
||||
},
|
||||
D.HME_ATTENTE_AVIONAGE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_OMI_EN_COURS,),
|
||||
},
|
||||
D.HME_OMI_EN_COURS: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||
KEY_CHOICES: (D.HME_OMI_ACTIVE,),
|
||||
},
|
||||
D.HME_OMI_ACTIVE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.HME_OMI_ANNULE,),
|
||||
},
|
||||
D.HME_OMI_ANNULE: {
|
||||
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||
KEY_CHOICES: (D.REMIS_A_DISPOSITION,),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# enchaînement des décisions
|
||||
DECISIONS = __init_decisions()
|
||||
|
||||
|
||||
def get_all_decisions() -> Tuple[DecisionChoices]:
|
||||
"""
|
||||
Renvoie tous les statuts de décisions possibles. Une décision vide correspondrait à une absence de décision et n'est donc pas présente.
|
||||
|
||||
:return: toutes les décisions
|
||||
:rtype: Tuple[DecisionChoices]
|
||||
"""
|
||||
return tuple(DecisionChoices)
|
||||
|
||||
|
||||
def get_available_decisions(
|
||||
administres: Union[List[Administres_Pams], Tuple[Administres_Pams]],
|
||||
user: CustomUser = None,
|
||||
profiles_by_adm: Dict[int, Tuple[Profiles]] = None
|
||||
) -> Dict[int, Dict[str, Tuple[Union[DecisionChoices, ExtraDecisions]]]]:
|
||||
"""
|
||||
Renvoie les décisions disponibles pour l'utilisateur s'il modifie le statut de décision des administrés donnés.
|
||||
|
||||
:param administres: Administres_Pams
|
||||
:type administres: Union[List[Administres_Pams], Tuple[Administres_Pams]]
|
||||
|
||||
:param user: utilisateur
|
||||
:type user: class:`CustomUser`, optional
|
||||
|
||||
:param profiles_by_adm: profils pour chaque administré (voir get_profiles_by_adm)
|
||||
:type profiles_by_adm: Dict[int, Tuple[Profiles]], optional
|
||||
|
||||
:return: dictionnaire de dictionnaires {<ID SAP>: {<KEY_CREATE>: <décisions>, <KEY_UPDATE>: <décisions>} }
|
||||
:rtype: Dict[int, Dict[str, Tuple[Union[DecisionChoices, ExtraDecisions]]]]
|
||||
"""
|
||||
pam_without_decision = tuple(x for x in StatutPam if not x.dec_enabled)
|
||||
pam_with_decision = tuple(x for x in StatutPam if x.dec_enabled)
|
||||
result = {}
|
||||
|
||||
# restrictions : dictionnaire de dictionnaires {<ID SAP>: <même forme qu'une valeur de DECISIONS>}
|
||||
restrictions_create_by_adm = {}
|
||||
restrictions_update_by_adm = {}
|
||||
|
||||
adm_to_process = []
|
||||
for adm in administres:
|
||||
adm_id = adm.pk
|
||||
pam = adm.a_statut_pam_annee
|
||||
if pam in pam_without_decision:
|
||||
# le statut PAM ne permet aucun choix
|
||||
result[adm_id] = {KEY_CREATE: (), KEY_UPDATE: ()}
|
||||
elif pam in pam_with_decision:
|
||||
# le statut PAM active l'arbre de décisions
|
||||
adm_to_process.append(adm)
|
||||
statut_decision = adm.decision.de_decision or None if hasattr(adm, Administres_Pams.Cols.REL_DECISION) else None
|
||||
restrictions_update_by_adm[adm_id] = DECISIONS.get(statut_decision) or {}
|
||||
else:
|
||||
logger.info('statut PAM non géré pour les décisions possibles : %s', pam)
|
||||
result[adm_id] = {KEY_CREATE: (), KEY_UPDATE: ()}
|
||||
|
||||
if adm_to_process:
|
||||
default_restrictions_create = DECISIONS.get(None) or {}
|
||||
|
||||
def get_decisions(profiles, restrictions) -> Tuple[Union[DecisionChoices, ExtraDecisions]]:
|
||||
allowed_profiles = restrictions.get(KEY_PROFILES) or ()
|
||||
choices = restrictions.get(KEY_CHOICES)
|
||||
decisions = ()
|
||||
if choices and any(p in allowed_profiles for p in profiles):
|
||||
decisions = tuple(choices)
|
||||
return decisions
|
||||
|
||||
_profiles_by_adm = profiles_by_adm or get_profiles_by_adm(user, *adm_to_process)
|
||||
for adm in adm_to_process:
|
||||
adm_id = adm.pk
|
||||
profiles = (_profiles_by_adm.get(adm_id) or {}).get(KEY_WRITE) or ()
|
||||
result.setdefault(adm_id, {
|
||||
KEY_CREATE: get_decisions(profiles, restrictions_create_by_adm.get(adm_id) or default_restrictions_create),
|
||||
KEY_UPDATE: get_decisions(profiles, restrictions_update_by_adm.get(adm_id) or {})
|
||||
})
|
||||
return result
|
||||
113
backend-django/backend/utils/decorator.py
Normal file
113
backend-django/backend/utils/decorator.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from django.db import connections
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Set, Tuple, Union
|
||||
import logging
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
import time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InfoAppel(Enum):
|
||||
""" infos supplémentaires possibles """
|
||||
|
||||
# ajouté au nom de logger
|
||||
CLASSE = 'classe'
|
||||
# ajouté dans le message
|
||||
PID = 'pid'
|
||||
|
||||
|
||||
def get_nom_module(func) -> str:
|
||||
mod = inspect.getmodule(func)
|
||||
return mod.__name__ if mod else None
|
||||
|
||||
|
||||
def get_nombre_requetes() -> int:
|
||||
""" renvoie le nombre total de requêtes (il peut y avoir plusieurs connexions) """
|
||||
|
||||
return sum(len(c.queries) for c in connections.all())
|
||||
|
||||
|
||||
def get_nom_classe(inclure: Union[ List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel] ] = (), *args) -> str:
|
||||
""" renvoie le nom de classe correspondant à l'appel de fonction """
|
||||
|
||||
nom_classe = None
|
||||
if InfoAppel.CLASSE in inclure and args:
|
||||
classe = getattr(args[0], '__class__', None)
|
||||
nom_classe = getattr(classe, '__name__') if classe else None
|
||||
return nom_classe
|
||||
|
||||
|
||||
def get_logger(nom_module: Optional[str], nom_classe: Optional[str]) -> logging.Logger:
|
||||
""" renvoie le logger correspondant au module et à la classe """
|
||||
|
||||
return logging.getLogger(f'{nom_module}.{nom_classe}' if nom_module and nom_classe else nom_classe or nom_module)
|
||||
|
||||
|
||||
def duree_execution(avertir_apres: int = None, inclure: Union[List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel]] = ()):
|
||||
"""
|
||||
décorateur pour tracer le temps d'exécution d'une fonction
|
||||
|
||||
:param avertir_apres: durée (en ms) au-delà de laquelle on souhaite un log d'avertissement, defaults to None
|
||||
:type avertir_apres: int, optional
|
||||
|
||||
:param inclure: infos supplémentaires, defaults to ()
|
||||
:type inclure: Union[List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel]], optional
|
||||
|
||||
:return: résultat de la fonction d'origine
|
||||
"""
|
||||
def inner(func):
|
||||
nom_module = get_nom_module(func)
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
temps_debut = time.time()
|
||||
try:
|
||||
resultat = func(*args, **kwargs)
|
||||
finally:
|
||||
try:
|
||||
temps_ecoule = round((time.time() - temps_debut) * 1000)
|
||||
|
||||
func_logger = get_logger(nom_module, get_nom_classe(inclure, *args))
|
||||
log_level = logging.WARNING if isinstance(avertir_apres, int) and temps_ecoule > avertir_apres else logging.DEBUG
|
||||
func_logger.log(log_level, "%s'%s' exécuté en %d ms", '[%s] ' % os.getpid() if InfoAppel.PID in inclure else '', func.__name__, temps_ecoule)
|
||||
except Exception:
|
||||
logger.exception('impossible de tracer la durée')
|
||||
return resultat
|
||||
return wrapper
|
||||
return inner
|
||||
|
||||
def nombre_requetes(avertir_apres: int = None, inclure: Union[List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel]] = ()):
|
||||
"""
|
||||
décorateur pour tracer le nombre de requêtes d'une fonction
|
||||
|
||||
:param avertir_apres: nombre de requêtes au-delà duquel on souhaite un log d'avertissement, defaults to None
|
||||
:type avertir_apres: int, optional
|
||||
|
||||
:param inclure: infos supplémentaires, defaults to ()
|
||||
:type inclure: Union[List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel]], optional
|
||||
|
||||
:return: résultat de la fonction d'origine
|
||||
"""
|
||||
def inner(func):
|
||||
nom_module = get_nom_module(func)
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
nb_debut = get_nombre_requetes()
|
||||
try:
|
||||
resultat = func(*args, **kwargs)
|
||||
finally:
|
||||
try:
|
||||
nb_requetes = get_nombre_requetes() - nb_debut
|
||||
|
||||
func_logger = get_logger(nom_module, get_nom_classe(inclure, *args))
|
||||
log_level = logging.WARNING if isinstance(avertir_apres, int) and nb_requetes > avertir_apres else logging.DEBUG
|
||||
func_logger.log(log_level, "%s'%s' a exécuté %d requête(s)", '[%s] ' % os.getpid() if InfoAppel.PID in inclure else '', func.__name__, nb_requetes)
|
||||
except Exception:
|
||||
logger.exception('impossible de tracer le nombre de requêtes')
|
||||
return resultat
|
||||
return wrapper
|
||||
return inner
|
||||
294
backend-django/backend/utils/decorators.py
Normal file
294
backend-django/backend/utils/decorators.py
Normal file
@@ -0,0 +1,294 @@
|
||||
import functools
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from enum import Enum, auto
|
||||
from logging import Logger, LoggerAdapter
|
||||
from types import (BuiltinFunctionType, BuiltinMethodType,
|
||||
ClassMethodDescriptorType, FunctionType,
|
||||
MethodDescriptorType, MethodType)
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from django.db import connections
|
||||
|
||||
from .logging import TAG_DATA_FEED, TAG_PERF, get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
# attribut de classe : logger
|
||||
CLASS_ATTR_LOGGER = 'logger'
|
||||
|
||||
# clé du dictionnaire contexte : désactivé
|
||||
CTX_KEY_DISABLED = 'disabled'
|
||||
|
||||
# clé du dictionnaire contexte : logger
|
||||
CTX_KEY_LOGGER = 'logger'
|
||||
|
||||
# attribut de décorateur : contexte
|
||||
DECORATOR_ATTR_CTX = 'decorator_ctx'
|
||||
|
||||
# attribut de décorateur : clé
|
||||
DECORATOR_ATTR_KEY = 'decorator_key'
|
||||
|
||||
# attribut de décorateur : cible (classe, fonction)
|
||||
DECORATOR_ATTR_TARGET = 'decorator_target'
|
||||
|
||||
# types de fonctions
|
||||
FUNCTION_TYPES = (
|
||||
BuiltinFunctionType,
|
||||
BuiltinMethodType,
|
||||
ClassMethodDescriptorType,
|
||||
FunctionType,
|
||||
MethodDescriptorType,
|
||||
MethodType,
|
||||
)
|
||||
|
||||
|
||||
def _get_query_count() -> int:
|
||||
""" renvoie le nombre total de requêtes (il peut y avoir plusieurs connexions) """
|
||||
|
||||
return sum(len(c.queries) for c in connections.all())
|
||||
|
||||
|
||||
class OnConflict(Enum):
|
||||
""" choix de gestion de conflit quand le même décorateur est déjà utilisé """
|
||||
|
||||
# laisse l'ancien décorateur
|
||||
SKIP = auto()
|
||||
|
||||
# remplace l'ancien décorateur
|
||||
REPLACE = auto()
|
||||
|
||||
# ajoute à l'ancien décorateur
|
||||
STACK = auto()
|
||||
|
||||
|
||||
# amélioration possible : et s'il y a plusieurs décorateurs de même clé ?
|
||||
def _find_former_decorator(key: str, target: Callable) -> Tuple[Optional[Callable], Optional[Callable]]:
|
||||
"""
|
||||
Trouve le décorateur de même clé si la cible est déjà décorée. Un décorateur désactivé est ignoré.
|
||||
note : ne fonctionne pas correctement s'il existe un décorateur intermédiaire sans attribut DECORATOR_ATTR_TARGET
|
||||
|
||||
:param key: clé de décorateur
|
||||
:type key: str
|
||||
|
||||
:param target: fonction potentiellement décorée
|
||||
:type target: Callable
|
||||
|
||||
:return: ancien décorateur + ancienne cible
|
||||
:rtype: Tuple[Callable, Callable]
|
||||
"""
|
||||
current = target
|
||||
decorator_key = getattr(current, DECORATOR_ATTR_KEY, None)
|
||||
decorator_target = getattr(current, DECORATOR_ATTR_TARGET, None)
|
||||
|
||||
while decorator_key and decorator_key != key and decorator_target:
|
||||
current = decorator_target
|
||||
decorator_key = getattr(current, DECORATOR_ATTR_KEY, None)
|
||||
decorator_target = getattr(current, DECORATOR_ATTR_TARGET, None)
|
||||
|
||||
# les décorateurs désactivés ne comptent pas
|
||||
if decorator_key == key and not getattr(current, DECORATOR_ATTR_CTX, {}).get(CTX_KEY_DISABLED, False):
|
||||
return current, decorator_target
|
||||
return None, None
|
||||
|
||||
|
||||
def _create_decorator_from_action(
|
||||
action: Callable,
|
||||
decorator_key: str,
|
||||
on_conflict: OnConflict = OnConflict.SKIP):
|
||||
"""
|
||||
Fonction d'ordre supérieur qui construit un décorateur de fonction/méthode à partir des paramètres. Ce décorateur exécute l'action fournie.
|
||||
|
||||
Un contexte (dictionnaire) est passé à l'appel. Il contient :
|
||||
- l'état désactivé : clé CTX_KEY_DISABLED optionnelle
|
||||
|
||||
:param action: action à exécuter
|
||||
:type action: Callable[[dict, Callable[P, R], P.args, P.kwargs], R]
|
||||
|
||||
:param decorator_key: clé correspondant au décorateur, permet de gérer les conflits
|
||||
:type decorator_key: str, optional
|
||||
|
||||
:param on_conflict: stratégie de gestion de conflit quand le décorateur est déjà utilisé, defaults to OnConflict.SKIP
|
||||
:type on_conflict: class:`OnConflict`
|
||||
"""
|
||||
def inner(func):
|
||||
if on_conflict is not OnConflict.STACK:
|
||||
former_decorator, former_target = _find_former_decorator(decorator_key, func)
|
||||
if former_decorator:
|
||||
if on_conflict is OnConflict.SKIP:
|
||||
# pas de nouveau décorateur
|
||||
return func
|
||||
|
||||
if on_conflict is OnConflict.REPLACE:
|
||||
if former_decorator is func and former_target:
|
||||
# ancien décorateur déjà fourni : on le remplace vraiment
|
||||
func = former_target
|
||||
else:
|
||||
# ancien décorateur déjà décoré : on le désactive
|
||||
former_ctx = getattr(former_decorator, DECORATOR_ATTR_CTX, None)
|
||||
if former_ctx is None:
|
||||
former_ctx = {}
|
||||
getattr(former_decorator, DECORATOR_ATTR_CTX, former_ctx)
|
||||
former_ctx.update({CTX_KEY_DISABLED: True})
|
||||
ctx = {}
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if ctx.get(CTX_KEY_DISABLED, False):
|
||||
# désactivé : simple appel de la fonction d'origine
|
||||
return func(*args, **kwargs)
|
||||
return action(ctx, func, *args, **kwargs)
|
||||
|
||||
setattr(wrapper, DECORATOR_ATTR_CTX, ctx)
|
||||
setattr(wrapper, DECORATOR_ATTR_KEY, decorator_key)
|
||||
setattr(wrapper, DECORATOR_ATTR_TARGET, func)
|
||||
return wrapper
|
||||
return inner
|
||||
|
||||
|
||||
def execution_time(
|
||||
level: int = logging.DEBUG,
|
||||
logger_name: Any = None,
|
||||
logger_factory: Callable[[Any], Union[Logger, LoggerAdapter]] = None,
|
||||
on_conflict: OnConflict = OnConflict.SKIP,
|
||||
warn_after: int = None):
|
||||
"""
|
||||
Décorateur de fonction pour tracer le temps d'exécution (au niveau <level> avec un logger de performances).
|
||||
Pour une classe, le décorateur s'applique aux fonctions/méthodes qui passent le filtre.
|
||||
|
||||
:param level: niveau de base du logger, defaults to logging.DEBUG
|
||||
:type level: int
|
||||
|
||||
:param logger_name: valeur pour déterminer le nom du logger, ignorée si 'logger_factory' est renseigné, defaults to None
|
||||
:type logger_name: str, optional
|
||||
|
||||
:param logger_factory: fonction de création du logger à partir de la fonction annotée, annule 'logger_name', defaults to None.
|
||||
:type logger_factory: Callable[[Any], Union[Logger, LoggerAdapter]], optional
|
||||
|
||||
:param on_conflict: stratégie de gestion de conflit quand le décorateur est déjà utilisé, defaults to OnConflict.SKIP
|
||||
:type on_conflict: class:`OnConflict`
|
||||
|
||||
:param warn_after: durée (en ms) au-delà de laquelle on souhaite un log d'avertissement, defaults to None
|
||||
:type warn_after: int, optional
|
||||
|
||||
:return: résultat de la fonction d'origine
|
||||
"""
|
||||
|
||||
def action(ctx: Dict, func, *args, **kwargs):
|
||||
if CTX_KEY_LOGGER not in ctx:
|
||||
# initialisation du logger, pas toujours possible avant le premier appel
|
||||
ctx.update({CTX_KEY_LOGGER: logger_factory(func) if logger_factory else get_logger(logger_name or func, TAG_PERF)})
|
||||
|
||||
temps_debut = time.time()
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
try:
|
||||
temps_ecoule = round((time.time() - temps_debut) * 1000)
|
||||
log_level = logging.WARNING if isinstance(warn_after, int) and temps_ecoule > warn_after else level
|
||||
ctx[CTX_KEY_LOGGER].log(log_level, "'%s' exécuté en %d ms", func.__name__, temps_ecoule)
|
||||
except Exception:
|
||||
logger.exception('impossible de tracer la durée')
|
||||
|
||||
return _create_decorator_from_action(action, inspect.currentframe().f_code.co_name, on_conflict=on_conflict)
|
||||
|
||||
|
||||
def query_count(
|
||||
level: int = logging.DEBUG,
|
||||
logger_name: Any = None,
|
||||
logger_factory: Callable[[Any], Union[Logger, LoggerAdapter]] = None,
|
||||
on_conflict: OnConflict = OnConflict.SKIP,
|
||||
warn_after: int = None):
|
||||
"""
|
||||
Décorateur de fonction pour tracer le nombre de requêtes (au niveau <level> avec un logger de performances).
|
||||
Pour une classe, le décorateur s'applique aux fonctions/méthodes qui passent le filtre.
|
||||
|
||||
:param level: niveau de base du logger, defaults to logging.DEBUG
|
||||
:type level: int
|
||||
|
||||
:param logger_name: valeur pour déterminer le nom du logger, ignorée si 'logger_factory' est renseigné, defaults to None
|
||||
:type logger_name: str, optional
|
||||
|
||||
:param logger_factory: fonction de création du logger à partir de la fonction annotée, annule 'logger_name', defaults to None.
|
||||
:type logger_factory: Callable[[Any], Union[Logger, LoggerAdapter]], optional
|
||||
|
||||
:param on_conflict: stratégie de gestion de conflit quand le décorateur est déjà utilisé, defaults to OnConflict.SKIP
|
||||
:type on_conflict: class:`OnConflict`
|
||||
|
||||
:param warn_after: nombre de requêtes au-delà duquel on souhaite un log d'avertissement, defaults to None
|
||||
:type warn_after: int, optional
|
||||
|
||||
:return: résultat de la fonction d'origine
|
||||
"""
|
||||
|
||||
def action(ctx: Dict, func, *args, **kwargs):
|
||||
if CTX_KEY_LOGGER not in ctx:
|
||||
# initialisation du logger, pas toujours possible avant le premier appel
|
||||
ctx.update({CTX_KEY_LOGGER: logger_factory(func) if logger_factory else get_logger(logger_name or func, TAG_PERF)})
|
||||
|
||||
nb_debut = _get_query_count()
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
try:
|
||||
nb_requetes = _get_query_count() - nb_debut
|
||||
log_level = logging.WARNING if isinstance(warn_after, int) and nb_requetes > warn_after else level
|
||||
ctx[CTX_KEY_LOGGER].log(log_level, "'%s' a exécuté %d requête(s)", func.__name__, nb_requetes)
|
||||
except Exception:
|
||||
logger.exception('impossible de tracer le nombre de requêtes')
|
||||
|
||||
return _create_decorator_from_action(action, inspect.currentframe().f_code.co_name, on_conflict=on_conflict)
|
||||
|
||||
|
||||
def class_logger(cls):
|
||||
"""
|
||||
Décorateur de classe pour stocker un logger standard dans l'attribut 'logger' (aucune gestion de conflit)
|
||||
"""
|
||||
if not inspect.isclass(cls):
|
||||
return cls
|
||||
|
||||
@functools.wraps(cls, updated=())
|
||||
class Wrapper(cls):
|
||||
decorated = 1
|
||||
|
||||
setattr(Wrapper, CLASS_ATTR_LOGGER, get_logger(Wrapper))
|
||||
return Wrapper
|
||||
|
||||
|
||||
def decorate_functions(
|
||||
decorator: Callable,
|
||||
func_filter: Callable[[Callable], bool],
|
||||
factory: bool = False):
|
||||
"""
|
||||
Décorateur de classe qui applique le décorateur donné aux fonctions/méthodes (attributs d'un type de FUNCTION_TYPES) qui passent le filtre.
|
||||
|
||||
:param decorator: décorateur à appliquer
|
||||
:type decorator: Callable
|
||||
|
||||
:param func_filter: filtre permettant de sélectionner les fonctions/méthodes à décorer
|
||||
:type func_filter: Callable[[Callable], bool]
|
||||
|
||||
:param factory: True indique que le décorateur est une méthode "factory" à exécuter avec la classe, defaults to False
|
||||
:type factory: bool
|
||||
"""
|
||||
|
||||
def decorate(cls):
|
||||
if not inspect.isclass(cls):
|
||||
return cls
|
||||
|
||||
@functools.wraps(cls, updated=())
|
||||
class Wrapper(cls):
|
||||
decorated = 1
|
||||
|
||||
_decorator = decorator(Wrapper) if factory else decorator
|
||||
for attr_name in dir(Wrapper):
|
||||
# __new__ et __init__ sont complexes à décorer, à gérer au cas par cas
|
||||
if attr_name != '__new__' and attr_name != '__init__':
|
||||
value = getattr(Wrapper, attr_name)
|
||||
if isinstance(value, FUNCTION_TYPES) and func_filter(value):
|
||||
setattr(Wrapper, attr_name, _decorator(value))
|
||||
return Wrapper
|
||||
return decorate
|
||||
1
backend-django/backend/utils/extraction/__init__.py
Normal file
1
backend-django/backend/utils/extraction/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .administre import *
|
||||
276
backend-django/backend/utils/extraction/administre.py
Normal file
276
backend-django/backend/utils/extraction/administre.py
Normal file
@@ -0,0 +1,276 @@
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from backend.models.administre import Administre
|
||||
from backend.models.administre import StatutPamChoices as StatutPam
|
||||
from backend.models.domaine import Domaine
|
||||
from backend.models.filiere import Filiere
|
||||
from backend.models.fonction import Fonction
|
||||
from backend.models.formation_emploi import FormationEmploi
|
||||
from backend.models.grade import Grade
|
||||
from ..alimentation import BOCols
|
||||
from ..alimentation_decorators import data_perf_logger_factory, get_data_logger
|
||||
from ..decorators import execution_time
|
||||
|
||||
logger = get_data_logger(__name__)
|
||||
|
||||
|
||||
@execution_time(logger_factory=data_perf_logger_factory)
|
||||
def to_table_administres_bo(bo: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Création de la table Administrés à partir du fichier de données BO. Sélection et renommage des champs.
|
||||
|
||||
:param bo: table BO
|
||||
:type bo: class:`pandas.DataFrame`
|
||||
|
||||
:return: data frame contenant les information des administrés
|
||||
:rtype: class:`pandas.DataFrame`
|
||||
"""
|
||||
Cols = Administre.Cols
|
||||
|
||||
# import des tables contenant les clés étrangères
|
||||
fonctions = pd.DataFrame.from_records(Fonction.objects.all().values())
|
||||
domaines = pd.DataFrame.from_records(Domaine.objects.all().values())
|
||||
filieres = pd.DataFrame.from_records(Filiere.objects.all().values())
|
||||
|
||||
grades = pd.DataFrame.from_records(Grade.objects.all().values())
|
||||
fe = pd.DataFrame.from_records(FormationEmploi.objects.all().values())
|
||||
|
||||
# sélection des attributs nécessaires à la table administres
|
||||
col_adm = BOCols.columns(
|
||||
BOCols.ID_SAP,
|
||||
BOCols.CREDO_FE,
|
||||
BOCols.FONCTION,
|
||||
BOCols.GRADE,
|
||||
BOCols.DATE_DEBUT_GRADE,
|
||||
BOCols.NOM,
|
||||
BOCols.PRENOM,
|
||||
BOCols.SEXE,
|
||||
BOCols.ID_DEF,
|
||||
BOCols.EIP,
|
||||
BOCols.EIS,
|
||||
BOCols.DOMAINE,
|
||||
BOCols.FILIERE,
|
||||
BOCols.NF,
|
||||
BOCols.DOMAINE_GESTION,
|
||||
BOCols.DATE_ENTREE_SERVICE,
|
||||
BOCols.ARME,
|
||||
BOCols.REGROUPEMENT_ORIGINE_RECRUTEMENT,
|
||||
BOCols.DATE_NAISSANCE,
|
||||
BOCols.DIPLOME_PLUS_HAUT_NIVEAU,
|
||||
BOCols.DATE_RDC,
|
||||
BOCols.DATE_DERNIER_ACR,
|
||||
BOCols.DERNIER_DIPLOME,
|
||||
BOCols.DATE_ARRIVEE_FE,
|
||||
BOCols.POSITION_STATUTAIRE,
|
||||
BOCols.DATE_POSITION_STATUAIRE,
|
||||
BOCols.INTERRUPTION_SERVICE,
|
||||
BOCols.SITUATION_FAMILIALE,
|
||||
BOCols.DATE_MARIAGE,
|
||||
BOCols.NOMBRE_ENFANTS,
|
||||
BOCols.ENFANTS,
|
||||
BOCols.ID_DEF_CONJOINT,
|
||||
BOCols.FONCTION_1,
|
||||
BOCols.FONCTION_2,
|
||||
BOCols.FONCTION_3,
|
||||
BOCols.FONCTION_4,
|
||||
BOCols.FONCTION_5,
|
||||
BOCols.FONCTION_6,
|
||||
BOCols.FONCTION_7,
|
||||
BOCols.FONCTION_8,
|
||||
BOCols.FONCTION_9,
|
||||
BOCols.DATE_FONCTION_1,
|
||||
BOCols.DATE_FONCTION_2,
|
||||
BOCols.DATE_FONCTION_3,
|
||||
BOCols.DATE_FONCTION_4,
|
||||
BOCols.DATE_FONCTION_5,
|
||||
BOCols.DATE_FONCTION_6,
|
||||
BOCols.DATE_FONCTION_7,
|
||||
BOCols.DATE_FONCTION_8,
|
||||
BOCols.DATE_FONCTION_9,
|
||||
BOCols.NF_POSTE,
|
||||
BOCols.DOMAINE_POSTE,
|
||||
BOCols.FILIERE_POSTE,
|
||||
BOCols.PLS_GB_MAX,
|
||||
BOCols.MARQUEUR_PN,
|
||||
BOCols.PROFESSION_CONJOINT,
|
||||
BOCols.ID_SAP_CONJOINT,
|
||||
BOCols.SEXE_CONJOINT,
|
||||
BOCols.ORIGINE_RECRUTEMENT,
|
||||
BOCols.DATE_LIEN_SERVICE,
|
||||
BOCols.AGE_ANNEES,
|
||||
BOCols.STATUT_CONCERTO,
|
||||
BOCols.DATE_STATUT_CONCERTO,
|
||||
BOCols.STATUT_CONCERTO_FUTUR,
|
||||
BOCols.DATE_STATUT_CONCERTO_FUTUR,
|
||||
BOCols.FUD,
|
||||
BOCols.DATE_FUD
|
||||
)
|
||||
administres = bo[col_adm]
|
||||
|
||||
# jointure avec les tables contenant les clés étrangères
|
||||
# mapping avec les postes
|
||||
administres = administres.merge(fonctions, how='left', left_on=BOCols.FONCTION.value, right_on='fon_libelle')
|
||||
logger.debug("Nombre d'administrés dans le fichier")
|
||||
logger.debug('total administres : %s', administres.shape[0])
|
||||
administres = administres.merge(grades, how='inner', left_on=BOCols.GRADE.value, right_on='gr_code')
|
||||
logger.debug("Filtrage par grade reconnu")
|
||||
logger.debug("Nombre d'administres : %s", administres.shape[0])
|
||||
administres = administres.merge(fe, how='inner', left_on=BOCols.CREDO_FE.value, right_on='fe_code')
|
||||
logger.debug("Filtrage par FE reconnue")
|
||||
logger.debug("Nombre d'administres : %s", administres.shape[0])
|
||||
|
||||
# sélection et renommage des champs (BIEN FAIRE LE MAPPING DES COLONNES QU'ON VEUT GARDER)
|
||||
adm_mapping = BOCols.col_mapping({
|
||||
BOCols.ID_SAP: Cols.PK,
|
||||
'fe_code': f'{Cols.REL_FORMATION_EMPLOI}_id',
|
||||
'fon_id': "a_code_fonction",
|
||||
BOCols.GRADE: f'{Cols.REL_GRADE}_id',
|
||||
BOCols.DATE_DEBUT_GRADE: "a_grade_date_debut",
|
||||
BOCols.FONCTION: "a_fonction",
|
||||
BOCols.NOM: "a_nom",
|
||||
BOCols.PRENOM: "a_prenom",
|
||||
BOCols.SEXE: "a_sexe",
|
||||
BOCols.ID_DEF: "a_id_def",
|
||||
BOCols.EIP: "a_eip",
|
||||
BOCols.EIP: "a_eip_fiche_detaille",
|
||||
BOCols.EIS: "a_eis",
|
||||
BOCols.DOMAINE: Cols.REL_DOMAINE,
|
||||
BOCols.FILIERE: Cols.REL_FILIERE,
|
||||
BOCols.NF: 'a_nf',
|
||||
BOCols.DOMAINE_GESTION: "a_domaine_gestion",
|
||||
BOCols.DATE_ENTREE_SERVICE: "a_date_entree_service",
|
||||
BOCols.ARME: "a_arme",
|
||||
BOCols.REGROUPEMENT_ORIGINE_RECRUTEMENT: "a_rg_origine_recrutement",
|
||||
BOCols.DATE_RDC: 'a_date_rdc',
|
||||
BOCols.DATE_DERNIER_ACR: 'a_date_dernier_acr',
|
||||
BOCols.DATE_NAISSANCE: "a_date_naissance",
|
||||
BOCols.DIPLOME_PLUS_HAUT_NIVEAU: "a_diplome_hl",
|
||||
BOCols.DERNIER_DIPLOME: "a_dernier_diplome",
|
||||
BOCols.CREDO_FE: "a_credo_fe",
|
||||
BOCols.DATE_ARRIVEE_FE: "a_date_arrivee_fe",
|
||||
BOCols.POSITION_STATUTAIRE: "a_pos_statuaire",
|
||||
BOCols.DATE_POSITION_STATUAIRE: "a_date_pos_statuaire",
|
||||
BOCols.INTERRUPTION_SERVICE: "a_interruption_service",
|
||||
BOCols.SITUATION_FAMILIALE: "a_situation_fam",
|
||||
BOCols.DATE_MARIAGE: "a_date_mariage",
|
||||
BOCols.NOMBRE_ENFANTS: "a_nombre_enfants",
|
||||
BOCols.ENFANTS: "a_enfants",
|
||||
BOCols.ID_SAP_CONJOINT: "a_sap_conjoint",
|
||||
BOCols.FONCTION_1: "a_fonction1",
|
||||
BOCols.FONCTION_2: "a_fonction2",
|
||||
BOCols.FONCTION_3: "a_fonction3",
|
||||
BOCols.FONCTION_4: "a_fonction4",
|
||||
BOCols.FONCTION_5: "a_fonction5",
|
||||
BOCols.FONCTION_6: "a_fonction6",
|
||||
BOCols.FONCTION_7: "a_fonction7",
|
||||
BOCols.FONCTION_8: "a_fonction8",
|
||||
BOCols.FONCTION_9: "a_fonction9",
|
||||
BOCols.DATE_FONCTION_1: "a_date_fonction1",
|
||||
BOCols.DATE_FONCTION_2: "a_date_fonction2",
|
||||
BOCols.DATE_FONCTION_3: "a_date_fonction3",
|
||||
BOCols.DATE_FONCTION_4: "a_date_fonction4",
|
||||
BOCols.DATE_FONCTION_5: "a_date_fonction5",
|
||||
BOCols.DATE_FONCTION_6: "a_date_fonction6",
|
||||
BOCols.DATE_FONCTION_7: "a_date_fonction7",
|
||||
BOCols.DATE_FONCTION_8: "a_date_fonction8",
|
||||
BOCols.DATE_FONCTION_9: "a_date_fonction9",
|
||||
BOCols.NF_POSTE: "a_nf_poste",
|
||||
BOCols.DOMAINE_POSTE: "a_domaine_poste",
|
||||
BOCols.FILIERE_POSTE: "a_filiere_poste",
|
||||
BOCols.PLS_GB_MAX: "a_pls_gb_max",
|
||||
BOCols.MARQUEUR_PN: "a_marqueur_pn",
|
||||
BOCols.PROFESSION_CONJOINT: "a_profession_conjoint",
|
||||
BOCols.ID_DEF_CONJOINT: "a_id_def_conjoint",
|
||||
BOCols.SEXE_CONJOINT: "a_sexe_conjoint",
|
||||
BOCols.ORIGINE_RECRUTEMENT: "a_origine_recrutement",
|
||||
BOCols.STATUT_CONCERTO: Cols.STATUT_CONCERTO,
|
||||
BOCols.DATE_STATUT_CONCERTO: Cols.DATE_STATUT_CONCERTO,
|
||||
BOCols.STATUT_CONCERTO_FUTUR: Cols.STATUT_CONCERTO_FUTUR,
|
||||
BOCols.DATE_STATUT_CONCERTO_FUTUR: Cols.DATE_STATUT_CONCERTO_FUTUR,
|
||||
BOCols.FUD: 'a_fud',
|
||||
BOCols.DATE_FUD: 'a_date_fud',
|
||||
BOCols.DATE_LIEN_SERVICE: "a_lien_service",
|
||||
BOCols.AGE_ANNEES: "a_age_en_annees"
|
||||
})
|
||||
administres = administres.rename(columns=adm_mapping, errors='raise')
|
||||
|
||||
# initialisation des colonnes vides
|
||||
adm_col_vides = {
|
||||
'a_liste_id_marques': '',
|
||||
'a_notes_gestionnaire': '',
|
||||
'a_notes_partagees': '',
|
||||
'a_flag_particulier': 0
|
||||
}
|
||||
for k, v in adm_col_vides.items():
|
||||
administres[k] = v
|
||||
|
||||
administres = administres[list(adm_mapping.values()) + list(adm_col_vides.keys())]
|
||||
|
||||
administres_dom = administres.merge(domaines, how='inner', left_on=Cols.REL_DOMAINE, right_on='d_code')
|
||||
df = pd.merge(administres, administres_dom, on=[Cols.PK, Cols.REL_DOMAINE], how="left", indicator=True)
|
||||
df = df[df['_merge'] == 'left_only']
|
||||
domaines_not_found = df[Cols.REL_DOMAINE].drop_duplicates().tolist()
|
||||
administres_excluded_by_dom = df[Cols.PK].tolist()
|
||||
logger.debug("Filtrage par domaine actuel reconnu")
|
||||
logger.debug("Nombre d'administres : %s", administres_dom.shape[0])
|
||||
logger.debug('Domaines non retrouvés : %s', domaines_not_found)
|
||||
logger.debug('****************')
|
||||
logger.debug(administres_excluded_by_dom)
|
||||
logger.debug('****************')
|
||||
administres_fil = administres_dom.merge(filieres, how='inner', left_on=Cols.REL_FILIERE, right_on='f_code')
|
||||
# administres.merge(administres_fil, how='')
|
||||
df = pd.merge(administres_dom, administres_fil, on=[Cols.PK, Cols.REL_FILIERE], how="left", indicator=True)
|
||||
df = df[df['_merge'] == 'left_only']
|
||||
filieres_not_found = df[Cols.REL_FILIERE].drop_duplicates().tolist()
|
||||
administres_excluded_by_fil = df[Cols.PK].tolist()
|
||||
logger.debug("Filtrage par filière reconnue")
|
||||
logger.debug("Nombre d'administres restant : %s", administres_fil.shape[0])
|
||||
logger.debug('Filières non retrouvées : %s', filieres_not_found)
|
||||
logger.debug('****************')
|
||||
logger.debug(administres_excluded_by_fil)
|
||||
logger.debug('****************')
|
||||
administres = administres_fil
|
||||
|
||||
administres = administres.merge(domaines, how='left', left_on='a_domaine_poste', right_on='d_code', indicator=True)
|
||||
administres.loc[administres['_merge'] == 'left_only', 'a_domaine_poste'] = None
|
||||
administres.drop(columns='_merge', inplace=True)
|
||||
administres = administres.merge(filieres, how='left', left_on='a_filiere_poste', right_on='f_code', indicator=True)
|
||||
administres.loc[administres['_merge'] == 'left_only', 'a_filiere_poste'] = None
|
||||
# administres = administres.merge(filieres, how='inner', left_on='a_filiere_poste', right_on='f_code')
|
||||
|
||||
administres['a_categorie'] = administres["a_nf"].replace(
|
||||
{'1A': 'MDR', '1B': 'MDR', '1C': 'MDR', '2.': 'SOFF', '3A': 'SOFF', '3B': 'SOFF', '3B NFS': 'SOFF', '4.': 'OFF',
|
||||
'5A': 'OFF', '5B': 'OFF', '5C': 'OFF', '6A': 'OGX', '6B': 'OGX'}, inplace=False)
|
||||
# administres["a_nom_prenom"].fillna('NOM Prenom', inplace=True)
|
||||
# # administres["a_nom_prenom"].replace('', 'NOM Prenom', inplace=True)
|
||||
# nom_prenom_split = administres['a_nom_prenom'].str.split(r" +", n=1).str
|
||||
# administres["a_nom"] = nom_prenom_split.get(0).fillna('')
|
||||
# administres["a_prenom"] = nom_prenom_split.get(1).fillna('')
|
||||
# administres.drop("a_nom_prenom", inplace=True, axis=1)
|
||||
administres['a_nom'] = administres['a_nom'].fillna('NOM')
|
||||
administres['a_prenom'] = administres['a_prenom'].fillna('Prénom')
|
||||
|
||||
administres[f'{Cols.REL_FORMATION_EMPLOI}_id'] = administres[f'{Cols.REL_FORMATION_EMPLOI}_id'].replace({np.nan: None})
|
||||
administres[f'{Cols.REL_GRADE}_id'] = administres[f'{Cols.REL_GRADE}_id'].replace({np.nan: None})
|
||||
|
||||
administres = administres.drop_duplicates(subset=[Cols.PK])
|
||||
logger.debug('Retrait des doublons')
|
||||
logger.debug("Nombre d'administres restants : %s", administres.shape[0])
|
||||
|
||||
administres = administres.reset_index(drop=True)
|
||||
administres['a_sap_conjoint'] = administres['a_sap_conjoint'].replace({np.nan: None})
|
||||
administres['a_nf_poste'] = (administres['a_nf_poste'].replace({np.nan, None})
|
||||
.apply(lambda x: str(x)[1:])
|
||||
.str.upper())
|
||||
administres['a_nf'] = administres['a_nf'].str.upper()
|
||||
administres['a_eip'] = administres[Cols.REL_DOMAINE] + administres[Cols.REL_FILIERE] + administres['a_nf']
|
||||
administres['a_marqueur_pn'] = administres['a_marqueur_pn'].apply(lambda x: True if x == 'X' else False)
|
||||
|
||||
administres = (administres.fillna(np.nan)
|
||||
.replace([np.nan], [None]))
|
||||
logger.debug("Nombre d'administres extrait : %s", administres.shape[0])
|
||||
return administres
|
||||
|
||||
|
||||
20
backend-django/backend/utils/functions.py
Normal file
20
backend-django/backend/utils/functions.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import inspect
|
||||
import sys
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def find_class(func: Callable):
|
||||
"""
|
||||
basé sur inspect._findclass mais plus robuste et qui prend en compte les fonctions locales
|
||||
note : la fonction doit avoir un module sinon None est renvoyé (ex : str => None)
|
||||
"""
|
||||
|
||||
cls = sys.modules.get(getattr(func, '__module__', None))
|
||||
if cls is None:
|
||||
return None
|
||||
for name in func.__qualname__.split('.')[:-1]:
|
||||
if name != '<locals>':
|
||||
cls = getattr(cls, name, None)
|
||||
if not inspect.isclass(cls):
|
||||
return None
|
||||
return cls
|
||||
187
backend-django/backend/utils/initial.py
Normal file
187
backend-django/backend/utils/initial.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Ce module contient les Utilitaires du backend
|
||||
"""
|
||||
from datetime import datetime
|
||||
from random import randint
|
||||
import re
|
||||
from django.forms import model_to_dict
|
||||
from backend import constants
|
||||
from backend.models import Administre, Poste, SousVivier, FormationEmploi, RefSvFil, SousVivierAssociation
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
|
||||
def nf2categorie(nf: str) -> str:
|
||||
"""
|
||||
Renvoie la catégorie associé au niveau fonctionnel en argument
|
||||
|
||||
:type nf: str
|
||||
:param nf: niveau fonctionnel
|
||||
|
||||
:return: catégorie associée au niveau fonctionnel
|
||||
:rtype: str
|
||||
"""
|
||||
list_mdr = ['1A', '1B', '1C']
|
||||
list_soff = ['2.', '3A', '3B', '3B NFS']
|
||||
list_off = ['4.', '5A', '5B', '5C', '6A', '6B']
|
||||
cat = 'MDR' if nf in list_mdr else 'SOFF' if nf in list_soff else 'OFF' if nf in list_off else None
|
||||
return cat
|
||||
|
||||
|
||||
def generate_sv_id(dom: str, fil: str, cat: str) -> str:
|
||||
"""
|
||||
Génère un id de sous-vivier à partir d'un domaine, d'une filière et d'une catégorie.
|
||||
|
||||
:type dom: str
|
||||
:param dom: domaine du sous-vivier
|
||||
|
||||
:type fil: str
|
||||
:param fil: filiere du sous-vivier
|
||||
|
||||
:type cat: str
|
||||
:param cat: catégorie du sous-vivier
|
||||
|
||||
:return: id du sous-vivier
|
||||
:rtype: str
|
||||
"""
|
||||
return f"{dom}, {fil}, {cat}"
|
||||
|
||||
|
||||
def sous_viviers_du_cellule(cellule_code):
|
||||
# Récupère les sous-viviers associés à la cellule
|
||||
sous_vivier = RefSvFil.objects.filter(ref_sv_fil_code=cellule_code)
|
||||
if sous_vivier.exists():
|
||||
sous_viviers = list(sous_vivier.values_list('sous_vivier_id', flat=True))
|
||||
else :
|
||||
return []
|
||||
return sous_viviers
|
||||
|
||||
|
||||
def without_keys(d, keys):
|
||||
"""
|
||||
Suppression des clés d'un dictionnaire
|
||||
|
||||
:type d: Dictionnaire
|
||||
:param d: Dictionnaire contenant les ids
|
||||
|
||||
:type keys: String
|
||||
:param keys: Les clés que nous devons supprimer du dictionnaire
|
||||
|
||||
:return: - **Dictionnaire** (*Dictionnaire*): Dictionnaire sans les clés données en argument.
|
||||
"""
|
||||
return {x: d[x] for x in d if x not in keys}
|
||||
|
||||
def check_positive(valeur_nb):
|
||||
"""
|
||||
Vérifier si une valeur est positive et retourner 1, si elle est nulle ou négative retourner 0.
|
||||
|
||||
:type poste_nb: DataFrame
|
||||
:param poste_nb: ID du sous-vivier
|
||||
|
||||
:return: - **valeur_nb_modifie** (*dataframe*): Dataframe contenant des valeurs 1 ou 0.
|
||||
"""
|
||||
valeur_nb_modifie = valeur_nb.apply(lambda x: 1 if x > 0 else 0)
|
||||
return valeur_nb_modifie
|
||||
|
||||
def cleanString(string):
|
||||
"""Cette fonction supprimera tous les caractères qui ne sont pas alphanumériques.
|
||||
|
||||
:type string: chaîne de caractères
|
||||
:param string: chaîne qui doit être nettoyée
|
||||
|
||||
:return: - **return** (*chaîne de caractères*): chaîne nettoyée.
|
||||
"""
|
||||
return ''.join([i for i in string if i.isalnum()])
|
||||
|
||||
|
||||
def intOrNone(value):
|
||||
"""Cette fonction renvoie l'entier de la valeur ou renvoie None.
|
||||
|
||||
:type value: float
|
||||
:param value: valeur a con
|
||||
|
||||
:return: - **res** (*int or None*): valeur à convertir.
|
||||
"""
|
||||
try:
|
||||
res = int(float(value))
|
||||
except Exception:
|
||||
res = None
|
||||
|
||||
return res
|
||||
|
||||
def impact_decisions(old_administre, administre, old_avis, avis, eip, fe_code, categorie):
|
||||
"""
|
||||
|
||||
:type administre: objet
|
||||
:param administre: instance du model Administre
|
||||
|
||||
:type old_avis: chaine de caractères
|
||||
:param old_avis: avis de l'administré avant la mise à jour
|
||||
|
||||
:type avis: chaine de caractère
|
||||
:param avis: nouvel avis de l'administré
|
||||
|
||||
:type eip: chaine de caractère
|
||||
:param eip: eip actuel du militaire
|
||||
|
||||
:type categorie: dataframe pandas
|
||||
:param categorie: Dataframe contenant les données pretraités à inserer
|
||||
|
||||
:return: - **list_error** (*liste*): liste des identifiants SAP pour lesquels il y a eu une erreur.
|
||||
"""
|
||||
|
||||
list_error = []
|
||||
if (old_avis == 'NON_ETUDIE' or old_avis == 'A_MAINTENIR' or old_avis == 'A_ETUDIER') and (avis == 'A_MUTER' or avis == 'PARTANT' or avis == 'NON_DISPONIBLE'):
|
||||
poste_qs = Poste.objects.filter(p_eip__iexact=eip, formation_emploi_id=fe_code)
|
||||
poste = poste_qs.first()
|
||||
fe = FormationEmploi.objects.get(fe_code=fe_code)
|
||||
fe.save()
|
||||
|
||||
if poste and poste.p_nb_non_etudie > 0:
|
||||
new_nb_p4, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p4 + 1, poste.p_nb_non_etudie - 1, poste.p_nb_vacant + 1, poste.p_nb_occupe - 1
|
||||
poste_qs.update(p_nb_p4=new_nb_p4, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
|
||||
else:
|
||||
list_error.append(administre.a_id_sap)
|
||||
|
||||
if (old_avis == 'A_MUTER' or old_avis == 'PARTANT' or old_avis == 'NON_DISPONIBLE') and (avis == 'NON_ETUDIE' or avis == 'A_MAINTENIR' or avis == 'A_ETUDIER'):
|
||||
poste_qs = Poste.objects.filter(p_eip__iexact=eip, formation_emploi_id=fe_code).exclude(p_nb_occupe=0)
|
||||
poste = poste_qs.first()
|
||||
fe = FormationEmploi.objects.get(fe_code=fe_code)
|
||||
if categorie == constants.CATEGORIE_MDR:
|
||||
fe.fe_nb_poste_vacant_mdr = fe.fe_nb_poste_vacant_mdr - 1
|
||||
fe.fe_nb_poste_occupe_mdr = fe.fe_nb_poste_occupe_mdr + 1
|
||||
elif categorie == constants.CATEGORIE_SOFF:
|
||||
fe.fe_nb_poste_vacant_soff = fe.fe_nb_poste_vacant_soff - 1
|
||||
fe.fe_nb_poste_occupe_soff = fe.fe_nb_poste_occupe_soff + 1
|
||||
elif categorie == constants.CATEGORIE_OFF:
|
||||
fe.fe_nb_poste_vacant_off = fe.fe_nb_poste_vacant_off - 1
|
||||
fe.fe_nb_poste_occupe_off = fe.fe_nb_poste_occupe_off + 1
|
||||
|
||||
fe.save()
|
||||
|
||||
if poste and poste.p_nb_p4 > 0:
|
||||
new_nb_p4, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p4 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||
poste_qs.update(p_nb_p4=new_nb_p4, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 > 0:
|
||||
new_nb_p3, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p3 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||
poste_qs.update(p_nb_p3=new_nb_p3, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 == 0 and poste.p_nb_p2 > 0:
|
||||
new_nb_p2, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p2 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||
poste_qs.update(p_nb_p2=new_nb_p2, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 == 0 and poste.p_nb_p2 == 0 and poste.p_nb_p1 > 0:
|
||||
new_nb_p1, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p1 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||
poste_qs.update(p_nb_p1=new_nb_p1, p_nb_non_etudie=new_nb_non_etudie,
|
||||
p_nb_vacant=new_nb_vacant,
|
||||
p_nb_occupe=new_nb_occupe)
|
||||
else:
|
||||
list_error.append(administre.a_id_sap)
|
||||
return list_error
|
||||
2
backend-django/backend/utils/insertion/__init__.py
Normal file
2
backend-django/backend/utils/insertion/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .administre import *
|
||||
from .commun import *
|
||||
448
backend-django/backend/utils/insertion/administre.py
Normal file
448
backend-django/backend/utils/insertion/administre.py
Normal file
@@ -0,0 +1,448 @@
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from backend.models.administre import Administre, Administres_Pams
|
||||
from backend.models.administre import StatutPamChoices as StatutPam
|
||||
from backend.models.pam import PAM
|
||||
from backend.models.domaine import Domaine
|
||||
from backend.models.filiere import Filiere
|
||||
from backend.models.formation_emploi import FormationEmploi
|
||||
from backend.models.grade import Grade
|
||||
from django.db.models import Q
|
||||
|
||||
from ..alimentation_decorators import data_perf_logger_factory, get_data_logger
|
||||
from ..decorators import execution_time
|
||||
from .commun import (InsertionCounters, batch_iterator, is_same_model,
|
||||
is_same_value)
|
||||
|
||||
logger = get_data_logger(__name__)
|
||||
|
||||
|
||||
@execution_time(level=logging.INFO, logger_factory=data_perf_logger_factory)
|
||||
def update_administre_fmob(df) -> None:
|
||||
"""
|
||||
Met à jour le statut PAM des administrés quand un formulaire de mobilité est annulé.
|
||||
"""
|
||||
ModelType = Administres_Pams
|
||||
Cols = ModelType.Cols
|
||||
status = StatutPam.A_MAINTENIR
|
||||
|
||||
annee_pam = str(int(df['fmob_millesime'].iloc[0]))
|
||||
|
||||
|
||||
updated = (ModelType.objects
|
||||
.filter(**{f'{Cols.O2M_FMOB}__fmob_annulation_fmob': True})
|
||||
.filter(~Q(**{Cols.STATUT_PAM: status}))
|
||||
.filter(a_statut_pam_annee=StatutPam.NON_ETUDIE)
|
||||
.filter(pam__pam_id=annee_pam)
|
||||
.update(**{Cols.STATUT_PAM: status}))
|
||||
if updated:
|
||||
logger.info('%s | mis à jour car FMOB annulé : %s', ModelType.__name__, updated)
|
||||
|
||||
logger.info('Début de la mise à jour des FMOB en "A traiter"')
|
||||
|
||||
next_status = StatutPam.A_TRAITER
|
||||
next_updated = (ModelType.objects
|
||||
.filter(**{f'{Cols.O2M_FMOB}__isnull' : False})
|
||||
.filter(~Q(**{Cols.STATUT_PAM: status}))
|
||||
.filter(a_statut_pam_annee=StatutPam.NON_ETUDIE)
|
||||
.filter(pam__pam_id=annee_pam)
|
||||
.update(**{Cols.STATUT_PAM: next_status}))
|
||||
if next_updated:
|
||||
logger.info('%s | mis à jour car FMOB existe: %s administrés qui ont un FORMOB', ModelType.__name__, next_updated)
|
||||
logger.info("%s[pk=%s] administré mis à jour", ModelType.__name__,next_updated)
|
||||
|
||||
return updated, next_updated
|
||||
|
||||
|
||||
|
||||
|
||||
@execution_time(level=logging.INFO, logger_factory=data_perf_logger_factory)
|
||||
def insert_administre_bo(df: pd.DataFrame) -> None:
|
||||
"""
|
||||
Insère ou met à jour des données de la table des administrés.
|
||||
|
||||
:param df: Dataframe contenant les données pretraités à inserer
|
||||
:type df: class:`pandas.DataFrame`
|
||||
"""
|
||||
logger.info('start update_administre')
|
||||
ModelType = Administre
|
||||
ModelType_2 = Administres_Pams
|
||||
Cols = ModelType.Cols
|
||||
Cols_2 = ModelType_2.Cols
|
||||
col_pk = Cols.PK
|
||||
fields_to_update = [
|
||||
'formation_emploi_id',
|
||||
'a_code_fonction',
|
||||
'grade_id',
|
||||
'a_grade_date_debut',
|
||||
'a_fonction',
|
||||
'a_nom',
|
||||
'a_prenom',
|
||||
'a_sexe',
|
||||
'a_id_def',
|
||||
'a_eip',
|
||||
'a_eip_fiche_detaille',
|
||||
'a_eis',
|
||||
Cols.REL_DOMAINE,
|
||||
Cols.REL_FILIERE,
|
||||
'a_nf',
|
||||
'a_domaine_gestion',
|
||||
'a_date_entree_service',
|
||||
'a_arme',
|
||||
'a_rg_origine_recrutement',
|
||||
'a_date_rdc',
|
||||
'a_date_dernier_acr',
|
||||
'a_date_naissance',
|
||||
'a_diplome_hl',
|
||||
'a_dernier_diplome',
|
||||
'a_credo_fe',
|
||||
'a_date_arrivee_fe',
|
||||
'a_date_pos_statuaire',
|
||||
'a_pos_statuaire',
|
||||
'a_interruption_service',
|
||||
'a_situation_fam',
|
||||
'a_date_mariage',
|
||||
'a_nombre_enfants',
|
||||
'a_enfants',
|
||||
'a_sap_conjoint',
|
||||
'a_fonction1',
|
||||
'a_fonction2',
|
||||
'a_fonction3',
|
||||
'a_fonction4',
|
||||
'a_fonction5',
|
||||
'a_fonction6',
|
||||
'a_fonction7',
|
||||
'a_fonction8',
|
||||
'a_fonction9',
|
||||
'a_date_fonction1',
|
||||
'a_date_fonction2',
|
||||
'a_date_fonction3',
|
||||
'a_date_fonction4',
|
||||
'a_date_fonction5',
|
||||
'a_date_fonction6',
|
||||
'a_date_fonction7',
|
||||
'a_date_fonction8',
|
||||
'a_date_fonction9',
|
||||
'a_pls_gb_max',
|
||||
'a_marqueur_pn',
|
||||
'a_profession_conjoint',
|
||||
'a_id_def_conjoint',
|
||||
'a_sexe_conjoint',
|
||||
'a_origine_recrutement',
|
||||
Cols.STATUT_CONCERTO,
|
||||
Cols.DATE_STATUT_CONCERTO,
|
||||
Cols.STATUT_CONCERTO_FUTUR,
|
||||
Cols.DATE_STATUT_CONCERTO_FUTUR,
|
||||
'a_fud',
|
||||
'a_date_fud',
|
||||
'a_lien_service',
|
||||
'a_categorie',
|
||||
]
|
||||
|
||||
|
||||
fields_to_update_2 = [
|
||||
'pam_id',
|
||||
'administre_id',
|
||||
'a_statut_pam_annee',
|
||||
]
|
||||
models_in_db = {m.pk: m for m in ModelType.objects.only(col_pk, *fields_to_update)}
|
||||
fields_db = ['a_id_sap'] + fields_to_update
|
||||
|
||||
# Integer ou Float Colonnes à mettre à jour
|
||||
fields_num = [
|
||||
'a_sap_conjoint',
|
||||
'a_pls_gb_max',
|
||||
'a_nombre_enfants',
|
||||
'a_id_sap'
|
||||
]
|
||||
|
||||
# Dict pour convertir toutes les colonnes non Integer en chaîne de caractères.
|
||||
dict_conv_str = {a: str for a in fields_db if a not in fields_num}
|
||||
|
||||
# Dict pour convertir toutes les colonnes Integer en Float.
|
||||
dict_conv_float = {a: float for a in fields_num }
|
||||
|
||||
# Lire tous les administrés de la base de données
|
||||
adm_in_db_df = pd.DataFrame.from_records(Administre.objects.all().values_list(*tuple(fields_db)), columns = fields_db)
|
||||
df = df.drop(['_merge'],1)
|
||||
|
||||
if not adm_in_db_df.empty :
|
||||
# Il va y avoir de modification de type donc c'est mieux de ne pas toucher df
|
||||
df_comparaison = df.copy()
|
||||
|
||||
# Modification de type de quelque champs
|
||||
logger.debug('Conversion des types pour la fusion')
|
||||
|
||||
|
||||
df_comparaison = df_comparaison.fillna(np.nan)
|
||||
df_comparaison = df_comparaison.replace('None', np.nan)
|
||||
adm_in_db_df = adm_in_db_df.fillna(np.nan)
|
||||
adm_in_db_df = adm_in_db_df.replace('None', np.nan)
|
||||
|
||||
adm_in_db_df = adm_in_db_df.astype(dict_conv_str)
|
||||
adm_in_db_df = adm_in_db_df.astype(dict_conv_float)
|
||||
|
||||
df_comparaison = df_comparaison.astype(dict_conv_str)
|
||||
df_comparaison = df_comparaison.astype(dict_conv_float)
|
||||
|
||||
compare = pd.DataFrame([df_comparaison[fields_db].dtypes,adm_in_db_df[fields_db].dtypes]).T
|
||||
logger.debug('Comparaison des types pour la fusion')
|
||||
# logger.debug(compare[compare[0]!=compare[1]].dropna())
|
||||
logger.debug('------------------------------------')
|
||||
|
||||
# Comparaison pour savoir ce qui doit etre creer, mis a jour ou supprimer
|
||||
|
||||
comparing_adm_sap = pd.merge(df_comparaison, adm_in_db_df, how='outer', on = 'a_id_sap', suffixes=(None, "_x"), indicator=True)
|
||||
same_rows = comparing_adm_sap[comparing_adm_sap['_merge'] == 'both'].drop('_merge', axis=1)
|
||||
new_rows = comparing_adm_sap[comparing_adm_sap['_merge'] == 'left_only'].drop('_merge', axis=1)
|
||||
delete_rows = comparing_adm_sap[comparing_adm_sap['_merge'] == 'right_only'].drop('_merge', axis=1)
|
||||
|
||||
|
||||
# Comparaison pour savoir des ligne a mettre a jour, lequel est deja a jour et lequel doit se mettre a jour
|
||||
|
||||
comparing_adm_both = pd.merge(same_rows, adm_in_db_df, how='left', on=fields_db,
|
||||
suffixes=(None, "_x"), indicator=True)
|
||||
|
||||
not_updated_rows = comparing_adm_both[comparing_adm_both['_merge'] == 'both'].drop(['_merge'], axis=1)
|
||||
updated_rows = comparing_adm_both[comparing_adm_both['_merge'] == 'left_only'].drop(['_merge'], axis=1)
|
||||
|
||||
# Creation du df final avec une colonne db_create_status qui dis si la ligne doit etre creer ou mis a jour
|
||||
|
||||
update = df.loc[df['a_id_sap'].isin(list(updated_rows['a_id_sap']))]
|
||||
update['db_create_status'] = 0
|
||||
create = df.loc[df['a_id_sap'].isin(list(new_rows['a_id_sap']))]
|
||||
create['db_create_status'] = 1
|
||||
|
||||
df = pd.concat([update,create])
|
||||
|
||||
|
||||
else :
|
||||
df['db_create_status'] = 1
|
||||
not_updated_rows = pd.DataFrame([])
|
||||
|
||||
|
||||
# IDs existants pour les clés étrangères
|
||||
|
||||
domaines_in_db = set(Domaine.objects.values_list('pk', flat=True))
|
||||
filieres_in_db = set(Filiere.objects.values_list('pk', flat=True))
|
||||
formations_in_db = set(FormationEmploi.objects.values_list('pk', flat=True))
|
||||
grades_in_db = set(Grade.objects.values_list('pk', flat=True))
|
||||
|
||||
fields_not_validated = [f.name for f in ModelType._meta.get_fields() if f.is_relation]
|
||||
fields_not_validated_2 = [f.name for f in ModelType_2._meta.get_fields() if f.is_relation]
|
||||
|
||||
dict_create = {}
|
||||
dict_update = {}
|
||||
dict_up_to_date = {}
|
||||
dict_create_2 = {}
|
||||
set_dom = set()
|
||||
set_fil = set()
|
||||
counters = InsertionCounters()
|
||||
|
||||
annee_pam = PAM.objects.filter(pam_statut='PAM en cours')[0].pam_id
|
||||
annee_pam_suivant = PAM.objects.filter(pam_statut='PAM A+1')[0].pam_id
|
||||
|
||||
def process_row(row):
|
||||
pk = str(row[col_pk])
|
||||
pk_2 = pk + str(annee_pam)
|
||||
pk_3 = pk + str(annee_pam_suivant)
|
||||
try:
|
||||
domaine = row['a_domaine']
|
||||
if domaine is not None and domaine not in domaines_in_db:
|
||||
# TODO déjà fait dans l'extraction, serait mieux placé ici
|
||||
logger.warning("%s[pk=%s] domaine ignoré car absent du référentiel : %s", ModelType.__name__, pk, domaine)
|
||||
set_dom.add(domaine)
|
||||
counters.ignored += 1
|
||||
return
|
||||
|
||||
filiere = row['a_filiere']
|
||||
if filiere is not None and filiere not in filieres_in_db:
|
||||
# TODO déjà fait dans l'extraction, serait mieux placé ici
|
||||
logger.warning("%s[pk=%s] filière ignoré car absente du référentiel : %s", ModelType.__name__, pk, filiere)
|
||||
set_fil.add(filiere)
|
||||
counters.ignored += 1
|
||||
return
|
||||
|
||||
grade = row['grade_id']
|
||||
if grade is not None and grade not in grades_in_db:
|
||||
# TODO déjà fait dans l'extraction, serait mieux placé ici
|
||||
logger.warning("%s[pk=%s] ignoré car grade inconnu : %s", ModelType.__name__, pk, grade)
|
||||
counters.ignored += 1
|
||||
return
|
||||
|
||||
formation = row['formation_emploi_id']
|
||||
if formation is not None and formation not in formations_in_db:
|
||||
# TODO déjà fait dans l'extraction, serait mieux placé ici
|
||||
logger.warning("%s[pk=%s] ignoré car formation-emploi inconnue : %s", ModelType.__name__, pk, formation)
|
||||
counters.ignored += 1
|
||||
return
|
||||
|
||||
|
||||
model_2 = ModelType_2(**{
|
||||
'id' :pk_2,
|
||||
'pam_id' :annee_pam,
|
||||
'administre_id' :pk,
|
||||
'a_statut_pam_annee' :StatutPam.NON_ETUDIE,
|
||||
})
|
||||
|
||||
model_3 = ModelType_2(**{
|
||||
'id' :pk_3,
|
||||
'pam_id' :annee_pam_suivant,
|
||||
'administre_id' :pk,
|
||||
'a_statut_pam_annee' :StatutPam.NON_ETUDIE,
|
||||
})
|
||||
|
||||
in_db = models_in_db.get(pk)
|
||||
model = ModelType(**{
|
||||
'pk': pk,
|
||||
'formation_emploi_id': formation,
|
||||
'grade_id': grade,
|
||||
'a_grade_date_debut': row['a_grade_date_debut'],
|
||||
'a_liste_id_marques': row['a_liste_id_marques'],
|
||||
'a_nom': row['a_nom'],
|
||||
'a_prenom': row['a_prenom'],
|
||||
'a_sexe': row['a_sexe'],
|
||||
'a_id_def': row['a_id_def'],
|
||||
'a_eip': row['a_eip'],
|
||||
'a_eip_fiche_detaille': row['a_eip_fiche_detaille'],
|
||||
f'{Cols.REL_DOMAINE}_id': domaine,
|
||||
'a_domaine_futur_id': domaine,
|
||||
f'{Cols.REL_FILIERE}_id': filiere,
|
||||
'a_filiere_futur_id': filiere,
|
||||
'a_fonction': row['a_fonction'],
|
||||
'a_code_fonction': row['a_code_fonction'],
|
||||
'a_nf': row['a_nf'],
|
||||
'a_nf_futur': row['a_nf'],
|
||||
'a_categorie': row['a_categorie'],
|
||||
'a_domaine_gestion': row['a_domaine_gestion'],
|
||||
'a_date_entree_service': row['a_date_entree_service'],
|
||||
'a_arme': row['a_arme'],
|
||||
'a_rg_origine_recrutement': row['a_rg_origine_recrutement'],
|
||||
'a_date_naissance': row['a_date_naissance'],
|
||||
'a_diplome_hl': row['a_diplome_hl'],
|
||||
'a_dernier_diplome': row['a_dernier_diplome'],
|
||||
'a_credo_fe': row['a_credo_fe'],
|
||||
'a_date_arrivee_fe': row['a_date_arrivee_fe'],
|
||||
'a_date_pos_statuaire': row['a_date_pos_statuaire'],
|
||||
'a_pos_statuaire': row['a_pos_statuaire'],
|
||||
'a_interruption_service': row['a_interruption_service'],
|
||||
'a_situation_fam': row['a_situation_fam'],
|
||||
'a_date_mariage': row['a_date_mariage'],
|
||||
'a_nombre_enfants': row['a_nombre_enfants'],
|
||||
'a_enfants': row['a_enfants'],
|
||||
'a_date_rdc': row['a_date_rdc'],
|
||||
'a_date_dernier_acr': row['a_date_dernier_acr'],
|
||||
'a_eis': row['a_eis'],
|
||||
'a_sap_conjoint': row['a_sap_conjoint'],
|
||||
'a_flag_particulier': row['a_flag_particulier'],
|
||||
'a_notes_gestionnaire': row['a_notes_gestionnaire'],
|
||||
'a_notes_partagees': row['a_notes_partagees'],
|
||||
'a_fonction1': row['a_fonction1'],
|
||||
'a_fonction2': row['a_fonction2'],
|
||||
'a_fonction3': row['a_fonction3'],
|
||||
'a_fonction4': row['a_fonction4'],
|
||||
'a_fonction5': row['a_fonction5'],
|
||||
'a_fonction6': row['a_fonction6'],
|
||||
'a_fonction7': row['a_fonction7'],
|
||||
'a_fonction8': row['a_fonction8'],
|
||||
'a_fonction9': row['a_fonction9'],
|
||||
'a_date_fonction1': row['a_date_fonction1'],
|
||||
'a_date_fonction2': row['a_date_fonction2'],
|
||||
'a_date_fonction3': row['a_date_fonction3'],
|
||||
'a_date_fonction4': row['a_date_fonction4'],
|
||||
'a_date_fonction5': row['a_date_fonction5'],
|
||||
'a_date_fonction6': row['a_date_fonction6'],
|
||||
'a_date_fonction7': row['a_date_fonction7'],
|
||||
'a_date_fonction8': row['a_date_fonction8'],
|
||||
'a_date_fonction9': row['a_date_fonction9'],
|
||||
'a_pls_gb_max': row['a_pls_gb_max'],
|
||||
'a_marqueur_pn': row['a_marqueur_pn'],
|
||||
'a_profession_conjoint': row['a_profession_conjoint'],
|
||||
'a_id_def_conjoint': row['a_id_def_conjoint'],
|
||||
'a_sexe_conjoint': row['a_sexe_conjoint'],
|
||||
'a_origine_recrutement': row['a_origine_recrutement'],
|
||||
'a_lien_service': row['a_lien_service'],
|
||||
'a_statut_concerto': row[Cols.STATUT_CONCERTO],
|
||||
'a_date_statut_concerto': row[Cols.DATE_STATUT_CONCERTO],
|
||||
'a_statut_concerto_futur': row[Cols.STATUT_CONCERTO_FUTUR],
|
||||
'a_date_statut_concerto_futur': row[Cols.DATE_STATUT_CONCERTO_FUTUR],
|
||||
'a_fud': row['a_fud'],
|
||||
'a_date_fud': row['a_date_fud'],
|
||||
})
|
||||
if row['db_create_status']:
|
||||
model_3.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||
dict_create_2.setdefault(pk_3, model_3)
|
||||
|
||||
model_2.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||
dict_create_2.setdefault(pk_2, model_2)
|
||||
|
||||
model.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||
dict_create.setdefault(pk, model)
|
||||
else:
|
||||
if not ModelType_2.objects.filter(Q(id=pk_2)).exists():
|
||||
model_2.full_clean(exclude=fields_not_validated_2, validate_unique=False)
|
||||
dict_create_2.setdefault(pk_2, model_2)
|
||||
|
||||
if not ModelType_2.objects.filter(Q(id=pk_3)).exists():
|
||||
model_3.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||
dict_create_2.setdefault(pk_3, model_3)
|
||||
model.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||
dict_update.setdefault(pk, model)
|
||||
|
||||
except Exception:
|
||||
counters.errors += 1
|
||||
logger.exception('%s une erreur est survenue à la ligne : %s (pk=%s)', ModelType.__name__, row['index'], pk)
|
||||
|
||||
df['index'] = df.index
|
||||
df.apply(process_row, axis=1)
|
||||
|
||||
if counters.errors:
|
||||
logger.warning("%s | en erreur : %s", ModelType.__name__, counters.errors)
|
||||
|
||||
if counters.ignored:
|
||||
logger.warning('%s | ignoré(s) : %s', ModelType.__name__, counters.ignored)
|
||||
|
||||
if set_dom:
|
||||
logger.warning('%s %s(s) ignoré(s) : %s', len(set_dom), Domaine.__name__, set_dom)
|
||||
|
||||
if set_fil:
|
||||
logger.warning('%s %s(s) ignorée(s) : %s', len(set_fil), Filiere.__name__, set_fil)
|
||||
|
||||
if not not_updated_rows.empty and fields_to_update:
|
||||
logger.info('%s | déjà à jour : %s', ModelType.__name__, len(not_updated_rows))
|
||||
|
||||
batch_size = 50
|
||||
if dict_create:
|
||||
logger.info('%s | à créer : %s', ModelType.__name__, len(dict_create))
|
||||
for idx, data_batch in enumerate(batch_iterator(list(dict_create.values()), batch_size)):
|
||||
ModelType.objects.bulk_create(data_batch)
|
||||
logger.debug('créé(s) : %s (lot %s)', len(data_batch), idx + 1)
|
||||
logger.info('%s | créé(s) : %s', ModelType.__name__, len(dict_create))
|
||||
else :
|
||||
logger.info('%s | rien à créer', ModelType.__name__)
|
||||
|
||||
if dict_create_2:
|
||||
logger.info('%s | à créer dans la vue A+1 : (%s)', ModelType_2.__name__, len(dict_create_2))
|
||||
for idx, data_batch in enumerate(batch_iterator(list(dict_create_2.values()), batch_size)):
|
||||
ModelType_2.objects.bulk_create(data_batch)
|
||||
logger.debug('créé(s) : %s (lot %s)', len(data_batch), idx + 1)
|
||||
logger.info('%s | créé(s) : %s', ModelType_2.__name__, len(dict_create_2))
|
||||
|
||||
if dict_update and fields_to_update:
|
||||
logger.info('%s | à mettre à jour : %s', ModelType.__name__, len(dict_update))
|
||||
for idx, data_batch in enumerate(batch_iterator(list(dict_update.values()), batch_size)):
|
||||
ModelType.objects.bulk_update(data_batch, fields=fields_to_update)
|
||||
logger.debug('mis à jour : %s (lot %s)', len(data_batch), idx + 1)
|
||||
logger.info('%s | mis à jour : %s', ModelType.__name__, len(dict_update))
|
||||
else :
|
||||
logger.info('%s | rien à mettre à jour', ModelType.__name__)
|
||||
|
||||
|
||||
adm_cree = len(dict_create) + len(dict_create_2)
|
||||
adm_modifie = len(dict_update)
|
||||
return adm_cree, adm_modifie, len(not_updated_rows), counters.errors, counters.ignored, set_dom, set_fil
|
||||
|
||||
# pas de suppression ici, il est prévu de faire un upload de fichier spécifique
|
||||
|
||||
61
backend-django/backend/utils/insertion/commun.py
Normal file
61
backend-django/backend/utils/insertion/commun.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from typing import Any, List, Tuple, Union
|
||||
|
||||
import pandas as pd
|
||||
from django.db.models import Choices, Model
|
||||
|
||||
from ..alimentation_decorators import get_data_logger
|
||||
|
||||
logger = get_data_logger(__name__)
|
||||
|
||||
|
||||
def batch_iterator(iterable: Union[List, Tuple, pd.DataFrame], batch_size: int) -> Union[List, Tuple, pd.DataFrame]:
|
||||
"""
|
||||
(je pense que le nom _batch_generator porterait à confusion)
|
||||
Générateur qui morcelle un itérable en lots de taille donnée.
|
||||
|
||||
:param iterable: itérable (supported un data frame)
|
||||
:type iterable: Union[List,Tuple,pd.DataFrame]
|
||||
|
||||
:param batch_size: taille de lot
|
||||
:type batch_size: int
|
||||
|
||||
:return: lot (valide, utilisateur, nouveau)
|
||||
:rtype: Tuple[bool, Optional[UserType], bool]
|
||||
"""
|
||||
length = len(iterable)
|
||||
for idx in range(0, length, batch_size):
|
||||
if isinstance(iterable, pd.DataFrame):
|
||||
yield iterable.iloc[idx:min(idx + batch_size, length)]
|
||||
else:
|
||||
yield iterable[idx:min(idx + batch_size, length)]
|
||||
|
||||
|
||||
def is_same_value(val1: Any, val2: Any) -> bool:
|
||||
"""
|
||||
Indique si deux valeurs sont équivalentes. Le cas le plus courant est une instance de Choices comparée à sa valeur.
|
||||
"""
|
||||
_1 = val1 if isinstance(val1, Choices) else val1
|
||||
_2 = val2 if isinstance(val2, Choices) else val2
|
||||
return _1 == _2
|
||||
|
||||
|
||||
def is_same_model(fields: Union[List[str], Tuple[str]], m1: Model, m2: Model) -> bool:
|
||||
"""
|
||||
Indique si deux modèles contiennent les mêmes données. Ce test permet de limiter les MAJ.
|
||||
"""
|
||||
|
||||
def _get_value(m: Model, field: str):
|
||||
""" Récupère la valeur d'un champ """
|
||||
attr = getattr(m, field)
|
||||
return attr.value if isinstance(attr, Choices) else attr
|
||||
|
||||
return not all(is_same_value(getattr(m1, f), getattr(m2, f)) for f in fields)
|
||||
|
||||
|
||||
class InsertionCounters:
|
||||
"""
|
||||
conteneur de compteurs pour faciliter l'incrémentation dans des fonctions imbriquées
|
||||
"""
|
||||
|
||||
ignored = 0
|
||||
errors = 0
|
||||
126
backend-django/backend/utils/logging.py
Normal file
126
backend-django/backend/utils/logging.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import inspect
|
||||
import sys
|
||||
import types
|
||||
from logging import Filter, Logger, LoggerAdapter, getLogger
|
||||
from typing import Any, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .functions import find_class
|
||||
|
||||
# nom du module de fonction built-in
|
||||
BUILTIN_MODULE = 'builtins'
|
||||
|
||||
# tag pour l'alimentation
|
||||
TAG_DATA_FEED = 'alimentation'
|
||||
|
||||
# tag pour la performance
|
||||
TAG_PERF = 'performance'
|
||||
|
||||
# clé du dictionnaire 'kwargs' (standard pour 'logging')
|
||||
KEY_EXTRA = 'extra'
|
||||
|
||||
# clé du dictionnaire 'extra' (le type attendu est une liste ou un tuple)
|
||||
KEY_EXTRA_TAGS = 'tags'
|
||||
|
||||
# cache des adaptateurs
|
||||
cache_adapters = {}
|
||||
|
||||
|
||||
def __get_full_name(module_name: Optional[str] = None, cls_name: Optional[str] = None) -> Optional[str]:
|
||||
""" renvoie le nom complet à partir du nom de module et du nom de classe """
|
||||
|
||||
if module_name or cls_name:
|
||||
return f'{module_name}.{cls_name}' if module_name and cls_name else cls_name or module_name
|
||||
return None
|
||||
|
||||
|
||||
def get_logger_name(value: Any = None) -> Optional[str]:
|
||||
"""
|
||||
Renvoie un nom de logger pour la valeur donnée :
|
||||
- None ou chaîne vide : None
|
||||
- chaîne non vide : la valeur initiale
|
||||
- un objet sans module ou de module BUILTIN_MODULE : None (utiliser une chaîne pour forcer le nom)
|
||||
- classe, instance de classe, fonction ou méthode : <module>.<nom qualifié de la classe> si possible, sinon <module>
|
||||
|
||||
:param valeur: valeur initiale
|
||||
:type valeur: Any, optional
|
||||
|
||||
:return: nom de logger
|
||||
:rtype: str
|
||||
"""
|
||||
if value is None or isinstance(value, str):
|
||||
return value or None
|
||||
|
||||
module = inspect.getmodule(value)
|
||||
module_name = module.__name__ if module else None
|
||||
if not module_name or module_name == BUILTIN_MODULE:
|
||||
return None
|
||||
|
||||
if inspect.isclass(value):
|
||||
return __get_full_name(module_name, value.__qualname__)
|
||||
|
||||
if isinstance(value, (
|
||||
types.BuiltinFunctionType,
|
||||
types.BuiltinMethodType,
|
||||
types.ClassMethodDescriptorType,
|
||||
types.FunctionType,
|
||||
types.MethodDescriptorType,
|
||||
types.MethodType,
|
||||
)):
|
||||
cls = find_class(value)
|
||||
return __get_full_name(module_name, cls.__qualname__ if cls else None)
|
||||
|
||||
return __get_full_name(module_name, value.__class__.__qualname__)
|
||||
|
||||
|
||||
def get_logger(value: Any = None, tags: Union[str, List[str], Set[str], Tuple[str]] = None) -> Union[Logger, LoggerAdapter]:
|
||||
"""
|
||||
Renvoie un logger standard avec un nom obtenu par 'get_logger_name'
|
||||
|
||||
:param valeur: valeur initiale
|
||||
:type valeur: Any, optional
|
||||
|
||||
:param tags: tag(s) à ajouter dans le contexte de log
|
||||
:type tags: Union[str, List[str], Set[str], Tuple[str]], optional
|
||||
|
||||
:raises TypeError: type inattendu
|
||||
|
||||
:return: logger
|
||||
:rtype: class:`logging.Logger` ou class:`logging.LoggerAdapter`
|
||||
"""
|
||||
logger_name = get_logger_name(value)
|
||||
if not tags:
|
||||
return getLogger(logger_name)
|
||||
|
||||
adapters = cache_adapters.setdefault(tags if isinstance(tags, str) else frozenset(tags), {})
|
||||
return adapters.get(logger_name) or adapters.setdefault(logger_name, TagLoggerAdapter(getLogger(logger_name), {KEY_EXTRA_TAGS: tags}))
|
||||
|
||||
|
||||
class TagLoggerAdapter(LoggerAdapter):
|
||||
"""
|
||||
Adaptateur qui ajout si nécessaire des tags.
|
||||
"""
|
||||
|
||||
def __init__(self, logger, extra):
|
||||
super().__init__(logger, extra)
|
||||
if not isinstance(self.extra, dict):
|
||||
self.extra = {}
|
||||
tags = self.extra.setdefault(KEY_EXTRA_TAGS, frozenset())
|
||||
if not isinstance(tags, frozenset):
|
||||
self.extra.update({KEY_EXTRA_TAGS: frozenset(tags) if isinstance(tags, (list, set, tuple)) else frozenset([tags])})
|
||||
|
||||
def process(self, msg, kwargs):
|
||||
""" ajoute les tags au contexte si nécessaire """
|
||||
tags = self.extra.get(KEY_EXTRA_TAGS)
|
||||
if tags:
|
||||
kw_extra = kwargs.setdefault(KEY_EXTRA, {})
|
||||
kw_tags = kw_extra.setdefault(KEY_EXTRA_TAGS, set())
|
||||
if not isinstance(kw_tags, (list, tuple, set)):
|
||||
kw_tags = set(kw_tags)
|
||||
kw_extra.update({KEY_EXTRA_TAGS: kw_tags})
|
||||
for tag in tags:
|
||||
if isinstance(kw_tags, set):
|
||||
kw_tags.add(tag)
|
||||
elif tag not in kw_tags:
|
||||
kw_tags = set(*kw_tags, tag)
|
||||
kw_extra.update({KEY_EXTRA_TAGS: kw_tags})
|
||||
return msg, kwargs
|
||||
404
backend-django/backend/utils/permissions.py
Normal file
404
backend-django/backend/utils/permissions.py
Normal file
@@ -0,0 +1,404 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from django.db.models import Exists, OuterRef, Q, QuerySet
|
||||
|
||||
|
||||
from ..models import (Administre, CustomUser, Decision, FormationEmploi, Poste,
|
||||
RefOrg, RefSvFil, Administres_Pams, Postes_Pams)
|
||||
from ..utils.attributes import safe_rgetattr
|
||||
from ..utils.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# clé d'un élément de "get_profiles_by_adm" : pour la lecture
|
||||
KEY_READ = 'read'
|
||||
|
||||
# clé d'un élément de "get_profiles_by_adm" : pour l'écriture
|
||||
KEY_WRITE = 'write'
|
||||
|
||||
|
||||
class Profiles(str, Enum):
|
||||
""" profils applicatifs """
|
||||
|
||||
# super utilisateur appelé administrateur
|
||||
SUPER = 'SUPER'
|
||||
|
||||
# gestionnaire de filière appelé gestionnaire BGCAT
|
||||
FILIERE = 'FILIERE'
|
||||
|
||||
# gestionnaire BVT
|
||||
BVT = 'BVT'
|
||||
|
||||
# gestionnaire inter-domaine
|
||||
ITD = 'ITD'
|
||||
|
||||
# gestionnaire PCP (sans détail actuel/futur) appelé pameur BMOB
|
||||
PCP = 'PCP'
|
||||
|
||||
# gestionnaire PCP actuel (ou gagnant), disponible quand l'administré change de FE
|
||||
PCP_ACTUEL = 'PCP_ACTUEL'
|
||||
|
||||
# gestionnaire PCP futur (ou perdant), disponible quand l'administré change de FE
|
||||
PCP_FUTUR = 'PCP_FUTUR'
|
||||
|
||||
# expert HME
|
||||
HME = 'HME'
|
||||
|
||||
def __repr__(self):
|
||||
return "%s.%s" % (self.__class__.__name__, self._name_)
|
||||
|
||||
|
||||
class ProfileSummary:
|
||||
"""
|
||||
résumé des profils de l'utilisateur
|
||||
il s'agit d'une vision simplifiée, sans le détail des sous-viviers ou des groupes FE
|
||||
"""
|
||||
|
||||
"""
|
||||
Méthode d'initialisation
|
||||
|
||||
:param org_code: code du référentiel organique lié à l'utilisateur, défaut : None
|
||||
:type org_code: str, optional
|
||||
|
||||
:param profiles: profils de l'utilisateur, dictionnaire {<KEY_READ>: <profils>, <KEY_WRITE>: <profils>} }
|
||||
:type profiles: Dict[str, Tuple[Profiles]], optional
|
||||
"""
|
||||
def __init__(self, org_code: str = None, profiles: Dict[str, Tuple[Profiles]] = {}):
|
||||
self.org_code = org_code
|
||||
self.profiles = profiles
|
||||
|
||||
|
||||
def is_truthy(val: Any) -> bool:
|
||||
"""indique si une valeur des référentiels est considérée comme vraie"""
|
||||
return val and val != 'nan'
|
||||
|
||||
def get_queryset_org_by_user(user: CustomUser) -> QuerySet:
|
||||
"""
|
||||
Crée un QuerySet pour récupérer le référentiel organique lié à l'utilisateur.
|
||||
|
||||
:param user: utilisateur
|
||||
:type user: class:`CustomUser`
|
||||
|
||||
:return: QuerySet pour récupérer le référentiel
|
||||
:rtype: class:`QuerySet`
|
||||
"""
|
||||
return RefOrg.objects.filter(ref_gest__isnull=False, ref_gest__pk=user.id)
|
||||
|
||||
def get_queryset_org_by_any_code(org_code: str) -> QuerySet:
|
||||
"""
|
||||
Crée un QuerySet pour récupérer les référentiel organiques liés au code donné.
|
||||
|
||||
:param org_code: code de référentiel (de niveau 1 à 4)
|
||||
:type org_code: str
|
||||
|
||||
:return: QuerySet pour récupérer les référentiels
|
||||
:rtype: class:`QuerySet`
|
||||
"""
|
||||
return RefOrg.objects.filter(Q(ref_org_code_niv_org1=org_code)
|
||||
| Q(ref_org_code_niv_org2=org_code)
|
||||
| Q(ref_org_code_niv_org3=org_code)
|
||||
| Q(ref_org_code_niv_org4=org_code))
|
||||
|
||||
def get_lvl4_org_codes_by_any_code(org_code: str) -> Tuple[str]:
|
||||
"""
|
||||
Retourne les codes de niveau 4 des référentiels organiques liés au code donné.
|
||||
|
||||
:param org_code: code de référentiel (de niveau 1 à 4)
|
||||
:type org_code: str
|
||||
|
||||
:return: codes de niveau 4
|
||||
:rtype: Tuple[str]
|
||||
"""
|
||||
return tuple(code for code in get_queryset_org_by_any_code(org_code).values_list('ref_org_code_niv_org4', flat=True).distinct() if is_truthy(code))
|
||||
|
||||
def get_adm_filter_by_lvl4_codes_fil(org_codes: Union[List[str], Tuple[str]]) -> Q:
|
||||
"""
|
||||
Crée un filtre pour récupérer les administrés liés à un gestionnaire de filière à partir de codes de référentiels de niveau 4.
|
||||
|
||||
:param org_codes: codes de référentiels de niveau 4
|
||||
:type org_code: str
|
||||
|
||||
:return: filtre pour récupérer les administrés
|
||||
:rtype: class:`Q`
|
||||
"""
|
||||
Cols = Administre.Cols
|
||||
Cols_Pams = Administres_Pams.Cols
|
||||
return Q(Exists(
|
||||
RefSvFil.objects
|
||||
.filter(ref_sv_fil_code__in=org_codes)
|
||||
.filter(ref_sv_fil_dom=OuterRef(f'{Cols_Pams.REL_ADMINISTRE}__{Cols.REL_DOMAINE}'),
|
||||
ref_sv_fil_fil=OuterRef(f'{Cols_Pams.REL_ADMINISTRE}__{Cols.REL_FILIERE}'),
|
||||
ref_sv_fil_cat=OuterRef(f'{Cols_Pams.REL_ADMINISTRE}__{Cols.CATEGORIE}'))
|
||||
))
|
||||
|
||||
def get_adm_filter_by_lvl4_codes_pcp(org_codes: Union[List[str], Tuple[str]]) -> Q:
|
||||
"""
|
||||
Crée un filtre pour récupérer les administrés liés à un gestionnaire PCP à partir de codes de référentiels de niveau 4.
|
||||
|
||||
:param org_codes: codes de référentiels de niveau 4
|
||||
:type org_code: str
|
||||
|
||||
:return: filtre pour récupérer les administrés
|
||||
:rtype: class:`Q`
|
||||
"""
|
||||
Cols = Administre.Cols
|
||||
Cols_Pams = Administres_Pams.Cols
|
||||
cat_filter = Q(**{f'{Cols_Pams.REL_ADMINISTRE}__{Cols.CATEGORIE}': 'MDR'})
|
||||
sub_qs = FormationEmploi.objects.filter(fe_code=OuterRef(f'{Cols_Pams.REL_ADMINISTRE}__{Cols.REL_FORMATION_EMPLOI}'))
|
||||
return (
|
||||
(~cat_filter & Exists(sub_qs.filter(fe_code_niv_org4__in=org_codes)))
|
||||
| (cat_filter & Exists(sub_qs.filter(fe_code_niv_org4_mdr__in=org_codes)))
|
||||
)
|
||||
|
||||
def get_adm_filter_by_lvl4_codes_future_pcp(org_codes: Union[List[str], Tuple[str]]) -> Q:
|
||||
"""
|
||||
Crée un filtre pour récupérer les administrés liés à un futur gestionnaire PCP à partir de codes de référentiels de niveau 4.
|
||||
|
||||
:param org_codes: codes de référentiels de niveau 4
|
||||
:type org_code: str
|
||||
|
||||
:return: filtre pour récupérer les administrés
|
||||
:rtype: class:`Q`
|
||||
"""
|
||||
Cols = Administres_Pams.Cols
|
||||
cat_filter = Q(**{f'{Cols.REL_ADMINISTRE}__{Administre.Cols.CATEGORIE}': 'MDR'})
|
||||
fe_code_path = f'{Cols.REL_DECISION}__{Decision.Cols.REL_POSTE}__{Poste.Cols.REL_FORMATION_EMPLOI}'
|
||||
sub_qs = FormationEmploi.objects.filter(fe_code=OuterRef(fe_code_path))
|
||||
return (Q(**{f'{fe_code_path}__isnull': False}) & (
|
||||
(~cat_filter & Exists(sub_qs.filter(fe_code_niv_org4__in=org_codes)))
|
||||
| (cat_filter & Exists(sub_qs.filter(fe_code_niv_org4_mdr__in=org_codes)))
|
||||
))
|
||||
|
||||
def get_poste_filter_by_lvl4_codes_fil(org_codes: Union[List[str], Tuple[str]]) -> Q:
|
||||
"""
|
||||
Crée un filtre pour récupérer les postes liés à un gestionnaire fil à partir de codes de référentiels de niveau 4.
|
||||
|
||||
:param org_codes: codes de référentiels de niveau 4
|
||||
:type org_code: str
|
||||
|
||||
:return: filtre pour récupérer les postes
|
||||
:rtype: class:`Q`
|
||||
"""
|
||||
Cols = Poste.Cols
|
||||
Cols_Pam = Postes_Pams.Cols
|
||||
return Q(Exists(
|
||||
RefSvFil.objects
|
||||
.filter(ref_sv_fil_code__in=org_codes)
|
||||
.filter(sous_vivier_id=OuterRef(f'{Cols_Pam.REL_POSTE}__{Cols.M2M_SOUS_VIVIERS}'))
|
||||
))
|
||||
|
||||
def get_poste_filter_by_lvl4_codes_pcp(org_codes: Union[List[str], Tuple[str]]) -> Q:
|
||||
"""
|
||||
Crée un filtre pour récupérer les postes liés à un gestionnaire PCP à partir de codes de référentiels de niveau 4.
|
||||
|
||||
:param org_codes: codes de référentiels de niveau 4
|
||||
:type org_code: str
|
||||
|
||||
:return: filtre pour récupérer les postes
|
||||
:rtype: class:`Q`
|
||||
"""
|
||||
Cols = Poste.Cols
|
||||
Cols_Pam = Postes_Pams.Cols
|
||||
cat_filter = Q(**{f'{Cols_Pam.REL_POSTE}__{Cols.CATEGORIE}': 'MDR'})
|
||||
sub_qs = FormationEmploi.objects.filter(fe_code=OuterRef(f'{Cols_Pam.REL_POSTE}__{Cols.REL_FORMATION_EMPLOI}'))
|
||||
return (
|
||||
(~cat_filter & Exists(sub_qs.filter(fe_code_niv_org4__in=org_codes)))
|
||||
| (cat_filter & Exists(sub_qs.filter(fe_code_niv_org4_mdr__in=org_codes)))
|
||||
)
|
||||
|
||||
def get_profile_summary(user: Optional[CustomUser]) -> ProfileSummary:
|
||||
"""
|
||||
Renvoie le résumé des profils de l'utilisateur.
|
||||
|
||||
:param user: utilisateur
|
||||
:type user: class:`CustomUser`
|
||||
|
||||
:return: résumé des profils
|
||||
:rtype: class:`ProfileSummary`
|
||||
"""
|
||||
if not user or not user.is_authenticated:
|
||||
return ProfileSummary()
|
||||
|
||||
org = (get_queryset_org_by_user(user)
|
||||
.only('pk', 'ref_org_droit_lect', 'ref_org_droit_ecr', 'ref_org_ref_fe', 'ref_org_ref_sv_fil', 'ref_org_expert_hme', 'ref_org_bvt', 'ref_org_itd')
|
||||
.first())
|
||||
|
||||
is_super = user.is_superuser
|
||||
is_read = org and is_truthy(org.ref_org_droit_lect)
|
||||
is_write = org and is_truthy(org.ref_org_droit_ecr)
|
||||
is_fil = org and is_truthy(org.ref_org_ref_sv_fil)
|
||||
is_hme = org and is_truthy(org.ref_org_expert_hme)
|
||||
is_bvt = org and is_truthy(org.ref_org_bvt)
|
||||
is_itd = org and is_truthy(org.ref_org_itd)
|
||||
is_pcp = org and is_truthy(org.ref_org_ref_fe)
|
||||
# logger.debug('user %s => superuser: %s, READ: %s, WRITE: %s, FIL: %s, PCP: %s, BVT: %s, ITD: %s, HME: %s', user.pk, is_super, is_read, is_write, is_fil, is_pcp, is_bvt, is_itd, is_hme)
|
||||
|
||||
profiles = []
|
||||
if is_super:
|
||||
profiles.append(Profiles.SUPER)
|
||||
if is_fil:
|
||||
profiles.append(Profiles.FILIERE)
|
||||
if is_pcp:
|
||||
profiles.append(Profiles.PCP)
|
||||
if is_bvt:
|
||||
profiles.append(Profiles.BVT)
|
||||
if is_itd:
|
||||
profiles.append(Profiles.ITD)
|
||||
if is_hme:
|
||||
profiles.append(Profiles.HME)
|
||||
|
||||
|
||||
return ProfileSummary(org_code=org.pk if org else None, profiles={
|
||||
KEY_READ: tuple(profiles) if is_read else (Profiles.SUPER,) if is_super else (),
|
||||
KEY_WRITE: tuple(profiles) if is_write else (Profiles.SUPER,) if is_super else(),
|
||||
})
|
||||
|
||||
def _group_adm_by_profile(org_code: Optional[str], administres: Tuple[Administres_Pams], profiles: Tuple[Profiles]) -> Dict[Profiles, Tuple[int]]:
|
||||
"""
|
||||
Groupe les administrés liés à certains profils, uniquement parmi les administrés en paramètre.
|
||||
Il ne s'agit pas d'un partitionnement car un même administré peut être lié à plusieurs profils.
|
||||
|
||||
:param org_code: code de référentiel (de niveau 1 à 4)
|
||||
:type org_code: str
|
||||
|
||||
:param administres: administrés à grouper
|
||||
:type administres: Tuple[Administres_Pams]
|
||||
|
||||
:param profiles: profils utilisés
|
||||
:type profiles: Tuple[Profiles]
|
||||
|
||||
:return: dictionnaire {<profil>: <ID d'administrés>}
|
||||
:rtype: Dict[Profiles, Tuple[int]]
|
||||
"""
|
||||
A = Administre.Cols
|
||||
AP = Administres_Pams.Cols
|
||||
D = Decision.Cols
|
||||
P = Profiles
|
||||
|
||||
adm_ids_fil = ()
|
||||
adm_ids_bvt = ()
|
||||
adm_ids_pcp = []
|
||||
adm_ids_current_pcp = []
|
||||
adm_ids_future_pcp = ()
|
||||
|
||||
if org_code:
|
||||
is_fil = P.FILIERE in profiles
|
||||
is_bvt = P.BVT in profiles
|
||||
is_pcp = P.PCP in profiles
|
||||
if is_fil or is_bvt or is_pcp:
|
||||
adm_by_id = {adm.pk: adm for adm in administres}
|
||||
qs = Administres_Pams.objects
|
||||
if is_bvt:
|
||||
adm_ids_bvt = tuple(
|
||||
qs.filter(pk__in=adm_by_id.keys())
|
||||
.filter(Q(**{f'{AP.REL_ADMINISTRE}__{A.REL_SOUS_VIVIER}': 'BVT'})).values_list('pk', flat=True))
|
||||
if is_fil or is_pcp:
|
||||
codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
|
||||
if codes_lvl4:
|
||||
if is_fil:
|
||||
adm_ids_fil = tuple(
|
||||
qs.filter(pk__in=adm_by_id.keys())
|
||||
.filter(get_adm_filter_by_lvl4_codes_fil(codes_lvl4)).values_list('pk', flat=True))
|
||||
if is_pcp:
|
||||
raw_adm_ids_current_pcp = set(
|
||||
qs.filter(pk__in=adm_by_id.keys())
|
||||
.filter(get_adm_filter_by_lvl4_codes_pcp(codes_lvl4)).values_list('pk', flat=True))
|
||||
for adm_id in raw_adm_ids_current_pcp:
|
||||
adm = adm_by_id.get(adm_id)
|
||||
fe_avant = safe_rgetattr(adm, f'{AP.REL_ADMINISTRE}.{A.REL_FORMATION_EMPLOI}_id')
|
||||
fe_apres = safe_rgetattr(adm, f'{AP.REL_DECISION}.{D.REL_POSTE}.{Poste.Cols.REL_FORMATION_EMPLOI}_id')
|
||||
if fe_apres is None or fe_avant == fe_apres:
|
||||
# pas de décision ou même FE : juste PCP
|
||||
adm_ids_pcp.append(adm_id)
|
||||
else:
|
||||
# sinon PCP_ACTUEL
|
||||
adm_ids_current_pcp.append(adm_id)
|
||||
adm_ids_future_pcp = tuple(
|
||||
qs.filter(pk__in=[adm_id for adm_id in adm_by_id.keys() if adm_id not in adm_ids_pcp])
|
||||
.filter(get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)).values_list('pk', flat=True))
|
||||
return {
|
||||
P.FILIERE: adm_ids_fil,
|
||||
P.BVT: adm_ids_bvt,
|
||||
P.PCP: tuple(adm_ids_pcp),
|
||||
P.PCP_ACTUEL: tuple(adm_ids_current_pcp),
|
||||
P.PCP_FUTUR: tuple(adm_ids_future_pcp)
|
||||
}
|
||||
|
||||
def _get_profiles_for_adm(adm_id: int, global_profiles: Tuple[Profiles], adm_ids_by_profile: Dict[Profiles, Tuple[int]]) -> Tuple[Profiles]:
|
||||
"""
|
||||
Renvoie les profils qui concernent l'administré. Ils sont différents des profils globaux car un utilisateur qui a le profil PCP
|
||||
n'est pas forcément PCP pour tous les administrés, il peut être PCP_ACTUEL et/ou PCP_FUTUR en fonction de l'administré.
|
||||
note : Profiles.SUPER est ignoré car il n'a pas de sens dans ce contexte
|
||||
|
||||
:param adm_id: ID de l'administré
|
||||
:type adm_id: int
|
||||
|
||||
:param global_profiles: profils globaux, ceux qu'on trouve dans un class:`ProfileSummary`.
|
||||
:type global_profiles: Tuple[Administre]
|
||||
|
||||
:param adm_ids_by_profile: IDs d'administrés groupés par profil, dictionnaire {<profil>: <ID d'administrés>}
|
||||
:type adm_ids_by_profile: Dict[Profiles, Tuple[int]]
|
||||
|
||||
:return: profils qui concernent l'administré
|
||||
:rtype: Tuple[Profiles]
|
||||
"""
|
||||
P = Profiles
|
||||
profiles = set()
|
||||
if P.HME in global_profiles:
|
||||
profiles.add(P.HME)
|
||||
if P.FILIERE in global_profiles and adm_id in adm_ids_by_profile.get(P.FILIERE):
|
||||
profiles.add(P.FILIERE)
|
||||
if P.BVT in global_profiles and adm_id in adm_ids_by_profile.get(P.BVT):
|
||||
profiles.add(P.BVT)
|
||||
if P.PCP in global_profiles:
|
||||
if adm_id in adm_ids_by_profile.get(P.PCP):
|
||||
profiles.add(P.PCP)
|
||||
if adm_id in adm_ids_by_profile.get(P.PCP_ACTUEL):
|
||||
profiles.add(P.PCP_ACTUEL)
|
||||
if adm_id in adm_ids_by_profile.get(P.PCP_FUTUR):
|
||||
profiles.add(P.PCP_FUTUR)
|
||||
return tuple(profiles)
|
||||
|
||||
def get_profiles_by_adm(user: Optional[CustomUser], administre: Administres_Pams, *args: Administres_Pams) -> Dict[int, Dict[str, Tuple[Profiles]]]:
|
||||
"""
|
||||
Renvoie un dictionnaire dont les clés sont les ID SAP des administrés donnés et les valeurs sont les profils de l'utilisateur.
|
||||
|
||||
:param user: utilisateur
|
||||
:type user: class:`CustomUser`
|
||||
|
||||
:param administre: administré
|
||||
:type administre: class:`Administres_Pams`
|
||||
|
||||
:param args: administrés supplémentaires
|
||||
:type args: class:`Administres_Pams` (multiple)
|
||||
|
||||
:return: dictionnaire de dictionnaires {<ID SAP>: {<KEY_READ>: <profils>, <KEY_WRITE>: <profils>} }
|
||||
:rtype: Dict[int, Dict[str, Tuple[Profiles]]]
|
||||
"""
|
||||
values = (administre, *args)
|
||||
if not values:
|
||||
return {}
|
||||
|
||||
summary = get_profile_summary(user)
|
||||
r_profiles = summary.profiles.get(KEY_READ, ())
|
||||
w_profiles = summary.profiles.get(KEY_WRITE, ())
|
||||
|
||||
if not r_profiles and not w_profiles:
|
||||
return {adm.administre.pk: {KEY_READ: (), KEY_WRITE: ()} for adm in values}
|
||||
|
||||
same_profiles = sorted(set(r_profiles)) == sorted(set(w_profiles))
|
||||
org_code = summary.org_code
|
||||
r_adm_ids_by_profile = _group_adm_by_profile(org_code, values, r_profiles)
|
||||
w_adm_ids_by_profile = _group_adm_by_profile(org_code, values, w_profiles) if not same_profiles else r_adm_ids_by_profile
|
||||
|
||||
result = {}
|
||||
for adm in values:
|
||||
adm_id = adm.pk
|
||||
r_profiles_adm = _get_profiles_for_adm(adm_id, r_profiles, r_adm_ids_by_profile)
|
||||
w_profiles_adm = _get_profiles_for_adm(adm_id, w_profiles, w_adm_ids_by_profile) if not same_profiles else r_profiles_adm
|
||||
result.setdefault(adm_id, {
|
||||
KEY_READ: r_profiles_adm,
|
||||
KEY_WRITE: w_profiles_adm
|
||||
})
|
||||
return result
|
||||
96
backend-django/backend/utils/predicates.py
Normal file
96
backend-django/backend/utils/predicates.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# from typing import Callable
|
||||
from .functions import find_class
|
||||
from typing import Tuple, Callable # Any, Optional, Union,
|
||||
|
||||
|
||||
def func_class_name_is_in(cls_name: str, *args: str) -> Callable[[Callable], bool]:
|
||||
"""
|
||||
Crée un prédicat qui renvoie True si le nom de la classe de la fonction/méthode existe et fait partie des valeurs données.
|
||||
|
||||
:param cls_name: nom de classe
|
||||
:type cls_name: str
|
||||
|
||||
:param args: noms supplémentaires
|
||||
:type args: str (multiple)
|
||||
|
||||
:return: filtre
|
||||
:rtype: Callable[[Callable], bool]
|
||||
"""
|
||||
values = (cls_name, *args)
|
||||
|
||||
def func_class_name_is_in(func: Callable) -> bool:
|
||||
cls = find_class(func)
|
||||
return bool(cls) and cls.__name__ in values
|
||||
return func_class_name_is_in
|
||||
|
||||
|
||||
def func_class_name_is_not_in(cls_name: str, *args: str) -> Callable[[Callable], bool]:
|
||||
"""
|
||||
Crée un prédicat qui renvoie True si le nom de la classe de la fonction/méthode existe et ne fait pas partie des valeurs données.
|
||||
|
||||
:param cls_name: nom de classe
|
||||
:type cls_name: str
|
||||
|
||||
:param args: noms supplémentaires
|
||||
:type args: str (multiple)
|
||||
|
||||
:return: filtre
|
||||
:rtype: Callable[[Callable], bool]
|
||||
"""
|
||||
values = (cls_name, *args)
|
||||
|
||||
def func_class_name_is_not_in(func: Callable) -> bool:
|
||||
cls = find_class(func)
|
||||
return bool(cls) and cls.__name__ not in values
|
||||
return func_class_name_is_not_in
|
||||
|
||||
|
||||
def func_name_is_in(name: str, *args: str) -> Callable[[Callable], bool]:
|
||||
"""
|
||||
Crée un prédicat qui renvoie True si le nom de la fonction/méthode fait partie des valeurs données.
|
||||
|
||||
:param name: nom de fonction/méthode
|
||||
:type name: str
|
||||
|
||||
:param args: noms supplémentaires
|
||||
:type args: str (multiple)
|
||||
|
||||
:return: filtre
|
||||
:rtype: Callable[[Callable], bool]
|
||||
"""
|
||||
values = (name, *args)
|
||||
|
||||
def func_name_is_in(func: Callable) -> bool:
|
||||
return func.__name__ in values
|
||||
return func_name_is_in
|
||||
|
||||
|
||||
def func_name_is_not_in(name: str, *args: str) -> Callable[[Callable], bool]:
|
||||
"""
|
||||
Crée un prédicat qui renvoie True si le nom de la fonction/méthode ne fait pas partie des valeurs données.
|
||||
|
||||
:param name: nom de fonction/méthode
|
||||
:type name: str
|
||||
|
||||
:param args: noms supplémentaires
|
||||
:type args: str (multiple)
|
||||
|
||||
:return: filtre
|
||||
:rtype: Callable[[Callable], bool]
|
||||
"""
|
||||
values = (name, *args)
|
||||
|
||||
def func_name_is_not_in(func: Callable) -> bool:
|
||||
return func.__name__ not in values
|
||||
return func_name_is_not_in
|
||||
|
||||
|
||||
def func_is_public(func: Callable) -> bool:
|
||||
"""
|
||||
Prédicat qui renvoie True si la fonction/méthode est "publique".
|
||||
(les notions de public, protected, private ne sont que des conventions de nommage en Python)
|
||||
|
||||
:return: filtre
|
||||
:rtype: Callable[[Callable], bool]
|
||||
"""
|
||||
return not func.__name__.startswith('_')
|
||||
14
backend-django/backend/utils/view_predicates.py
Normal file
14
backend-django/backend/utils/view_predicates.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from .predicates import func_name_is_in
|
||||
|
||||
|
||||
# fonctions exposées par les vues Django
|
||||
VIEW_FUNCTIONS = ['get', 'post', 'put', 'patch', 'delete']
|
||||
|
||||
# fonctions exposées par les viewsets Django
|
||||
VIEWSET_FUNCTIONS = [*VIEW_FUNCTIONS, 'create', 'list', 'retrieve', 'update', 'partial_update', 'destroy']
|
||||
|
||||
# prédicat qui renvoie True si le nom de la fonction/méthode fait partie de VIEW_FUNCTIONS.
|
||||
view_functions = func_name_is_in(*VIEW_FUNCTIONS)
|
||||
|
||||
# prédicat qui renvoie True si le nom de la fonction/méthode fait partie de VIEWSET_FUNCTIONS.
|
||||
viewset_functions = func_name_is_in(*VIEWSET_FUNCTIONS)
|
||||
192
backend-django/backend/utils_calcul.py
Normal file
192
backend-django/backend/utils_calcul.py
Normal file
@@ -0,0 +1,192 @@
|
||||
"""
|
||||
Ce module contient la fonction permettant de démarrer le scoring sur le sous-vivier désiré
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import Calcul
|
||||
from .models import StatutCalculChoices as StatutCalcul
|
||||
from .utils.logging import get_logger
|
||||
from .utils_extraction import to_table_liste_preference, to_table_liste_preference_selectif
|
||||
from .utils_insertion import (insert_liste_preference, insert_matching,
|
||||
insert_Notation)
|
||||
from .utils_matching import (matching_parfait, preprocess_matching,
|
||||
preprocess_matchingSelectif)
|
||||
from .utils_scoring import notations, notations_liste
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Fonction de lancement du scoring
|
||||
def lancer_calculs(pam_id,sv_id):
|
||||
"""
|
||||
Fonction de lancement du calcul du scoring
|
||||
|
||||
:param sv_id: L'id de sous vivier étudié
|
||||
:type sv_id: char
|
||||
"""
|
||||
Statut = StatutCalcul
|
||||
statut = Statut.TERMINE
|
||||
pourcentage_incr = 16.67
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
start_time = time.time()
|
||||
logger.info('---------------------Scoring beginning------------------------')
|
||||
notations_list = notations(pam_id,sv_id)
|
||||
logger.info('---------------------Scoring ending---------------------------')
|
||||
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage += pourcentage_incr
|
||||
calcul.ca_statut_pourcentage = round(calcul.ca_statut_pourcentage, 2)
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
logger.info("------------------Scoring time -- %d seconds -----------------", time.time() - start_time)
|
||||
start_time_insert = time.time()
|
||||
logger.info('---------------------insert scoring results begining-----------')
|
||||
insert_Notation(notations_list,pam_id,sv_id)
|
||||
logger.info('---------------------insert scoring results ending-----------')
|
||||
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage += pourcentage_incr
|
||||
calcul.ca_statut_pourcentage = round(calcul.ca_statut_pourcentage, 2)
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
logger.info("------------------Insert time -- %d seconds -----------------", time.time() - start_time_insert)
|
||||
|
||||
start_time2 = time.time()
|
||||
logger.info('---------------------Preference list creation beginning-----------------')
|
||||
df_preference = to_table_liste_preference(pam_id,sv_id)
|
||||
|
||||
logger.info('---------------------Preference list creation ending-------------------')
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage += pourcentage_incr
|
||||
calcul.ca_statut_pourcentage = round(calcul.ca_statut_pourcentage, 2)
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
try:
|
||||
|
||||
|
||||
start_time3 = time.time()
|
||||
logger.info('---------------------Matching beginning------------------------')
|
||||
proposant, disposant, capacite = preprocess_matching(pam_id, sv_id, df_preference)
|
||||
results = matching_parfait(proposant, disposant, capacite)
|
||||
logger.info('---------------------Matching ending---------------------------')
|
||||
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage += pourcentage_incr
|
||||
calcul.ca_statut_pourcentage = round(calcul.ca_statut_pourcentage, 2)
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
logger.info("------------------Matching time -- %d seconds -----------------", time.time() - start_time3)
|
||||
start_time_insert = time.time()
|
||||
logger.info('---------------------insert matching results begining-----------')
|
||||
insert_matching(results, pam_id)
|
||||
logger.info('---------------------insert matching results ending-----------')
|
||||
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage = 100
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
logger.info("------------------Insert time -- %d seconds -----------------", time.time() - start_time_insert)
|
||||
except Exception as e:
|
||||
logger.warning('Arret du scoring, veuillez renseigner au moins un département',e)
|
||||
|
||||
|
||||
|
||||
# Fonction de lancement du scoring sélectif
|
||||
def lancer_calculSelectif(sv_id, pam_id, l_a_id, l_p_id):
|
||||
"""
|
||||
Fonction de lancement du calcul du scoring sélectif
|
||||
|
||||
:param sv_id: L'id de sous vivier étudié
|
||||
:param l_a_id: Liste d'ids d'administrés
|
||||
:param l_p_id: Liste d'ids de postes
|
||||
:type sv_id: char
|
||||
"""
|
||||
Statut = StatutCalcul
|
||||
pourcentage_incr = 16.67
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id)+'selectif',pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
start_time = time.time()
|
||||
logger.info('---------------------Scoring beginning------------------------')
|
||||
notations_list = notations_liste(sv_id, pam_id, l_a_id, l_p_id)
|
||||
logger.info('---------------------Scoring ending---------------------------')
|
||||
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id)+'selectif',pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage += pourcentage_incr
|
||||
calcul.ca_statut_pourcentage = round(calcul.ca_statut_pourcentage, 2)
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
logger.info("------------------Scoring time -- %d seconds -----------------", time.time() - start_time)
|
||||
start_time_insert = time.time()
|
||||
logger.info('---------------------insert scoring results begining-----------')
|
||||
insert_Notation(notations_list, pam_id, sv_id)
|
||||
logger.info('---------------------insert scoring results ending-----------')
|
||||
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id)+'selectif',pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage += pourcentage_incr
|
||||
calcul.ca_statut_pourcentage = round(calcul.ca_statut_pourcentage, 2)
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
logger.info("------------------Insert time -- %d seconds -----------------", time.time() - start_time_insert)
|
||||
|
||||
start_time2 = time.time()
|
||||
logger.info('---------------------Preference list creation beginning-----------------')
|
||||
df_preference = to_table_liste_preference_selectif(sv_id, pam_id,l_a_id,l_p_id)
|
||||
|
||||
logger.info('---------------------Preference list creation ending-------------------')
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id)+'selectif',pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage += pourcentage_incr
|
||||
calcul.ca_statut_pourcentage = round(calcul.ca_statut_pourcentage, 2)
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
try:
|
||||
|
||||
|
||||
start_time3 = time.time()
|
||||
logger.info('---------------------Matching beginning------------------------')
|
||||
proposant, disposant, capacite = preprocess_matchingSelectif(sv_id, pam_id,l_a_id,l_p_id, df_preference)
|
||||
results = matching_parfait(proposant, disposant, capacite)
|
||||
logger.info('---------------------Matching ending---------------------------')
|
||||
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id)+'selectif',pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage += pourcentage_incr
|
||||
calcul.ca_statut_pourcentage = round(calcul.ca_statut_pourcentage, 2)
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
logger.info("------------------Matching time -- %d seconds -----------------", time.time() - start_time3)
|
||||
start_time_insert = time.time()
|
||||
logger.info('---------------------insert matching results begining-----------')
|
||||
insert_matching(results, pam_id)
|
||||
logger.info('---------------------insert matching results ending-----------')
|
||||
|
||||
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id)+'selectif',pam_id=pam_id,sous_vivier_id=sv_id)
|
||||
calcul.ca_statut_pourcentage = 100
|
||||
calcul.save()
|
||||
if calcul.ca_statut == Statut.EN_ATTENTE_ARRET:
|
||||
raise Exception('Arret du calcul')
|
||||
|
||||
logger.info("------------------Insert time -- %d seconds -----------------", time.time() - start_time_insert)
|
||||
except Exception as e:
|
||||
logger.warning('Arret du scoring, veuillez renseigner au moins un département',e)
|
||||
|
||||
return
|
||||
|
||||
|
||||
1367
backend-django/backend/utils_extraction.py
Normal file
1367
backend-django/backend/utils_extraction.py
Normal file
File diff suppressed because it is too large
Load Diff
2502
backend-django/backend/utils_insertion.py
Normal file
2502
backend-django/backend/utils_insertion.py
Normal file
File diff suppressed because it is too large
Load Diff
190
backend-django/backend/utils_matching.py
Normal file
190
backend-django/backend/utils_matching.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
Ce module contient les Utilitaires de matching
|
||||
"""
|
||||
# import des pré requis
|
||||
import time
|
||||
|
||||
import pandas as pd
|
||||
from matching.games import HospitalResident
|
||||
|
||||
from .models import Notation, Poste, PreferencesListe, StatutPamChoices, AvisPosteChoices as AvisPoste
|
||||
from .utils.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Fonction de preprocessing
|
||||
def preprocess_matching(pam_id,sv_id,df_pref):
|
||||
"""
|
||||
Retourne les liste de souhait des militaires et des postes
|
||||
|
||||
:type sv_id: char
|
||||
:param sv_id: id du sous vivier étudié
|
||||
|
||||
|
||||
:return: - **gs_proposants** (*liste*): liste des proposants
|
||||
- **gs_disposants** (*liste*): liste des disposants
|
||||
- **gs_capacite** (*liste*): liste des capacités pour chaque disposants
|
||||
|
||||
"""
|
||||
logger.debug('preprocessing matching %s', sv_id)
|
||||
|
||||
statut_adm = [StatutPamChoices.A_MUTER,StatutPamChoices.A_ETUDIER]
|
||||
statut_poste =[AvisPoste.P1,AvisPoste.P2,AvisPoste.P3,AvisPoste.P4]
|
||||
# liste_preference = PreferencesListe.objects.filter(pam_id=pam_id,administre_pam__administre__sous_vivier_id=sv_id).values()
|
||||
notation = Notation.objects.filter(pam_id=pam_id,
|
||||
administre_pam__administre__sous_vivier_id=sv_id,
|
||||
administre_pam__a_statut_pam_annee__in=statut_adm,
|
||||
administre_pam__decision__de_decision__isnull=True,
|
||||
poste_pam__poste__sous_viviers=sv_id,
|
||||
poste_pam__decisions__de_decision__isnull=True,
|
||||
poste_pam__p_avis_pam__in=statut_poste).values()
|
||||
|
||||
liste_preference_df = df_pref
|
||||
notation_df = pd.DataFrame.from_records(notation)
|
||||
liste_admin_lp = liste_preference_df.drop_duplicates(subset=['administre_id'])
|
||||
gs_proposants = {}
|
||||
gs_disposants = {}
|
||||
gs_capacite = {}
|
||||
l2 = []
|
||||
if not liste_preference_df.empty:
|
||||
for admin in liste_admin_lp['administre_id']:
|
||||
l = []
|
||||
new_lp_df = liste_preference_df.loc[liste_preference_df['administre_id'] == admin].drop_duplicates(subset=['poste_id'])
|
||||
new_lp_df = new_lp_df.drop_duplicates(subset=['lp_rang_poste'])
|
||||
new_lp_df = new_lp_df.sort_values(
|
||||
by=['lp_rang_poste']).reset_index()
|
||||
|
||||
for i in range(len(new_lp_df)):
|
||||
l.append(new_lp_df.at[i, 'poste_id'])
|
||||
l2.append(new_lp_df.at[i, 'poste_id'])
|
||||
gs_proposants[admin] = l
|
||||
l2 = list(set(l2))
|
||||
|
||||
notation_df = notation_df[notation_df.administre_id.isin(liste_admin_lp['administre_id'])]
|
||||
|
||||
for poste in l2:
|
||||
l1 = []
|
||||
new_no_df = notation_df.loc[notation_df['poste_id'] == poste].sort_values(by=['no_score_administre'],
|
||||
ascending=False).reset_index()
|
||||
for i in range(len(new_no_df)):
|
||||
if poste in gs_proposants[new_no_df.at[i, 'administre_id']]:
|
||||
l1.append(new_no_df.at[i, 'administre_id'])
|
||||
gs_disposants[poste] = l1
|
||||
gs_capacite[poste] = 1
|
||||
logger.debug("Administre proposants : %s", gs_proposants)
|
||||
logger.debug("Poste disposants : %s", gs_disposants)
|
||||
logger.debug('end preprocessing matching func')
|
||||
return gs_proposants, gs_disposants, gs_capacite
|
||||
|
||||
# Fonction de preprocessing
|
||||
def preprocess_matchingSelectif(sv_id, pam_id,l_a_id,l_p_id, df_pref):
|
||||
"""
|
||||
Retourne les liste de souhait des militaires et des postes
|
||||
|
||||
:type sv_id: char
|
||||
:param sv_id: id du sous vivier étudié
|
||||
|
||||
|
||||
:return: - **gs_proposants** (*liste*): liste des proposants
|
||||
- **gs_disposants** (*liste*): liste des disposants
|
||||
- **gs_capacite** (*liste*): liste des capacités pour chaque disposants
|
||||
|
||||
"""
|
||||
logger.debug('preprocessing matching %s', sv_id)
|
||||
|
||||
statut_adm = [StatutPamChoices.A_MUTER,StatutPamChoices.A_ETUDIER]
|
||||
statut_poste =[AvisPoste.P1,AvisPoste.P2,AvisPoste.P3,AvisPoste.P4]
|
||||
notation = Notation.objects.filter(pam_id=pam_id,
|
||||
administre_pam__administre__sous_vivier_id=sv_id,
|
||||
administre_pam__a_statut_pam_annee__in=statut_adm,
|
||||
administre_pam__administre_id__in=l_a_id,
|
||||
administre_pam__decision__de_decision__isnull=True,
|
||||
poste_pam__poste__sous_viviers=sv_id,
|
||||
poste_pam__decisions__de_decision__isnull=True,
|
||||
poste_pam__p_avis_pam__in=statut_poste).values()
|
||||
|
||||
liste_preference_df = df_pref
|
||||
notation_df = pd.DataFrame.from_records(notation)
|
||||
liste_admin_lp = liste_preference_df.drop_duplicates(subset=['administre_id'])
|
||||
gs_proposants = {}
|
||||
gs_disposants = {}
|
||||
gs_capacite = {}
|
||||
l2 = []
|
||||
if not liste_preference_df.empty:
|
||||
for admin in liste_admin_lp['administre_id']:
|
||||
l = []
|
||||
new_lp_df = liste_preference_df.loc[liste_preference_df['administre_id'] == admin].drop_duplicates(subset=['poste_id'])
|
||||
new_lp_df = new_lp_df.drop_duplicates(subset=['lp_rang_poste'])
|
||||
new_lp_df = new_lp_df.sort_values(
|
||||
by=['lp_rang_poste']).reset_index()
|
||||
|
||||
for i in range(len(new_lp_df)):
|
||||
l.append(new_lp_df.at[i, 'poste_id'])
|
||||
l2.append(new_lp_df.at[i, 'poste_id'])
|
||||
gs_proposants[admin] = l
|
||||
l2 = list(set(l2))
|
||||
|
||||
notation_df = notation_df[notation_df.administre_id.isin(liste_admin_lp['administre_id'])]
|
||||
|
||||
for poste in l2:
|
||||
l1 = []
|
||||
new_no_df = notation_df.loc[notation_df['poste_id'] == poste].sort_values(by=['no_score_administre'],
|
||||
ascending=False).reset_index()
|
||||
for i in range(len(new_no_df)):
|
||||
if poste in gs_proposants[new_no_df.at[i, 'administre_id']]:
|
||||
l1.append(new_no_df.at[i, 'administre_id'])
|
||||
gs_disposants[poste] = l1
|
||||
gs_capacite[poste] = 1
|
||||
logger.debug("Administre proposants : %s", gs_proposants)
|
||||
logger.debug("Poste disposants : %s", gs_disposants)
|
||||
logger.debug('end preprocessing matching func')
|
||||
|
||||
return gs_proposants, gs_disposants, gs_capacite
|
||||
|
||||
|
||||
# Fonction de matching
|
||||
def matching_parfait(gs_proposants, gs_disposants, gs_capacite):
|
||||
"""
|
||||
Retourne le dictionnaire python presentant le resultats du matching
|
||||
|
||||
:type gs_proposants: liste
|
||||
:param gs_proposants: liste des proposants
|
||||
:type gs_disposants: liste
|
||||
:param gs_disposants: liste des disposants
|
||||
:type gs_capacite: liste
|
||||
:param gs_capacite: liste des capacités pour chaque disposants
|
||||
|
||||
|
||||
:return: - **matching** (*dictionnaire*): dictionnaire python presentant le resultats du matching
|
||||
"""
|
||||
|
||||
logger.debug('start matching parfait')
|
||||
# Traitement des entrées
|
||||
game = HospitalResident.create_from_dictionaries(
|
||||
gs_proposants, gs_disposants, gs_capacite
|
||||
)
|
||||
# Creation du jeu
|
||||
matching = game.solve(optimal='hospital')
|
||||
# Test de validation et de stabilité
|
||||
logger.debug('Validity Check : %s', game.check_validity())
|
||||
assert game.check_validity()
|
||||
logger.debug('Stability Check : %s', game.check_stability())
|
||||
assert game.check_stability()
|
||||
|
||||
# Creation des listes d'affectés et de non afféctes
|
||||
matched_residents = []
|
||||
for _, residents in matching.items():
|
||||
for resident in residents:
|
||||
matched_residents.append(resident.name)
|
||||
unmatched_residents = set(gs_proposants.keys()) - set(matched_residents)
|
||||
|
||||
# KPI matching
|
||||
logger.debug("Nb demandeurs : %s", len(gs_proposants))
|
||||
logger.debug("Poste demandeurs : %s", gs_proposants)
|
||||
logger.debug("Nb affectés : %s", len(matched_residents))
|
||||
logger.debug("Administres affectés : %s", matched_residents)
|
||||
logger.debug("Nb non affecte : %s", len(unmatched_residents))
|
||||
logger.debug("Administres non affecte : %s", unmatched_residents)
|
||||
logger.debug('end matching parfait')
|
||||
|
||||
return (matching)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user