This commit is contained in:
2022-11-08 21:19:51 +01:00
commit 4c456eafc3
160 changed files with 21472 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
from .alimentation import *
from .alimentation_commentaires import *
from .alimentation_ref_droits import *
from .alimentation_zones_geo import *
from .chargement_competences import *
from .current_user import *
from .decision import *
from .exportation_fichiers import *
from .fiche_detaillee import *
from .formation_emploi import *
from .initial import *
from .notation import *
from .references import *
from .scoring import *
from .suppression_administres import *
from .chargement_pam import *

View File

@@ -0,0 +1,405 @@
from datetime import datetime
import time
import datetime
from django.http import Http404
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
import datetime
from ..models import Administre, PAM
from ..models import StatutPamChoices as StatutPam
from ..serializers import AlimentationSerializer
from ..utils.alimentation_decorators import (data_perf_logger_factory,
get_data_logger)
from ..utils.decorators import execution_time, query_count
from ..utils.extraction.administre import (to_table_administres_bo)
from ..utils.insertion.administre import (insert_administre_bo,
update_administre_fmob)
from ..utils_extraction import (DataFrameTypes, FileTypes, read_files_by_type,
to_table_administre_notation,
to_table_affectation, to_table_diplome,
to_table_domaines, to_table_fe,
to_table_filieres, to_table_fmob_femp,
to_table_fmob_fmob,
to_table_fonctions, to_table_fud,
to_table_garnisons, to_table_groupesMarques,
to_table_marques, to_table_pam, to_table_postes,
to_table_ref_gest, to_table_ref_org,
to_table_ref_sv_fil, to_table_reo_ocv,
to_table_sous_vivier,
to_table_zone_geographique)
from ..utils_insertion import (insert_PAM, insert_administre_notation, insert_Affectation,
insert_Diplome, insert_Filiere,
insert_FMOB_femp, insert_FMOB_fmob,
insert_Fonction,
insert_FormationEmploi, insert_Fud,
insert_Garnison, insert_Grade, insert_Marque,
insert_MarquesGroupe, insert_Poste,
insert_RefFeMere, insert_RefGest, insert_RefOrg,
insert_RefSvFil, insert_SousVivier,
insert_SousVivier_instances,
insert_ZoneGeographique, update_domaine,
update_m2m_links_gestionnaire, update_poste_ocv, insert_delta)
import pandas as pd
class AlimentationView(APIView):
"""
Cette page est l'alimentation principale d'Ogure.
Elle permet à l'administrateur de charger un ensemble de fichiers pour alimenter ou mettre à jour la base de données.
"""
permission_classes = [IsAuthenticated, IsAdminUser]
serializer_class = AlimentationSerializer
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_data_logger(self)
def get(self, request):
"""La fonction get renvoie une reponse contenant Formulaire d'alimentation d'OGURE NG
:type request: rest_framework.request.Request
:param request: Request
:return: - **return** (*json*): json contenant "Formulaire d'alimentation d'OGURE NG".
"""
return Response("Formulaire d'alimentation d'OGURE NG")
@execution_time(logger_factory=data_perf_logger_factory)
@query_count(logger_factory=data_perf_logger_factory)
def post(self, request):
"""La fonction post charge les fichiers.
:type request: rest_framework.request.Request
:param request: Request contenant les fichiers
:return: - **Response** (*Response*): Reponse contient les erreurs de chargement des données .
"""
try:
# TODO: préparer une variable qui renvoie les erreurs sous 3 axes : données référentielles, administrés, postes
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
self.logger.info('---------------------Upload begining---------------------------')
start_time = time.time()
file_donnees_bo = serializer.validated_data.get('Donnees_BO_ADT')
file_reo = serializer.validated_data.get('REO')
file_reo_suivant = serializer.validated_data.get('REO_PAM_SUIVANT')
file_reo_ocv = serializer.validated_data.get('REO_OCV')
file_ref_gest = serializer.validated_data.get('referentiel_gestionnaire')
file_ref_org = serializer.validated_data.get('referentiel_organique')
file_ref_sv_fil = serializer.validated_data.get('refeferentiel_sous_vivier_filiere')
file_ref_fe = serializer.validated_data.get('referentiel_fe')
file_fmob = serializer.validated_data.get('FMOB')
file_fmob_suivant = serializer.validated_data.get('FMOB_PAM_SUIVANT')
file_filiere_domaine = serializer.validated_data.get('domaine_filiere')
file_insee = serializer.validated_data.get('insee_maping')
file_diplomes = serializer.validated_data.get('diplomes')
file_fud = serializer.validated_data.get('FUD')
file_ref_zones_geo = serializer.validated_data.get('ref_zones_geo')
self.logger.info('---------------------Upload ending-----------------------------')
self.logger.debug("--------------upload time -- %d seconds ------------------------", time.time() - start_time)
df_by_type = read_files_by_type({
FileTypes.BO: file_donnees_bo,
FileTypes.DIPLOME: file_diplomes,
FileTypes.DOM_FIL: file_filiere_domaine,
FileTypes.FMOB_FEMP: file_fmob,
FileTypes.FMOB_FEMP_PAM_SUIVANT: file_fmob_suivant,
FileTypes.FUD: file_fud,
FileTypes.INSEE: file_insee,
FileTypes.REF_FE: file_ref_fe,
FileTypes.REF_GEO: file_ref_zones_geo,
FileTypes.REF_GEST: file_ref_gest,
FileTypes.REF_ORG: file_ref_org,
FileTypes.REF_SV_FIL: file_ref_sv_fil,
FileTypes.REO: file_reo,
FileTypes.REO_PAM_SUIVANT: file_reo_suivant,
FileTypes.REO_OCV: file_reo_ocv,
})
DF = DataFrameTypes
diplomes_df = df_by_type.get(DF.DIPLOME)
donnees_bo_df = df_by_type.get(DF.BO)
femp_df = df_by_type.get(DF.FEMP)
filiere_domaine_df = df_by_type.get(DF.DOM_FIL)
fmob_df = df_by_type.get(DF.FMOB)
fud_df = df_by_type.get(DF.FUD)
insee_df = df_by_type.get(DF.INSEE)
ref_fe_df = df_by_type.get(DF.REF_FE)
ref_gest_df = df_by_type.get(DF.REF_GEST)
ref_org_df = df_by_type.get(DF.REF_ORG)
ref_sv_fil_df = df_by_type.get(DF.REF_SV_FIL)
ref_zones_geo_df = df_by_type.get(DF.REF_GEO)
reo_df = df_by_type.get(DF.REO)
reo_ocv_df = df_by_type.get(DF.REO_OCV)
#dataframe pam + 1
femp_suivant_df = df_by_type.get(DF.FEMP_PAM_SUIVANT)
fmob_suivant_df = df_by_type.get(DF.FMOB_PAM_SUIVANT)
reo_suivant_df = df_by_type.get(DF.REO_PAM_SUIVANT)
self.logger.info('-------------------- Insert beginning ---------------------')
start_time_insert = time.time()
text_response = []
if ref_sv_fil_df is not None:
sv_cree, sv_modifie, sv_erreur, sv_supprime = insert_SousVivier(to_table_sous_vivier(ref_sv_fil_df))
text_response.append([f"Référentiel de sous-viviers/filières : {sv_cree} SousVivier créés, {sv_modifie} SousVivier mis à jour, {sv_erreur} SousVivier en erreur et {sv_supprime} SousVivier supprimés."])
self.logger.info('Sous-viviers ---------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : sous-viviers (nécessite %s)', DF.REF_SV_FIL.value[1])
if ref_org_df is not None:
ref_org_cree, ref_org_modifie, ref_org_erreur, ref_org_supprime = insert_RefOrg(to_table_ref_org(ref_org_df))
text_response.append([f"Référentiel organique : {ref_org_cree} RefOrg créés, {ref_org_modifie} RefOrg mis à jour, {ref_org_erreur} RefOrg en erreur et {ref_org_supprime} RefOrg supprimés."])
self.logger.info('Référentiel organique ------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : référentiel organique')
if ref_gest_df is not None:
ref_gest_cree, ref_gest_modifie, ref_gest_erreur, ref_gest_supprime, user_cree, user_modifie, user_supprime, user_ignore = insert_RefGest(to_table_ref_gest(ref_gest_df))
text_response.append([f"Référentiel de gestionnaires : {ref_gest_cree} RefGest créés, {ref_gest_modifie} RefGest mis à jour, {ref_gest_erreur} RefGest en erreur et {ref_gest_supprime} RefGest supprimés."])
text_response.append([f"Référentiel de gestionnaires : {user_cree} User créés, {user_modifie} User mis à jour, {user_supprime} User supprimés et {user_ignore} User ignorés."])
self.logger.info('Référentiel gestionnaire ---------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : référentiel de gestionnaires')
if ref_sv_fil_df is not None:
ref_sv_cree, ref_sv_delete, ref_sv_erreur, ref_sv_ignore = insert_RefSvFil(to_table_ref_sv_fil(ref_sv_fil_df))
text_response.append([f"Référentiel de sous-viviers/filières : {ref_sv_cree} RefSvFil créés, {ref_sv_delete} RefSvFil supprimés, {ref_sv_erreur} RefSvFil en erreur et {ref_sv_ignore} RefSvFil ignorés."])
self.logger.info('Référentiel sous-vivier filière --------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : référentiel de sous-viviers/filières')
if ref_gest_df is not None or ref_org_df is not None or ref_sv_fil_df is not None:
update_m2m_links_gestionnaire('SV')
self.logger.info('Liens gestionnaires/sous-viviers -------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : liens gestionnaires/sous-viviers (nécessite %s ou %s ou %s)',
DF.REF_GEST.value[1], DF.REF_ORG.value[1], DF.REF_SV_FIL.value[1])
if filiere_domaine_df is not None:
update_domaine(to_table_domaines(filiere_domaine_df))
self.logger.info('Domaine --------------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : domaines (nécessite %s)', DF.DOM_FIL.value[1])
if insee_df is not None and donnees_bo_df is not None:
gar_cree, gar_maj , error = insert_Garnison(to_table_garnisons(insee_df, donnees_bo_df))
text_response.append([f"INSEE et Données BO : {gar_cree} garnisons crées, {gar_maj} garnisons mises à jour, {error} garnisons en erreur."])
self.logger.info('Garnison -------------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : garnisons (nécessite %s + %s)', DF.INSEE.value[1], DF.BO.value[1])
if reo_df is not None:
reo_cree = insert_Fonction(to_table_fonctions(reo_df))
text_response.append([f"REO : {reo_cree} fonctions de postes créés."])
self.logger.info('Fonction -------------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : fonctions (nécessite %s)', DF.REO.value[1])
if reo_suivant_df is not None:
reo_suivant_cree = insert_Fonction(to_table_fonctions(reo_suivant_df))
text_response.append([f"REO : {reo_suivant_cree} fonctions de postes A+1 créés."])
self.logger.info('Fonction A+1 -------------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : fonctions A + 1 (nécessite %s)', DF.REO_PAM_SUIVANT.value[1])
# TODO if ....
insert_Grade()
self.logger.info('Grade ----------------------------------------------> Succès')
if filiere_domaine_df is not None:
dom_fil_cree, dom_fil_modifie, dom_fil_erreur, dom_fil_supprime = insert_Filiere(to_table_filieres(filiere_domaine_df))
text_response.append([f"Domaines - filières : {dom_fil_cree} couples domaine/filière créés, {dom_fil_modifie} couples domaine/filière mis à jour, {dom_fil_erreur} couples domaine/filière en erreur et {dom_fil_supprime} couples domaine/filière supprimés."])
self.logger.info('Filières ------------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : filières (nécessite %s)', DF.DOM_FIL.value[1])
# TODO if ....
insert_MarquesGroupe(to_table_groupesMarques())
self.logger.info('MarquesGroupe -------------------------------------> Succès')
# TODO if ....
insert_Marque(to_table_marques())
self.logger.info('Marque --------------------------------------------> Succès')
if ref_fe_df is not None:
df = to_table_fe(ref_fe_df)
self.logger.debug('Extraction des données du référentiel FE ----------> Succès')
fe_cree, fe_maj, error_count = insert_FormationEmploi(df)
text_response.append([f"Référentiel FE : {fe_cree} formation d'emplois créées, {fe_maj} formation d'emplois mises à jour, {error_count} formation d'emplois en erreur."])
self.logger.info('FormationEmplois ----------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : formations-emplois (nécessite %s)', DF.REF_FE.value[1])
if ref_fe_df is not None:
insert_RefFeMere(ref_fe_df)
self.logger.info('Référentiel FE mère -------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : formations-emplois mères (nécessite %s)', DF.REF_FE.value[1])
if ref_gest_df is not None or ref_org_df is not None or ref_fe_df is not None:
update_m2m_links_gestionnaire('FE')
self.logger.info('Liens gestionnaires/formations-emplois ------------> Succès')
else:
self.logger.info('Mise à jour ignorée : liens gestionnaires/formations-emplois (nécessite %s ou %s ou %s)',
DF.REF_GEST.value[1], DF.REF_ORG.value[1], DF.REF_FE.value[1])
if donnees_bo_df is not None:
bo_adm_cree, bo_adm_modifie, bo_adm_deja_modifie, bo_adm_erreur, bo_adm_ignore, bo_dom_ignore, bo_fil_ignore = insert_administre_bo(to_table_administres_bo(donnees_bo_df))
text_response.append([f"Données BO : {bo_adm_cree} administrés créés, {bo_adm_modifie} administrés mis à jour, {bo_adm_deja_modifie} adminsitrés déjà à jour, {bo_adm_erreur} administrés en erreur, {bo_adm_ignore} administrés ignorés, {bo_dom_ignore} domaines ignorés et {bo_fil_ignore} filières ignorées."])
self.logger.info('Administre ----------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : administrés (nécessite %s)', DF.BO.value[1])
if diplomes_df is not None:
diplome_cree, diplome_maj, diplome_sup = insert_Diplome(to_table_diplome(diplomes_df))
text_response.append([f"Diplômes : {diplome_cree} diplômes créés, {diplome_maj} diplômes mis à jour, {diplome_sup} supprimés."])
self.logger.info('Diplome -------------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : diplômes (nécessite %s)', DF.DIPLOME.value[1])
if donnees_bo_df is not None:
donnee_bo_cree, donnee_bo_sup = insert_Affectation(to_table_affectation(donnees_bo_df))
text_response.append([f"Données BO : {donnee_bo_cree} affectation créés, {donnee_bo_sup} affectation mis à jour."])
self.logger.info('Affectations --------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : affectations (nécessite %s)', DF.BO.value[1])
if fud_df is not None:
fud_cree, fud_sup = insert_Fud(to_table_fud(fud_df))
text_response.append([f"FUD : {fud_cree} FUD créés, {fud_sup} FUD supprimées."])
self.logger.info('FUD -----------------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : FUD (nécessite %s)', DF.FUD.value[1])
if donnees_bo_df is not None:
bo_cree, bo_maj, error_count = insert_administre_notation(to_table_administre_notation(donnees_bo_df))
text_response.append([f"Données BO : {bo_cree} notations créées, {bo_maj} notations mises à jour, {error_count} notations en erreurs."])
self.logger.info('Administre Notation -------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : administrés/notations (nécessite %s)', DF.BO.value[1])
if reo_df is not None:
annee_pam = PAM.objects.get(pam_statut='PAM en cours').pam_id
reo_cree, reo_suivant_cree, reo_modifie, reo_erreur, reo_ignore = insert_Poste(to_table_postes(reo_df),annee_pam)
text_response.append([f"REO : {reo_cree} postes créés, {reo_modifie} postes mis à jour, {reo_erreur} postes en erreur et {reo_ignore} postes ignorés."])
self.logger.info('Poste ---------------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : postes (nécessite %s)', DF.REO.value[1])
if reo_suivant_df is not None:
annee_pam = PAM.objects.get(pam_statut='PAM A+1').pam_id
reo_cree, reo_suivant_cree, reo_modifie, reo_erreur, reo_ignore = insert_Poste(to_table_postes(reo_suivant_df),annee_pam)
#info reo
text_response.append([f"REO A + 1 : {reo_suivant_cree} postes A+1 créés, {reo_erreur} postes A+1 en erreur et {reo_ignore} postes A+1 ignorés."])
self.logger.info('Poste A+1 ---------------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : postes A + 1 (nécessite %s)', DF.REO_PAM_SUIVANT.value[1])
if reo_suivant_df is not None:
self.logger.info('Calcul du delta de la colonne Info REO, veuillez patienter ...')
insert_delta(to_table_postes(reo_suivant_df))
text_response.append([f"Mise à jour de la colonne info REO"])
self.logger.info('Calcul du delta Info Reo ---------------------------------------------> Succès')
else:
self.logger.info('Mise à jour de la colonne "info reo" ignorée')
# TODO if ....
insert_SousVivier_instances()
self.logger.info('Sous-viviers instances ---------------------------> Succès')
if reo_ocv_df is not None:
reo_ocv_modifie, reo_ocv_erreur, reo_ocv_ignore = update_poste_ocv(to_table_reo_ocv(reo_ocv_df))
text_response.append([f"Requêtes OCV : {reo_ocv_modifie} postes-ocv mis à jour, {reo_ocv_erreur} postes-ocv en erreur et {reo_ocv_ignore} postes-ocv ignorés."])
self.logger.info('Poste-ocv -----------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : postes/administrés (nécessite %s)', DF.REO_OCV.value[1])
if ref_zones_geo_df is not None:
ref_zones_geo_df_cree, ref_zones_geo_df_sup, error_count = insert_ZoneGeographique(to_table_zone_geographique(ref_zones_geo_df))
text_response.append([f"Référentiels Zones Géographiques : {ref_zones_geo_df_cree} zones-geo créées, {ref_zones_geo_df_sup} zones-geo supprimés et {error_count} zones-geo en erreur."])
self.logger.info('Référentiel zones géographiques -------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : zones géographiques (nécessite %s)', DF.REF_GEO.value[1])
if fmob_df is not None:
fmob_cree, fmob_maj, ignore_count = insert_FMOB_fmob(to_table_fmob_fmob(fmob_df))
text_response.append([f"Formulaire de mobilité : {fmob_cree} fmob créés, {fmob_maj} fmob mis à jour et {ignore_count} fmob ignorés."])
self.logger.debug('Fichier FMOB --------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : FMOB (nécessite %s)', DF.FMOB.value[1])
if femp_df is not None:
femp_cree, femp_maj, ignore_count = insert_FMOB_femp(to_table_fmob_femp(femp_df))
text_response.append([f"Formulaire de mobilité : {femp_cree} femp créés, {femp_maj} femp mis à jour et {ignore_count} femp ignorés."])
self.logger.debug('Fichier FEMP --------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : FEMP (nécessite %s)', DF.FEMP.value[1])
if fmob_suivant_df is not None:
fmob_cree, fmob_maj, ignore_count = insert_FMOB_fmob(to_table_fmob_fmob(fmob_suivant_df))
text_response.append([f"Formulaire de mobilité A+1 : {fmob_cree} fmob A+1 créés, {fmob_maj} fmob A+1 mis à jour et {ignore_count} fmob A+1 ignorés."])
self.logger.debug('Fichier FMOB A+1--------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : FMOB A + 1(nécessite %s)', DF.FMOB_PAM_SUIVANT.value[1])
if femp_suivant_df is not None:
femp_cree, femp_maj, ignore_count = insert_FMOB_femp(to_table_fmob_femp(femp_suivant_df))
text_response.append([f"Formulaire de mobilité A+1 : {femp_cree} femp A+1 créés, {femp_maj} femp A+1 mis à jour et {ignore_count} femp A+1 ignorés."])
self.logger.debug('Fichier FEMP A+1 --------------------------------------> Succès')
else:
self.logger.info('Mise à jour ignorée : FEMP A + 1(nécessite %s)', DF.FEMP_PAM_SUIVANT.value[1])
if fmob_df is not None:
a_maintenir, a_traiter = update_administre_fmob(to_table_fmob_fmob(fmob_df))
text_response.append([f"Formulaire de mobilité : {a_maintenir} administrés mis à jour en 'A maintenir' car FMOB annulé, {a_traiter} administrés mis à jour en 'A muter' car ils ont un FMOB."])
self.logger.debug('Mise à jour du statut PAM si annulation FMOB et si FMOB existe------> Succès')
else:
self.logger.info('Mise à jour ignorée : statut PAM si annulation FMOB (nécessite %s ou %s)', DF.BO.value[1], DF.FMOB.value[1])
if fmob_suivant_df is not None:
a_maintenir, a_traiter = update_administre_fmob(to_table_fmob_fmob(fmob_suivant_df))
text_response.append([f"Formulaire de mobilité A+1 : {a_maintenir} administrés A+1 mis à jour en 'A maintenir' car FMOB annulé, {a_traiter} administrés A+1 mis à jour en 'A muter' car ils ont un FMOB A+1."])
self.logger.debug('Mise à jour du statut PAM A+1 si annulation FMOB et si FMOB existe ------> Succès')
else:
self.logger.info('Mise à jour ignorée : statut PAM A+1 si annulation FMOB (nécessite %s ou %s)', DF.BO.value[1], DF.FMOB_PAM_SUIVANT.value[1])
self.logger.debug('Administres Pams -------------------------> Success')
self.logger.debug('--------------- Insert time : %d seconds -----------------', time.time() - start_time_insert)
self.logger.info('---------------------- Insert ending ----------------------')
text_response_df = pd.DataFrame(text_response)
if text_response_df.empty:
return Response(text_response_df)
else:
return Response(text_response_df[0])
except (Http404, APIException):
raise
except BaseException:
message = "Impossible d'alimenter le(s) référentiel(s)"
self.logger.exception(message)
raise APIException(message)

