# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """ScoDoc: génération feuille émargement et placement Contribution J.-M. Place 2021 basée sur une idée de M. Salomon, UFC / IUT DE BELFORT-MONTBÉLIARD, 2016 """ import random import time from copy import copy import wtforms.validators from flask import request, render_template from flask_login import current_user from flask_wtf import FlaskForm from openpyxl.styles import PatternFill, Alignment, Border, Side, Font from wtforms import ( StringField, SubmitField, SelectField, RadioField, HiddenField, SelectMultipleField, ) from app.formations import edit_module from app.models import Evaluation, ModuleImpl import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc import sco_preferences from app.scodoc import sco_evaluations from app.scodoc import sco_excel from app.scodoc.sco_excel import ScoExcelBook, COLORS from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl from app.scodoc.gen_tables import GenTable from app.scodoc import sco_etud import sco_version _ = lambda x: x # sans babel _l = _ COORD = "Coordonnées" SEQ = "Continue" TOUS = "Tous" def _get_group_info(evaluation_id): # groupes groups = sco_groups.do_evaluation_listegroupes(evaluation_id, include_default=True) has_groups = False groups_tree = {} for group in groups: partition = group["partition_name"] or TOUS group_id = group["group_id"] group_name = group["group_name"] or TOUS if partition not in groups_tree: groups_tree[partition] = {} groups_tree[partition][group_name] = group_id if partition != TOUS: has_groups = True else: has_groups = False nb_groups = sum([len(groups_tree[p]) for p in groups_tree]) return groups_tree, has_groups, nb_groups class PlacementForm(FlaskForm): """Formulaire pour placement des étudiants en Salle""" evaluation_id = HiddenField("evaluation_id") file_format = RadioField( "Format de fichier", choices=["pdf", "xls"], validators=[ wtforms.validators.DataRequired("indiquez le format du fichier attendu"), ], ) surveillants = StringField("Surveillants", validators=[]) batiment = StringField("Batiment") salle = StringField("Salle") nb_rangs = SelectField( "nb de places en largeur", coerce=int, choices=[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], description="largeur de la salle, en nombre de places", ) etiquetage = RadioField( "Numérotation", choices=[SEQ, COORD], validators=[ wtforms.validators.DataRequired("indiquez le style de numérotation"), ], ) groups = SelectMultipleField( "Groupe(s)", validators=[], ) submit = SubmitField("OK") def __init__(self, formdata=None, data=None): super().__init__(formdata=formdata, data=data) self.groups_tree = {} self.has_groups = None self.nb_groups = None self.tous_id = None self.set_evaluation_infos(data["evaluation_id"]) def set_evaluation_infos(self, evaluation_id): """Initialise les données du formulaire avec les données de l'évaluation.""" _ = Evaluation.get_evaluation(evaluation_id) # check exist ? self.groups_tree, self.has_groups, self.nb_groups = _get_group_info( evaluation_id ) choices = [] for partition in self.groups_tree: for groupe in self.groups_tree[partition]: if ( groupe == TOUS ): # Affichage et valeur spécifique pour le groupe TOUS self.tous_id = str(self.groups_tree[partition][groupe]) choices.append((TOUS, TOUS)) else: groupe_id = str(self.groups_tree[partition][groupe]) choices.append((groupe_id, "%s (%s)" % (str(groupe), partition))) self.groups.choices = choices # self.groups.default = [TOUS] # Ne fonctionnne pas... (ni dans la déclaration de PlaceForm.groups) # la réponse [] est de toute façon transposée en [ self.tous_id ] lors du traitement (cas du groupe unique) class _DistributeurContinu: """Distribue les places selon un ordre numérique.""" def __init__(self): self.position = 1 def suivant(self): """Retounre la désignation de la place suivante""" retour = self.position self.position += 1 return retour class _Distributeur2D: """Distribue les places selon des coordonnées sur nb_rangs.""" def __init__(self, nb_rangs): self.nb_rangs = nb_rangs self.rang = 1 self.index = 1 def suivant(self): """Retounre la désignation de la place suivante""" retour = (self.index, self.rang) self.rang += 1 if self.rang > self.nb_rangs: self.rang = 1 self.index += 1 return retour def placement_eval_selectetuds(evaluation_id): """Creation de l'écran de placement""" form = PlacementForm( request.form, data={"evaluation_id": int(evaluation_id), "groups": TOUS}, ) if form.validate_on_submit(): runner = PlacementRunner(form) if not runner.check_placement(): return ( """
(vérifiez que le semestre n'est pas verrouillé et que vous avez l'autorisation d'effectuer cette opération)
""" % runner.__dict__ ) return runner.exec_placement() # calcul et generation du fichier return render_template( "scodoc/forms/placement.j2", evaluations_description=sco_evaluations.evaluation_describe( evaluation_id=evaluation_id ), form=form, ) class PlacementRunner: """Execution de l'action définie par le formulaire""" def __init__(self, form): """Calcul et génération du fichier sur la base des données du formulaire""" self.evaluation_id = form["evaluation_id"].data self.etiquetage = form["etiquetage"].data self.surveillants = form["surveillants"].data self.batiment = form["batiment"].data self.salle = form["salle"].data self.nb_rangs = form["nb_rangs"].data self.file_format = form["file_format"].data if len(form["groups"].data) == 0: self.groups_ids = [form.tous_id] else: # On remplace le mot-clé TOUS le l'identiant de ce groupe self.groups_ids = [ gid if gid != TOUS else form.tous_id for gid in form["groups"].data ] self.evaluation = Evaluation.get_evaluation(self.evaluation_id) self.groups = sco_groups.listgroups(self.groups_ids) self.gr_title_filename = sco_groups.listgroups_filename(self.groups) # gr_title = sco_groups.listgroups_abbrev(d['groups']) self.current_user = current_user self.moduleimpl_id = self.evaluation.moduleimpl_id self.moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(self.moduleimpl_id) # TODO: à revoir pour utiliser modèle ModuleImpl self.moduleimpl_data = sco_moduleimpl.moduleimpl_list( moduleimpl_id=self.moduleimpl_id )[0] self.module_data = edit_module.module_list( args={"module_id": self.moduleimpl_data["module_id"]} )[0] self.sem = sco_formsemestre.get_formsemestre( self.moduleimpl_data["formsemestre_id"] ) self.evalname = "%s-%s" % ( self.module_data["code"] or "?", ( self.evaluation.date_debut.strftime("%Y-%m-%d_%Hh%M") if self.evaluation.date_debut else "" ), ) if self.evaluation.description: self.evaltitre = self.evaluation.description else: self.evaltitre = f"""évaluation{ self.evaluation.date_debut.strftime(' du %d/%m/%Y à %Hh%M') if self.evaluation.date_debut else ''}""" self.desceval = [ # une liste de chaines: description de l'evaluation self.sem["titreannee"], "Module : %s - %s" % (self.module_data["code"] or "?", self.module_data["abbrev"] or ""), "Surveillants : %s" % self.surveillants, "Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__, "Controle : %s (coef. %g)" % (self.evaltitre, self.evaluation.coefficient), ] self.styles = None self.plan = None self.listetud = None def check_placement(self): """Vérifie que l'utilisateur courant a le droit d'édition sur les notes""" # Check access (admin, respformation, and responsable_id) return self.moduleimpl.can_edit_notes(self.current_user) def exec_placement(self): """Excéute l'action liée au formulaire""" self._repartition() if self.file_format == "xls": return self._production_xls() return self._production_pdf() def _repartition(self): """ Calcule le placement. retourne une liste de couples ((nom, prenom), position) """ # Construit liste des etudiants et les réparti self.groups = sco_groups.listgroups(self.groups_ids) self.listetud = self._build_listetud() self.plan = self._affectation_places() def _build_listetud(self): get_all_students = None in [ g["group_name"] for g in self.groups ] # tous les etudiants etudid_etats = sco_groups.do_evaluation_listeetuds_groups( self.evaluation_id, self.groups, getallstudents=get_all_students, include_demdef=True, ) listetud = [] # liste de couples (nom,prenom) for etudid, etat in etudid_etats: # infos identite etudiant (xxx sous-optimal: 1/select par etudiant) ident = sco_etud.etudident_list(ndb.GetDBConnexion(), {"etudid": etudid})[0] if etat != "D": nom = ident["nom"].upper() prenom = ident["prenom"].lower().capitalize() etudid = ident["etudid"] listetud.append((nom, prenom, etudid)) random.shuffle(listetud) return listetud def _affectation_places(self): plan = [] if self.etiquetage == SEQ: distributeur = _DistributeurContinu() else: distributeur = _Distributeur2D(self.nb_rangs) for etud in self.listetud: plan.append((etud, distributeur.suivant())) return plan def _production_xls(self): filename = "placement_%s_%s" % (self.evalname, self.gr_title_filename) xls = self._excel_feuille_placement() return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE) def _production_pdf(self): pdf_title = "