Exemple #1
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)
Exemple #2
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()
Exemple #3
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)
Exemple #4
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
Exemple #5
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
Exemple #6
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()
Exemple #7
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)
Exemple #8
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
Exemple #9
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()