View File

@@ -0,0 +1,70 @@
import pandas as pd
from django.db.transaction import atomic
from django.http import Http404
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from ..serializers import AlimentationCommentairesSerializer
from ..utils.alimentation_decorators import (data_perf_logger_factory,
get_data_logger)
from ..utils.decorators import execution_time, query_count
from ..utils_extraction import (DataFrameTypes, FileTypes, read_files_by_type,
to_table_commentaires)
from ..utils_insertion import insert_Commentaires
class AlimentationCommentairesView(APIView):
""" Vue pour alimenter la base avec les commentaires """
permission_classes = [IsAuthenticated, IsAdminUser]
serializer_class = AlimentationCommentairesSerializer
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_data_logger(self)
def get(self, request):
return Response("Formulaire d'alimentation d'OGURE NG pour les commentaires")
@atomic
@execution_time(logger_factory=data_perf_logger_factory)
@query_count(logger_factory=data_perf_logger_factory)
def post(self, request):
"""
Charge le(s) fichier(s) et met à jour la base.
:param request: requête, contient les fichiers
:type request: class:`rest_framework.request.Request`
:raises: class:`rest_framework.exceptions.APIException`
:return: réponse
:rtype: class:`rest_framework.response.Response`
"""
try:
validator = self.serializer_class(data=request.data)
validator.is_valid(raise_exception=True)
df_comments = read_files_by_type({
FileTypes.COMMENTS: validator.validated_data.get('commentaires')
}).get(DataFrameTypes.COMMENTS)
if df_comments is not None:
df = to_table_commentaires(df_comments)
self.logger.info('Extraction des commentaires ------> Succès')
insert_Commentaires(df)
self.logger.info('Insertion des commentaires ------> Succès')
else:
self.logger.info('Mise à jour ignorée : commentaires')
return Response({'Insertion réussie'})
except (Http404, APIException):
raise
except BaseException:
message = "Impossible d'alimenter le(s) référentiel(s)"
self.logger.exception(message)
raise APIException(message)

View File

@@ -0,0 +1,153 @@
import time
from django.db.transaction import atomic
from django.http import Http404
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from ..serializers import AlimentationRefsDroitSerializer
from ..utils.alimentation_decorators import (data_perf_logger_factory,
get_data_logger)
from ..utils.decorators import execution_time, query_count
from ..utils_extraction import (DataFrameTypes, FileTypes, read_files_by_type,
to_table_fe, to_table_ref_gest,
to_table_ref_org, to_table_ref_sv_fil,
to_table_sous_vivier)
from ..utils_insertion import (insert_FormationEmploi, insert_RefFeMere,
insert_RefGest, insert_RefOrg, insert_RefSvFil,
insert_SousVivier, insert_SousVivier_instances,
update_m2m_links_gestionnaire)
class AlimentationReferentielsDroitView(APIView):
"""
Page d'alimentation des référentiels destinés à la gestion des droits.
- Chargement des sous-viviers via le référentiel sous-viviers/flières,
- Chargement des référentiels gestionnaires, organique, sous-viviers/filières et FE,
- Chargement des liens entre les formation-emplois et les gestionnaires suivant la gestion de droits mise en place,
- Chargement des liens entre les sous-viviers et les gestionnaires suivant la gestion de droits mise en place,
- Attribution des sous-viviers aux postes et aux administrés.
"""
permission_classes = [IsAuthenticated, IsAdminUser]
serializer_class = AlimentationRefsDroitSerializer
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_data_logger(self)
def get(self, request):
return Response("Formulaire d'alimentation d'OGURE NG pour les référentiels destinés à la gestion des droits")
@atomic
@execution_time(logger_factory=data_perf_logger_factory)
@query_count(logger_factory=data_perf_logger_factory)
def post(self, request):
"""
Charge le(s) fichier(s) et met à jour la base.
:param request: requête, contient les fichiers
:type request: class:`rest_framework.request.Request`
:raises: class:`rest_framework.exceptions.APIException`
:return: réponse
:rtype: class:`rest_framework.response.Response`
"""
try:
validator = self.serializer_class(data=request.data)
validator.is_valid(raise_exception=True)
df_by_type = read_files_by_type({
FileTypes.REF_FE: validator.validated_data.get('referentiel_fe'),
FileTypes.REF_GEST: validator.validated_data.get('referentiel_gestionnaire'),
FileTypes.REF_ORG: validator.validated_data.get('referentiel_organique'),
FileTypes.REF_SV_FIL: validator.validated_data.get('refeferentiel_sous_vivier_filiere'),
})
DF = DataFrameTypes
ref_fe_df = df_by_type.get(DF.REF_FE)
ref_gest_df = df_by_type.get(DF.REF_GEST)
ref_org_df = df_by_type.get(DF.REF_ORG)
ref_sv_fil_df = df_by_type.get(DF.REF_SV_FIL)
self.logger.info('-------------------- Insert beginning ---------------------')
start_time_insert = time.time()
if ref_sv_fil_df is not None:
df = to_table_sous_vivier(ref_sv_fil_df)
self.logger.info('Extraction des données de sous-viviers ------> Succès')
insert_SousVivier(df)
self.logger.info('Insertion des sous-viviers ------> Succès')
else:
self.logger.info('Mise à jour ignorée : sous-viviers (nécessite %s)', DF.REF_SV_FIL.value[1])
if ref_org_df is not None:
df = to_table_ref_org(ref_org_df)
self.logger.info('Extraction des données du référentiel organique DRHAT ------> Succès')
insert_RefOrg(df)
self.logger.info('Insertion du référentiel organique ------> Succès')
else:
self.logger.info('Mise à jour ignorée : référentiel organique')
if ref_gest_df is not None:
df = to_table_ref_gest(ref_gest_df)
self.logger.info('Extraction des données de gestionnaires DRHAT ------> Succès')
insert_RefGest(df)
self.logger.info('Insertion du référentiel gestionnaire / utilisateurs ------> Succès')
else:
self.logger.info('Mise à jour ignorée : référentiel de gestionnaires')
if ref_sv_fil_df is not None:
df = to_table_ref_sv_fil(ref_sv_fil_df)
self.logger.info('Extraction des données du référentiel de sous-viviers/filières ------> Succès')
insert_RefSvFil(df)
self.logger.info('Insertion du référentiel de sous-viviers/filières ------> Succès')
else:
self.logger.info('Mise à jour ignorée : référentiel de sous-viviers/filières')
if ref_gest_df is not None or ref_org_df is not None or ref_sv_fil_df is not None:
update_m2m_links_gestionnaire('SV')
self.logger.info('Insertion des liens M2M entre sous-viviers et gestionnaires ------> Succès')
else:
self.logger.info('Mise à jour ignorée : liens gestionnaires/sous-viviers (nécessite %s ou %s ou %s)',
DF.REF_GEST.value[1], DF.REF_ORG.value[1], DF.REF_SV_FIL.value[1])
if ref_fe_df is not None:
df = to_table_fe(ref_fe_df)
self.logger.info('Extraction des données du référentiel FE ------> Succès')
insert_FormationEmploi(df)
self.logger.info('Insertion du référentiel FE ------> Succès')
else:
self.logger.info('Mise à jour ignorée : formations-emplois (nécessite %s)', DF.REF_FE.value[1])
if ref_fe_df is not None:
insert_RefFeMere(ref_fe_df)
self.logger.info('Référentiel FE mère ------> Succès')
else:
self.logger.info('Mise à jour ignorée : formations-emplois mères (nécessite %s)', DF.REF_FE.value[1])
if ref_gest_df is not None or ref_org_df is not None or ref_fe_df is not None:
update_m2m_links_gestionnaire('FE')
self.logger.info('Insertion des liens M2M entre formations-emplois et gestionnaires ------> Succès')
else:
self.logger.info('Mise à jour ignorée : liens gestionnaires/formations-emplois (nécessite %s ou %s ou %s)',
DF.REF_GEST.value[1], DF.REF_ORG.value[1], DF.REF_FE.value[1])
# TODO if ....
insert_SousVivier_instances()
self.logger.info('Insertion des liens entre sous-vivivers et administrés/postes ------> Succès')
self.logger.debug('--------------- Insert time : %d seconds -----------------', time.time() - start_time_insert)
self.logger.info('---------------------- Insert ending ----------------------')
return Response({'Insertion réussie'})
except (Http404, APIException):
raise
except BaseException:
message = "Impossible d'alimenter les référentiels"
self.logger.exception(message)
raise APIException(message)

View File

@@ -0,0 +1,70 @@
import pandas as pd
from django.db.transaction import atomic
from django.http import Http404
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from ..serializers.alimentation import AlimentationZoneGeographiqueSerializer
from ..utils.alimentation_decorators import (data_perf_logger_factory,
get_data_logger)
from ..utils.decorators import execution_time, query_count
from ..utils_extraction import (DataFrameTypes, FileTypes, read_files_by_type,
to_table_zone_geographique)
from ..utils_insertion import insert_ZoneGeographique
class AlimentationZoneGeographiqueView(APIView):
""" Vue pour alimenter la base à partir de référentiels """
permission_classes = [IsAuthenticated, IsAdminUser]
serializer_class = AlimentationZoneGeographiqueSerializer
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_data_logger(self)
def get(self, request):
return Response("Formulaire d'alimentation d'OGURE NG pour les zones geographiques")
@atomic
@execution_time(logger_factory=data_perf_logger_factory)
@query_count(logger_factory=data_perf_logger_factory)
def post(self, request):
"""
Charge le(s) fichier(s) et met à jour la base.
:param request: requête, contient les fichiers
:type request: class:`rest_framework.request.Request`
:raises: class:`rest_framework.exceptions.APIException`
:return: réponse
:rtype: class:`rest_framework.response.Response`
"""
try:
validator = self.serializer_class(data=request.data)
validator.is_valid(raise_exception=True)
ref_zones_geo_df = read_files_by_type({
FileTypes.REF_GEO: validator.validated_data.get('ref_zones_geo')
}).get(DataFrameTypes.REF_GEO)
if ref_zones_geo_df is not None:
df = to_table_zone_geographique(ref_zones_geo_df)
self.logger.info('Extraction des données du référentiel ------> Succès')
insert_ZoneGeographique(df)
self.logger.info('Mise à jour du référentiel ------> Succès')
else:
self.logger.info('Mise à jour ignorée : zones géographiques (nécessite %s)', DataFrameTypes.REF_GEO.value[1])
return Response({'Insertion réussie'})
except (Http404, APIException):
raise
except BaseException:
message = "Impossible d'alimenter le(s) référentiel(s)"
self.logger.exception(message)
raise APIException(message)

View File

