Files
test_OgureNG/backend-django/backend/views.py
2022-11-08 21:19:51 +01:00

1658 lines
75 KiB
Python

import numpy as np
from asgiref.sync import sync_to_async
from django.db.models import ManyToOneRel, Window, F, Q, Sum, Case, When, IntegerField, Prefetch
from django.db.models.functions import Rank
from django.forms import model_to_dict
from django.http import JsonResponse
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, viewsets, serializers, status
from rest_framework import viewsets, status
from rest_framework.views import APIView
from rest_framework.response import Response
import pandas as pd
from . import constants
from django.db.models import Q
from .filters import AdministreFilter, RelatedOrderingFilter, PosteFilter
from .paginations import HeavyDataPagination
from .serializers import AdministreSerializer, FileCompetenceSerializer, FmobSerializer, FormationEmploiSerializer, \
PcpFeGroupeSerializer, \
PosteSerializer, \
NotationSerializer, MarqueSerializer, MarquesGroupeSerializer, FileSerializer, \
FiliereSerializer, DomaineSerializer, FileSVSerializer, DecisionSerializer, \
SousVivierAssociationSerializer, ScoringValidator, AlimentationReferentielSerializer
from .models import Administre, Competence, FMOB, FormationEmploi, PcpFeGroupe, Poste, Notation, \
PreferencesListe, Marque, MarquesGroupe, \
Filiere, Domaine, SousVivier, Grade, CustomUser, Decision, Calcul, SousVivierAssociation, \
StatutPamChoices as StatutPam
from .utils import cleanString, impact_decisions
from .utils_extraction import to_table_administres, to_table_domaines, to_table_fe, to_table_filieres, \
to_table_fmob, to_table_fonctions, to_table_garnisons, to_table_grades, \
to_table_fmob, to_table_fonctions, to_table_garnisons, to_table_grades, \
to_table_liste_preference, \
to_table_groupesMarques, to_table_marques, to_table_postes, process_files, open_excel
from .utils_insertion import insert_Administre, insert_Domaine, insert_FMOB, insert_Filiere, insert_Fonction, \
insert_FormationEmploi, insert_Garnison, insert_Grade, insert_MarquesGroupe, insert_Notation, \
insert_Marque, insert_Poste, insert_liste_preference, insert_matching
from .utils_matching import matching_parfait, preprocess_matching
from .utils_calcul import lancer_calculs, notations
from .reporting import poste_vacant_vivier, reporting_taux_armement_pcp, reporting_taux_armement_gestionnaire, \
reporting_suivi_pam_admin, \
reporting_suivi_pam_poste
import json
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from datetime import datetime, date
from django.conf import settings
from django.db.transaction import atomic
from django.http import Http404
from rest_framework import serializers, status
from rest_framework.exceptions import APIException
from typing import Optional, Tuple
import logging
import requests
import time
logger = logging.getLogger(__name__)
def api_exception(status_code: int, message: Optional[str] = None) -> APIException:
return type('MyException', (APIException,), {'status_code': status_code, 'default_detail': message})
# Vue de lancement pour le scoring
# Calcul les scores des administrés à muter pour les postes à pourvoir et stocke les résultats dans la table Calcul
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.
"""
def __recuperer_chemin_requete(self, 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):
""" 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)
headers = request.headers
cookies = request.COOKIES
try:
requests.patch(url, headers=request.headers, cookies=request.COOKIES, timeout=0.001)
except requests.exceptions.ReadTimeout:
pass
except Exception as e:
logger.debug("échec de l'appel à %s: %s", url, e)
@atomic
def __creer_calcul(self, 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 = Calcul.Statut
COL_ID = 'sous_vivier_id'
COL_STATUT = 'ca_statut'
COL_DEBUT = 'ca_date_debut'
colonnes = (COL_ID, 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_ID].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_ID].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(sous_vivier_id=sv_id) & ~Q(ca_statut__in=(Statut.EN_COURS, Statut.EN_ATTENTE))).delete()
if calcul_autorise:
return Calcul.objects.create(sous_vivier_id=sv_id, ca_date_debut=date_debut,ca_statut_pourcentage =0 , ca_statut=Statut.EN_COURS)
return Calcul.objects.create(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 = Calcul.Statut
COL_ID = 'sous_vivier_id'
COL_STATUT = 'ca_statut'
COL_DEBUT = 'ca_date_debut'
colonnes = (COL_ID, 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_ID].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, 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 = Calcul.Statut
COL_ID = 'sous_vivier_id'
COL_STATUT = 'ca_statut'
COL_DEBUT = 'ca_date_debut'
qs_calcul = Calcul.objects.filter(sous_vivier_id=sv_id, ca_date_debut=date_debut)
qs_en_cours = qs_calcul.filter(ca_statut=Statut.EN_COURS)
try:
lancer_calculs(sv_id)
except SystemExit:
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(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.ARRETER)
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):
"""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: - **return** (*JsonResponse*): json 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']
calcul = Calcul.objects.get(sous_vivier_id=sv_id)
debut = calcul.ca_date_debut
fin = calcul.ca_date_fin
administres_statuts = ['A_MUTER']
administres = Administre.objects.filter(sous_vivier_id=sv_id, a_statut_pam__in=administres_statuts).count()
postes = Poste.objects.filter(Q(sous_vivier_id=sv_id) & (
Q(p_nb_p1__gt=0) | Q(p_nb_p2__gt=0) | Q(p_nb_p3__gt=0) | Q(p_nb_p4__gt=0) | Q(
p_nb_vacant__gt=0))).count()
statut = calcul.ca_statut
statut_pourcentage = calcul.ca_statut_pourcentage
except:
statut = "AUCUN"
statut_pourcentage = 0
return JsonResponse(
{"statut": statut, "statut_pourcentage": statut_pourcentage, "date_debut": debut, "date_fin": fin, "administres": administres, "postes": postes})
def patch(self, request, id=None):
"""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 = Calcul.Statut
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 api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, 'lancement du prochain calcul arrêté')
except:
message = 'impossible de lancer le prochain calcul en attente'
logger.exception(message)
raise api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, message)
def post(self, request):
""" 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
"""
Statut = Calcul.Statut
try:
# validation
validator = ScoringValidator(data=request.data)
validator.is_valid(raise_exception=True)
sv_id = validator.validated_data.get('sous_vivier_id')
generics.get_object_or_404(SousVivier.objects, sv_id=sv_id)
# calcul
calcul = self.__creer_calcul(sv_id)
if calcul.ca_statut == Statut.EN_COURS:
try:
calcul = self.__executer_calcul(sv_id, calcul.ca_date_debut)
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 api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, 'calcul arrêté')
except:
message = 'impossible de lancer le calcul'
logger.exception(message)
raise api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, message)
class ArretCalul(APIView):
"""
Cette classe est dédiée a l'arret du calcul.
"""
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.
"""
sv_id = request.data['sous_vivier_id']
calcul = Calcul.objects.get(sous_vivier_id=sv_id)
if Calcul.objects.filter(sous_vivier_id=sv_id).exists() and calcul.ca_statut == 'EN_COURS':
calcul.ca_statut = 'EN_ATTENTE_ARRET'
calcul.save()
elif Calcul.objects.filter(sous_vivier_id=sv_id).exists() and calcul.ca_statut == 'EN_ATTENTE':
calcul.ca_statut = 'ARRETER'
calcul.save()
return JsonResponse(
{"statut": calcul.ca_statut})
# Vue pour la fiche détaillée des administrés
class FicheDetailleeView(APIView):
"""
Cette classe est dédiée au vue de la fiche détaillée des administrés
"""
def get(self, request):
"""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é.
"""
res = {"result": "error"}
if 'administre_id' in request.query_params:
administre_id = request.query_params['administre_id']
try:
administre = Administre.objects.get(a_id_sap=administre_id)
res = model_to_dict(administre)
fmobs = FMOB.objects.filter(administre_id=administre_id).order_by('-fmob_millesime')
res["fmob"] = 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', 'garnison__gar_code_postal', 'garnison__gar_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():
print('has conjoint')
conjoint_dict = model_to_dict(conjoint[0])
conjoint_dict["fe"] = None
fe_conjoint = list(FormationEmploi.objects.filter(fe_code=conjoint[0].formation_emploi_id).values(
*('fe_code', 'garnison__gar_code_postal', 'garnison__gar_lieu', 'fe_libelle')))
if len(fe):
conjoint_dict["fe"] = fe_conjoint[0]
res["conjoint"] = conjoint_dict
except Exception as e:
print(e)
print(e.args)
print(res)
# TODO: return error
return Response(res)
# Vue de calcul des indicateurs
# Renvoie les indicateurs pour les vues :
# - taux armement FE pour les PCP
# - taux armement FE pour les gestionnaires
# - suivi pam des gestionnaires pour les administrés
# - suivi pam des gestionnaires pour les postes
class ReportingView(APIView):
"""
Cette classe est dédiée au vue du calcul des indicateurs
- taux armement FE pour les PCP
- taux armement FE pour les gestionnaires
- suivi pam des gestionnaires pour les administrés
- suivi pam des gestionnaires pour les postes
"""
def get(self, request):
"""La fonction get renvoie les indicateurs
:type request: rest_framework.request.Request
:param request: Request contenant le type, sous_vivier id, niveau fonctinonel et l'id de la formation emploi.
:return: - **return** (*json*): Si le type est SUIVI_PAM_GESTIONNAIRE le json contiendra l'information sur le suivi pam des
gestionnaires pour les administrés et les postes. Si le type est TAUX_ARMEMENT_FE le json contiendra les indicateurs sur les taux d'armement.
"""
type = request.query_params["type"]
sv_id = []
if 'sv_id' in request.query_params:
sv_id = request.query_params["sv_id"]
if 'vue' in request.query_params:
vue = request.query_params["vue"]
if "nf" in request.query_params:
nf = request.query_params.getlist("nf")
else:
nf = ['1A', '1B', '1C', '2.', '3A', '3B', '3B NFS', '4.', '5A', '5B', '5C', '6A', '6B']
if 'fe_id' in request.query_params: # a voir
fe_id = request.query_params.getlist('fe_id')
else:
fe_id = list(FormationEmploi.objects.values_list('fe_code', flat=True))
# Vues pour les indicateurs sur le suivi du pam
if (type == "SUIVI_PAM_GESTIONNAIRE"):
if "d_code" in request.query_params:
d_id = request.query_params.getlist("d_code")
else:
filieres = list(SousVivierAssociation.objects.filter(sous_vivier__sv_id=sv_id).values_list('filiere_id',
flat=True).distinct())
d_id = list(Filiere.objects.filter(f_code__in=filieres).values_list('domaine_id', flat=True).distinct())
if "f_code" in request.query_params:
f_id = request.query_params.getlist("f_code")
else:
f_id = list(SousVivierAssociation.objects.filter(sous_vivier__sv_id=sv_id).values_list('filiere_id',
flat=True).distinct())
if "categorie" in request.query_params:
categorie = request.query_params.getlist("categorie")
else:
categorie = list(
SousVivierAssociation.objects.filter(sous_vivier__sv_id=sv_id).values_list('sva_categorie',
flat=True).distinct())
# résultats pour la vue suivi pam des gestionnaires pour les administrés
nb_a_etudier, nb_a_muter, nb_a_maintenir, nb_non_etudie_administres, nb_a_partant, nb_a_non_dispo, nb_prepos_administres, nb_pos_administres, nb_omi_active_administres, nb_omi_en_cours_administres, reste_a_realiser_administres, reste_a_realiser_a_etudier, reste_a_realiser_a_muter = reporting_suivi_pam_admin(
sv_id, f_id, d_id, nf, categorie)
# résultats pour la vue suivi pam des gestionnaires pour les postes
nb_p1, nb_p2, nb_p3, nb_p4, nb_gele, nb_non_etudie_postes, reste_a_realiser_postes, reste_a_realiser_p1, reste_a_realiser_p2, reste_a_realiser_p3, reste_a_realiser_p4, nb_prepos_postes, nb_pos_postes, nb_omi_active_postes, nb_omi_en_cours_postes = reporting_suivi_pam_poste(
sv_id, f_id, d_id, nf, categorie)
return JsonResponse(
{"nb_a_etudier": nb_a_etudier, "nb_a_muter": nb_a_muter, "nb_a_maintenir": nb_a_maintenir,
"nb_a_partant": nb_a_partant, "nb_a_non_dispo": nb_a_non_dispo,
"nb_non_etudie_administres": nb_non_etudie_administres,
"nb_prepos_administres": nb_prepos_administres, "nb_pos_administres": nb_pos_administres,
"nb_omi_en_cours_administres": nb_omi_en_cours_administres,
"nb_omi_active_administres": nb_omi_active_administres,
"reste_a_realiser_administres": reste_a_realiser_administres,
"reste_a_realiser_a_etudier": reste_a_realiser_a_etudier,
"reste_a_realiser_a_muter": reste_a_realiser_a_muter,
"nb_p1": nb_p1, "nb_p2": nb_p2, "nb_p3": nb_p3, "nb_p4": nb_p4, "nb_gele": nb_gele,
"nb_non_etudie_postes": nb_non_etudie_postes, "reste_a_realiser_postes": reste_a_realiser_postes,
"reste_a_realiser_p1": reste_a_realiser_p1, "reste_a_realiser_p2": reste_a_realiser_p2,
"reste_a_realiser_p3": reste_a_realiser_p3, "reste_a_realiser_p4": reste_a_realiser_p4,
"nb_prepos_postes": nb_prepos_postes, "nb_pos_postes": nb_pos_postes,
"nb_omi_en_cours_postes": nb_omi_en_cours_postes, "nb_omi_active_postes": nb_omi_active_postes},
safe=False)
# Vues pour les indicateurs sur les taux d'armement
elif (type == 'TAUX_ARMEMENT_FE'):
if "d_code" in request.query_params:
d_id = request.query_params.getlist("d_code")
else:
d_id = list(Domaine.objects.all().values_list('d_code', flat=True))
if "f_code" in request.query_params:
f_id = request.query_params.getlist("f_code")
else:
f_id = list(Filiere.objects.all().values_list('f_code', flat=True))
if "categorie" in request.query_params:
categorie = request.query_params.getlist("categorie")
else:
categorie = ['MDR', 'SOFF', 'OFF', 'OGX']
if len(sv_id) == 0:
# résultats pour la vue taux armement FE pour les PCP
nb_militaires_actuel, nb_postes_actuel, ecart_actuel, taux_armement_actuel, nb_militaires_entrants, nb_militaires_sortants, nb_militaires_projete, taux_armement_projete, taux_armement_cible = reporting_taux_armement_pcp(
fe_id, f_id, d_id, nf, categorie)
else:
# résultats pour la vue taux armement FE pour les gestionnaires
nb_militaires_actuel, nb_postes_actuel, ecart_actuel, taux_armement_actuel, nb_militaires_entrants, nb_militaires_sortants, nb_militaires_projete, taux_armement_projete, taux_armement_cible = reporting_taux_armement_gestionnaire(
fe_id, f_id, d_id, nf, categorie, sv_id)
return JsonResponse({'nb_militaires_actuel': nb_militaires_actuel, "nb_postes_actuel": nb_postes_actuel,
'ecart_actuel': ecart_actuel, 'taux_armement_actuel': taux_armement_actuel,
'nb_militaires_entrants': nb_militaires_entrants,
'nb_militaires_sortants': nb_militaires_sortants,
'nb_militaires_projete': nb_militaires_projete,
'taux_armement_projete': taux_armement_projete,
'taux_armement_cible': taux_armement_cible}, safe=False)
else:
return JsonResponse({"autre vue": "autres indicateurs demandes"})
# Vue pour afficher le nombre de postes vacants par fe et sous-vivier
class PosteVacantView(APIView):
"""
Cette classe est dédiée au vue pour afficher le nombre de postes vacants par fe et sous-vivier
"""
def get(self, request):
"""La fonction get renvoie les postes vaccants
:type request: rest_framework.request.Request
:param request: Requset contenant l'identifiant de la formation emploi et l'identifiant du sous-vivier.
:return: - **return** (*json*): json contenant le nombre de poste vaccant du sous vivier.
"""
fe_id = request.query_params["fe_id"]
sv_id = request.query_params["sv_id"]
nb_poste_vacant_vivier = poste_vacant_vivier(sv_id, fe_id)
return JsonResponse({'nb_poste_vacant_vivier': nb_poste_vacant_vivier}, safe=False)
# Vue de chargement des sous-viviers :
# - Charge et traite le fichier de définition des sous-viviers
# - Attribue les sous-viviers présents aux administrés et postes correspondants
class ChargementSVView(APIView):
"""
Cette classe est dédiée au vue de chargement des sous-viviers. Charge et traite le fichier de définition des sous-viviers et attribue les sous-viviers présents aux administrés et postes correspondants
"""
serializer_class = FileSVSerializer
def get(self, request):
"""La fonction get renvoie une reponse contenant ok
:type request: rest_framework.request.Request
:param request: Requset
:return: - **return** (*json*): json contenant "ok".
"""
return Response({"get": "ok"})
def post(self, request):
"""La fonction post charge les sous-viviers
:type request: rest_framework.request.Request
:param request: Request contenant le fichier SV
:return: - **Response** (*Response*): Reponse contient la liste des sous-viviers créés, ignorés et où il y a eu une erreur.
"""
serializer = FileSVSerializer(data=request.data)
if not serializer.is_valid():
return Response(
data=serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
sv_file = request.data['SV']
sv_df = open_excel(sv_file, sheetname="GESTIONNAIRES", engine='openpyxl')
sv = pd.DataFrame(columns=['gestionnaire_id_sap', 'f_code', 'asso_sv_categorie', 'arme'])
for i in range(len(sv_df)):
sv.loc[i] = [sv_df.iloc[i, 0], sv_df.iloc[i, 3], sv_df.iloc[i, 4], sv_df.iloc[i, 5]]
sv.dropna(subset=['f_code'], inplace=True)
sv['arme'] = sv['arme'].replace({np.nan: None})
sv.reset_index(drop=True, inplace=True)
sv['asso_sv_categorie'].replace({"SOUS-OFFICIER": "SOFF", "OFFICIER": "OFF", "MILITAIRE DU RANG": "MDR"},
inplace=True)
sv['sv_id'] = sv.f_code + '_' + sv.asso_sv_categorie
sv.f_code = sv.f_code.apply(lambda x: x.split(','))
sv = sv.explode(column='f_code')
sv.reset_index(drop=True, inplace=True)
errors = []
created = []
ignored = []
sv.fillna(np.nan, inplace=True)
sv.replace([np.nan], [None], inplace=True)
print(sv.head())
for i in range(len(sv)):
try:
filiere = Filiere.objects.get(f_code=sv.at[i, "f_code"])
categorie = sv.at[i, "asso_sv_categorie"]
asso_sv = SousVivierAssociation.objects.filter(sva_categorie=categorie,
filiere__f_code=sv.at[i, "f_code"])
# Vérifier que filiere et catégorie n'ont pas déjà de sous vivier
if asso_sv.count() == 0 and categorie is not None:
# Créer le sous-vivier correspondant dans la table SousVivier
sous_vivier = SousVivier(sv_id=sv.at[i, "sv_id"],
sv_libelle=str(sv.at[i, "sv_id"]))
print(model_to_dict(sous_vivier))
sous_vivier.save()
print(sv.at[i, "sv_id"])
# Insertion du nouveau sous-vivier dans la table d'association (sans le droit arme dans un premier temps)
sva = SousVivierAssociation(sva_id=i, sous_vivier_id=sous_vivier.sv_id, filiere_id=filiere.f_code,
sva_categorie=categorie, sva_arme=sv.at[i, "arme"])
sva.save()
# Mise à jour du sous_vivier_id pour les postes et administres ayant la filiere et la categorie en cours
Administre.objects.filter(a_categorie=categorie, a_filiere_id=filiere.f_code).update(
sous_vivier_id=sous_vivier.sv_id)
Poste.objects.filter(p_categorie=categorie, p_filiere_id=filiere.f_code).update(
sous_vivier_id=sous_vivier.sv_id)
created.append(str(sv.at[i, "asso_sv_categorie"]) + " " + str(sv.at[i, "f_code"]))
else:
ignored.append(str(sv.at[i, "asso_sv_categorie"]) + " " + str(sv.at[i, "f_code"]))
# Si l'association appartient déjà à un sous_vivier, on ne fait rien
except Filiere.DoesNotExist:
errors.append("Filiere " + sv.at[i, 'f_code'] + "non retrouvée")
return Response({"created": created, "ignored": ignored, "errors": errors})
# 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
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
"""
serializer_class = FileCompetenceSerializer
def get(self, request):
"""La fonction get renvoie une reponse contenant ok
:type request: rest_framework.request.Request
:param request: Requset
:return: - **return** (*json*): json contenant "ok".
"""
return Response({"get": "ok"})
def post(self, request):
""" La fonction post charge les competences.
:type request: rest_framework.request.Request
:param request: Request contenant le fichier de competence
:return: - **Response** (*Response*): Reponse contient un message de la réussite pour charger la compétence .
"""
serializer = FileCompetenceSerializer(data=request.data)
if not serializer.is_valid():
return Response(
data=serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
competence_file_v1 = request.data['competence_v1']
competence_file_v2 = request.data['competence_v2']
competence_poste_v1 = open_excel(competence_file_v1, sheetname='Feuil1', engine='openpyxl')
competence_poste_v1.drop_duplicates(subset=['Macro compétence (0 à n par officier)'], inplace=True)
competence_mili = open_excel(competence_file_v2, sheetname="MILITAIRE", engine='openpyxl')
competence_mili.reset_index(drop=True, inplace=True)
competence_poste = open_excel(competence_file_v2, sheetname='REO', engine='openpyxl')
competence_poste.dropna(subset=['COMPETENCE 1'], inplace=True)
competence_poste.reset_index(drop=True, inplace=True)
print("Preprocessing OK")
# Ajout des compétences du fichier version 1, dans la table Compétence
for i in range(len(competence_poste_v1)):
comp = Competence(comp_id=cleanString(competence_poste_v1.at[i, 'Macro compétence (0 à n par officier)']),
comp_libelle=competence_poste_v1.at[i, 'Macro compétence (0 à n par officier)'])
comp.save()
print("Ajout des compétences du fichier 1 dans la table Competence --> OK")
# Ajout des compétences du fichier version 2, dans la table Compétence
competence_mili_1 = competence_mili[['COMPETENCE 1']].drop_duplicates().reset_index(drop=True)
competence_mili_2 = competence_mili[['COMPETENCE 2']].drop_duplicates().reset_index(drop=True).rename(
columns={'COMPETENCE 2': "COMPETENCE 1"})
competence_mili_3 = competence_mili[['COMPETENCE 2.1']].drop_duplicates().reset_index(drop=True).rename(
columns={'COMPETENCE 2.1': "COMPETENCE 1"})
competence_poste_1 = competence_poste[['COMPETENCE 1']].drop_duplicates(subset=["COMPETENCE 1"]).reset_index(
drop=True)
total_competences = pd.concat([competence_mili_1, competence_mili_2, competence_mili_3,
competence_poste_1]).drop_duplicates().reset_index(drop=True)
total_competences.dropna(inplace=True)
total_competences.reset_index(drop=True, inplace=True)
for i in range(len(total_competences)):
competence = Competence(comp_id=cleanString(total_competences.at[i, "COMPETENCE 1"]),
comp_libelle=total_competences.at[i, 'COMPETENCE 1'])
competence.save()
print('Ajout des compétences du fichier 2 dans la table Competence --> OK')
# Ajout des compétences du fichier 2 dans la table Poste
print(competence_poste)
for i in range(len(competence_poste)):
postes = Poste.objects.filter(p_code_fonction=competence_poste.at[i, 'CODE FONCTION'],
formation_emploi_id=competence_poste.at[i, 'CODE FE'],
p_domaine=competence_poste.at[i, 'DOM EIP'],
p_filiere=competence_poste.at[i, "FIL EIP"],
p_nf=competence_poste.at[i, 'NR EIP'])
postes.update(competence_id=cleanString(competence_poste.at[i, "COMPETENCE 1"]))
print('Ajout des compétences du fichier 2 dans la table Poste --> OK')
# Ajout des compétences du fichier 2 dans la table d'association Administrés et Compétences
liste_admin = []
for i in range(len(competence_mili)):
sap = competence_mili.at[i, 'N SAP']
admin_competences = ""
if not pd.isna(competence_mili.at[i, 'COMPETENCE 1']):
ca1_id = cleanString(competence_mili.at[i, 'COMPETENCE 1'])
admin_competences = admin_competences + ca1_id + ","
if not pd.isna(competence_mili.at[i, 'COMPETENCE 2']):
ca2_id = cleanString(competence_mili.at[i, 'COMPETENCE 2'])
admin_competences = admin_competences + ca2_id + ","
if not pd.isna(competence_mili.at[i, 'COMPETENCE 2.1']):
ca3_id = cleanString(competence_mili.at[i, 'COMPETENCE 2.1'])
admin_competences = admin_competences + ca3_id + ","
liste_admin.append(Administre(a_id_sap=sap, a_liste_id_competences=admin_competences))
Administre.objects.bulk_update(liste_admin, fields=['a_liste_id_competences'])
print('Ajout des compétences du fichier 2 dans la table CompetenceAdministre --> OK')
return JsonResponse({"msg": "Chargement competence ok"})
class CurrentUserView(APIView):
"""
Cette classe est dédiée au vue de l'utilisateur courant:
"""
def get(self, request):
"""La fonction get verifie si l'utilisateur est authentifié
:type request: rest_framework.request.Request
:param request: Request contenant l'identifiant de l'utilisateur
:return: - **Response** (*json*): json contenant l'identité et le role de l'utilisateur. En plus de la formation emploi group et le sous-vivier specifique à cet utilisateur.
"""
if request.user.is_authenticated:
current_user = CustomUser.objects.get(id=request.user.id)
try:
administre = model_to_dict(current_user.administre)
except:
administre = None
current_user = model_to_dict(current_user, fields=[field.name for field in CustomUser._meta.get_fields() if
field.name not in ["password", "logentry", "groups",
"user_permissions"]])
current_user['sous_viviers'] = list(SousVivier.objects.filter(gestionnaire_id=request.user.id).values())
current_user['identite'] = administre
try:
pcpFeGroupe = model_to_dict(PcpFeGroupe.objects.get(gestionnaire_id=request.user.id))
except:
pcpFeGroupe = None
current_user['fe_groupe'] = pcpFeGroupe
current_user['role'] = "ADMINISTRATEUR" if request.user.is_superuser else "GESTIONNAIRE"
return Response(current_user)
return Response({"msg": "Authentification requise"})
class ReferencesView(APIView):
"""
Cette classe est dédiée au vue de la reference.
"""
def get(self, request):
"""
La fonction get envoie les references
:return: - **JsonReponse** (*json*): json contenant les groupes marques, les marques, les competences, les domaines et les filieres.
"""
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')]
# 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_code')]
return JsonResponse(
{"groupesMarques": groupesMarques, "marques": marques,
"domaines": domaines, "filieres": filieres,
"grades": grades, "competences": competences, "FEs": FEs}, safe=False)
# Vue pour l'alimentation et l'update en BDD :
# - Charge et traite les fichiers de données
# - Remplit la base de données pour les modèles Domaines, Filieres, Grade, Fonction, Garnison, FormationEmploi, Marque, MarquesGroupe, Administre, Poste, FMOB,
class AlimentationView(APIView):
"""
Cette classe est dédiée au vue de l'alimentation et l'update en BDD. Charge et traite les fichiers de données et remplit la base de données pour les modèles Domaines, Filieres, Grade, Fonction, Garnison, FormationEmploi, Marque, MarquesGroupe, Administre, Poste, FMOB.
"""
serializer_class = FileSerializer
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")
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 .
"""
# TODO: préparer une variable qui renvoie les erreurs sous 3 axes : données référentielles, administrés, postes
serializer = FileSerializer(data=request.data)
if not serializer.is_valid():
return Response(
data=serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
logger.info('---------------------Upload begining---------------------------')
start_time = time.time()
file_donnees_bo = request.data['Donnees_BO_ADT']
file_zpropaf = request.data['ZPROPAF']
file_reo = request.data['REO']
file_fmob = request.data['FMOB']
file_filiere_domaine = request.data['domaine_filiere']
file_insee = request.data['insee_maping']
file_diplomes = request.data['diplomes']
# file_suivi_pam = request.data['suivi_pam']
logger.info('---------------------Upload ending-----------------------------')
logger.debug("--------------upload time -- %d seconds ------------------------", time.time() - start_time)
# ETL from xlsx source : Ordre : domaines, filieres, garnisons, fonctions, grades, fe, groupesMarques, marques, gestionnaires, administres, flag_pam_admnistres, postes, fmob, listePreferences
filiere_domaine_df, donnees_bo_df, zpropaf_df, reo_df, fmob_df, femp_df, insee_df, diplomes_df = process_files(
file_filiere_domaine,
file_donnees_bo,
file_zpropaf,
file_reo,
file_fmob,
file_insee,
file_diplomes)
logger.info('---------------------Insert beginning---------------------------')
start_time_insert = time.time()
insert_Domaine(to_table_domaines(filiere_domaine_df))
logger.debug('1. Domaine -----------> Success')
insert_Garnison(to_table_garnisons(insee_df, donnees_bo_df))
logger.debug('2. Garnison ----------> Success')
insert_Fonction(to_table_fonctions(reo_df))
logger.debug('3. Fonction ----------> Success')
insert_Grade()
logger.debug('4. Grade -------------> Success')
insert_Filiere(to_table_filieres(filiere_domaine_df))
logger.debug('5. Filières -----------> Success')
insert_MarquesGroupe(to_table_groupesMarques())
logger.debug('6. MarquesGroupe -----> Success')
insert_Marque(to_table_marques())
logger.debug('7. Marque ------------> Success')
insert_FormationEmploi(to_table_fe(reo_df, donnees_bo_df))
logger.debug('8. FormationEmplois ---> Success')
administres_list, administres_error = insert_Administre(
to_table_administres(donnees_bo_df, zpropaf_df, insee_df, diplomes_df))
logger.debug('9. Administre --------> Success')
postes_list = insert_Poste(to_table_postes(reo_df))
logger.debug('10. Poste --------------> Success')
logger.debug('time: %d s', time.time() - start_time_insert)
fmob_error = insert_FMOB(to_table_fmob(fmob_df, femp_df, zpropaf_df))
logger.debug('11. Fmob ----------------> Success')
logger.info('---------------------Insert ending -----------------------------')
logger.info('-------------------- Début des mises à jour --------------------')
# administre : le statut PAM par défaut est A_TRAITER s'il y a un FMOB
(Administre.objects
.filter(a_statut_pam=StatutPam.A_ETUDIER, fmobs__isnull=False)
.update(a_statut_pam=StatutPam.A_TRAITER))
logger.info('--------------------- Fin des mises à jour ---------------------')
logger.debug("------------------insert time -- %d seconds --------------------", time.time() - start_time_insert)
# # result = {"administres_error": administres_error, "fmob_error": fmob_error}
# result = {"administres_error": "", "fmob_error": fmob_error}
return Response({"administres_error": ""})
class AlimentationReferentielView(APIView):
""" Vue pour alimenter la base à partir de référentiels """
serializer_class = AlimentationReferentielSerializer
def get(self, request):
return Response("Formulaire d'alimentation d'OGURE NG (référentiel")
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 = AlimentationReferentielSerializer(data=request.data)
validator.is_valid(raise_exception=True)
# récupération des fichiers
referentiel_fe = request.data.get('referentiel_fe', None)
if referentiel_fe:
def process_referentiel_fe(referentiel) -> pd.DataFrame:
"""
Fonction de lecture du référentiel de FE.
:param referentiel: Fichier du référentiel FE
:type referentiel: XLSX
:return: DataFrame
:rtype: class:`pandas.DataFrame`
"""
return open_excel(referentiel, sheetname=0, engine='openpyxl')
def convertir_fe(df: pd.DataFrame) -> pd.DataFrame:
"""
Fonction de conversion du DataFrame de FE.
:param df: DataFrame du référentiel FE
:type df: class:`pandas.DataFrame`
:return: DataFrame
:rtype: class:`pandas.DataFrame`
"""
col_pk_avant = 'FE CREDO'
col_mere_avant = 'FE mère CREDO'
col_zone_def_avant = 'Zone de Défense'
Cols = FormationEmploi.Cols
col_pk_apres = Cols.PK
col_mere_apres = Cols.REL_MERE
col_zone_def_apres = Cols.ZONE_DEFENSE
return (df[[col_pk_avant, col_mere_avant, col_zone_def_avant]]
.drop_duplicates(subset=col_pk_avant, keep='first')
.rename(columns={
col_pk_avant: col_pk_apres,
col_mere_avant: col_mere_apres,
col_zone_def_avant: col_zone_def_apres
}))
def mettre_a_jour_fe(df: pd.DataFrame) -> None:
"""
Met à jour les FE base à partir du DataFrame de FE.
:param df: DataFrame du référentiel FE
:type df: class:`pandas.DataFrame`
:return: DataFrame
:rtype: class:`pandas.DataFrame`
"""
TypeModele = FormationEmploi
Cols = TypeModele.Cols
champs_maj = (Cols.REL_MERE, Cols.ZONE_DEFENSE)
modeles_en_base = { m.pk: m for m in TypeModele.objects.select_related(Cols.REL_MERE).only('pk', *champs_maj) }
taille_batch = 100
dict_create = {}
dict_update = {}
for rec in df.to_dict('records'):
pk = str(rec.get(Cols.PK, None))
id_mere = rec.get(Cols.REL_MERE, None)
mere = modeles_en_base.get(str(id_mere), None) if id_mere else None
zone_defense = rec.get(Cols.ZONE_DEFENSE, None)
en_base = modeles_en_base.get(pk, None)
if not en_base or (mere != getattr(en_base, Cols.REL_MERE, None) or zone_defense != getattr(en_base, Cols.ZONE_DEFENSE, None)):
try:
modele = TypeModele(pk=pk, mere=mere, zone_defense=zone_defense)
except Exception as e:
raise RuntimeError(f'la création d\'un modèle de type "{TypeModele.__name__}" a échoué') from e
(dict_create if not en_base else dict_update).setdefault(pk, modele)
# TODO pas de création pour l'instant (ni de suppression)
# if dict_create:
# FormationEmploi.objects.bulk_create(dict_create.values(), batch_size=taille_batch)
if dict_update and champs_maj:
FormationEmploi.objects.bulk_update(dict_update.values(), batch_size=taille_batch, fields=champs_maj)
logger.debug(f"MAJ: %s", len(dict_update))
df_referentiel_fe = process_referentiel_fe(referentiel_fe)
df_referentiel_fe = convertir_fe(df_referentiel_fe)
mettre_a_jour_fe(df_referentiel_fe)
return Response({'message':'OK'})
except (Http404, APIException):
raise
except:
message = "impossible d'alimenter le référentiel"
logger.exception(message)
raise api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, message)
# Vue API des administrés
# Pagination OK
# Ordering OK
# Search NOK
# Filtering NOK
class AdministreView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue de l'administre.
"""
serializer_class = AdministreSerializer
filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
# filterset_fields = ['category', 'in_stock']
filterset_class = AdministreFilter
ordering_fields = 'a_id_sap'
ordering = ['a_id_sap']
pagination_class = HeavyDataPagination
# important : mettre à jour quand le serializer change
def get_queryset(self):
Cols = Administre.Cols
return Administre.objects.select_related(
Cols.REL_FONCTION, Cols.REL_GRADE,
).prefetch_related(
Cols.M2M_COMPETENCES,
Cols.O2M_FMOB,
Prefetch(Cols.REL_DECISION, queryset=Decision.objects.select_related('poste__formation_emploi__garnison')),
Prefetch(Cols.REL_FORMATION_EMPLOI, queryset=FormationEmploi.objects.select_related(FormationEmploi.Cols.REL_GARNISON, FormationEmploi.Cols.REL_MERE)),
Prefetch(Cols.REL_SOUS_VIVIER, queryset=SousVivier.objects.select_related(SousVivier.Cols.REL_GESTIONNAIRE)),
)
@atomic
def put(self, request):
"""La fonction put met à jour une liste d'administres.
:type request: rest_framework.request.Request
:param request: Request contenant la liste d'administres.
:return: - **Response** (*Response*): Reponse contient un message de la réussite de met à jour des administres .
"""
try:
req_data = request.data
is_list = isinstance(req_data, list)
validator = self.serializer_class(data=req_data, many=is_list, partial=True)
validator.is_valid(raise_exception=True)
today = date.today()
Cols = Administre.Cols
def copy_data_item(input, pk):
"""Copie et complète si besoin le dictionnaire en entrée"""
result = {**input}
if pk and Cols.PK not in result:
result[Cols.PK] = pk
return result
if is_list: # Update multiple elements
data = [copy_data_item(item, req_data[idx][Cols.PK]) for idx, item in enumerate(validator.validated_data)]
fields = [key for key in data[0].keys() if key != Cols.PK]
objs = [Administre(**data_item) for data_item in data]
if Cols.STATUT_PAM in fields:
list_error = []
for i in range(len(objs)):
old_administre = Administre.objects.get(a_id_sap=objs[i].a_id_sap)
administre = objs[i]
eip = administre.a_eip
fe_code = old_administre.formation_emploi_id
avis = administre.a_statut_pam
old_avis = old_administre.a_statut_pam
try:
categorie = constants.CATEGORIE_BY_NF[old_administre.a_nf.upper()]
except:
erreur = 'la catégorie de l\'administré n\'est pas reconnue dans ogure.'
print(erreur)
raise Exception(erreur)
liste_error = impact_decisions(old_administre, administre, old_avis, avis, eip, fe_code, categorie)
Administre.objects.bulk_update(objs, fields=fields)
else: # Update one element
data = copy_data_item(validator.validated_data, validator.validated_data[Cols.PK])
# c'est un bug la récupération de a_id_sap en-dessous, non ?
a_id_sap = Administre.objects.update(data)
db_instance = Administre.objects.filter(a_id_sap=a_id_sap).first()
db_instance.tag.clear()
return Response({'msg': 'updated'})
except APIException:
raise
except Exception:
message = 'échec de la mise à jour de N administrés'
logger.exception(message)
raise APIException(message)
def partial_update(self, request, pk=None):
""" La fonction put met à jour un administre.
:type request: rest_framework.request.Request
:param request: Request contenant l'administre.
:type pk: integer
:param pk: Primary Key de l'administre.
:return: - **Response** (*Response*): Reponse contient un message de la réussite de met à jour de l'administre .
"""
try:
logger.debug("Mise à jour administré")
req_data = request.data
# TODO valider les données (il faut d'abord corriger la MAJ des compétences)
# validator = self.serializer_class(data=req_data, partial=True)
# validator.is_valid(raise_exception=True)
Cols = Administre.Cols
# Copie et complète si besoin le dictionnaire en entrée
# administre = {**validator.validated_data}
administre = {**req_data}
administre[Cols.PK] = pk
fields = [key for key in administre.keys() if key != Cols.PK] # and key != 'a_liste_id_competences']
if 'a_domaine_futur' in fields:
administre['a_domaine_futur_id'] = administre['a_domaine_futur']
del administre['a_domaine_futur']
if 'a_filiere_futur' in fields:
administre['a_filiere_futur_id'] = administre['a_filiere_futur']
del administre['a_filiere_futur']
if Cols.STATUT_PAM in fields:
old_administre = Administre.objects.get(a_id_sap=pk)
eip = old_administre.a_eip
fe_code = old_administre.formation_emploi_id
avis = administre[Cols.STATUT_PAM]
old_avis = old_administre.a_statut_pam
try:
categorie = constants.CATEGORIE_BY_NF[old_administre.a_nf.upper()]
except:
erreur = 'la catégorie de l\'administré n\'est pas reconnue dans ogure.'
print(erreur)
raise APIException(erreur)
liste_error = impact_decisions(old_administre, Administre(**administre), old_avis, avis, eip, fe_code,
categorie)
if 'a_liste_id_competences' in fields:
adm=Administre(pk=pk)
#adm=request.data['a_liste_id_competences']
if administre['a_liste_id_competences']:
adm.a_liste_id_competences.set(administre['a_liste_id_competences'].split(','))
else:
adm.a_liste_id_competences.set("")
else:
copy = { k: v for k, v in administre.items() if k != 'a_liste_id_competences'}
adm = Administre(**copy)
if fields:
Administre.objects.bulk_update([adm], fields=fields)
return Response({'msg': 'updated'})
except (Http404, APIException):
raise
except Exception:
message = "échec de la mise à jour de l'administré"
logger.exception(message)
raise APIException(message)
# GET /api/administres => 0 administrés
# POST /api/administres => Crée 1 nouvel administres => id=1
# GET /api/administres/1 => 1 administre précédemment créé
# POST /api/administres/1 => 1 administre précédemment créé
class PosteView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des postes.
"""
lookup_value_regex = r"[\w.]+"
serializer_class = PosteSerializer
pagination_class = HeavyDataPagination
filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
# filterset_fields = ['category', 'in_stock']
filterset_class = PosteFilter
ordering_fields = '__all_related__'
ordering = ['p_id']
# important : mettre à jour quand le serializer change
def get_queryset(self):
"""Cette fonction permet d'ajouter plus de logique à l'attribut queryset."""
Cols = Poste.Cols
return Poste.objects.select_related(
Cols.REL_FONCTION
).prefetch_related(
Cols.M2M_COMPETENCES,
Cols.O2M_DECISION,
Prefetch(Cols.REL_FORMATION_EMPLOI, queryset=FormationEmploi.objects.select_related(FormationEmploi.Cols.REL_GARNISON, FormationEmploi.Cols.REL_MERE)),
Prefetch(Cols.REL_SOUS_VIVIER, queryset=SousVivier.objects.select_related(SousVivier.Cols.REL_GESTIONNAIRE)),
).annotate(
p_nb_prepositionne=Sum(
Case(When(decisions__de_decision=constants.DECISION_PREPOSITIONNE, then=1), default=0,
output_field=IntegerField())),
p_nb_positionne=Sum(Case(When(decisions__de_decision=constants.DECISION_POSITIONNE, then=1), default=0,
output_field=IntegerField())),
p_nb_omi_en_cours=Sum(Case(When(decisions__de_decision=constants.DECISION_OMI_EN_COURS, then=1), default=0,
output_field=IntegerField())),
p_nb_omi_active=Sum(Case(When(decisions__de_decision=constants.DECISION_OMI_ACTIVE, then=1), default=0,
output_field=IntegerField())),
)
def put(self, request):
"""La fonction put met à jour une liste des postes.
:type request: rest_framework.request.Request
:param request: Request contenant la liste des postes.
:return: - **Response** (*Response*): Reponse contient un message de la réussite de met à jour des postes .
"""
data = request.data
serialized = self.serializer_class(data=data, many=isinstance(data, list), partial=True)
serialized.is_valid(raise_exception=True)
if isinstance(data, list): # Update multiple elements
try:
fields = [key for key in data[0].keys() if key != 'p_id' ]
objs = [Poste(**data[i]) for i in range(len(data))]
except:
raise Exception("bulk update error")
Poste.objects.bulk_update(objs, fields=fields)
else: # Update one element
a_id_sap = Poste.objects.update(serialized.validated_data)
db_instance = Poste.objects.filter(
a_id_sap=a_id_sap).first()
db_instance.tag.clear()
return Response({'msg': 'updated'})
def partial_update(self, request, pk=None):
""" La fonction put met à jour un poste.
:type request: rest_framework.request.Request
:param request: Request contenant le poste.
:type pk: integer
:param pk: Primary Key du poste.
:return: - **Response** (*Response*): Reponse contient un message de la réussite de met à jour du poste .
"""
poste = request.data
fields = [key for key in poste.keys() if key != 'p_id']# and key != 'competence']
poste['p_id'] = pk
if 'competence' in fields:
pos=Poste(pk=pk)
if poste['competence']:
pos.competences.set(poste['competence'].split(','))
else:
pos.competences.set("")
else:
copy = { k: v for k, v in poste.items() if k != 'competence'}
pos = Poste(**copy)
if fields:
Poste.objects.bulk_update([pos], fields=fields)
# copy = { k: v for k, v in poste.items() if k != 'competence'}
# pos = Poste(**copy)
# if fields:
# Poste.objects.bulk_update([copy], fields=fields)
# if 'competence' in poste.items():
# pos.competence.set(poste['competence'].split(','))
# pos.save()
return Response({'msg': 'updated'})
class NotationView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des notations.
"""
serializer_class = NotationSerializer
queryset = Notation.objects.all()
pagination_class = HeavyDataPagination
def list(self, request, pk=None):
"""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 = []
q = 'poste'
if 'administre_id' in request.query_params:
administre_id = request.query_params['administre_id']
administres_keys = (
'poste__p_id', 'poste__p_nf', 'poste__p_domaine', 'poste__p_filiere', 'poste__p_eip',
'poste__formation_emploi__fe_code', 'poste__competences',
'poste__p_notes_gestionnaire', 'poste__p_liste_id_marques', 'poste__p_code_fonction',
'poste__p_avis',
'poste__p_nb_p1',
'poste__p_nb_p2',
'poste__p_nb_p3',
'poste__p_nb_p4',
'poste__p_dep',
'poste__formation_emploi__fe_libelle', 'poste__competences', 'poste__p_fonction',
'poste__formation_emploi__garnison',
'poste__p_notes_gestionnaire', 'poste__p_liste_id_marques',
'poste__formation_emploi__garnison__gar_lieu', 'no_score_administre',
'no_flag_cple_ideal')
counter = 10
notation_qs = Notation.objects.filter(administre_id=administre_id).order_by('-no_score_administre')[
:counter].select_related('poste')
notation_qs_matching_parfait = Notation.objects.filter(administre_id=administre_id,
no_flag_cple_ideal=True).select_related('poste')
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__p_id']
topCounter = 0
allNotationsInvolved = Notation.objects.filter(poste_id=poste_id)
for note in allNotationsInvolved:
topList = list(
Notation.objects.filter(no_id=note.no_id).order_by('-no_score_administre')[:10].values(
'poste_id'))
topPostes = [poste['poste_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"
poste_id = request.query_params['poste_id']
poste_unit = Poste.objects.get(p_id=poste_id)
try:
counter = poste_unit.p_nb_p1 + poste_unit.p_nb_p2 + poste_unit.p_nb_p3 + poste_unit.p_nb_p4
if counter < 10:
counter = 10
except:
counter = 10
postes_keys = (
'administre__a_id_sap', 'administre__a_nom', 'administre__a_prenom', 'administre__a_statut_pam',
'administre__grade_id', 'administre__a_liste_id_marques', 'administre__decision__de_decision',
'administre__decision__de_date_decision', 'no_score_administre', 'no_flag_cple_ideal', 'administre__a_notes_gestionnaire',
'administre__a_fonction', 'administre__a_code_fonction',
'administre__a_liste_id_marques',
'administre__a_liste_id_competences',
'administre__decision__poste_id')
notation_qs = Notation.objects.filter(poste_id=poste_id).order_by('-no_score_administre')[
:counter].select_related(
'administre')
notation_qs_matching_parfait = Notation.objects.filter(poste_id=poste_id,
no_flag_cple_ideal=True).select_related('administre')
notation_qs.union(notation_qs_matching_parfait)
notations_list = list(
notation_qs.values(*postes_keys))
for notation in notations_list:
administre_id = notation['administre__a_id_sap']
topCounter = 0
allNotationsInvolved = Notation.objects.filter(administre_id=administre_id)
for note in allNotationsInvolved:
topList = list(
Notation.objects.filter(no_id=note.no_id).order_by('-no_score_administre')[:10].values(
'administre_id'))
topAdministres = [administre['administre_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 = {q: {}}
for key in notation:
if (q + "__") in key:
res_notation[q][key.replace(q + '__', '')] = notation[key]
else:
res_notation[key] = notation[key]
if q == "poste":
res_notation[q]['p_nb_prepositionne'] = Decision.objects.filter(
de_decision=constants.DECISION_PREPOSITIONNE).count()
res_notation[q]['p_nb_positionne'] = Decision.objects.filter(
de_decision=constants.DECISION_POSITIONNE).count()
res_notation[q]['p_nb_omi_active'] = Decision.objects.filter(
de_decision=constants.DECISION_OMI_ACTIVE).count()
res_notation[q]['p_nb_omi_en_cours'] = Decision.objects.filter(
de_decision=constants.DECISION_OMI_EN_COURS).count()
result.append(res_notation)
return Response(result)
class DecisionView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des decisions.
"""
serializer_class = DecisionSerializer
queryset = Decision.objects.all()
def list(self, request, pk=None):
"""Cette fonction envoie les informations du poste lié à l'administre dans une décision avec le score de l'administre et inversement.
:type request: rest_framework.request.Request
:param request: Request contenant l'administre ou le poste.
:return: - **res_decision** (*JsonResponse*): Json contenant le classement.
"""
decision = []
q = 'poste'
administre_id = None
poste_id = None
if 'administre_id' in request.query_params:
administre_id = request.query_params['administre_id']
administres_keys = (
'poste__p_id', 'poste__p_nf', 'poste__p_domaine', 'poste__p_filiere',
'poste__p_eip','poste__p_avis',
'poste__formation_emploi__fe_code', 'poste__competences',
'poste__p_notes_gestionnaire', 'poste__p_liste_id_marques',
'poste__formation_emploi__fe_libelle', 'poste__formation_emploi__garnison__gar_lieu',
'poste__p_dep', 'poste__p_code_fonction', 'poste__competences',
'poste__p_fonction',
# 'poste__decision__de_decision',
'poste__p_nb_p1',
'poste__p_nb_p2',
'poste__p_nb_p3',
'poste__p_nb_p4',
'poste__p_notes_gestionnaire', 'poste__p_liste_id_marques',
'de_decision', 'de_date_decision', 'de_notes_gestionnaire')
decision = Decision.objects.filter(administre_id=administre_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']
postes_keys = (
'administre__a_id_sap', 'administre__a_nom', 'administre__a_prenom', 'administre__a_statut_pam',
'administre__a_fonction', 'administre__a_code_fonction', 'administre__a_liste_id_competences',
'administre__grade_id', 'administre__a_liste_id_marques', 'administre__decision__de_decision',
'administre__decision__de_date_decision', '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_id=poste_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).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=constants.DECISION_PREPOSITIONNE).count()
res_decision_unit[q]['p_nb_positionne'] = Decision.objects.filter(
de_decision=constants.DECISION_POSITIONNE).count()
res_decision_unit[q]['p_nb_omi_active'] = Decision.objects.filter(
de_decision=constants.DECISION_OMI_ACTIVE).count()
res_decision_unit[q]['p_nb_omi_en_cours'] = Decision.objects.filter(
de_decision=constants.DECISION_OMI_EN_COURS).count()
res_decision.append(res_decision_unit)
return Response(res_decision)
def create(self, request): # équivalent post
"""Cette fonction crée une decision.
:type request: rest_framework.request.Request
:param request: Request contenant l'administre, le poste et la decision.
:return: - **response** (*JsonResponse*): Json contenant le message "Décision changée".
"""
result = {}
# TODO : try except
administre_id = request.data['administre_id']
poste_id = request.data['poste_id']
de_decision = request.data['de_decision']
decision = Decision(administre_id=administre_id, poste_id=poste_id)
administre_decision = Decision.objects.filter(administre_id=administre_id)
# Lorsqu'on crée une décision on supprime toute décision précédente sur l'administré
# poste_decision = Decision.objects.filter(poste_id=poste_id)
if administre_decision.exists():
administre_decision.delete()
decision.de_decision = de_decision
decision.de_date_decision = timezone.now()
decision.save()
poste = Poste.objects.get(p_id=poste_id)
poste.p_nb_vacant = poste.p_nb_vacant - 1
poste.save()
response = JsonResponse({'status': 'success', 'message': 'Décision changée'})
return response
class FormationEmploiView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des FormationEmplois.
"""
serializer_class = FormationEmploiSerializer
# important : mettre à jour quand le serializer change
def get_queryset(self):
Cols = FormationEmploi.Cols
return FormationEmploi.objects.select_related(Cols.REL_GARNISON, Cols.REL_MERE)
class ListesPreferencesView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des ListesPreferences.
"""
serializer_class = PreferencesListe
queryset = PreferencesListe.objects.all()
class MarqueView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des marques.
"""
serializer_class = MarqueSerializer
queryset = Marque.objects.all()
class MarquesGroupeView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des marquegroupes.
"""
serializer_class = MarquesGroupeSerializer
queryset = MarquesGroupe.objects.all()
class FmobView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des Fmob.
"""
serializer_class = FmobSerializer
queryset = FMOB.objects.all()
class FiliereView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des filieres.
"""
serializer_class = FiliereSerializer
queryset = Filiere.objects.all()
class DomaineView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue des domaines.
"""
serializer_class = DomaineSerializer
queryset = Domaine.objects.all()
class SousVivierAssociationView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue du SousVivierAssociation.
"""
serializer_class = SousVivierAssociationSerializer
queryset = SousVivierAssociation.objects.all()
class PcpFeGroupeView(viewsets.ModelViewSet):
"""
Cette classe est dédiée au vue du PcpFeGroupe.
"""
serializer_class = PcpFeGroupeSerializer
queryset = PcpFeGroupe.objects.all()