Пример #1
0
 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
Пример #2
0
 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})
Пример #3
0
    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
Пример #4
0
 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
Пример #5
0
 def choix_comm(self, **kwargs):
     """ Appelée quand un jury sélectionne un fichier dans son menu. Retourne la page de traitement de ce dossier. 
     """
     # récupère le client
     client = self.get_client_cour(
     )  # quel jury ? (sur quel machine ? on le sait grâce au cookie)
     # Teste si le fichier n'a pas été choisi par un autre jury
     fichier = kwargs.get(
         "fichier")  # nom du fichier sélectionné par le jury
     a = fichier in self.fichiers_utilises.values()
     b = fichier != self.fichiers_utilises.get(client, 'rien')
     if (a and b):
         # Si oui, retour menu
         return self.affiche_menu()
     else:
         # sinon, mise à jour des attributs du client : l'attribut fichier du client va recevoir une instance d'un
         # objet Fichier, construit à partir du nom de fichier.
         client.set_fichier(Fichier(fichier))
         # Mise à jour de la liste des fichiers utilisés
         self.fichiers_utilises[client] = fichier
         # On émet un message SSE : ajout d'un fichier à la liste des fichiers en cours de traitement
         self.add_sse_message('add', fichier)
         # mem_scroll initialisé : cookie qui stocke la position de l'ascenseur dans la liste des dossiers
         cherrypy.session['mem_scroll'] = '0'
         # Affichage de la page de gestion des dossiers
         return self.affi_dossier()
Пример #6
0
 def page_impression(self, **kwargs):
     """ Appelée par l'admin (2e menu, clique sur un fichier class_XXX.xml). Lance le menu d'impression des fiches 
     bilan de commission. Retourne la page impression (elle ne contient que le bouton 'RETOUR' (merci le css). """
     client = self.get_client_cour(
     )  # récupère le client (c'est un admin !)
     # Mise à jour des attributs du client
     client.set_fichier(
         Fichier(kwargs["fichier"])
     )  # son fichier courant devient celui qu'il vient de choisir
     return self.html_compose.page_impression(client)
Пример #7
0
 def genere_liste_stat(self, qui):
     """ Sous-fonction pour le menu admin : affichage des statistiques de candidatures """
     liste_stat = ''
     if len(glob.glob(os.path.join(
             os.curdir, "data",
             "admin_*.xml"))) > 0:  # si les fichiers admin existent
         # lecture du fichier stat
         chem = os.path.join(os.curdir, "data", "stat")
         if not (
                 os.path.exists(chem)
         ):  # le fichier stat n'existe pas (cela ne devrait pas arriver)
             # on le créé
             list_fich = [
                 Fichier(fich) for fich in glob.glob(
                     os.path.join(os.curdir, "data", "admin_*.xml"))
             ]
             qui.stat()
         # maintenant on peut effectivement lire le fichier stat
         with open(os.path.join(os.curdir, "data", "stat"), 'br') as fich:
             stat = pickle.load(fich)
         # Création de la liste à afficher
         liste_stat = '<h4>Statistiques : {} candidats dont {} ayant validé.</h4>'.format(
             stat['nb_cand'], stat['nb_cand_valid'])
         # Pour commencer les sommes par filières
         liste_stat += '<ul style = "margin-top:-5%">'
         deja_fait = [0
                      ]  # sert au test ci-dessous si on n'a pas math.log2()
         for i in range(len(filieres)):
             liste_stat += '<li>{} dossiers {} validés</li>'.format(
                 stat[2**i], filieres[i].upper())
             deja_fait.append(2**i)
         # Ensuite les requêtes croisées
         liste_stat += 'dont :<ul>'
         for i in range(2**len(filieres)):
             if not (
                     i in deja_fait
             ):  # avec la fonction math.log2 ce test serait facile !!!
                 seq = []
                 bina = bin(
                     i
                 )[2:]  # bin revoie une chaine qui commence par 'Ob' : on vire !
                 while len(bina) < len(filieres):
                     bina = '0{}'.format(
                         bina)  # les 0 de poids fort sont restaurés
                 for char in range(len(bina)):
                     if bina[char] == '1':
                         seq.append(filieres[len(filieres) - char -
                                             1].upper())
                 txt = ' + '.join(seq)
                 liste_stat += '<li>{} dossiers {}</li>'.format(
                     stat[i], txt)
         liste_stat += '</ul></ul>'
     return liste_stat
Пример #8
0
 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)
Пример #9
0
 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