@@ -0,0 +1,340 @@
from typing import List, Tuple, Union
import numpy as np
import pandas as pd
from django.core.files.base import File
from django.db.models import Q
from django.db.transaction import atomic
from django.http import Http404
from rest_framework import status
from rest_framework.exceptions import APIException
from rest_framework.parsers import MultiPartParser
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from ..models import Administre, Poste
from ..models.competence import Competence
from ..models.domaine import Domaine
from ..models.filiere import Filiere
from ..serializers.alimentation import ChargementCompetencesSerializer
from ..utils import cleanString
from ..utils.alimentation_decorators import (data_perf_logger_factory,
get_data_logger)
from ..utils.decorators import execution_time, query_count
from ..utils_extraction import open_excel
class _SkillFiles():
""" Regroupe les constantes de fichiers de compétences """
class Ref():
""" Constantes pour les colonnes du fichier de référence """
PK = 'Macro compétence libellé court (15 caractères MAXI)' # E (tous onglets)
CATEGORIE = 'Catégorie' # A (tous onglets)
DOMAINE = 'Domaine' # B (tous onglets)
FILIERE = 'Filière' # C (tous onglets)
LIBELLE = 'Macro compétence libellé long' # D (tous onglets)
class Specific():
""" Constantes pour les colonnes du fichier de compétences particulières """
ADMINISTRE_PK = 'N SAP' # A
ADMINISTRE_COMPETENCE_1 = 'COMPETENCE 1' # Q
ADMINISTRE_COMPETENCE_2 = 'COMPETENCE 2' # R
ADMINISTRE_COMPETENCE_3 = 'COMPETENCE 2.1' # S la notation .1 est documentée dans 'read_excel' (pandas)
POSTE_DOMAINE = 'DOM EIP' # N
POSTE_FILIERE = 'FIL EIP' # O
POSTE_FE = 'CODE FE' # C
POSTE_FONCTION = 'CODE FONCTION' # T
POSTE_NF = 'NR EIP' # P
POSTE_COMPETENCE_1 = 'COMPETENCE 1' # Q
POSTE_COMPETENCE_2 = 'COMPETENCE 2' # R
POSTE_COMPETENCE_3 = 'COMPETENCE 2.1' # S la notation .1 est documentée dans 'read_excel' (pandas)
class ChargementCompetenceView(APIView):
"""
Cette classe est dédiée au vue de chargement des competences.
- Charge et traite les fichiers de compétences.
- Attribue les compétences présentes aux administrés et postes correspondants
"""
permission_classes = [IsAuthenticated, IsAdminUser]
parser_classes = [MultiPartParser]
serializer_class = ChargementCompetencesSerializer
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_data_logger(self)
def get(self, request):
return Response("Formulaire de chargement des référentiels de compétences")
def _read_ref_skills(self, file: File) -> pd.DataFrame:
"""
Extrait les données du référentiel de compétences.
:param file: référentiel de compétences
:type file: class:`django.core.files.base.File`
:return: DataFrame
:rtype: class:`pandas.DataFrame`
"""
COLS = _SkillFiles.Ref
col_mapping = {
COLS.PK: Competence.Cols.PK,
COLS.LIBELLE: Competence.Cols.LIBELLE,
COLS.CATEGORIE: Competence.Cols.CATEGORIE,
COLS.DOMAINE: Competence.Cols.REL_DOMAINE + '_id',
COLS.FILIERE: Competence.Cols.REL_FILIERE + '_id'
}
df = (pd.concat([open_excel(file, sheetname=i, usecols=col_mapping.keys(), engine='openpyxl') for i in range(3)])
.dropna(subset=[COLS.PK])
.fillna(np.nan)
.astype(str)
.replace([np.nan, 'nan'], [None, None]))
df[COLS.PK] = df[COLS.PK].str.replace('[^a-zA-Z0-9]', '_', regex=True)
return (df.drop_duplicates(subset=[COLS.PK])
.rename(columns=col_mapping))
def _read_specific_skills(self, administre: bool, file: File) -> pd.DataFrame:
"""
Extrait les données du fichier de compétences particulières.
:param administre: True pour les administré, False pour les postes
:type administre: bool
:param file: fichier de compétences particulières
:type file: class:`django.core.files.base.File`
:return: DataFrame
:rtype: class:`pandas.DataFrame`
"""
COLS = _SkillFiles.Specific
if administre:
col_mapping = {COLS.ADMINISTRE_PK: Administre.Cols.PK}
col_types = {COLS.ADMINISTRE_PK: 'int32'}
cols_skill = [COLS.ADMINISTRE_COMPETENCE_1, COLS.ADMINISTRE_COMPETENCE_2, COLS.ADMINISTRE_COMPETENCE_3]
col_skill = COLS.ADMINISTRE_COMPETENCE_1
sheetname = 0
else:
col_mapping = {
COLS.POSTE_DOMAINE: Poste.Cols.REL_DOMAINE + '_id',
COLS.POSTE_FILIERE: Poste.Cols.REL_FILIERE + '_id',
COLS.POSTE_FE: Poste.Cols.REL_FORMATION_EMPLOI + '_id',
COLS.POSTE_FONCTION: Poste.Cols.FONCTION,
COLS.POSTE_NF: Poste.Cols.NIVEAU_FONCTIONNEL,
}
col_types = {c: 'str' for c in col_mapping.keys()}
cols_skill = [COLS.POSTE_COMPETENCE_1, COLS.POSTE_COMPETENCE_2, COLS.POSTE_COMPETENCE_3]
col_skill = COLS.POSTE_COMPETENCE_1
sheetname = 1
dfs = []
for temp_col_skill in cols_skill:
dfs.append(open_excel(file, sheetname=sheetname, usecols=[*col_mapping.keys(), temp_col_skill], engine='openpyxl')
.rename(columns={temp_col_skill: col_skill}))
df = (pd.concat(dfs)
.dropna(subset=[col_skill])
.fillna(np.nan)
.astype({**col_types, COLS.ADMINISTRE_COMPETENCE_1: 'str'})
.replace([np.nan, 'nan'], [None, None]))
df[col_skill] = df[col_skill].str.replace('[^a-zA-Z0-9]', '_', regex=True)
return (df.drop_duplicates()
.rename(columns=col_mapping))
@atomic
def _update_ref(self, df: pd.DataFrame, domaines_in_db: Union[Tuple[str], List[str]], filieres_in_db: Union[Tuple[str], List[str]]) -> None:
"""
Met à jour la table des compétences à partir du DataFrame de données de référence.
:param df: données de référence
:type df: class:`pandas.DataFrame`
"""
ModelType = Competence
Cols = ModelType.Cols
col_pk = Cols.PK
fields_to_update = (Cols.LIBELLE, Cols.CATEGORIE, Cols.REL_DOMAINE + '_id', Cols.REL_FILIERE + '_id')
models_in_db = {m.pk: m for m in ModelType.objects.only(col_pk, *fields_to_update)}
batch_size = 100
dict_create = {}
dict_update = {}
dict_up_to_date = {}
error_count = 0
to_ignore = {}
for idx, rec in enumerate(df.to_dict('records')):
pk = rec.get(col_pk)
try:
domaine = rec.get(Cols.REL_DOMAINE + '_id')
if domaine is not None and domaine not in domaines_in_db:
to_ignore.setdefault(pk, {}).setdefault(Domaine, domaine)
continue
filiere = rec.get(Cols.REL_FILIERE + '_id')
if filiere is not None and filiere not in filieres_in_db:
to_ignore.setdefault(pk, {}).setdefault(Filiere, filiere)
continue
in_db = models_in_db.get(pk)
model = ModelType(pk=pk, **{f: rec.get(f) for f in fields_to_update})
if not in_db:
model.full_clean(validate_unique=False)
dict_create.setdefault(pk, model)
elif any(getattr(in_db, f) != getattr(model, f) for f in fields_to_update):
model.full_clean(validate_unique=False)
dict_update.setdefault(pk, model)
else:
dict_up_to_date.setdefault(pk, model)
except Exception:
error_count = error_count + 1
self.logger.exception('%s une erreur est survenue à la ligne : %s (pk=%s)', ModelType.__name__, idx, pk)
if error_count:
self.logger.warning("%s(s) en erreur : %s", ModelType.__name__, error_count)
if to_ignore:
self.logger.warning('%s(s) ignorée(s) : %s', ModelType.__name__, len(to_ignore))
for _pk, _dict in to_ignore.items():
self.logger.warning('- %s car :', _pk)
for _type, v in _dict.items():
self.logger.warning(' - %s absent(e) du référentiel : %s', _type.__name__, v)
if dict_create:
ModelType.objects.bulk_create(dict_create.values(), batch_size=batch_size)
self.logger.info('%s(s) créée(s) : %s', ModelType.__name__, len(dict_create))
if fields_to_update:
if dict_update:
ModelType.objects.bulk_update(dict_update.values(), batch_size=batch_size, fields=fields_to_update)
self.logger.info('%s(s) mise(s) à jour : %s', ModelType.__name__, len(dict_update))
if dict_up_to_date:
self.logger.info('%s(s) déjà à jour : %s', ModelType.__name__, len(dict_up_to_date))
deleted = ModelType.objects.filter(~Q(pk__in={*dict_create.keys(), *dict_update.keys(), *dict_up_to_date.keys()})).delete()[0]
if deleted:
self.logger.info('%s(s) supprimée(s) : %s', ModelType.__name__, deleted)
@atomic
def _update_specific(self, administre: bool, df: pd.DataFrame, skills_in_db: Union[Tuple[str], List[str]]) -> None:
"""
Met à jour les liens M2M entre le modèle et les compétences.
:param administre: True pour les administré, False pour les postes
:type administre: bool
:param df: données de référence
:type df: class:`pandas.DataFrame`
:param skills_in_db: clés de toutes les compétences en base, les autres compétences sont ignorées
:type skills_in_db: Union[Tuple[str], List[str]]
"""
if administre:
ModelType = Administre
Cols = ModelType.Cols
LinkModelType = getattr(ModelType, Cols.M2M_COMPETENCES).through
fields_to_filter = (Cols.PK,)
col_skill = _SkillFiles.Specific.ADMINISTRE_COMPETENCE_1
else:
ModelType = Poste
Cols = ModelType.Cols
LinkModelType = getattr(ModelType, Cols.M2M_COMPETENCES).through
fields_to_filter = (Cols.REL_DOMAINE + '_id', Cols.REL_FILIERE + '_id', Cols.REL_FORMATION_EMPLOI + '_id', Cols.FONCTION, Cols.NIVEAU_FONCTIONNEL)
col_skill = _SkillFiles.Specific.POSTE_COMPETENCE_1
link_dict = {}
to_ignore = set()
for rec in df.to_dict('records'):
skill = rec.get(col_skill)
if skill not in skills_in_db:
to_ignore.add(skill)
else:
key = tuple(rec.get(f) for f in fields_to_filter)
link_dict.setdefault(key, set()).add(skill)
if to_ignore:
self.logger.warning('%s(s) ignorée(s) car absente(s) du référentiel : %s (%s)', Competence.__name__, len(to_ignore), to_ignore)
batch_size = 100
error_count = 0
to_create = []
for in_db in ModelType.objects.only('pk', *fields_to_filter):
try:
links = link_dict.get(tuple(getattr(in_db, f) for f in fields_to_filter)) or ()
for link in links:
to_create.append(LinkModelType(**{f'{ModelType.__name__.lower()}_id': in_db.pk, 'competence_id': link}))
except Exception:
error_count = error_count + 1
self.logger.exception("une erreur est survenue lors de l'ajout de lien(s) %s[pk=%s]/%s", ModelType.__name__, in_db.pk, Competence.__name__)
if error_count:
self.logger.warning("lien(s) %s/%s en erreur : %s", ModelType.__name__, Competence.__name__, error_count)
deleted = LinkModelType.objects.all().delete()[0]
if deleted:
self.logger.info('lien(s) %s/%s supprimé(s) : %s', ModelType.__name__, Competence.__name__, deleted)
if to_create:
LinkModelType.objects.bulk_create(to_create, batch_size=batch_size)
self.logger.info('lien(s) %s/%s créé(s) : %s', ModelType.__name__, Competence.__name__, len(to_create))
@execution_time(warn_after=30000, logger_factory=data_perf_logger_factory)
@query_count(warn_after=50, logger_factory=data_perf_logger_factory)
def post(self, request: Request) -> Response:
"""
Charge les competences, met à jour la table de compétences et les liens M2M avec les administrés et les postes.
:param request: Request contenant le fichier de competence
:type request: rest_framework.request.Request
:return: un message
:rtype: class:`rest_framework.response.Response`
"""
try:
# validation et récupération des fichiers
ser = self.serializer_class(data=request.data)
ser.is_valid(raise_exception=True)
ref_skill_file = ser.validated_data.get('ref_skills')
specific_skill_file = ser.validated_data.get('specific_skills')
try:
df_ref = self._read_ref_skills(ref_skill_file)
self.logger.info('Lecture du fichier de référentiel de compétences ------> Succès')
self._update_ref(df_ref,
domaines_in_db=list(Domaine.objects.values_list('pk', flat=True)),
filieres_in_db=list(Filiere.objects.values_list('pk', flat=True)))
self.logger.info('Mise à jour du référentiel de compétences ------> Succès')
except Exception as e :
self.logger.info('Lecture du fichier de référentiel de compétences ------> Ignoré')
self.logger.info(e)
try:
df_specific_administre = self._read_specific_skills(True, specific_skill_file)
df_specific_poste = self._read_specific_skills(False, specific_skill_file)
self.logger.info("Lecture des compétences particulières d'administrés ------> Succès")
self.logger.info('Lecture des compétences particulières de postes ------> Succès')
ref_data = list(Competence.objects.values_list('pk', flat=True))
self._update_specific(True, df_specific_administre, ref_data)
self.logger.info("Mise à jour des compétences particulières d'administrés ------> Succès")
self._update_specific(False, df_specific_poste, ref_data)
self.logger.info('Mise à jour des compétences particulières de postes ------> Success')
except Exception as e :
self.logger.info("Mise à jour des compétences particulières d'administrés et postes ------> Ignoré")
self.logger.info(e)
return Response({'Insertion réussie'})
except (Http404, APIException):
raise
except BaseException:
message = "Impossible d'alimenter le référentiel de compétences"
self.logger.exception(message)
raise APIException(message)

View File

@@ -0,0 +1,189 @@
from django.db.transaction import atomic
from django.http import Http404
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from django.db.models import Q
import datetime
from typing import Any, List, Tuple, Union
from ..utils_extraction import to_table_pam
from ..utils.alimentation_decorators import (data_perf_logger_factory,
get_data_logger)
from ..utils.decorators import execution_time, query_count
from ..utils_insertion import insert_PAM
from ..models import Administres_Pams, Postes_Pams,Calcul, Poste, PAM, StatutPamChoices as StatutPam, AvisPosteChoices
from ..utils.insertion.commun import batch_iterator
class AlimentationPamView(APIView):
""" Vue pour charger un nouveau PAM """
permission_classes = [IsAuthenticated, IsAdminUser]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_data_logger(self)
def get(self, request):
try:
self.logger.debug('Début de la mise à jour de la table PAM, veuillez patienter...')
# today = datetime.date(2023,1,1)
today = datetime.datetime.now()
insert_PAM(to_table_pam(today))
self.logger.debug('Insertion/Mise à jour de la table PAM --------------------------------------------> Success')
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
self.logger.debug('Mise à jour des données des administrés pour les PAM A et A+1, veuillez patienter ...')
dict_create_administres = {}
dict_update_administres = {}
qs = Administres_Pams.objects.all().filter(pam_id=annee_pam).values('administre_id')
batch_size = 50
admin_suivant = dict((f"{o.administre_id}", o) for o in Administres_Pams.objects.all().filter(pam_id=annee_pam_suivant))
for a_id_sap in qs:
administre_id=a_id_sap['administre_id']
pk = str(administre_id) + str(annee_pam_suivant)
model_administre = Administres_Pams(**{
'id' :pk,
'pam_id' :annee_pam_suivant,
'administre_id' :administre_id,
'a_statut_pam_annee' :StatutPam.NON_ETUDIE,
'notes_pam' :None,
'a_ciat_pam' :False,
'a_specifique_pam' :None,
'a_liste_depts_souhaites_pam' :None,
'a_liste_zones_geographiques_shm_pam' :None,
'a_situationfuture_notes_fe' :None,
})
if str(administre_id) not in admin_suivant :
dict_create_administres.setdefault(pk, model_administre)
else:
dict_update_administres.setdefault(pk, model_administre)
if dict_create_administres:
self.logger.debug("Nombre d'administrés à créer dans l'année %s : %s'",annee_pam_suivant,len(dict_create_administres))
for idx, data_batch in enumerate(batch_iterator(list(dict_create_administres.values()), batch_size)):
try:
Administres_Pams.objects.bulk_create(data_batch)
except Exception as e:
self.logger.exception("Cet administré existe déjà dans la base : %s", e)
self.logger.debug('Mise à jour des liens Administres/Pams terminés.')
self.logger.debug('Mise à jour des données des postes pour les PAM A et A+1, veuillez patienter ...')
dict_create_postes = {}
dict_update_postes = {}
# def mise_a_jour_info_reo(info_reo, annee_pam):
# if "SUREFF" in info_reo:
# model.info_reo=info_reo
# else:
# model.info_reo=f'REO {annee_pam}'
# return model.info_reo
qs = Postes_Pams.objects.all().filter(p_pam_id=annee_pam).values_list('poste_id','info_reo')
batch_size = 50
poste_suivant = dict((f"{o.poste_id}", o) for o in Postes_Pams.objects.all().filter(p_pam_id=annee_pam_suivant))
for poste in qs:
poste_id=poste[0]
info_reo= poste[1]
pk = str(poste_id) + str(annee_pam_suivant)
model_poste = Postes_Pams(**{
'id' :pk,
'p_pam_id' :annee_pam_suivant,
'poste_id' :poste_id,
'p_avis_pam' :StatutPam.NON_ETUDIE,
'p_avis_fe_pam' :StatutPam.NON_ETUDIE,
'p_direct_commissionne_pam' :None,
'p_notes_gestionnaire_pam' :None,
'p_priorisation_pcp_pam' :None,
'info_reo' :info_reo if "SUREFF" in info_reo else f'REO {annee_pam}',
})
if str(poste_id) not in poste_suivant :
dict_create_postes.setdefault(pk, model_poste)
else:
dict_update_postes.setdefault(pk, model_poste)
if dict_create_postes:
self.logger.debug("Nombre de postes à créer dans l'année %s : %s'",annee_pam_suivant,len(dict_create_postes))
for idx, data_batch in enumerate(batch_iterator(list(dict_create_postes.values()), batch_size)):
try:
Postes_Pams.objects.bulk_create(data_batch)
except Exception as e:
self.logger.exception("Ce poste existe déjà dans la base : %s", e)
self.logger.debug('Mise à jour des liens Postes/Pams terminés.')
self.logger.debug(f'Début de la suppression des données du PAM {int(annee_pam)-1}, veuillez patienter ...')
PAM.objects.filter(pam_id=int(annee_pam)-1).delete()
self.logger.debug("Fin de l'initialisation/Mise à jour du PAM")
return Response({'Initialisation/Mise à jour du Pam réussie'})
except (Http404, APIException):
message = "Impossible de mettre à jour ce PAM"
self.logger.exception(message)
raise APIException(message)
class NettoyagePamView(APIView):
""" Vue pour nettoyer les postes tagués en SUP REO """
permission_classes = [IsAuthenticated, IsAdminUser]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_data_logger(self)
def get(self, request):
try:
self.logger.debug("Suppression des postes tagués en SUP REO de l'année A --------------------------------------------> Success")
annee_pam = PAM.objects.filter(pam_statut='PAM en cours')[0].pam_id
qs = Postes_Pams.objects.all().filter(p_pam_id=annee_pam).values('poste_id')
for p_id in qs:
poste_id=p_id['poste_id']
if Postes_Pams.objects.filter(Q(info_reo='SUP REO') & Q(poste_id=poste_id)):
self.logger.debug(f'Suppression du poste {poste_id}')
Poste.objects.filter(p_id=poste_id).delete()
#Mise à jour des postes tagues CREE REO A en REO A
if not Postes_Pams.objects.filter(Q(p_pam_id=annee_pam) & Q(poste_id=poste_id) & Q(info_reo__contains='SUREFF')):
Postes_Pams.objects.filter(Q(p_pam_id=annee_pam) & Q(poste_id=poste_id)).update(info_reo=f'REO {annee_pam}')
self.logger.debug(f'Début de la suppression des administrés du PAM {int(annee_pam)-1}')
Administres_Pams.objects.filter(pam_id=int(annee_pam)-1).delete()
self.logger.debug(f'Début de la suppression des postes du PAM {int(annee_pam)-1}')
Postes_Pams.objects.filter(p_pam_id=int(annee_pam)-1).delete()
Calcul.objects.filter(pam_id=int(annee_pam)-1).delete()
self.logger.debug(f'Fin de la suppression des données du PAM {int(annee_pam)-1}')
return Response({'Mise à jour du Pam réussie'})
except (Http404, APIException):
message = "Impossible de mettre à jour ce PAM"
self.logger.exception(message)
raise APIException(message)

