def page_impression(self, qui): """ Fabrication de la page 'impression des fiches bilan' """ cand = qui.get_cand() fich = qui.fichier entete = '<h1 align="center" class="titre">{} - {}.</h1>'.format( self.titre, fich.filiere().upper()) txt = '' saut = '<div style = "page-break-after: always;"></div>' # on saute une page entre chaque candidat for cand in fich: nb = nb_classes[fich.filiere().lower( )] # récupération du contenu du fichier config.py # Si nb n'est pas convertible en un entier positif alors on classe tous les candidats try: nb_max = int(nb) if nb_max < 0: nb_max = len(fich) except: nb_max = len(fich) a = (Fichier.get(cand, 'traité') == 'oui') b = (Fichier.get(cand, 'Correction') != 'NC') c = not (b) or (int(Fichier.get(cand, 'Rang final')) <= nb_max) if a and b and c: # seulement les classés dont le rang est inférieur à la limite fixée txt += entete txt += '<div class = encadre>Candidat classé : {}</div>'.format( Fichier.get(cand, 'Rang final')) txt += Composeur.html['contenu_dossier'].format( **self.genere_dossier(qui, cand)) txt += Composeur.html['contenu_action'].format( **self.genere_action(qui, cand)) txt += saut txt = txt[:-len( saut )] # On enlève le dernier saut de page... sinon on a une page blanche ! return Composeur.html['page_impress'].format(**{'pages': txt})
def genere_action(self, client, cand): """ Renvoie le dictionnaire de la partie 'action' de la page HTML""" ### # Estimation du rang final du candidat rg_fin = '' visib = 'none' # n'est visible que pour les jurys if isinstance(client, Jury): # seulement pour les jurys. rg_fin = client.get_rgfinal(cand) visib = '' # est visible pour les jurys rang_final = '<td style = "display:{};">Estimation du rang final : {}</td>'.format( visib, rg_fin) ### Partie correction : # récupération correction cor = Fichier.get(cand, 'Correction') # Sous forme de chaîne de caractère ! if cor == 'NC': correc = min_correc # NC correspond à la correction minimale rg_fin = 'NC' else: correc = float( cor.replace(',', '.') ) # valeur numérique de la correction -> placement du curseur. # Construction de la barre de correction qu'on alimente avec les infos courantes.. barre = Composeur.barre.format(correc, Fichier.get(cand, 'Jury'), cor) ### Partie motivations : motifs = Composeur.motifs.format(Fichier.get(cand, 'Motifs')) # On met tout ça dans un dico data pour passage en argument à html['contenu_action'] data = { 'barre': barre, 'scoreb': Fichier.get(cand, 'Score brut'), 'scoref': Fichier.get(cand, 'Score final'), 'rg_fin': rang_final, 'motifs': motifs } return data
def stat(self): """ Effectue des statistiques sur les candidats """ # Récupère la liste des fichiers concernés list_fichiers = [Fichier(fich) for fich in glob.glob(os.path.join(os.curdir, "data", "admin_*.xml"))] # On ordonne la liste de fichiers transmise selon l'ordre spécifié dans filieres (parametres.py) list_fich = sorted(list_fichiers, key = lambda f: filieres.index(f.filiere().lower())) # L'info de candidatures est stockée dans un mot binaire où 1 bit # correspond à 1 filière. Un dictionnaire 'candid' admet ces mots binaires pour clés, # et les valeurs sont des nombres de candidats. # candid = {'001' : 609, '011' : 245, ...} indique que 609 candidats ont demandé # la filière 1 et 245 ont demandé à la fois la filière 1 et la filière 2 # Initialisation du dictionnaire stockant toutes les candidatures candid = {i : 0 for i in range(2**len(filieres))} # Variables de décompte des candidats (et pas candidatures !) candidats = 0 candidats_ayant_valide = 0 # Recherche des candidatures # je suis très fier de cet algorithme !! # Construction des éléments de recherche l_dict = [ {Fichier.get(cand, 'Num ParcoursSup') : cand for cand in fich} for fich in list_fich ] # liste de dicos l_set = [ set(d.keys()) for d in l_dict ] # list d'ensembles (set()) d'identifiants ParcoursSup # Création des statistiques for (k,n) in enumerate(l_set): # k = index filière ; n = ensemble des identifiants des candidats dans la filière while len(n) > 0: # tant qu'il reste des identifiants dans n a = n.pop() # on en prélève 1 (et il disparait de n) candidats += 1 cc, liste = 2**k, [k] # filière k : bit de poids 2**k au niveau haut. for i in range(k+1, len(list_fich)): # on cherche cet identifiant dans les autres filières. if a in l_set[i]: # s'il y est : cc |= 2**i # on met le bit 2**i au niveau haut (un ou exclusif est parfait) l_set[i].remove(a) # on supprime cet identifiant de l'ensemble des identifiants de la filière i liste.append(i) # on ajoute la filière i à la liste des filières demandées par le candidat [Fichier.set(l_dict[j][a], 'Candidatures', cc) for j in liste] # On écrit le noeud 'Candidatures' flag = True # pour ne compter qu'une validation par candidat ! for j in liste: # le test ci-dessous pourrait exclure les filières inadéquates (bien ou pas ?).. if not('non validée' in Fichier.get(l_dict[j][a], 'Motifs')): candid[2**j]+= 1 # ne sont comptés que les candidatures validées if flag: candidats_ayant_valide += 1 flag = False if len(liste) > 1: # si candidat dans plus d'une filière candid[cc] += 1 # incrémentation du compteur correspondant # Sauvegarder [fich.sauvegarde() for fich in list_fich] # Ajouter deux éléments dans le dictionnaire candid candid['nb_cand'] = candidats candid['nb_cand_valid'] = candidats_ayant_valide # Écrire le fichier stat with open(os.path.join(os.curdir, "data", "stat"), 'wb') as stat_fich: pickle.dump(candid, stat_fich) return
def get_rgfinal(self, cand): """ Renvoie une estimation du rg final d'un candidat """ # On extrait du fichier les dossiers traités et non NC doss = [ca for ca in self.fichier if (Fichier.get(ca, 'traité') == 'oui' and Fichier.get(ca, 'Correction') != 'NC')] # On classes ceux-ci par ordre de score final décroissant doss[:] = sorted(doss, key = lambda cand: -float(Fichier.get(cand, 'Score final').replace(',','.'))) # On calcule le rang du score_final actuel (celui de cand) dans cette liste rg = Fichier.rang(cand, doss, 'Score final') # À ce stade, rg est le rang dans la liste du jury. # La suite consiste à calculer n*(rg-1) + k # où n est le nombre de jurys pour cette filière et k l'indice du jury courant. q = parse('Jury {:w}{:d}', self._droits) # n et k sont cachés dans les droits du jury ! n = int(nb_jurys[q[0].lower()]) k = int(q[1]) return n*(rg-1)+k
def clore_commission(self): """ Cloture la commission """ # Objectif : récolter les fichiers comm en fin de commission, calculer tous les scores finals, classés les # candidats et reconstituer un fichier unique par filière (class_XXX.xml). Enfin, construire des tableaux *.csv # nécessaires à la suite du traitement administratif du recrutement (ces tableaux sont définis dans config.py) for fil in filieres: # pour chaque filière path = os.path.join(os.curdir, "data", "comm_{}*.xml".format(fil.upper())) list_fich = [Fichier(fich) for fich in glob.glob(path)] # récupération des fichiers comm de la filière list_fich = sorted(list_fich, key = lambda fich: fich.nom) # l'ordre est important pour la suite list_doss = [] # contiendra les dossiers de chaque sous-comm # Pour chaque sous-commission for fich in list_fich: # Les fichiers non vus se voient devenir NC, score final = 0, avec # motifs = "Dossier moins bon que le dernier classé" (sauf s'il y a déjà un motif - Admin) for c in fich: if Fichier.get(c, 'traité') != 'oui': Fichier.set(c, 'Correction', 'NC') Fichier.set(c, 'Score final', '0') if Fichier.get(c, 'Jury') == 'Auto': # 'Auto' est la valeur par défaut du champ-xml 'Jury' Fichier.set(c, 'Motifs', 'Dossier moins bon que le dernier classé.') # list_doss récupère la liste des dossiers classée selon score_final + age list_doss.append(fich.ordonne('score_f')) # Ensuite, on entremêle les dossiers de chaque sous-comm doss_fin = [] # contiendra les dossiers intercalés comme il se doit.. if list_doss: # Y a-t-il des dossiers dans cette liste ? nb = len(list_doss[0]) # (taille du fichier du 1er jury de cette filière) num = 0 for i in range(nb): # list_doss[0] est le plus grand !! doss_fin.append(list_doss[0][i]) for k in range(1, len(list_doss)): # reste-t-il des candidats classés dans les listes suivantes ? if i < len(list_doss[k]): doss_fin.append(list_doss[k][i]) res = etree.Element('candidats') # Création d'une arborescence xml 'candidats' [res.append(c) for c in doss_fin] # qu'on remplit avec les candidats classés. # Calcul et renseignement du rang final (index dans res) rg = 1 for cand in res: nu = 'NC' if Fichier.get(cand, 'Correction') != 'NC': # si le candidat est classé nu = str(rg) rg += 1 Fichier.set(cand, 'Rang final', nu) # rang final = NC si non classé # Sauvegarde du fichier class... nom = os.path.join(os.curdir, "data", "class_{}.xml".format(fil.upper())) with open(nom, 'wb') as fichier: fichier.write(etree.tostring(res, pretty_print=True, encoding='utf-8')) # On lance la génération des tableaux bilan de commission list_fich = [Fichier(fich) for fich in glob.glob(os.path.join(os.curdir, "data", "class_*.xml"))] self.tableaux_bilan(list_fich)
def traiter(self, **kwargs): """ Traiter un dossier """ # Fonction lancée par la fonction "traiter" du Serveur, elle même lancée par un clic sur 'Classé' ou 'NC' cand = self.get_cand() # on récupère le candidat # On récupère la correction apportée par le jury cor = kwargs['correc'] cor_prec = Fichier.get(cand, 'Correction') # précédente correction si le jury revient sur un candidat # Mise à jour du fichier décomptes # ce fichier a été créé par admin, dans la méthode generation_comm a = (Fichier.get(cand, 'traité') == 'oui') b = (cor_prec == 'NC') c = (float(cor) == float(min_correc)) if (not(a ^ b ^ c) and not(b and c)): # doit-on changer le nb de candidats classés change_decompte = 1 if c: change_decompte = -1 # -1 si candidat classé qui devient non classé with open(os.path.join(os.curdir,"data","decomptes"), 'br') as fich: decompt = pickle.load(fich) qui = self._droits for key in decompt.keys(): if key in qui: decompt[key] += change_decompte with open(os.path.join(os.curdir, "data", "decomptes"), 'wb') as stat_fich: pickle.dump(decompt, stat_fich) # On met à jour le contenu de ce dossier : # tout d'abord, calcul du score final if float(cor) == float(min_correc): # cas d'un choix 'NC' cor, scoref = 'NC', '0' else: note = float(Fichier.get(cand, 'Score brut').replace(',','.')) + float(cor) scoref = '{:.2f}'.format(note).replace('.',',') # mise en forme pour que les tableaux bilan cor = '{:.2f}'.format(float(cor)).replace('.',',') # de commission soient jolis ! # Écriture des différents champs # 1/ correction et score final Fichier.set(cand, 'Correction', cor) # écriture de la correction dans le noeud xml du candidat Fichier.set(cand, 'Score final', scoref) # écriture du score final dans le noeud xml du candidat # 2/ Qui a traité le dossier : écriture du noeud xml adéquat Fichier.set(cand, 'Jury', self._droits) # 3/ noeud 'traité' : le dossier a été traité (classé ou non) Fichier.set(cand, 'traité', 'oui') # 4/ motivation du jury Fichier.set(cand, 'Motifs', kwargs['motif']) ## Fin mise à jour dossier # On sélectionne le dossier suivant if self.num_doss < len(self.fichier)-1: self.num_doss += 1 # Et on sauvegarde le tout self.fichier.sauvegarde() # sauvegarde physique du fichier.
def tableaux_bilan(self, list_fich): """ Cette fonction créé les tableaux dont a besoin l'admin pour la suite du recrutement """ # Un peu de ménage... dest = os.path.join(os.curdir, "tableaux") restaure_virginite(dest) # Pour chaque filière : for fich in list_fich: # Tableaux candidats classés for name in tableaux_candidats_classes.keys(): # Création du fichier csv nom = os.path.join(os.curdir, "tableaux", "{}_{}.csv".format(fich.filiere(), name)) cw = csv.writer(open(nom, 'w')) entetes = tableaux_candidats_classes[name] cw.writerow(entetes) for cand in fich: # On ne met dans ces tableaux que les candidats traités, classés et dont le rang est inférieur à la # limite prévue dans config.py. nb = nb_classes[fich.filiere().lower()] # récupération du contenu du fichier config.py # Si nb n'est pas convertible en un entier positif alors on classe tous les candidats try: nb_max = int(nb) if nb_max < 0: nb_max = len(fich) except: nb_max = len(fich) a = (Fichier.get(cand, 'traité') == 'oui') b = (Fichier.get(cand, 'Correction') != 'NC') c = not(b) or (int(Fichier.get(cand, 'Rang final')) <= nb_max) if a and b and c: data = [Fichier.get(cand, champ) for champ in entetes] cw.writerow(data) # Tableaux tous candidats : ces tableaux-là contiennent tous les candidats. for name in tableaux_tous_candidats: # Création du fichier csv nom = os.path.join(os.curdir, "tableaux", "{}_{}.csv".format(fich.filiere(), name)) cw = csv.writer(open(nom, 'w')) entetes = tableaux_tous_candidats[name] cw.writerow(entetes) for cand in fich.ordonne('alpha'): data = [Fichier.get(cand, champ) for champ in entetes] cw.writerow(data)
def genere_liste(self, client, mem_scroll): """ Génère la partie liste de la page HTML""" # Construction de la chaine lis : code html de la liste des dossiers. lis = '<form id = "form_liste" action = "click_list" method=POST>' lis += '<input type="hidden" name = "scroll_mem" value = "{}"/>'.format(mem_scroll) for index, cand in enumerate(client.fichier): # Les candidats rejetés par admin n'apparaissent pas aux jurys a = isinstance(client, Jury) b = Fichier.get(cand, 'Correction') == 'NC' c = Fichier.get(cand, 'Jury') == 'Admin' if not(a and b and c): lis += '<input type = "submit" name="num" ' clas = 'doss' if cand == client.get_cand(): # affecte la class css "doss_courant" au dossier courant clas += ' doss_courant' # candidat sélectionné entouré d'un cadre coloré if Fichier.get(cand, 'traité'): # affecte la classe css "doss_traite" aux dossiers qui le sont clas += ' doss_traite' # candidat traité = background vert. if Fichier.get(cand, 'Correction') == 'NC': # affecte la classe css "doss_rejete" aux dossiers NC clas += ' doss_rejete' # candidat NC = background gris. ### dossiers à mettre en évidence (fond rouge) : # client Admin ET alerte déposée par nettoie.py if isinstance(client, Admin) and '- Alerte' in Fichier.get(cand, 'Motifs'): clas += ' doss_incomplet' # LE TERME INCOMPLET N'EST PLUS ADÉQUAT # Si Admin a écrit un commentaire : if '- Admin' in Fichier.get(cand, 'Motifs'): if isinstance(client, Admin): # si admin clas += ' doss_commente' if isinstance(client, Jury): # si jury clas += ' doss_incomplet' # LE TERME INCOMPLET N'EST PLUS ADÉQUAT ### fin dossiers à mettre en évidence lis += 'class = "{}"'.format(clas) nom = '{}, {}'.format(Fichier.get(cand, 'Nom'), Fichier.get(cand, 'Prénom')) # La variable txt qui suit est le texte du bouton. Attention, ses 3 premiers # caractères doivent être le numéro du dossier dans la liste des # dossiers (client_get_dossiers())... Cela sert dans click_list(), pour identifier sur qui on a clické.. # une police monospace est utilisée pour l'esthétique et la largeur affectée au nom est fixée. Ainsi, # les informations de candidatures multiples sont alignées. txt = '{:3d}) {: <30}{}'.format(index+1, nom[:29], Fichier.get(cand, 'Candidatures')) lis += ' value="{}"></input><br>'.format(txt) lis += '-'*7 + ' fin de liste ' + '-'*7 lis = lis + '</form>' return lis
def genere_dossier(self, qui, cand, format_admin = False): """ Renvoie le dictionnaire contenant les infos du dossier en cours""" #### Début data = {} # Pédigré liste_attr = ['Nom', 'Prénom', 'Date de naissance', 'Établissement', 'Département', 'Pays', 'Num ParcoursSup', 'INE'] for attr in liste_attr: data[attr] = Fichier.get(cand, attr) # récup filiere pour connaître le chemin vers le dossier pdf (dans répertoire docs_candidats) fil = qui.fichier.filiere() data['ref_fich'] = os.path.join('docs_candidats', '{}'.format(fil.lower()), 'docs_{}'.format(Fichier.get(cand, 'Num ParcoursSup'))) # Formatage des champs de notes et de classe actuelle en fonction du client (ou de format_comm) # En effet, Admin a la possibilité d'écrire dans ces champs alors que Jury est en lecture seule. formateur_clas_actu = '{}' formateur_note = '{note}' activ = 'disabled' if format_admin: formateur_clas_actu = '<input type="text" id="Classe actuelle" name="Classe actuelle" size="10" value="{}"/>' formateur_note = '<input type="text" class="notes grossi" id="{}" name="{}" value="{note}"\ onfocusout="verif_saisie()"/>' activ = '' ### Suite de la création du dictionnaire # classe actuelle data['Classe actuelle'] = formateur_clas_actu.format(Fichier.get(cand, 'Classe actuelle')) # Notes matiere = ['Mathématiques', 'Physique/Chimie'] date = ['trimestre 1', 'trimestre 2', 'trimestre 3'] classe = ['Première', 'Terminale'] for cl in classe: for mat in matiere: for da in date: key = '{} {} {}'.format(mat, cl, da) data[key] = formateur_note.format(key, key, note=Fichier.get(cand, key)) # Autres notes liste = ['Mathématiques CPES', 'Physique/Chimie CPES', 'Écrit EAF', 'Oral EAF'] for li in liste: if not('cpes' in Fichier.get(cand, 'Classe actuelle').lower()) and 'cpes' in li.lower(): data[li] = formateur_note.format(li, li, note='-') else: data[li] = formateur_note.format(li, li, note=Fichier.get(cand, li)) # Suite data['cand'] = Fichier.get(cand, 'Candidatures impr') return data
def genere_dossier(self, qui, cand, format_admin=False): """ Renvoie le dictionnaire contenant les infos du dossier en cours""" # format_admin = False lorsque la demande est destinée à concevoir les fiches bilan de commission. # En effet, cette demande est lancée par Admin, mais l'impression se fait avec un dossier formaté # comme pour Jury : les notes de sont pas des <input type="text" .../>. C'est ensuite le code css # qui se charge d'adapter l'aspect de la page pour l'impression (fond blanc, barre de correction # invisible, remplacée par une indication du jury qui a traité le dossier et la correction faite, # liste des dossiers également invisible; vive le css !) #### Début data = {} # Pédigré liste_attr = [ 'Nom', 'Prénom', 'Date de naissance', 'Établissement', 'Département', 'Pays', 'Num ParcoursSup', 'INE' ] for attr in liste_attr: data[attr] = Fichier.get(cand, attr) # récup filiere pour connaître le chemin vers le dossier pdf (dans répertoire docs_candidats) fil = qui.fichier.filiere() data['ref_fich'] = os.path.join( 'docs_candidats', '{}'.format(fil.lower()), 'docs_{}'.format(Fichier.get(cand, 'Num ParcoursSup'))) # Formatage des champs de notes et de classe actuelle en fonction de format_admin # En effet, Admin a la possibilité d'écrire dans ces champs alors que Jury est en lecture seule. formateur_clas_actu = '{}' formateur_note = '{note}' activ = 'disabled' if format_admin: formateur_clas_actu = '<input type="text" id="Classe actuelle" name="Classe actuelle" size="10" value="{}"/>' formateur_note = '<input type="text" class="notes grossi" id="{}" name="{}" value="{note}"\ onfocusout="verif_saisie()"/>' activ = '' ### Suite de la création du dictionnaire # classe actuelle data['Classe actuelle'] = formateur_clas_actu.format( Fichier.get(cand, 'Classe actuelle')) # Notes matiere = ['Mathématiques', 'Physique/Chimie'] date = ['trimestre 1', 'trimestre 2', 'trimestre 3'] classe = ['Première', 'Terminale'] for cl in classe: for mat in matiere: for da in date: key = '{} {} {}'.format(mat, cl, da) data[key] = formateur_note.format(key, key, note=Fichier.get( cand, key)) # Autres notes liste = [ 'Mathématiques CPES', 'Physique/Chimie CPES', 'Écrit EAF', 'Oral EAF' ] for li in liste: if not ('cpes' in Fichier.get( cand, 'Classe actuelle').lower()) and 'cpes' in li.lower(): data[li] = formateur_note.format(li, li, note='-') else: data[li] = formateur_note.format(li, li, note=Fichier.get(cand, li)) # Suite data['cand'] = Fichier.get(cand, 'Candidatures impr') return data
def menu_admin(self, qui, fichiers_utilises, comm_en_cours): """ Compose le menu administrateur contenu : selon l'état (phase 1, 2 ou 3) du traitement phase 1 : avant la commission, l'admin gère ce qui provient de ParcoursSup, commente et/ou complète les dossiers phase 2 : l'admin a généré les fichiers *_comm_* destinés à la commission. Les différents jurys doivent se prononcer sur les dossiers. C'est le coeur de l'opération de sélection. phase 3 : commission terminée. L'admin doit gérer "l'après sélection" : recomposer un fichier ordonné par filière, générer tous les tableaux récapitulatifs. """ data = {} ## entête page = self.genere_entete('{} - Accès {}.'.format( self.titre, qui.get_droits())) list_fich_comm = glob.glob( os.path.join(os.curdir, "data", "comm_*.xml")) patron = 'menu_admin_' if len(list_fich_comm) > 0: # phase 2 ou 3 data['decompt'] = self.genere_liste_decompte() data['liste_stat'] = self.genere_liste_stat(qui) if comm_en_cours: # phase 2 patron += 'pendant' txt = '' for fich in fichiers_utilises.values(): txt += '<input type = "submit" class ="fichier" name = "fichier" value = "{}"/><br>'.format( fich) data['liste_jurys'] = txt else: # phase 3 patron += 'apres' # Etape 4 bouton data['bout_etap4'] = '<input type = "button" class ="fichier"' data[ 'bout_etap4'] += ' value = "Récolter les fichiers" onclick = "recolt_wait();"/>' # Etape 5 bouton et Etape 6 list_fich_class = glob.glob( os.path.join(os.curdir, "data", "class_*.xml")) data['liste_impression'] = '' if len(list_fich_class) > 0: data['liste_impression'] = self.genere_liste_impression() else: # avant commission patron += 'avant' # liste csv data['liste_csv'] = self.genere_liste_csv() # liste pdf data['liste_pdf'] = self.genere_liste_pdf() # liste admin data['liste_admin'] = self.genere_liste_admin() # liste_stat data['liste_stat'] = self.genere_liste_stat(qui) # Etape 3 bouton : ce bouton n'est actif que si admin a levé toutes les alertes. ### Testons s'il reste encore des alertes dans les fichiers admin # Récupération des fichiers admin list_fich = { Fichier(fich) for fich in glob.glob( os.path.join(os.curdir, "data", "admin_*.xml")) } alertes = False while not (alertes) and len( list_fich ) > 0: # à la première alerte détectée alertes = True fich = list_fich.pop() alertes = (True in { '- Alerte :' in Fichier.get(cand, 'Motifs') for cand in fich if Fichier.get(cand, 'Correction') != 'NC' }) ### Suite txt = '' if len(data['liste_admin'] ) > 0: # si les fichiers admin existent : txt = '<input type = "button" class ="fichier" value = "Générer les fichiers commission"' affich = '' if (alertes): affich = 'disabled' txt += 'onclick = "genere_wait();" {}/>'.format(affich) data['bout_etap3'] = txt # Envoyez le menu contenu = Composeur.html[patron].format(**data) # Composition de la page page += Composeur.html["MEP_MENU"].format(**{ 'contenu': contenu, 'script': qui.script_menu }) page += '</html>' return page
def traiter(self, **kwargs): """ Traiter un dossier """ # Fonction lancée par la fonction "traiter" du Serveur, elle même lancée par un clic sur 'Classé' ou 'NC' # On récupère le candidat courant cand = self.get_cand() # Ici, on va répercuter les complétions de l'administrateur dans tous les dossiers que le candidat a déposé. # Attention ! le traitement du fichier en cours est fait à part car deux objets 'Fichier' qui # auraient le même nom sont malgré tout différents !! On rajoute la bonne instance Fichier juste après. # Recherche de tous les fichiers existants (sauf fichier en cours) : list_fich_admin = [Fichier(fich) for fich in glob.glob(os.path.join(os.curdir, "data", "admin_*.xml"))\ if fich != self.fichier.nom] # On restreint la liste aux fichiers contenant le candidat en cours list_fich_cand = [fich for fich in list_fich_admin if cand in fich] # On rajoute le fichier suivi actuellement list_fich_cand.append(self.fichier) # list_fich_cand contient tous les fichiers dans lesquels le candidat courant se trouve. # ############### Admin a-t-il changé qqc ? Si oui, mise à jour. # Classe actuelle ? if Fichier.get(cand, 'Classe actuelle') != kwargs['Classe actuelle']: for fich in list_fich_cand: Fichier.set(fich.get_cand(cand), 'Classe actuelle', kwargs['Classe actuelle']) # Cas des notes matiere = ['Mathématiques', 'Physique/Chimie'] date = ['trimestre 1', 'trimestre 2', 'trimestre 3'] classe = ['Première', 'Terminale'] for cl in classe: for mat in matiere: for da in date: key = '{} {} {}'.format(mat, cl, da) if Fichier.get(cand, key) != kwargs[key]: # la note a-t-elle été modifiée ? for fich in list_fich_cand: Fichier.set(fich.get_cand(cand), key, kwargs[key]) # CPES et EAF #liste = ['Mathématiques CPES', 'Physique/Chimie CPES', 'Écrit EAF', 'Oral EAF'] # Seulement EAF depuis 2020 liste = ['Écrit EAF', 'Oral EAF'] for li in liste: if 'cpes' in li.lower(): if ('cpes' in Fichier.get(cand, 'Classe actuelle').lower()) and Fichier.get(cand, li) != kwargs[li]: for fich in list_fich_cand: Fichier.set(fich.get_cand(cand), li, kwargs[li]) else: if Fichier.get(cand, li) != kwargs[li]: for fich in list_fich_cand: Fichier.set(fich.get_cand(cand), li, kwargs[li]) # Commentaire éventuel admin + gestion des 'NC' # Les commentaires admin sont précédés de '- Admin :' c'est à cela qu'on les reconnaît. Et le jury les # verra sur fond rouge dans la liste de ses dossiers. # Par ailleurs, dossiers_jury.js exclut qu'un tel commentaire soit considéré comme une motivation de jury. motif = kwargs['motif'] if not('- Admin :' in motif or motif == '' or '- Alerte :' in motif): motif = '- Admin : {}'.format(motif) # Récupération de la correction. On en fait qqc seulement si elle est minimale (NC) cor = kwargs['correc'] # récupération de la correction et calcul du score final if float(cor) == float(min_correc): # L'admin a validé le formulaire avec la correction NC (le candidat ne passera pas en commission) # Pour ce cas là, on ne recopie pas dans toutes les filières. Admin peut exclure une candidature # dans une filière sans l'exclure des autres. Sécurité ! Fichier.set(cand, 'Correction', 'NC') # la fonction calcul_scoreb renverra 0 ! Fichier.set(cand, 'Jury', 'Admin') # Cette exclusion est un choix de l'admin (apparaît dans les tableaux) Fichier.set(cand, 'Motifs', motif) else: Fichier.set(cand, 'Correction', '0') # 2 lignes nécessaires si l'admin a NC un candidat, puis a changé d'avis. Fichier.set(cand, 'Jury', '') for fich in list_fich_cand: Fichier.set(fich.get_cand(cand), 'Motifs', motif) # On (re)calcule le score brut ! Fichier.calcul_scoreb(cand) # On sauvegarde tous les fichiers retouchés for fich in list_fich_cand: fich.sauvegarde()