Пример #10
0
 def choix_admin(self, **kwargs):
     """ Appelée quand l'admin sélectionne un fichier 'admin_XXX.xml' dans son premier menu. Retourne la page de 
     traitement de ce dossier. """
     cherrypy.response.headers["content-type"] = "text/html"
     # récupère le client
     client = self.get_client_cour(
     )  # quel client ? (sur quel machine ? on le sait grâce au cookie)
     # Mise à jour des attributs du client : l'attribut fichier du client va recevoir une instance d'un objet
     # Fichier, construit à partir du nom de fichier.
     client.set_fichier(Fichier(kwargs["fichier"]))
     ## Initialisation des paramètres
     # mem_scroll : cookie qui stocke la position de l'ascenseur dans la liste des dossiers
     cherrypy.session['mem_scroll'] = '0'
     # Affichage de la page de gestion des dossiers
     return self.affi_dossier()
Пример #11
0
 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
Пример #12
0
 def generation_comm(self):
     """ Création des fichiers commission """
     # Objectif : classer les candidats (fichier admin) par ordre de score brut décroissant et générer autant de 
     # fichiers qu'il y a de jurys dans la filière concernées. Ces fichiers sont construits de façon à ce qu'ils 
     # contiennent des candidatures également solides.
     # Récupération des fichiers admin
     list_fich = [Fichier(fich) for fich in glob.glob(os.path.join(os.curdir, "data", "admin_*.xml"))]
     # Pour chaque fichier "admin_*.xml"
     for fich in list_fich:
         # Tout d'abord, calculer (et renseigner le noeud) le score brut de chaque candidat 
         for cand in fich:
             Fichier.calcul_scoreb(cand)
         # Classement par scoreb décroissant
         doss = fich.ordonne('score_b')
         # Calcul du rang de chaque candidat et renseignement du noeuds 'rang_brut'
         for cand in fich:
             Fichier.set(cand, 'Rang brut',  str(Fichier.rang(cand, doss, 'Score brut')))
         # Récupération de la filière et du nombre de jurys 
         nbjury = int(nb_jurys[fich.filiere().lower()])
         # Découpage en n listes de dossiers
         for j in range(nbjury):
             dossier = []    # deepcopy ligne suivante sinon les candidats sont retirés de doss à chaque append
             [dossier.append(copy.deepcopy(doss[i])) for i in range(len(doss)) if i%nbjury == j]
             # Sauvegarde dans un fichier comm_XXXX.xml
             res = etree.Element('candidats')
             [res.append(cand) for cand in dossier]
             nom = os.path.join(os.curdir, "data", "comm_{}{}.xml".format(fich.filiere().upper(), j+1))
             with open(nom, 'wb') as fichier:
                 fichier.write(etree.tostring(res, pretty_print=True, encoding='utf-8'))
     # Création fichier decompte : celui-ci contiendra en fin de commission le nombre de candidats traités pour 
     # chacune des filières. Ici, il est créé et initialisé. Il contient un dictionnaire {'filière' : nb, ...}
     decompt = {}
     for fil in filieres:
         decompt['{}'.format(fil.upper())] = 0
     with open(os.path.join(os.curdir, "data", "decomptes"), 'wb') as stat_fich:
         pickle.dump(decompt, stat_fich)
Пример #13
0
    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
Пример #14
0
    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
Пример #15
0
 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.
Пример #16
0
    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()
Пример #17
0
 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)
Пример #18
0
def filtre(candidat, test = False):
    prefixe = ''
    commentaire = ''
    # Candidature validée ?
    if candidat.xpath('synoptique/établissement/candidature_validée')[0].text.lower() != 'oui':
        commentaire = 'Candidature non validée sur ParcoursSup'
        Fichier.set(candidat, 'Correction', 'NC')
        Fichier.set(candidat, 'Jury', 'Admin')
    else: # si validée,
        # on récupère la série
        serie = ''
        probs = candidat.xpath('bulletins/bulletin[classe="Terminale"]')
        for prob in probs: # fausse boucle (normalement)
            serie = prob.xpath('série')[0].text
        # Si série non valide, on exclut
        if serie in series_non_valides:
            commentaire = 'Série {}'.format(serie)
            Fichier.set(candidat, 'Correction', 'NC')
            Fichier.set(candidat, 'Jury', 'Admin')
        else: # sinon, on alerte Admin sur certaines anomalies rencontrées
            prefixe = '- Alerte :'
            # 1/ Série reconnue ?
            if not(serie in series_valides):
                commentaire += ' | Vérifier la série |'
            # 2/ Le dossier est-il complet (toutes les notes présentes + classe actuelle)
            if not(Fichier.is_complet(candidat)):
                commentaire += ' Dossier incomplet |'
    if commentaire != '':
        Fichier.set(candidat, 'Motifs', '{} {}'.format(prefixe, commentaire))
    else: # si aucune remarque, on calcule le score brut
        Fichier.calcul_scoreb(candidat)
    # Fin des filtres; on retourne un candidat mis à jour
    return candidat