View File

@@ -0,0 +1,292 @@
# --------------------------------------------------------------------------------
"""
Définitions communes à plusieurs vues
"""
# --------------------------------------------------------------------------------
from django.db.models import Q
from rest_framework.exceptions import APIException
from rest_framework.permissions import SAFE_METHODS, BasePermission
from ..models import Administre, Decision, Poste, SpecifiqueChoices, Administres_Pams, Postes_Pams
from ..utils.decorators import (class_logger, decorate_functions,
execution_time, query_count)
from ..utils.permissions import (Profiles, KEY_READ, KEY_WRITE,
get_profile_summary,
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_queryset_org_by_user, is_truthy)
from ..utils.view_predicates import viewset_functions
# décorateur pour tracer le temps d'exécution d'une vue
execution_time_viewset = decorate_functions(
lambda cls: execution_time(warn_after=5000, logger_name=cls),
viewset_functions,
factory=True
)
# décorateur pour tracer le nombre de requêtes d'une vue
query_count_viewset = decorate_functions(
lambda cls: query_count(warn_after=20, logger_name=cls),
viewset_functions,
factory=True
)
def api_exception(status_code: int, message: str = None) -> APIException:
"""
Crée une APIException avec un statut personnalisé.
note : inutile d'appeler cette fonction pour un code 500 car APIException(message) suffit
:param status_code: code de statut HTTP
:type status_code: int
:param message: message de l'exception
:type message: str, optional
:return: exception
:rtype: class:`APIException`
"""
return type('MyException', (APIException,), {'status_code': status_code, 'default_detail': message})
@class_logger
class GestionnairePermission(BasePermission):
"""
Classe de gestion des droits de lecture et d'écriture d'un gestionnaire
"""
message = "Le gestionnaire n'a pas les droits de lecture ou d'écriture"
def is_in_list_ok(self, list, list_ok): # A modif en utilisant set
"""
Cette fonction vérifie que tous les objets de la liste list soient dans la liste list_ok
:param list: liste 1
:type list: list
:param list: liste 2
:type list: list
:return: booléen indiquant si les objets de la liste 1 sont dans la liste 2
:rtype: bool
"""
for obj in list:
if obj not in list_ok:
return False
return True
def verif_droits_adm(self, pk, codes_lvl4, is_fil, is_pcp, is_bvt):
"""
Cette fonction vérifie si le gestionnaire a bien le droit de modifier (mettre à jour)
les attributs du ou des administres d'identifiant pk.
:param pk: id sap du ou des administrés édités
:type org_code: int ou list
:param codes_lvl4: codes de niveau 4 des référentiels organiques liés à l'utilisateur
:type codes_lvl4: Tuple[str]
:param is_fil: profil filiere
:type is_fil: bool
:param is_pcp: profil pcp
:type is_pcp: bool
:param is_bvt: profil bvt
:type is_bvt: bool
:return: booléen pour indiqué si l'utilisateur a les droits ou non sur l'édition
du ou des administrés concernés
:rtype: bool
"""
# On récupère le ou les administrés dont l'id match avec pk (liste ou non)
qs = Administre.objects.filter(a_id_sap__in=pk) if isinstance(pk, list) else Administre.objects.filter(a_id_sap=pk)
list_a = list(qs.values_list('pk', flat=True))
# On récupère le ou les administrés autorisés
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
list_a_ok = Administres_Pams.objects.filter(adm_filter).values_list('administre_id', flat=True)
perm = self.is_in_list_ok(list_a, list_a_ok)
msg = 'Administre rights verification ok\n' if perm else 'Administre rights verification not ok\n'
self.logger.debug(msg)
return perm
def verif_droits_poste(self, pk, codes_lvl4, is_fil, is_pcp, is_bvt, is_itd):
"""
Cette fonction vérifie si le gestionnaire a bien le droit de modifier (mettre à jour)
les attributs du ou des postes d'identifiant pk.
:param pk: id sap du ou des administrés édités
:type org_code: int ou list
:param codes_lvl4: codes de niveau 4 des référentiels organiques liés à l'utilisateur
:type codes_lvl4: Tuple[str]
:param is_fil: profil filiere
:type is_fil: bool
:param is_pcp: profil pcp
:type is_pcp: bool
:param is_bvt: profil bvt
:type is_bvt: bool
:param is_itd: profil itd
:type is_itd: bool
:return: booléen pour indiqué si l'utilisateur a les droits ou non sur l'édition
du ou des postes concernés
:rtype: bool
"""
# On récupère le ou les postes dont l'id match avec pk (liste ou non)
qs = Poste.objects.filter(p_id__in=pk) if isinstance(pk, list) else Poste.objects.filter(p_id=pk)
list_p = list(qs.values_list('pk', flat=True))
# On créer le filtre avec le ou les postes autorisés
poste_filter = None
if is_fil:
poste_filter = get_poste_filter_by_lvl4_codes_fil(codes_lvl4)
if is_pcp:
pcp_filter = get_poste_filter_by_lvl4_codes_pcp(codes_lvl4)
poste_filter = poste_filter | pcp_filter if poste_filter else pcp_filter
if is_itd:
itd_filter = Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_specifique' : SpecifiqueChoices.ITD})
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
list_p_ok = Postes_Pams.objects.filter(poste_filter).values_list('poste_id', flat=True)
perm = self.is_in_list_ok(list_p, list_p_ok)
msg = 'Poste rights verification ok\n' if perm else 'Poste rights verification not ok\n'
self.logger.debug(msg)
return perm
def commun_verif(self, request, view, obj=None):
"""
Partie commune de la vérification des droits de lecture aux fonctions has_permission et has_object_permission
"""
P = Profiles
user = request.user
if not user or not user.is_authenticated:
self.logger.debug('No user connected\n')
return False
is_super = user.is_superuser
if is_super and str(view) in ['ReportingView']:
self.logger.debug('Rights verification ok : the user is a superuser\n')
return True
summary = get_profile_summary(user)
profiles = summary.profiles
r_profiles = profiles.get(KEY_READ, ())
w_profiles = profiles.get(KEY_WRITE, ())
if not profiles:
self.logger.debug('The user as no profile\n')
return False
if request.method in SAFE_METHODS:
resp = True if (r_profiles and r_profiles!=(P.SUPER,)) else False
if not resp:
msg = 'The user does not have reading rights\n'
self.logger.debug(msg)
return resp
# La gestion de droits des administrateurs est faite via IsAdminUser pour les api d'alimentation
is_fil = P.FILIERE in w_profiles
is_pcp = P.PCP in w_profiles
is_bvt = P.BVT in w_profiles
is_itd = P.ITD in w_profiles
is_hme = P.HME in w_profiles
codes_lvl4 = get_lvl4_org_codes_by_any_code(summary.org_code)
if obj: # Modification d'une seule instance
if not w_profiles or w_profiles==(P.SUPER,):
self.logger.debug('The user does not have writing rights\n')
return False
if isinstance(obj, Administre):
pk = obj.pk
return self.verif_droits_adm(pk, codes_lvl4, is_fil, is_pcp, is_bvt)
elif isinstance(obj, Poste):
pk = obj.pk
return self.verif_droits_poste(pk, codes_lvl4, is_fil, is_pcp, is_bvt, is_itd)
elif isinstance(obj, Decision):
a_pk = obj.administre.pk
p_pk = obj.poste.pk
return self.verif_droits_poste(p_pk, codes_lvl4, is_fil, is_pcp, is_bvt, is_itd) or \
self.verif_droits_adm(a_pk, codes_lvl4, is_fil, is_pcp, is_bvt)
else: # Vérification globale ou modification multiple
self.logger.debug('---- Writing profiles ----')
self.logger.debug('Expert HME : %s', is_hme)
self.logger.debug('Pameur BMOB : %s', is_pcp)
self.logger.debug('Gestionnaire BVT : %s', is_bvt)
self.logger.debug('Gestionnaire ITD : %s', is_itd)
self.logger.debug('Gestionnaire BGCAT : %s', is_fil)
self.logger.debug('--------------------------')
if isinstance(request.data, list):
if not w_profiles or w_profiles==(P.SUPER,):
self.logger.debug('The user does not have writing rights\n')
return False
if 'a_id_sap' in request.data[0].keys():
pk = [adm['a_id_sap'] for adm in request.data]
return self.verif_droits_adm(pk, codes_lvl4, is_fil, is_pcp, is_bvt)
elif 'p_id' in request.data[0].keys():
pk = [p['p_id'] for p in request.data]
return self.verif_droits_poste(pk, codes_lvl4, is_fil, is_pcp, is_bvt, is_itd)
self.logger.debug('Global rights verification ok\n')
return True # On autorise une permission globale pour ensuite permettre des vérifications au niveau des instances avec has_object_permission
self.logger.debug('User rights verification failed\n')
return False
def has_permission(self, request, view):
if request.method != 'GET':
self.logger.debug('------------------------- Permissions verification ---------------------')
self.logger.debug('Request method : %s', request.method)
self.logger.debug('Request data : %s', request.data)
self.logger.debug('View : %s', view)
return self.commun_verif(request=request, view=view)
def has_object_permission(self, request, view, obj):
self.logger.debug('--------------------- Object permissions verification ------------------')
self.logger.debug('Request method : %s', request.method)
self.logger.debug('Request data : %s', request.data)
self.logger.debug('View : %s', view)
self.logger.debug('Object : %s', obj)
return self.commun_verif(request=request, view=view, obj=obj)

View File

@@ -0,0 +1,46 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from ..models import PcpFeGroupe, SousVivier, FormationEmploi
from ..serializers.current_user import (CTX_KEY_PCP_FE_GROUPE,
CTX_KEY_PROFILES, CTX_KEY_SOUS_VIVIERS,
CTX_KEY_FORMATION_EMPLOIS, CTX_KEY_SOUS_VIVIERS_SUPER,
UserInfoSerializer)
from ..utils.decorators import class_logger
from ..utils.permissions import KEY_READ, Profiles, get_profile_summary
from .commun import execution_time_viewset, query_count_viewset
@class_logger
@execution_time_viewset
@query_count_viewset
class CurrentUserView(APIView):
"""
Cette classe est dédiée au vue de l'utilisateur courant:
"""
permission_classes = [IsAuthenticated]
# TODO adapter au nouveau système de gestion de profils
def get(self, request: Request) -> Response:
"""
Renvoie les informations de l'utilisateur connecté.
:param request: requête contenant l'utilisateur authentifié
:type request: class:`rest_framework.request.Request`
:return: réponse contenant les informations de l'utilisateur dont la formation emploi group et les sous-viviers specifiques à cet utilisateur.
:rtype: class:`rest_framework.response.Response`
"""
user = request.user
summary = get_profile_summary(request.user)
profiles = summary.profiles.get(KEY_READ, ())
return Response(UserInfoSerializer(user, context={
# CTX_KEY_SOUS_VIVIERS_SUPER: SousVivier.objects.all() if Profiles.SUPER in profiles else None,
CTX_KEY_SOUS_VIVIERS: SousVivier.objects.filter(gestionnaires__id=request.user.id),
CTX_KEY_FORMATION_EMPLOIS: FormationEmploi.objects.filter(gestionnaires__id=request.user.id).order_by('fe_mere_credo').distinct('fe_mere_credo'),
CTX_KEY_PCP_FE_GROUPE: PcpFeGroupe.objects.filter(gestionnaire_id=user.pk).first(),
CTX_KEY_PROFILES: summary.profiles
}).data)

View File

@@ -0,0 +1,152 @@
from django.db.transaction import atomic
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
import datetime
from ..models import Administre, Decision, DecisionChoices, Notation, Poste
from ..serializers.decision import CreateDecisionSerializer, DecisionSerializer
from ..utils.decorators import class_logger
from .commun import (GestionnairePermission, api_exception,
execution_time_viewset, query_count_viewset)
@class_logger
@execution_time_viewset
@query_count_viewset
class DecisionView(ModelViewSet):
"""
Cette classe est dédiée au vue des decisions.
"""
permission_classes = [IsAuthenticated, GestionnairePermission]
serializer_class = DecisionSerializer
queryset = Decision.objects.all()
def get_serializer_class(self):
""" Renvoie un serializer particulier pour la création """
if self.action == 'create':
return CreateDecisionSerializer
return self.serializer_class
def list(self, request: Request, pk=None) -> Response:
"""Cette fonction envoie les informations du poste lié à l'administre dans une décision avec le score de l'administre et inversement.
TODO: rajouter la possibilité d'envoyer les informations du poste lié à l'administré selon l'année du PAM dans lequel on se situe
:type request: rest_framework.request.Request
:param request: Request contenant l'administre ou le poste.
:return: - **res_decision** (*Response*): Json contenant le classement.
"""
decision = []
q = 'poste'
administre_id = None
poste_id = None
if 'pam_annee' in request.query_params:
annee_pam = request.query_params['pam_annee']
else :
annee_pam = None
if 'administre_id' in request.query_params:
administre_id = request.query_params['administre_id']
administre_pam_id = str(administre_id) + str(annee_pam)
administres_keys = (
'poste__p_id', 'poste__p_nf', 'poste__p_domaine', 'poste__p_filiere',
'poste__p_eip', #'poste__p_avis', TODO : Change p_avis to postesPAM
'poste__formation_emploi__fe_code', 'poste__competences',
'poste__p_notes_gestionnaire', 'poste__p_liste_id_marques',
'poste__formation_emploi__fe_libelle',
'poste__formation_emploi__fe_garnison_lieu',
'poste__p_dep', 'poste__p_code_fonction',
'poste__p_fonction',
'de_decision', 'de_date_decision', 'de_notes_gestionnaire')
decision = Decision.objects.filter(administre_pam_id = administre_pam_id).select_related('poste')
decision = list(decision.values(*administres_keys))
if 'poste_id' in request.query_params:
q = "administre"
poste_id = request.query_params['poste_id']
poste_pam_id = poste_id + str(annee_pam)
postes_keys = (
'administre__a_id_sap', 'administre__a_nom', 'administre__a_prenom', 'administre__administre__a_statut_pam_annee',
'administre__a_fonction', 'administre__a_code_fonction', 'administre__a_liste_id_competences',
'administre__grade_id', 'administre__a_liste_id_marques', 'de_decision', 'de_date_decision',
'de_notes_gestionnaire', 'administre__a_notes_gestionnaire', 'administre__a_liste_id_competences',
'administre__decision__poste_id')
decision = Decision.objects.filter(poste_pam_id = poste_pam_id).select_related('administre')
decision = list(decision.values(*postes_keys))
res_decision = []
if len(decision) > 0:
for k in range(len(decision)):
decision_unit = decision[k]
try:
administre_id = decision_unit.administre_id
except:
administre_id = None
try:
poste_id = decision_unit.poste_id
except:
poste_id = None
try:
res_decision_unit = {q: {}, 'no_score_administre': Notation.objects.get(
administre_id=administre_id,
poste_id=poste_id, pam_id = annee_pam).no_score_administre}
except:
res_decision_unit = {q: {}, 'no_score_administre': None}
for key in decision_unit:
if (q + "__") in key:
res_decision_unit[q][key.replace(q + '__', '')] = decision_unit[key]
else:
res_decision_unit[key] = decision_unit[key]
# Ajout du relevé des décisions sur le poste (cas q = "poste")
if q == "poste":
res_decision_unit[q]['p_nb_prepositionne'] = Decision.objects.filter(
de_decision=DecisionChoices.PREPOSITIONNE).count()
res_decision_unit[q]['p_nb_positionne'] = Decision.objects.filter(
de_decision=DecisionChoices.POSITIONNE).count()
res_decision_unit[q]['p_nb_omi_active'] = Decision.objects.filter(
de_decision=DecisionChoices.OMI_ACTIVE).count()
res_decision_unit[q]['p_nb_omi_en_cours'] = Decision.objects.filter(
de_decision=DecisionChoices.OMI_EN_COURS).count()
res_decision.append(res_decision_unit)
return Response(res_decision)
@atomic
def perform_create(self, serializer) -> None:
"""Cette fonction crée une decision à partir de données validées """
data = serializer.validated_data
annee_pam = self.request.query_params['pam__in']
administre_id = data.get('administre_id')
administre_id_pam = str(administre_id) + annee_pam
poste_id = data.get('poste_id')
poste_id_pam = poste_id + annee_pam
de_decision = data.get('de_decision')
delete_former = data.get('delete_former')
a = get_object_or_404(Administre, pk=administre_id)
p = get_object_or_404(Poste, pk=poste_id)
self.check_object_permissions(self.request, a)
self.check_object_permissions(self.request, p)
qs_former = Decision.objects.filter(pk=administre_id)
if delete_former:
qs_former.delete()
elif qs_former.exists():
raise api_exception(status.HTTP_400_BAD_REQUEST, "une décision existe déjà pour cet administré")
Decision.objects.create(administre_id=administre_id, poste_id=poste_id, de_decision=de_decision, de_date_decision=timezone.now(),
administre_pam_id=administre_id_pam, poste_pam_id=poste_id_pam )
def perform_update(self, serializer) -> None:
""" Met à jour la décision à partir de données validées. La date est mise à jour en même temps que le statut """
data = serializer.validated_data
if hasattr(data, Decision.Cols.STATUT):
setattr(data, Decision.Cols.DATE, timezone.now())
super().perform_update(serializer)

View File

@@ -0,0 +1,580 @@
import time
import pandas as pd
import numpy as np
from django.utils import timezone
from django.db.transaction import atomic
from django.http import Http404, HttpResponse
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from ..utils_extraction import APP_NAN
from ..utils.alimentation import BOCols, ReoCols
from ..serializers import ExportationSerializer
from ..models import Administre, Affectation, Administre_Notation, Poste, FichiersExporte
from ..utils.alimentation_decorators import (data_perf_logger_factory,
get_data_logger)
from ..utils.decorators import execution_time, query_count
class ExportationFichiersView(APIView):
""" Vue pour exporter les données de la base dans des fichiers au format de ceux utilisés pour l'insertion"""
permission_classes = [IsAuthenticated, IsAdminUser]
serializer_class = ExportationSerializer
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_data_logger(self)
def get(self, request):
resp = []
resp.append(["Formulaire d\'OGURE NG permettant d'exporter les données de la base au format des fichiers d'insertion."])
resp.append(["Selectionnez le fichier que vous souhaitez exporter puis appuyez sur le bouton 'POST' pour lancer l'exportation."])
resp = pd.DataFrame(resp)
return Response(resp[0])
def export_adm_bo_df(self):
self.logger.info("Lecture et traitement de la table des administrés... ")
start_time_adm = time.time()
Cols = Administre.Cols
administres = Administre.objects.all().values_list(
Cols.PK,
Cols.REL_GRADE,
'a_nom',
'a_prenom',
'a_id_def',
'a_eip',
'a_eis',
'a_domaine_gestion',
Cols.REL_DOMAINE,
Cols.REL_FILIERE,
'a_nf',
'a_arme',
'a_rg_origine_recrutement',
'a_date_entree_service',
'a_nombre_enfants',
'a_origine_recrutement',
'a_date_naissance',
'a_fonction',
'a_diplome_hl',
'a_dernier_diplome',
'a_date_rdc',
'a_credo_fe',
f'{Cols.REL_FORMATION_EMPLOI}__fe_garnison_lieu',
'a_date_arrivee_fe',
'a_date_dernier_acr',
'a_pos_statuaire',
'a_date_pos_statuaire',
'a_interruption_service',
'a_grade_date_debut',
'a_profession_conjoint',
'a_sap_conjoint',
'a_id_def_conjoint',
'a_sexe_conjoint',
'a_date_fonction1',
'a_fonction1',
'a_date_fonction2',
'a_fonction2',
'a_date_fonction3',
'a_fonction3',
'a_date_fonction4',
'a_fonction4',
'a_date_fonction5',
'a_fonction5',
'a_date_fonction6',
'a_fonction6',
'a_date_fonction7',
'a_fonction7',
'a_date_fonction8',
'a_fonction8',
'a_date_fonction9',
'a_fonction9',
'a_date_mariage',
'a_situation_fam',
'a_sexe',
'a_date_fud',
'a_fud',
Cols.DATE_STATUT_CONCERTO,
Cols.STATUT_CONCERTO,
Cols.DATE_STATUT_CONCERTO_FUTUR,
Cols.STATUT_CONCERTO_FUTUR,
'a_domaine_poste',
'a_filiere_poste',
'a_nf_poste',
'a_lien_service',
'a_marqueur_pn',
'a_pls_gb_max',
'a_enfants',
)
administres_df = pd.DataFrame.from_records(administres)
adm_bo_cols = BOCols.columns(
BOCols.ID_SAP, # A
BOCols.GRADE, # B
BOCols.NOM, # C
BOCols.PRENOM, # D
BOCols.ID_DEF, # E
BOCols.EIP, # F
BOCols.EIS, # G
BOCols.DOMAINE_GESTION, # H
BOCols.DOMAINE, # I
BOCols.FILIERE, # J
BOCols.NF, # K
BOCols.ARME, # L
BOCols.REGROUPEMENT_ORIGINE_RECRUTEMENT, # M
BOCols.DATE_ENTREE_SERVICE, # N
BOCols.NOMBRE_ENFANTS, # O
BOCols.ORIGINE_RECRUTEMENT, # Q
BOCols.DATE_NAISSANCE, # R
BOCols.FONCTION, # T
BOCols.DIPLOME_PLUS_HAUT_NIVEAU, # U
BOCols.DERNIER_DIPLOME, # V
BOCols.DATE_RDC, # W
BOCols.CREDO_FE, # X
BOCols.GARNISON, # Z (utilisé dans la table des garnisons)
BOCols.DATE_ARRIVEE_FE, # AA
BOCols.DATE_DERNIER_ACR, # AC
BOCols.POSITION_STATUTAIRE, # AE
BOCols.DATE_POSITION_STATUAIRE, # AF
BOCols.INTERRUPTION_SERVICE, # AG
BOCols.DATE_DEBUT_GRADE, # AH
BOCols.PROFESSION_CONJOINT, # AL
BOCols.ID_SAP_CONJOINT, # AM
BOCols.ID_DEF_CONJOINT, # AN
BOCols.SEXE_CONJOINT, # AT
BOCols.DATE_FONCTION_1, # BM
BOCols.FONCTION_1, # BN
BOCols.DATE_FONCTION_2, # BO
BOCols.FONCTION_2, # BP
BOCols.DATE_FONCTION_3, # BQ
BOCols.FONCTION_3, # BR
BOCols.DATE_FONCTION_4, # BS
BOCols.FONCTION_4, # BT
BOCols.DATE_FONCTION_5, # BU
BOCols.FONCTION_5, # BV
BOCols.DATE_FONCTION_6, # BW
BOCols.FONCTION_6, # BX
BOCols.DATE_FONCTION_7, # BY
BOCols.FONCTION_7, # BZ
BOCols.DATE_FONCTION_8, # CA
BOCols.FONCTION_8, # CB
BOCols.DATE_FONCTION_9, # CC
BOCols.FONCTION_9, # CD
BOCols.DATE_MARIAGE, # CH
BOCols.SITUATION_FAMILIALE, # CI
BOCols.SEXE, # CJ
BOCols.DATE_FUD, # DV
BOCols.FUD, # DW
BOCols.DATE_STATUT_CONCERTO, # DX
BOCols.STATUT_CONCERTO, # DY
BOCols.DATE_STATUT_CONCERTO_FUTUR, # DZ
BOCols.STATUT_CONCERTO_FUTUR, # EA
BOCols.DOMAINE_POSTE, # ED (non utilisé)
BOCols.FILIERE_POSTE, # EE (non utilisé)
BOCols.NF_POSTE, # EF (non utilisé)
BOCols.DATE_LIEN_SERVICE, # EG
BOCols.MARQUEUR_PN, # EH
BOCols.PLS_GB_MAX, # EI
BOCols.ENFANTS, # EK
)
administres_df.columns = adm_bo_cols
# Changement des types des colonnes dates
administres_df[BOCols.DATE_ARRIVEE_FE] = pd.to_datetime(administres_df[BOCols.DATE_ARRIVEE_FE], errors='coerce').dt.strftime('%d/%m/%Y') # AA
administres_df[BOCols.DATE_DEBUT_GRADE] = pd.to_datetime(administres_df[BOCols.DATE_DEBUT_GRADE], errors='coerce').dt.strftime('%d/%m/%Y') # AH
administres_df[BOCols.DATE_DERNIER_ACR] = pd.to_datetime(administres_df[BOCols.DATE_DERNIER_ACR], errors='coerce').dt.strftime('%d/%m/%Y') # AC
administres_df[BOCols.DATE_ENTREE_SERVICE] = pd.to_datetime(administres_df[BOCols.DATE_ENTREE_SERVICE], errors='coerce').dt.strftime('%d/%m/%Y') # N
administres_df[BOCols.DATE_FUD] = pd.to_datetime(administres_df[BOCols.DATE_FUD], errors='coerce').dt.strftime('%d/%m/%Y') # DV
administres_df[BOCols.DATE_LIEN_SERVICE] = pd.to_datetime(administres_df[BOCols.DATE_LIEN_SERVICE], errors='coerce').dt.strftime('%d/%m/%Y') # EG
administres_df[BOCols.DATE_NAISSANCE] = pd.to_datetime(administres_df[BOCols.DATE_NAISSANCE], errors='coerce').dt.strftime('%d/%m/%Y') # R
administres_df[BOCols.DATE_POSITION_STATUAIRE] = pd.to_datetime(administres_df[BOCols.DATE_POSITION_STATUAIRE], errors='coerce').dt.strftime('%d/%m/%Y') # AF
administres_df[BOCols.DATE_RDC] = pd.to_datetime(administres_df[BOCols.DATE_RDC], errors='coerce').dt.strftime('%d/%m/%Y') # W
administres_df[BOCols.DATE_STATUT_CONCERTO] = pd.to_datetime(administres_df[BOCols.DATE_STATUT_CONCERTO], errors='coerce').dt.strftime('%d/%m/%Y') # DX
administres_df[BOCols.DATE_STATUT_CONCERTO_FUTUR] = pd.to_datetime(administres_df[BOCols.DATE_STATUT_CONCERTO_FUTUR], errors='coerce').dt.strftime('%d/%m/%Y') # DZ
administres_df[BOCols.DATE_MARIAGE] = pd.to_datetime(administres_df[BOCols.DATE_MARIAGE], errors='coerce').dt.strftime('%d/%m/%Y') # CH
for i in range(1, 10):
administres_df[f'Fonction -{i} DD'] = pd.to_datetime(administres_df[f'Fonction -{i} DD']).dt.strftime('%d/%m/%Y')
# Changement des types des colonnes bool
administres_df[[BOCols.MARQUEUR_PN]] = administres_df[[BOCols.MARQUEUR_PN]].replace([True, False], ['X', None])
# CHangement des types des colonnes int
administres_df[BOCols.ID_SAP_CONJOINT] = administres_df[BOCols.ID_SAP_CONJOINT].fillna(0).astype(int).replace({0: None})
administres_df[BOCols.PLS_GB_MAX] = administres_df[BOCols.PLS_GB_MAX].fillna(0).astype(int).replace({0: None})
administres_df = (administres_df.fillna(APP_NAN)
.replace({APP_NAN: None})
.replace({np.nan: None})
.replace({'nan': None}))
self.logger.info("Table des administrés lue et traitée en %d secondes : %s lignes et %s colonnes extraites\n", time.time()-start_time_adm, administres_df.shape[0], administres_df.shape[1])
return administres_df
def export_aff_bo_df(self):
self.logger.info("Lecture et traitement de la table des affectations... ")
start_time_aff = time.time()
aff = pd.DataFrame.from_records(Affectation.objects.all().values()).sort_values('affect_date', ascending=False)
aff_grouped = aff.groupby(['administre_id']).agg(
affect_libelle=('affect_libelle', ',,,'.join),
affect_date=('affect_date', ',,,'.join),
)
aff_date_cols = [f'Affectation -{i} DD' for i in range(1,10)]
aff_libelle_cols = [f'Affectation -{i} L' for i in range(1,10)]
aff_date_df = aff_grouped['affect_date'].str.split(',,,', expand=True)
aff_libelle_df = aff_grouped['affect_libelle'].str.split(',,,', expand=True)
nb_cols_date = aff_date_df.shape[1]
nb_cols_libelle = aff_date_df.shape[1]
aff_date_cols_tronc = aff_date_cols[:nb_cols_date]
aff_libelle_cols_tronc = aff_libelle_cols[:nb_cols_libelle]
aff_grouped[aff_date_cols_tronc] = aff_date_df
aff_grouped[aff_libelle_cols_tronc] = aff_libelle_df
aff_grouped = (aff_grouped.drop(['affect_libelle', 'affect_date'], axis=1)
.reset_index())
# Changement des types des colonnes dates
for i in range(1, 10):
aff_grouped[f'Affectation -{i} DD'] = pd.to_datetime(aff_grouped[f'Affectation -{i} DD']).dt.strftime('%d/%m/%Y')
aff_grouped = (aff_grouped.fillna(APP_NAN)
.replace({APP_NAN: None})
.replace({np.nan: None})
.replace({'nan': None}))
self.logger.info("Table des affectations lue et traitée en %d secondes : %s lignes et %s colonnes extraites\n", time.time()-start_time_aff, aff_grouped.shape[0], aff_grouped.shape[1])
return aff_grouped
def export_no_bo_df(self):
self.logger.info("Lecture et traitement de la table des notations... ")
start_time_no = time.time()
no = pd.DataFrame.from_records(Administre_Notation.objects.all().values()).sort_values('no_age_annees', ascending=False)
no_grouped = no.groupby(["administre_id"]).agg(
no_annne_de_notation=("no_annne_de_notation", ",,,".join),
no_nr_ou_iris=("no_nr_ou_iris", ",,,".join),
no_rac_ou_iris_cumule=("no_rac_ou_iris_cumule", ",,,".join),
no_rf_qsr=("no_rf_qsr", ",,,".join),
no_aptitude_emploie_sup=("no_aptitude_emploie_sup", ",,,".join),
no_potentiel_responsabilite_sup=("no_potentiel_responsabilite_sup", ",,,".join),
no_age_annees=("no_age_annees", ",,,".join),
)
no_age_annees_cols = ['Age en années (au 31/12)']
no_annne_de_notation_cols = ['Année notation A'] + [f'Année notation A-{i}' for i in range(1,6)]
no_nr_ou_iris_cols = ['IRIS / RAC retenu A'] + [f'IRIS / RAC retenu A-{i}' for i in range(1,6)]
no_rac_ou_iris_cumule_cols = ['NR/NGC cumulé A'] + [f'NR/NGC cumulé A-{i}' for i in range(1,6)]
no_rf_qsr_cols = ['QSR A'] + [f'QSR A-{i}' for i in range(1,6)]
no_aptitude_emploie_sup_cols = ['Apt resp / Emp sup A'] + [f'Apt resp / Emp sup A-{i}' for i in range(1,6)]
no_potentiel_responsabilite_sup_cols = ['Potentiel responsabilités catégorie sup A'] + [f'Potentiel responsabilités catégorie sup A-{i}' for i in range(1,6)]
no_grouped[no_age_annees_cols] = no_grouped['no_age_annees'].str.split(',,,', expand=True)[0]
no_grouped[no_annne_de_notation_cols] = no_grouped['no_annne_de_notation'].str.split(',,,', expand=True)
no_grouped[no_nr_ou_iris_cols] = no_grouped['no_nr_ou_iris'].str.split(',,,', expand=True)
no_grouped[no_rac_ou_iris_cumule_cols] = no_grouped['no_rac_ou_iris_cumule'].str.split(',,,', expand=True)
no_grouped[no_rf_qsr_cols] = no_grouped['no_rf_qsr'].str.split(',,,', expand=True)
no_grouped[no_aptitude_emploie_sup_cols] = no_grouped['no_aptitude_emploie_sup'].str.split(',,,', expand=True)
no_grouped[no_potentiel_responsabilite_sup_cols] = no_grouped['no_potentiel_responsabilite_sup'].str.split(',,,', expand=True)
no_grouped = (no_grouped.drop(['no_annne_de_notation',
'no_nr_ou_iris',
'no_rac_ou_iris_cumule',
'no_rf_qsr',
'no_aptitude_emploie_sup',
'no_potentiel_responsabilite_sup',
'no_age_annees'], axis=1)
.reset_index())
# Changement des types des colonnes int
no_grouped[f'Année notation A'] = no_grouped[f'Année notation A'].astype(float).fillna(0).astype(int).replace({0: None})
no_grouped[f'IRIS / RAC retenu A'] = no_grouped[f'IRIS / RAC retenu A'].astype(float).fillna(0).astype(int).replace({0: None})
no_grouped[f'NR/NGC cumulé A'] = no_grouped[f'NR/NGC cumulé A'].astype(float).fillna(0).astype(int).replace({0: None})
for i in range(1, 6):
no_grouped[f'Année notation A-{i}'] = no_grouped[f'Année notation A-{i}'].astype(float).fillna(0).astype(int).replace({0: None})
no_grouped[f'IRIS / RAC retenu A-{i}'] = no_grouped[f'IRIS / RAC retenu A-{i}'].astype(float).fillna(0).astype(int).replace({0: None})
no_grouped[f'NR/NGC cumulé A-{i}'] = no_grouped[f'NR/NGC cumulé A-{i}'].astype(float).fillna(0).astype(int).replace({0: None})
no_grouped = (no_grouped.fillna(APP_NAN)
.replace({APP_NAN: None})
.replace({np.nan: None})
.replace({'nan': None}))
self.logger.info("Table des notations lue et traitée en %d secondes : %s lignes et %s colonnes extraites\n", time.time()-start_time_no, no_grouped.shape[0], no_grouped.shape[1])
return no_grouped
def export_pos_reo_df(self):
self.logger.info("Lecture et traitement de la table des postes... ")
start_time_pos = time.time()
Cols = Poste.Cols
postes = Poste.objects.all().values_list(
'p_annee',
Cols.REL_FORMATION_EMPLOI,
'p_dep',
Cols.CATEGORIE,
'p_eip',
Cols.REL_DOMAINE,
Cols.REL_FILIERE,
Cols.NIVEAU_FONCTIONNEL,
Cols.PK,
f'{Cols.REL_FONCTION}__fon_id',
f'{Cols.REL_FONCTION}__fon_libelle',
'p_nfs',
)
postes_df = pd.DataFrame.from_records(postes)
pos_reo_cols = ReoCols.columns(
ReoCols.ANNEE_PROJET, # B
ReoCols.FORMATION_EMPLOI, # F
ReoCols.CODE_POSTAL, # J
ReoCols.CATEGORIE, # P
ReoCols.EIP, # T
ReoCols.DOMAINE, # U
ReoCols.FILIERE, # V
ReoCols.CODE_NF, # W
ReoCols.ID_POSTE, # X
ReoCols.FONCTION_ID, # Y
ReoCols.FONCTION_LIBELLE, # Z
ReoCols.DOMAINE_GESTION, # AA
)
postes_df.columns = pos_reo_cols
postes_df = (postes_df.fillna(APP_NAN)
.replace({APP_NAN: None})
.replace({np.nan: None})
.replace({'nan': None}))
self.logger.info("Table des postes lue et traitée en %d secondes : %s lignes et %s colonnes extraites\n", time.time()-start_time_pos, postes_df.shape[0], postes_df.shape[1])
return postes_df
@atomic
# @execution_time(logger_factory=data_perf_logger_factory)
@query_count(logger_factory=data_perf_logger_factory)
def export_bo(self, request):
self.logger.info("--------- Début de l'exportation du fichier 'Données BO' --------- ")
start_time_export = time.time()
administres_df = self.export_adm_bo_df()
affectations_df = self.export_aff_bo_df()
notations_df = self.export_no_bo_df()
bo_df = administres_df.merge(affectations_df, how='left', left_on=BOCols.ID_SAP, right_on='administre_id')
bo_df = bo_df.drop(['administre_id'], axis=1)
bo_df = bo_df.merge(notations_df, how='left', left_on=BOCols.ID_SAP, right_on='administre_id')
bo_df = bo_df.drop(['administre_id'], axis=1)
bo_cols = BOCols.columns(
BOCols.ID_SAP, # A
BOCols.GRADE, # B
BOCols.NOM, # C
BOCols.PRENOM, # D
BOCols.ID_DEF, # E
BOCols.EIP, # F
BOCols.EIS, # G
BOCols.DOMAINE_GESTION, # H
BOCols.DOMAINE, # I
BOCols.FILIERE, # J
BOCols.NF, # K
BOCols.ARME, # L
BOCols.REGROUPEMENT_ORIGINE_RECRUTEMENT, # M
BOCols.DATE_ENTREE_SERVICE, # N
BOCols.NOMBRE_ENFANTS, # O
BOCols.ORIGINE_RECRUTEMENT, # Q
BOCols.DATE_NAISSANCE, # R
BOCols.FONCTION, # T
BOCols.DIPLOME_PLUS_HAUT_NIVEAU, # U
BOCols.DERNIER_DIPLOME, # V
BOCols.DATE_RDC, # W
BOCols.CREDO_FE, # X
BOCols.GARNISON, # Z (utilisé dans la table des garnisons)
BOCols.DATE_ARRIVEE_FE, # AA
BOCols.DATE_DERNIER_ACR, # AC
BOCols.POSITION_STATUTAIRE, # AE
BOCols.DATE_POSITION_STATUAIRE, # AF
BOCols.INTERRUPTION_SERVICE, # AG
BOCols.DATE_DEBUT_GRADE, # AH
BOCols.PROFESSION_CONJOINT, # AL
BOCols.ID_SAP_CONJOINT, # AM
BOCols.ID_DEF_CONJOINT, # AN
BOCols.SEXE_CONJOINT, # AT
BOCols.DATE_AFFECTATION_1, # AU
BOCols.AFFECTATION_1, # AV
BOCols.DATE_AFFECTATION_2, # AW
BOCols.AFFECTATION_2, # AX
BOCols.DATE_AFFECTATION_3, # AY
BOCols.AFFECTATION_3, # AZ
BOCols.DATE_AFFECTATION_4, # BA
BOCols.AFFECTATION_4, # BB
BOCols.DATE_AFFECTATION_5, # BC
BOCols.AFFECTATION_5, # BD
BOCols.DATE_AFFECTATION_6, # BE
BOCols.AFFECTATION_6, # BF
BOCols.DATE_AFFECTATION_7, # BG
BOCols.AFFECTATION_7, # BH
BOCols.DATE_AFFECTATION_8, # BI
BOCols.AFFECTATION_8, # BJ
BOCols.DATE_AFFECTATION_9, # BK
BOCols.AFFECTATION_9, # BL
BOCols.DATE_FONCTION_1, # BM
BOCols.FONCTION_1, # BN
BOCols.DATE_FONCTION_2, # BO
BOCols.FONCTION_2, # BP
BOCols.DATE_FONCTION_3, # BQ
BOCols.FONCTION_3, # BR
BOCols.DATE_FONCTION_4, # BS
BOCols.FONCTION_4, # BT
BOCols.DATE_FONCTION_5, # BU
BOCols.FONCTION_5, # BV
BOCols.DATE_FONCTION_6, # BW
BOCols.FONCTION_6, # BX
BOCols.DATE_FONCTION_7, # BY
BOCols.FONCTION_7, # BZ
BOCols.DATE_FONCTION_8, # CA
BOCols.FONCTION_8, # CB
BOCols.DATE_FONCTION_9, # CC
BOCols.FONCTION_9, # CD
BOCols.AGE_ANNEES, # CG
BOCols.DATE_MARIAGE, # CH
BOCols.SITUATION_FAMILIALE, # CI
BOCols.SEXE, # CJ
BOCols.ANNEE_NOTATION, # CL
BOCols.RAC_OU_IRIS_CUMULE, # CM
BOCols.NR_OU_IRIS, # CN
BOCols.RF_QSR, # CO
BOCols.APTITUDE_EMPLOI_SUP, # CP
BOCols.POTENTIEL_RESPONSABILITE_SUP, # CQ
BOCols.ANNEE_NOTATION_1, # CR
BOCols.RAC_OU_IRIS_CUMULE_1, # CS
BOCols.NR_OU_IRIS_1, # CT
BOCols.RF_QSR_1, # CU
BOCols.APTITUDE_EMPLOI_SUP_1, # CV
BOCols.POTENTIEL_RESPONSABILITE_SUP_1, # CW
BOCols.ANNEE_NOTATION_2, # CX
BOCols.RAC_OU_IRIS_CUMULE_2, # CY
BOCols.NR_OU_IRIS_2, # CZ
BOCols.RF_QSR_2, # DA
BOCols.APTITUDE_EMPLOI_SUP_2, # DB
BOCols.POTENTIEL_RESPONSABILITE_SUP_2, # DC
BOCols.ANNEE_NOTATION_3, # DD
BOCols.RAC_OU_IRIS_CUMULE_3, # DE
BOCols.NR_OU_IRIS_3, # DF
BOCols.RF_QSR_3, # DG
BOCols.APTITUDE_EMPLOI_SUP_3, # DH
BOCols.POTENTIEL_RESPONSABILITE_SUP_3, # DI
BOCols.ANNEE_NOTATION_4, # DJ
BOCols.RAC_OU_IRIS_CUMULE_4, # DK
BOCols.NR_OU_IRIS_4, # DL
BOCols.RF_QSR_4, # DM
BOCols.APTITUDE_EMPLOI_SUP_4, # DN
BOCols.POTENTIEL_RESPONSABILITE_SUP_4, # DO
BOCols.ANNEE_NOTATION_5, # DP
BOCols.RAC_OU_IRIS_CUMULE_5, # DQ
BOCols.NR_OU_IRIS_5, # DR
BOCols.RF_QSR_5, # DS
BOCols.APTITUDE_EMPLOI_SUP_5, # DT
BOCols.POTENTIEL_RESPONSABILITE_SUP_5, # DU
BOCols.DATE_FUD, # DV
BOCols.FUD, # DW
BOCols.DATE_STATUT_CONCERTO, # DX
BOCols.STATUT_CONCERTO, # DY
BOCols.DATE_STATUT_CONCERTO_FUTUR, # DZ
BOCols.STATUT_CONCERTO_FUTUR, # EA
BOCols.DOMAINE_POSTE, # ED (non utilisé)
BOCols.FILIERE_POSTE, # EE (non utilisé)
BOCols.NF_POSTE, # EF (non utilisé)
BOCols.DATE_LIEN_SERVICE, # EG
BOCols.MARQUEUR_PN, # EH
BOCols.PLS_GB_MAX, # EI
BOCols.ENFANTS, # EK
)
bo_df = bo_df.reindex(columns=bo_cols)
if bo_df.empty:
return Response("Aucune donnée à extraire.")
else:
try:
self.logger.info("Exportation du fichier 'Données BO'...")
start_time_export_bo = time.time()
now = timezone.now().date()
bo_df.to_excel(f'..\\fichiers_exportes\\{now}_donnees_BO_export.xlsx', index=False, header=True)
bo_obj = FichiersExporte(nom_fichier=f'{now}_donnees_BO_export.xlsx')
bo_obj.save()
self.logger.info("Exportation du fichier 'Données BO' réalisée en %d secondes : %s lignes et %s colonnes exportées", time.time()-start_time_export_bo, bo_df.shape[0], bo_df.shape[1])
self.logger.info("---- Exportation totale réalisée en : %d minutes et %d secondes ----\n", (time.time()-start_time_export)//60, (time.time()-start_time_export)%60)
return Response("Exportation terminée. Vous trouverez le fichier 'Données BO' exporté dans le dossier 'fichiers_exportes'.")
except (Http404, APIException):
raise
except BaseException:
message = "Imposible de réaliser l'exportation du fichier 'Données BO'. Vérifiez que les fichiers Excel d'exportation sont bien fermés."
self.logger.exception(message)
raise APIException(message)
@atomic
# @execution_time(logger_factory=data_perf_logger_factory)
@query_count(logger_factory=data_perf_logger_factory)
def export_reo(self, request):
self.logger.info("--------- Début de l'exportation du fichier 'REO' --------- ")
start_time_export = time.time()
postes_df = self.export_pos_reo_df()
reo_df = postes_df
if reo_df.empty:
return Response("Aucune donnée à extraire.")
else:
try:
self.logger.info("Exportation du fichier 'REO'...")
start_time_export_reo = time.time()
now = timezone.now().date()
reo_df.to_excel(f'..\\fichiers_exportes\\{now}_REO_export.xlsx', index=False, header=True)
reo_obj = FichiersExporte(nom_fichier=f'{now}_REO_export.xlsx')
reo_obj.save()
self.logger.info("Exportation du fichier 'REO' réalisée en %d secondes : %s lignes et %s colonnes exportées", time.time()-start_time_export_reo, reo_df.shape[0], reo_df.shape[1])
self.logger.info("------- Exportation totale réalisée en : %d secondes ------\n", time.time()-start_time_export)
return Response("Exportation terminée. Vous trouverez le fichier 'REO' exporté dans le dossier 'fichiers_exportes'.")
except (Http404, APIException):
raise
except BaseException:
message = "Imposible de réaliser l'exportation du fichier 'REO'. Vérifiez que les fichiers Excel d'exportation sont bien fermés."
self.logger.exception(message)
raise APIException(message)
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
nom_fichier = serializer.validated_data.get('fichier_exporte')
if nom_fichier == '1':
resp = self.export_bo(request)
return resp
if nom_fichier == '2':
resp = self.export_reo(request)
return resp
return Response("Imposible de réaliser l'exportation.")

View File

@@ -0,0 +1,110 @@
from django.forms import model_to_dict
from django.http import Http404
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from ..models import FMOB, Administre, FormationEmploi
from ..utils.decorators import class_logger
from .commun import (GestionnairePermission, execution_time_viewset,
query_count_viewset)
@class_logger
@execution_time_viewset
@query_count_viewset
class FicheDetailleeView(APIView):
"""
Cette classe est dédiée au vue de la fiche détaillée des administrés
"""
permission_classes = [IsAuthenticated, GestionnairePermission]
def get(self, request: Request) -> Response:
"""La fonction get recupére les infos administrés
:type request: rest_framework.request.Request
:param request: Request contenant l'identifiant de l'administré
:return: - **Response** (*objet*): informations concernant l'administré, informations sur le formulaire de mobilité.
"""
try:
res = {"result": "error"}
if 'administre_id' in request.query_params:
administre_id = request.query_params['administre_id']
annee_pam = request.query_params['pam__in']
administre_id_pam = request.query_params['administre_id'] + annee_pam
administre = Administre.objects.get(a_id_sap=administre_id)
res = model_to_dict(administre, fields=[field.name for field in administre._meta.fields if field != 'a_liste_id_competences'])
res['a_liste_id_competences'] = ','.join(list(administre.a_liste_id_competences.values_list('comp_libelle', flat=True)))
fmobs = FMOB.objects.filter(administre_id=administre_id_pam)
res["fmob"] = None
res['pam'] = None
if fmobs.exists():
fmob_dict = model_to_dict(fmobs[0])
res["fmob"] = fmob_dict
res["fe"] = None
fe = list(FormationEmploi.objects.filter(fe_code=administre.formation_emploi_id).values(
*('fe_code', 'fe_code_postal', 'fe_garnison_lieu', 'fe_libelle')))
# if fe.exists():
# fe = fe[0]
# garnison_dict = model_to_dict()
# res["garnison"] = garnison_dict
if len(fe):
res["fe"] = fe[0]
res["conjoint"] = None
conjoint = Administre.objects.filter(a_id_sap=administre.a_sap_conjoint)
if conjoint.exists():
self.logger.debug('has conjoint')
conjoint_dict = model_to_dict(conjoint[0])
conjoint_dict['pam'] = None
conjoint_dict["fe"] = None
fe_conjoint = list(FormationEmploi.objects.filter(fe_code=conjoint_dict['formation_emploi']).values(
*('fe_code', 'fe_code_postal', 'fe_garnison_lieu', 'fe_libelle')))
if len(fe):
conjoint_dict["fe"] = fe_conjoint[0]
# Verifier si le conjoint est formobé
conjoint_dict["fmob_O_N"] = "Oui" if FMOB.objects.filter(administre_id=str(conjoint_dict['a_id_sap']) + annee_pam).exists() else "Non"
res["conjoint"] = conjoint_dict
adm_affect = list(administre.adm_affec.values())
adm_affect_sorted = sorted(adm_affect, key=lambda d: d['affect_date'], reverse=True)
for i in range(len(adm_affect_sorted)):
res['a_affectation{}'.format(i + 1)] = adm_affect_sorted[i]['affect_libelle']
res['a_date_affectation{}'.format(i + 1)] = adm_affect_sorted[i]['affect_date']
adm_dip = list(administre.adm_dip.values())
adm_dip_sorted = sorted(adm_dip, key=lambda d: d['diplome_date'], reverse=True)
for i in range(len(adm_dip)):
res['a_diplome_{}'.format(i + 1)] = adm_dip_sorted[i]['diplome_libelle']
res['a_diplome_{}_date'.format(i + 1)] = adm_dip_sorted[i]['diplome_date']
res['a_diplome_{}_note'.format(i + 1)] = adm_dip_sorted[i]['diplome_note']
adm_fud = list(administre.adm_fud.values())
adm_fud_sorted = sorted(adm_fud, key=lambda d: d['fud_date_debut'], reverse=True)
for i in range(len(adm_fud)):
res['a_fud_{}_dd'.format(i + 1)] = adm_fud_sorted[i]['fud_date_debut']
res['a_fud_{}_df'.format(i + 1)] = adm_fud_sorted[i]['fud_date_fin']
res['a_fud_{}_l'.format(i + 1)] = adm_fud_sorted[i]['fud_libelle']
adm_not = list(administre.adm_not.values())
adm_not_sorted = sorted(adm_not, key=lambda d: d['no_age_annees'], reverse=False)
for i in range(len(adm_not_sorted)):
res['no_annne_de_notation_A_{}'.format(i + 1)] = adm_not_sorted[i]['no_annne_de_notation']
res['no_nr_ou_iris_A_{}'.format(i + 1)] = adm_not_sorted[i]['no_nr_ou_iris']
res['no_rac_ou_iris_cumule_A_{}'.format(i + 1)] = adm_not_sorted[i]['no_rac_ou_iris_cumule']
res['no_rf_qsr_A_{}'.format(i + 1)] = adm_not_sorted[i]['no_rf_qsr']
res['no_aptitude_emploie_sup_A_{}'.format(i + 1)] = adm_not_sorted[i]['no_aptitude_emploie_sup']
res['no_potentiel_responsabilite_sup_A_{}'.format(i + 1)] = adm_not_sorted[i]['no_potentiel_responsabilite_sup']
res['no_age_annees_A_{}'.format(i + 1)] = adm_not_sorted[i]['no_age_annees']
return Response(res)
except (Http404, APIException):
raise
except:
message = "impossible d'afficher la vue détaillée"
self.logger.exception(message)
raise APIException(message)

View File

@@ -0,0 +1,23 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
from ..models import FormationEmploi
from ..serializers.formation_emploi import FormationEmploiSerializer
from .commun import (GestionnairePermission, execution_time_viewset,
query_count_viewset)
@execution_time_viewset
@query_count_viewset
class FormationEmploiView(ModelViewSet):
"""
Cette classe est dédiée au vue des FormationEmplois.
"""
permission_classes = [IsAuthenticated, GestionnairePermission]
serializer_class = FormationEmploiSerializer
# important : mettre à jour quand le serializer change
def get_queryset(self):
Cols = FormationEmploi.Cols
return FormationEmploi.objects.select_related(Cols.REL_MERE)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
from django.shortcuts import get_object_or_404
from django.db.models import Q
from numpy import True_
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from ..models import Decision, DecisionChoices, Notation, Poste
from ..paginations import HeavyDataPagination
from ..serializers.notation import NotationSerializer
from .commun import (GestionnairePermission, execution_time_viewset,
query_count_viewset)
@execution_time_viewset
@query_count_viewset
class NotationView(ModelViewSet):
"""
Cette classe est dédiée au vue des notations.
"""
permission_classes = [IsAuthenticated, GestionnairePermission]
serializer_class = NotationSerializer
queryset = Notation.objects.all()
pagination_class = HeavyDataPagination
def list(self, request: Request, pk=None) -> Response:
"""La fonction list envoie le classement des postes pour un administré particulier et inversement.
:type request: rest_framework.request.Request
:param request: Request contenant l'administre ou le poste.
:return: - **JsonResponse** (*JsonResponse*): Json contenant le classement.
"""
notations_list = []
counter = 10
if 'administre_id' in request.query_params:
q = 'poste_pam'
q1 = 'poste'
administre_id = request.query_params['administre_id']
administres_keys = (
'poste_pam__id',
'poste_pam__poste__p_id',
'poste_pam__poste__p_nf',
'poste_pam__poste__p_domaine',
'poste_pam__poste__p_filiere',
'poste_pam__poste__p_eip',
'poste_pam__poste__formation_emploi__fe_code',
'poste_pam__poste__p_notes_gestionnaire',
'poste_pam__poste__p_liste_id_marques',
'poste_pam__poste__p_code_fonction',
'poste_pam__p_avis_pam',
'poste_pam__poste__p_dep',
'poste_pam__poste__formation_emploi__fe_libelle',
'poste_pam__poste__p_fonction',
'poste_pam__poste__formation_emploi__fe_garnison_lieu',
'poste_pam__poste__formation_emploi__fe_code_postal',
'no_score_administre',
'no_flag_cple_ideal')
notation_qs = (Notation.objects.filter(Q(administre_pam_id=administre_id) & Q(poste_pam__decisions__isnull=True))
.order_by('-no_score_administre')
.select_related('poste_pams'))
notation_qs_matching_parfait = (Notation.objects.filter(administre_pam_id=administre_id,
no_flag_cple_ideal=True,
poste_pam__decisions__isnull=True)
.select_related('poste_pams'))
notation_qs.union(notation_qs_matching_parfait)
notation_qs = notation_qs | notation_qs_matching_parfait
notations_list = list(notation_qs.values(*administres_keys))
for notation in notations_list:
poste_id = notation['poste_pam__id']
topCounter = 0
allNotationsInvolved = Notation.objects.filter(poste_pam_id=poste_id)
for note in allNotationsInvolved:
topList = list(
Notation.objects.filter(no_id=note.no_id).order_by('-no_score_administre').values('poste_pam_id'))
topPostes = [poste['poste_pam_id'] for poste in topList]
if poste_id in topPostes:
topCounter += 1
notation['poste__nb_top'] = topCounter
if 'poste_id' in request.query_params:
q = "administre_pam"
q1 = 'administre'
poste_id = request.query_params['poste_id']
postes_keys = (
'administre_pam__id',
'administre_pam__administre__a_id_sap',
'administre_pam__administre__a_nom',
'administre_pam__administre__a_prenom',
'administre_pam__a_statut_pam_annee',
'administre_pam__administre__grade_id',
'administre_pam__administre__a_liste_id_marques',
'administre_pam__decision__de_decision',
'administre_pam__decision__de_date_decision',
'no_score_administre',
'no_flag_cple_ideal',
'administre_pam__notes_pam',
'administre_pam__administre__a_notes_gestionnaire',
'administre_pam__administre__a_fonction',
'administre_pam__administre__a_code_fonction',
'administre_pam__administre__a_liste_id_marques',
'administre_pam__decision__poste_id')
notation_qs = (Notation.objects.filter(Q(poste_pam_id=poste_id) & Q(administre_pam__decision__isnull=True))
.order_by('-no_score_administre')
.select_related('administre_pam'))
notation_qs_matching_parfait = (Notation.objects.filter(poste_pam_id=poste_id,
no_flag_cple_ideal=True,
administre_pam__decision__isnull=True)
.select_related('administre_pam'))
notation_qs.union(notation_qs_matching_parfait)
notations_list = list(
notation_qs.values(*postes_keys))
for notation in notations_list:
administre_id = notation['administre_pam__id']
topCounter = 0
allNotationsInvolved = Notation.objects.filter(administre_pam_id=administre_id)
for note in allNotationsInvolved:
topList = list(Notation.objects.filter(no_id=note.no_id)
.order_by('-no_score_administre')
.values('administre_pam_id'))
topAdministres = [administre['administre_pam_id'] for administre in topList]
if administre_id in topAdministres:
topCounter += 1
notation['administre__nb_top'] = topCounter
result = []
for notation in notations_list:
res_notation = {q1: {}}
for key in notation:
if (q + "__") in key:
res_notation[q1][key.replace(q + '__', '').replace(q1 + '__', '')] = notation[key]
else:
res_notation[key] = notation[key]
result.append(res_notation)
return Response(result)

View File

@@ -0,0 +1,71 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from django.db.models import Q
from ..models import AvisPosteChoices as AvisPoste
from ..models import (Competence, Domaine, Filiere, FormationEmploi, Grade,
SousVivier, PAM,
Marque, MarquesGroupe, RefSvFil, SpecifiqueChoices)
from ..models import StatutFuturChoices as StatutFutur
from ..models import StatutPamChoices as StatutPam
from ..models import ZoneGeographique
from ..models import Fonction
from ..serializers import (ChoicesSerializer, RefAvisPosteChoicesSerializer,
RefStatutPamChoicesSerializer)
from ..serializers.commun import AssignmentState
from ..utils.decisions import get_all_decisions
from .commun import execution_time_viewset, query_count_viewset
@execution_time_viewset
@query_count_viewset
class ReferencesView(APIView):
"""
Cette classe est dédiée au vue de la reference.
"""
permission_classes = [IsAuthenticated]
def get(self, request):
"""
La fonction get envoie les données de référence
:return: réponse contenant les données de référence
"""
groupesMarques = [obj.as_dict() for obj in MarquesGroupe.objects.all().order_by('gm_code')]
marques = [obj.as_dict() for obj in Marque.objects.all().order_by('mar_code')]
competences = [obj.as_dict() for obj in Competence.objects.all().order_by('comp_id')]
domaines = [obj.as_dict() for obj in Domaine.objects.all().order_by('d_code')]
filieres = [obj.as_dict() for obj in Filiere.objects.all().order_by('domaine__d_code', 'f_code')]
zones = [obj.as_dict() for obj in ZoneGeographique.objects.all()]
# TODO: Vérifier la présence de l'ordre sur les postes'
grades = [obj.as_dict() for obj in Grade.objects.all()]
FEs = [obj.as_dict() for obj in FormationEmploi.objects.all().order_by('fe_mere_credo')]
garnison_lieux = [obj.as_dict() for obj in FormationEmploi.objects.all().order_by('fe_mere_credo', 'fe_garnison_lieu')]
RefSvFils_init = {obj.as_dict()['ref_sv_fil_code'] for obj in RefSvFil.objects.all().order_by('ref_sv_fil_code')}
RefSvFils = [{'ref_sv_fil_code': obj} for obj in RefSvFils_init]
Specifique = [{'code': i.value} for i in SpecifiqueChoices]
SousViviers = [obj.as_dict() for obj in SousVivier.objects.filter(~Q(sv_id='BVT')).order_by('sv_libelle')]
Pams = [obj.as_dict() for obj in PAM.objects.filter(~Q(pam_id='SORG')& Q(pam_statut__in=['PAM en cours', 'PAM A+1'])).order_by('pam_id')]
Etr = [obj.as_dict() for obj in Fonction.objects.all()]
return Response({
'groupesMarques': groupesMarques,
'marques': marques,
'domaines': domaines,
'filieres': filieres,
'grades': grades,
'competences': competences,
'FEs': FEs,
'statutFutur': ChoicesSerializer(StatutFutur, many=True).data,
'Zones': zones,
'decisions': ChoicesSerializer(get_all_decisions(), many=True).data,
'avisAdministre': RefStatutPamChoicesSerializer(StatutPam, many=True).data,
'avisPoste': RefAvisPosteChoicesSerializer(AvisPoste, many=True).data,
'RefSvFils': RefSvFils,
'Specifique': Specifique,
'etatsFeBmob': tuple(e.value for e in AssignmentState),
'sousViviers': SousViviers,
'pams': Pams,
'etr' : Etr,
})

View File

@@ -0,0 +1,426 @@
from datetime import datetime
from typing import Optional, Tuple
import pandas as pd
import requests
from django.conf import settings
from django.db.models import Q
from django.db.transaction import atomic
from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import status
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from ..models import Administre, StatutPamChoices
from ..models import AvisPosteChoices as AvisPoste
from ..models import Poste, SousVivier
from ..models.calcul import Calcul
from ..models.calcul import StatutCalculChoices as StatutCalcul
from ..serializers import ScoringSelectifValidator, ScoringValidator
from ..utils.decorators import class_logger
from ..utils_calcul import lancer_calculs, lancer_calculSelectif
from .commun import (GestionnairePermission, api_exception,
execution_time_viewset, query_count_viewset)
import _json
# @execution_time_viewset
# @query_count_viewset
@class_logger
class ScoringView(APIView):
"""
Cette classe est dédiée au vue de lancement pour le scoring et au calcul des scores des administrés à muter avec les postes à pourvoir.
"""
permission_classes = [IsAuthenticated, GestionnairePermission]
def __recuperer_chemin_requete(self, request: Request) -> str:
""" récupère et reconstruit le chemin de la requête (mais pas forcément tel que le client la connaît)
:type request: rest_framework.request.Request
:param request: Request contenant le port et l'adrresse ip du serveur
:return: - **return** (*string*): une chaine de caractère contenant le statut modifié du calcul.
"""
port = request.get_port()
return f"{request.scheme}://localhost{':' + port if port else ''}{request.path}"
def __appeler_api_depiler_calculs(self, request: Request) -> None:
""" appelle l'API pour lancer le prochain calcul en attente, ce qui permet d'attribuer l'opération à un worker indéfini
:type request: rest_framework.request.Request
:param request: Request contenant le chemin de la requète
"""
url = self.__recuperer_chemin_requete(request)
try:
requests.patch(url, headers=request.headers, cookies=request.COOKIES, timeout=0.001)
except requests.exceptions.ReadTimeout:
pass
except Exception as e:
self.logger.debug("échec de l'appel à %s: %s", url, e)
@atomic
def __creer_calcul(self, pam_id: str, sv_id: str) -> Calcul:
""" crée un calcul en base sauf s'il existe déjà un calcul en cours ou en attente pour le même sous-vivier
Raises
------
ApiException:
un calcul est déjà en cours ou en attente pour le même sous-vivier
IntegrityError:
la création a échoué
:type request: rest_framework.request.Request
:type sv_id: str
:param sv_id: clé primaire du sous vivier
:return: - **Object** (*Object*): retourne l'objet calcul crée
"""
Statut = StatutCalcul
COL_ID = 'id'
COL_SV = 'sous_vivier_id'
COL_PAM = 'pam_id'
COL_STATUT = 'ca_statut'
COL_DEBUT = 'ca_date_debut'
colonnes = (COL_ID, COL_SV, COL_PAM, COL_DEBUT, COL_STATUT)
df = pd.DataFrame.from_records(Calcul.objects.values_list(*colonnes), columns=colonnes)
en_cours = df[df[COL_STATUT] == Statut.EN_COURS][COL_SV].values
if sv_id in en_cours:
raise api_exception(status.HTTP_400_BAD_REQUEST, 'un calcul est déjà en cours pour ce sous-vivier')
en_attente = df[df[COL_STATUT] == Statut.EN_ATTENTE][COL_SV].values
if sv_id in en_attente:
raise api_exception(status.HTTP_400_BAD_REQUEST, 'un calcul est déjà en attente pour ce sous-vivier')
if en_attente.size:
calcul_autorise = False
else:
nb_max = getattr(settings, 'MAX_CALCULS', None)
calcul_autorise = not isinstance(nb_max, int) or len(en_cours) < nb_max
date_debut = timezone.now()
Calcul.objects.filter(Q(id=str(sv_id)+str(pam_id)) & Q(pam_id=pam_id) & Q(sous_vivier_id=sv_id) & ~Q(ca_statut__in=(Statut.EN_COURS, Statut.EN_ATTENTE))).delete()
if calcul_autorise:
return Calcul.objects.create(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id, ca_date_debut=date_debut, ca_statut_pourcentage=0, ca_statut=Statut.EN_COURS)
return Calcul.objects.create(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id, ca_date_debut=date_debut, ca_statut_pourcentage=0, ca_statut=Statut.EN_ATTENTE)
@atomic
def __creer_calculSelectif(self, sv_id: str, pam_id: str, l_a_id: list, l_p_id: list) -> Calcul:
""" crée un calcul en base sauf s'il existe déjà un calcul en cours ou en attente pour le même sous-vivier
Raises
------
ApiException:
un calcul est déjà en cours ou en attente pour le même sous-vivier
IntegrityError:
la création a échoué
:type request: rest_framework.request.Request
:type sv_id: str
:param sv_id: clé primaire du sous vivier
:return: - **Object** (*Object*): retourne l'objet calcul crée
"""
Statut = StatutCalcul
COL_ID = 'id'
COL_SV = 'sous_vivier_id'
COL_STATUT = 'ca_statut'
COL_DEBUT = 'ca_date_debut'
COL_PAM = 'pam_id'
colonnes = (COL_ID, COL_SV, COL_DEBUT, COL_STATUT, COL_PAM)
df = pd.DataFrame.from_records(Calcul.objects.values_list(*colonnes), columns=colonnes)
en_cours = df[df[COL_STATUT] == Statut.EN_COURS][COL_SV].values
if l_a_id and l_p_id:
if sv_id in en_cours:
raise api_exception(status.HTTP_400_BAD_REQUEST, 'un calcul est déjà en cours pour ce sous-ensemble')
en_attente = df[df[COL_STATUT] == Statut.EN_ATTENTE][COL_SV].values
if sv_id in en_attente:
raise api_exception(status.HTTP_400_BAD_REQUEST, 'un calcul est déjà en attente pour ce sous-ensemble')
if en_attente.size:
calcul_autorise = False
else:
nb_max = getattr(settings, 'MAX_CALCULS', None)
calcul_autorise = not isinstance(nb_max, int) or len(en_cours) < nb_max
date_debut = timezone.now()
Calcul.objects.filter(Q(id=str(sv_id)+str(pam_id)+'selectif') & Q(pam_id=pam_id) & Q(sous_vivier_id=sv_id) & ~Q(ca_statut__in=(Statut.EN_COURS, Statut.EN_ATTENTE))).delete()
if calcul_autorise:
return Calcul.objects.create(id=str(sv_id)+str(pam_id)+'selectif',pam_id=pam_id,sous_vivier_id=sv_id, ca_date_debut=date_debut, ca_statut_pourcentage=0, ca_statut=Statut.EN_COURS)
return Calcul.objects.create(id=str(sv_id)+str(pam_id)+'selectif',pam_id=pam_id,sous_vivier_id=sv_id, ca_date_debut=date_debut, ca_statut_pourcentage=0, ca_statut=Statut.EN_ATTENTE)
@atomic
def __modifier_statut_premier_calcul_en_attente(self) -> Optional[Tuple[str, datetime]]:
""" lance le calcul en attente le plus ancien (si possible). Il ne s'agit que d'une modification en base.
(ID de sous-vivier, date de début) du calcul lancé ou None si aucun calcul ne peut être lancé
:type request: rest_framework.request.Request
:type sv_id: str
:param sv_id: clé primaire du sous vivier
:type ca_date_debut : date
:param ca_date_debut: date de début calcul en attente
:return: - **Object** (*Object*): retourne l'objet calcul crée
Raises
------
ApiException:
un calcul est déjà en cours ou en attente pour le même sous-vivier
IntegrityError:
la création a échoué
"""
Statut = StatutCalcul
COL_ID = 'id'
COL_SV = 'sous_vivier_id'
COL_STATUT = 'ca_statut'
COL_DEBUT = 'ca_date_debut'
COL_PAM = 'pam_id'
colonnes = (COL_ID, COL_SV, COL_PAM, COL_DEBUT, COL_STATUT)
df = pd.DataFrame.from_records(Calcul.objects.order_by(COL_DEBUT).values_list(*colonnes), columns=colonnes)
en_attente = df[df[COL_STATUT] == Statut.EN_ATTENTE]
if en_attente.empty:
return None
en_cours = df[df[COL_STATUT] == Statut.EN_COURS][COL_SV].values
nb_max = getattr(settings, 'MAX_CALCULS', None)
calcul_autorise = not isinstance(nb_max, int) or en_cours.size < nb_max
if not calcul_autorise:
return None
a_lancer = en_attente.iloc[0]
sv_id = a_lancer[COL_ID]
date_attente = a_lancer[COL_DEBUT]
date_debut = timezone.now()
# la date de début est mise à jour aussi pour éviter de compter le temps d'attente
mis_a_jour = (
Calcul.objects
.filter(sous_vivier_id=sv_id, ca_date_debut=date_attente, ca_statut=Statut.EN_ATTENTE)
.update(ca_date_debut=date_debut, ca_statut=Statut.EN_COURS)
)
return (sv_id, date_debut) if mis_a_jour else None
def __executer_calcul(self, pam_id: str,sv_id: str, date_debut: datetime) -> Calcul:
""" exécute un calcul (ID de sous-vivier, date de début) du calcul lancé ou None si aucun calcul ne peut être lancé
:type request: rest_framework.request.Request
:type sv_id: str
:param sv_id: clé primaire du sous vivier
:type ca_date_debut : date
:param ca_date_debut: date de début calcul en attente
:return: - **Object** (*Object*): retourne l'objet calcul ainsi que son statut
"""
Statut = StatutCalcul
qs_calcul = Calcul.objects.filter(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id, ca_date_debut=date_debut)
qs_en_cours = qs_calcul.filter(ca_statut=Statut.EN_COURS)
try:
lancer_calculs(pam_id,sv_id)
except SystemExit:
self.logger.info("OGURE M'A TUER")
qs_en_cours.update(ca_date_fin=timezone.now(), ca_statut=Statut.TERMINE_DE_FORCE)
raise
except Exception as e:
qs_calcul = Calcul.objects.filter(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id, ca_date_debut=date_debut)
if str(e) == "Arret du calcul":
qs_calcul.update(ca_date_fin=timezone.now(), ca_statut=Statut.TERMINE_DE_FORCE)
raise
else:
qs_calcul.update(ca_date_fin=timezone.now(), ca_statut=Statut.ERREUR)
raise
qs_en_cours.update(ca_date_fin=timezone.now(), ca_statut=Statut.TERMINE)
return qs_calcul.first()
def __executer_calculSelectif(self, sv_id: str, pam_id: str, l_a_id: list, l_p_id: list, date_debut: datetime) -> Calcul:
""" exécute un calcul (ID de sous-vivier, date de début) du calcul lancé ou None si aucun calcul ne peut être lancé
:type request: rest_framework.request.Request
:type sv_id: str
:param sv_id: clé primaire du sous vivier
:type ca_date_debut : date
:param ca_date_debut: date de début calcul en attente
:return: - **Object** (*Object*): retourne l'objet calcul ainsi que son statut
"""
Statut = StatutCalcul
qs_calcul = Calcul.objects.filter(id=str(sv_id)+str(pam_id)+'selectif',sous_vivier_id=sv_id, pam_id=pam_id, ca_date_debut=date_debut)
qs_en_cours = qs_calcul.filter(ca_statut=Statut.EN_COURS)
try:
lancer_calculSelectif(sv_id, pam_id, l_a_id, l_p_id)
except SystemExit:
qs_en_cours.update(ca_date_fin=timezone.now(), ca_statut=Statut.TERMINE_DE_FORCE)
raise
except Exception as e:
qs_calcul = Calcul.objects.filter(id=str(sv_id)+str(pam_id)+'selectif',sous_vivier_id=sv_id, pam_id=pam_id,ca_date_debut=date_debut)
if str(e) == "Arret du calcul":
qs_calcul.update(ca_date_fin=timezone.now(), ca_statut=Statut.TERMINE_DE_FORCE)
raise
else:
qs_calcul.update(ca_date_fin=timezone.now(), ca_statut=Statut.ERREUR)
raise
qs_en_cours.update(ca_date_fin=timezone.now(), ca_statut=Statut.TERMINE)
return qs_calcul.first()
def get(self, request: Request) -> Response:
"""La fonction get vérifie s'il y a un calcul en cours et renvoie des informations sur le calcul.
:type request: rest_framework.request.Request
:param request: Request contenant l'identifiant du sous vivier
:return: - réponse contenant les informations sur le calcul comme statut, date_debut, date_fin, administres et postes.
"""
debut = None
fin = None
administres = 0
postes = 0
try:
sv_id = request.query_params['sous_vivier_id']
pam_id = request.query_params['pam_id']
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id)
debut = calcul.ca_date_debut
fin = calcul.ca_date_fin
administres_statuts = [StatutPamChoices.A_MUTER,StatutPamChoices.A_ETUDIER]
administres = Administre.objects.filter(pam__pam_id=pam_id,sous_vivier_id=sv_id,a_statut_pam__in=administres_statuts).count()
postes = Poste.objects.values_list('p_avis').filter(Q(p_pam__pam_id=pam_id) & Q(sous_viviers=sv_id) & Q(p_avis__in=[AvisPoste.P1,AvisPoste.P2,AvisPoste.P3,AvisPoste.P4])).count()
statut = calcul.ca_statut
statut_pourcentage = calcul.ca_statut_pourcentage
except:
statut = StatutCalcul.AUCUN
statut_pourcentage = 0
return Response(
{"statut": statut, "statut_pourcentage": statut_pourcentage, "date_debut": debut, "date_fin": fin, "administres": administres, "postes": postes})
def patch(self, request: Request, id=None) -> Response:
"""La fonction patch est une méthode privée qui execute des méthodes privées qui exécute le calcul et le dépile.
:type request: rest_framework.request.Request
:param request: Request contenant l'identifiant du sous vivier et la date de dbut du calcul
"""
Statut = StatutCalcul
try:
id_et_debut = self.__modifier_statut_premier_calcul_en_attente()
if id_et_debut is None:
return Response({'message': "aucun calcul n'a été lancé"})
try:
calcul = self.__executer_calcul(id_et_debut[0], id_et_debut[1])
return Response({'statut': calcul.ca_statut if calcul else Statut.AUCUN})
finally:
self.__appeler_api_depiler_calculs(request)
except (Http404, APIException):
raise
except SystemExit:
raise APIException('lancement du prochain calcul arrêté')
except:
message = 'impossible de lancer le prochain calcul en attente'
self.logger.exception(message)
raise APIException(message)
def post(self, request: Request) -> Response:
""" lance le calcul sur un sous vivier
:type request: rest_framework.request.Request
:param request: Request contenant l'identifiant du sous vivier
:type sv_id: str
:param sv_id: clé primaire du sous vivier
:return: - **return** (*Response*): retourne le statut des postes
"""
try:
# Gestion des 2 cas, calculs sélectifs ou calculs sur l'ensemble du sous-vivier
if 'administre_id' in request.data.keys() and 'poste_id' in request.data.keys():
Statut = StatutCalcul
validator = ScoringSelectifValidator(data=request.data)
validator.is_valid(raise_exception=True)
sv_id = validator.validated_data.get('sous_vivier_id')
pam_id = validator.validated_data.get('pam_id')
get_object_or_404(SousVivier.objects, sv_id=sv_id)
l_a_id = validator.validated_data.get('administre_id')
l_p_id = validator.validated_data.get('poste_id')
# calcul
calcul = self.__creer_calculSelectif(sv_id, pam_id, l_a_id, l_p_id)
if calcul.ca_statut == Statut.EN_COURS:
try:
calcul = self.__executer_calculSelectif(sv_id, pam_id, l_a_id, l_p_id, calcul.ca_date_debut)
except Exception as e:
Statut= Statut.ERREUR
calcul.ca_statut = Statut
calcul.save()
error = str(e).split(',')
return JsonResponse({'info': error[0]})
finally:
self.__appeler_api_depiler_calculs(request)
else:
Statut = StatutCalcul
validator = ScoringValidator(data=request.data)
# validation
validator.is_valid(raise_exception=True)
sv_id = validator.validated_data.get('sous_vivier_id')
pam_id = validator.validated_data.get('pam_id')
get_object_or_404(SousVivier.objects, sv_id=sv_id)
# calcul
calcul = self.__creer_calcul(pam_id,sv_id)
if calcul.ca_statut == Statut.EN_COURS:
try:
calcul = self.__executer_calcul(pam_id,sv_id, calcul.ca_date_debut)
except Exception as e:
Statut= Statut.ERREUR
calcul.ca_statut = Statut
calcul.save()
error = str(e).split(',')
return JsonResponse({'info': error[0]})
finally:
self.__appeler_api_depiler_calculs(request)
return Response({'statut': calcul.ca_statut if calcul else Statut.AUCUN})
except (Http404, APIException):
raise
except SystemExit:
raise APIException('calcul arrêté')
except:
message = 'impossible de lancer le calcul'
self.logger.exception(message)
raise APIException(message)
@class_logger
@execution_time_viewset
@query_count_viewset
class ArretCalcul(APIView):
"""
Cette classe est dédiée a l'arrêt du calcul.
"""
permission_classes = [IsAuthenticated, GestionnairePermission]
def post(self, request):
"""La fonction post change le statut d'un calcul à EN_ATTENTE_ARRET
:type request: rest_framework.request.Request
:param request: Request contenant l'identifiant du sous vivier
:return: - **return** (*json*): json contenant le statut modifié du calcul.
"""
Statut = StatutCalcul
sv_id = request.data['sous_vivier_id']
pam_id = request.data['pam_id']
calcul = Calcul.objects.get(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id)
if Calcul.objects.filter(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id).exists() and calcul.ca_statut == Statut.EN_COURS:
calcul.ca_statut = Statut.EN_ATTENTE_ARRET
calcul.save()
elif Calcul.objects.filter(id=str(sv_id)+str(pam_id),pam_id=pam_id,sous_vivier_id=sv_id).exists() and calcul.ca_statut == Statut.EN_ATTENTE:
calcul.ca_statut = Statut.TERMINE
calcul.save()
return Response({"statut": calcul.ca_statut})

View File

@@ -0,0 +1,70 @@
import pandas as pd
from django.db.transaction import atomic
from django.http import Http404
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from ..serializers import SuppressionAdministresSerializer
from ..utils.alimentation_decorators import (data_perf_logger_factory,
get_data_logger)
from ..utils.decorators import execution_time, query_count
from ..utils_extraction import (DataFrameTypes, FileTypes, read_files_by_type,
to_table_suppression_administres)
from ..utils_insertion import suppression_administres
class SuppressionAdministresView(APIView):
""" Vue pour supprimer les administrés de la base """
permission_classes = [IsAuthenticated, IsAdminUser]
serializer_class = SuppressionAdministresSerializer
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_data_logger(self)
def get(self, request):
return Response("Formulaire d\'OGURE NG permettant de supprimer des administés de la base de données")
@atomic
@execution_time(logger_factory=data_perf_logger_factory)
@query_count(logger_factory=data_perf_logger_factory)
def post(self, request):
"""
Charge le(s) fichier(s) et met à jour la base.
:param request: requête, contient les fichiers
:type request: class:`rest_framework.request.Request`
:raises: class:`rest_framework.exceptions.APIException`
:return: réponse
:rtype: class:`rest_framework.response.Response`
"""
try:
validator = self.serializer_class(data=request.data)
validator.is_valid(raise_exception=True)
adm_suppr = 0
df_adm_suppr = read_files_by_type({
FileTypes.ADM_SUPPR: validator.validated_data.get('administres')
}).get(DataFrameTypes.ADM_SUPPR)
if df_adm_suppr is not None:
df = to_table_suppression_administres(df_adm_suppr)
self.logger.info('Extraction des administés à supprimer ------> Succès')
adm_suppr = suppression_administres(df)
self.logger.info('Suppression des administrés ------> Succès')
else:
self.logger.info('Mise à jour ignorée : suppression des administrés')
return Response({f'Suppression de {adm_suppr} administré(s) réussie'})
except (Http404, APIException):
raise
except BaseException:
message = "Echec de la suppression"
self.logger.exception(message)
raise APIException(message)