diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py index adeeebba6..aae6a01d6 100644 --- a/app/scodoc/sco_excel.py +++ b/app/scodoc/sco_excel.py @@ -35,6 +35,7 @@ from enum import Enum from tempfile import NamedTemporaryFile import openpyxl.utils.datetime +from flask import make_response from openpyxl import Workbook, load_workbook from openpyxl.cell import WriteOnlyCell from openpyxl.styles import Font, Border, Side, Alignment, PatternFill @@ -64,14 +65,21 @@ class COLORS(Enum): LIGHT_YELLOW = "FFFFFF99" +def send_from_flask(data, filename, mime=scu.XLSX_MIMETYPE): + scu.make_filename(filename) + response = make_response(data) + response.headers['Content-Type'] = mime + response.headers['Content-Disposition'] = 'attachment; filename="%s"' % filename + + def send_excel_file(request, data, filename, mime=scu.XLSX_MIMETYPE): """publication fichier. (on ne doit rien avoir émis avant, car ici sont générés les entetes) """ filename = ( scu.unescape_html(scu.suppress_accents(filename)) - .replace("&", "") - .replace(" ", "_") + .replace("&", "") + .replace(" ", "_") ) request.RESPONSE.setHeader("content-type", mime) request.RESPONSE.setHeader( @@ -136,16 +144,16 @@ class ScoExcelBook: def excel_make_style( - bold=False, - italic=False, - outline=False, - color: COLORS = COLORS.BLACK, - bgcolor: COLORS = None, - halign=None, - valign=None, - number_format=None, - font_name="Arial", - size=10, + bold=False, + italic=False, + outline=False, + color: COLORS = COLORS.BLACK, + bgcolor: COLORS = None, + halign=None, + valign=None, + number_format=None, + font_name="Arial", + size=10, ): """Contruit un style. Les couleurs peuvent être spécfiées soit par une valeur de COLORS, @@ -228,12 +236,12 @@ class ScoExcelSheet: self.row_dimensions = {} def excel_make_composite_style( - self, - alignment=None, - border=None, - fill=None, - number_format=None, - font=None, + self, + alignment=None, + border=None, + fill=None, + number_format=None, + font=None, ): style = {} if font is not None: @@ -374,7 +382,7 @@ class ScoExcelSheet: def excel_simple_table( - titles=None, lines=None, sheet_name=b"feuille", titles_styles=None, comments=None + titles=None, lines=None, sheet_name=b"feuille", titles_styles=None, comments=None ): """Export simple type 'CSV': 1ere ligne en gras, le reste tel quel""" ws = ScoExcelSheet(sheet_name) @@ -643,13 +651,13 @@ def _excel_to_list(filelike): # we may need 'encoding' argument ? def excel_feuille_listeappel( - sem, - groupname, - lines, - partitions=None, - with_codes=False, - with_paiement=False, - server_name=None, + sem, + groupname, + lines, + partitions=None, + with_codes=False, + with_paiement=False, + server_name=None, ): """generation feuille appel""" if partitions is None: @@ -751,7 +759,7 @@ def excel_feuille_listeappel( for t in lines: n += 1 nomprenom = ( - t["civilite_str"] + " " + t["nom"] + " " + t["prenom"].lower().capitalize() + t["civilite_str"] + " " + t["nom"] + " " + t["prenom"].lower().capitalize() ) style_nom = style2t3 if with_paiement: diff --git a/app/scodoc/sco_placement.py b/app/scodoc/sco_placement.py index 24795ab18..665bf64f4 100644 --- a/app/scodoc/sco_placement.py +++ b/app/scodoc/sco_placement.py @@ -35,9 +35,8 @@ import random import time from copy import copy -import flask import wtforms.validators -from flask import request, render_template +from flask import request, render_template, url_for from flask_login import current_user from werkzeug import Response from flask_wtf import FlaskForm @@ -141,273 +140,7 @@ class PlacementForm(FlaskForm): self.groups.choices = choices -def placement_eval_selectetuds(evaluation_id): - """Creation de l'écran de placement""" - form = PlacementForm( - request.form, - data={"evaluation_id": int(evaluation_id), "groups": PlacementForm.TOUS}, - ) - form.set_evaluation_infos(evaluation_id) - if form.validate_on_submit(): - exec_placement(form) # calcul et generation du fichier - return flask.redirect(titi()) - H = [html_sco_header.sco_header(init_jquery_ui=True)] - H.append(sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)) - H.append("

Placement et émargement des étudiants

") - H.append(render_template("forms/placement.html", form=form)) - F = html_sco_header.sco_footer() - return "\n".join(H) + "

" + F - - -# def do_placement_selectetuds(): -# """ -# Choisi les étudiants et les infos sur la salle pour leur placement. -# """ -# # M = sco_moduleimpl.do_moduleimpl_list( moduleimpl_id=E["moduleimpl_id"])[0] -# # description de l'evaluation -# H = [ -# sco_evaluations.evaluation_describe(evaluation_id=evaluation_id), -# "

Placement et émargement des étudiants

", -# ] -# # -# descr = [ -# ("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}), -# ( -# "placement_method", -# { -# "input_type": "radio", -# "default": "xls", -# "allow_null": False, -# "allowed_values": ["pdf", "xls"], -# "labels": ["fichier pdf", "fichier xls"], -# "title": "Format de fichier :", -# }, -# ), -# ("teachers", {"size": 25, "title": "Surveillants :"}), -# ("building", {"size": 25, "title": "Batiment :"}), -# ("room", {"size": 10, "title": "Salle :"}), -# ( -# "columns", -# { -# "input_type": "radio", -# "default": "5", -# "allow_null": False, -# "allowed_values": ["3", "4", "5", "6", "7", "8"], -# "labels": [ -# "3 colonnes", -# "4 colonnes", -# "5 colonnes", -# "6 colonnes", -# "7 colonnes", -# "8 colonnes", -# ], -# "title": "Nombre de colonnes :", -# }, -# ), -# ( -# "numbering", -# { -# "input_type": "radio", -# "default": "coordinate", -# "allow_null": False, -# "allowed_values": ["continuous", "coordinate"], -# "labels": ["continue", "coordonnées"], -# "title": "Numérotation :", -# }, -# ), -# ] -# if no_groups: -# submitbuttonattributes = [] -# descr += [ -# ( -# "group_ids", -# { -# "default": [ -# g["group_id"] # pylint: disable=invalid-sequence-index -# for g in groups -# ], -# "input_type": "hidden", -# "type": "list", -# }, -# ) -# ] -# else: -# descr += [ -# ( -# "group_ids", -# { -# "input_type": "checkbox", -# "title": "Choix groupe(s) d'étudiants :", -# "allowed_values": grnams, -# "labels": grlabs, -# "attributes": ['onchange="gr_change(this);"'], -# }, -# ) -# ] -# -# if not ("group_ids" in REQUEST.form and REQUEST.form["group_ids"]): -# submitbuttonattributes = ['disabled="1"'] -# else: -# submitbuttonattributes = [] # groupe(s) preselectionnés -# H.append( -# # JS pour desactiver le bouton OK si aucun groupe selectionné -# """ -# """ -# ) -# -# tf = TrivialFormulator( -# REQUEST.URL0, -# REQUEST.form, -# descr, -# cancelbutton="Annuler", -# submitbuttonattributes=submitbuttonattributes, -# submitlabel="OK", -# formid="gr", -# ) -# if tf[0] == 0: -# # H.append( """
-# # Choix du groupe et de la localisation -# # """) -# H.append("""
""") -# return "\n".join(H) + "\n" + tf[1] + "\n
" -# elif tf[0] == -1: -# return flask.redirect( -# "%s/Notes/moduleimpl_status?moduleimpl_id=%s" -# % (scu.ScoURL(), E["moduleimpl_id"]) -# ) -# else: -# placement_method = tf[2]["placement_method"] -# teachers = tf[2]["teachers"] -# building = tf[2]["building"] -# room = tf[2]["room"] -# group_ids = tf[2]["group_ids"] -# columns = tf[2]["columns"] -# numbering = tf[2]["numbering"] -# if columns in ("3", "4", "5", "6", "7", "8"): -# gs = [ -# ("group_ids%3Alist=" + six.moves.urllib.parse.quote_plus(x)) -# for x in group_ids -# ] -# query = ( -# "evaluation_id=%s&placement_method=%s&teachers=%s&building=%s&room=%s&columns=%s&numbering=%s&" -# % ( -# evaluation_id, -# placement_method, -# teachers, -# building, -# room, -# columns, -# numbering, -# ) -# + "&".join(gs) -# ) -# return flask.redirect(scu.NotesURL() + "/do_placement?" + query) -# else: -# raise ValueError( -# "invalid placement_method (%s)" % tf[2]["placement_method"] -# ) - - -def exec_placement(form): - """Calcul et génération du fichier sur la base des données du formulaire""" - d = { - "evaluation_id": form["evaluation_id"].data, - "etiquetage": form["etiquetage"].data, - "surveillants": form["surveillants"].data, - "batiment": form["batiment"].data, - "salle": form["salle"].data, - "nb_rangs": form["nb_rangs"].data, - "groups": form["groups"].data, - } - breakpoint() - d["eval_data"] = sco_evaluations.do_evaluation_list(d)[0] - # Check access (admin, respformation, and responsable_id) - d["current_user"] = current_user - d["moduleimpl_id"] = d["eval_data"]["moduleimpl_id"] - if not sco_permissions_check.can_edit_notes(d["current_user"], d["moduleimpl_id"]): - return ( - """

Génération du placement impossible pour %(current_user)s

-

(vérifiez que le semestre n'est pas verrouillé et que vous -avez l'autorisation d'effectuer cette opération)

-

Continuer

-""" - % d - ) - d["cnx"] = ndb.GetDBConnexion() - d["plan"] = repartition(d) - d["gr_title_filename"] = sco_groups.listgroups_filename(d['groups']) - # gr_title = sco_groups.listgroups_abbrev(d['groups']) - d["moduleimpl_data"] = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=d["moduleimpl_id"]) - d["Mod"] = sco_edit_module.do_module_list(args={"module_id": d["moduleimpl_id"]})[0] - d["sem"] = sco_formsemestre.get_formsemestre(d["moduleimpl_data"]["formsemestre_id"]) - d["evalname"] = "%s-%s" % (d["Mod"]["code"], ndb.DateDMYtoISO(eval_data["jour"])) - if d["eval_data"]["description"]: - d["evaltitre"] = d["eval_data"]["description"] - else: - d["evaltitre"] = "évaluation du %s" % eval_data["jour"] - d["desceval"] = [ - ["%s" % d["sem"]["titreannee"]], - ["Module : %s - %s" % (d["Mod"]["code"], d["Mod"]["abbrev"])], - ["Surveillants : %(surveillant)s" % d], - ["Batiment : %(batiment)s - Salle : %(salle)s" % d], - ["Controle : %s (coef. %g)" % (d["evaltitre"], d["eval_data"]["coefficient"])], - ] # une liste de liste de chaines: description de l'evaluation - if form["file_format"].data == "xls": - production_xls(d) - else: - production_pdf(d) - - -def repartition(d): - """ - Calcule le placement. retourne une liste de couples ((nom, prenom), position) - """ - # Construit liste des etudiants - groups = sco_groups.listgroups(d["groups"]) - d["listetud"] = build_listetud(d) - return affectation_places(d) - - -def build_listetud(cnx, groups, evaluation_id, moduleimpl_data): - if None in [g["group_name"] for g in groups]: # tous les etudiants - getallstudents = True - gr_title_filename = "tous" - else: - getallstudents = False - etudids = sco_groups.do_evaluation_listeetuds_groups( - evaluation_id, groups, getallstudents=getallstudents, include_dems=True - ) - listetud = [] # liste de couples (nom,prenom) - for etudid in etudids: - # infos identite etudiant (xxx sous-optimal: 1/select par etudiant) - ident = sco_etud.etudident_list(cnx, {"etudid": etudid})[0] - # infos inscription - inscr = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( - {"etudid": etudid, "formsemestre_id": moduleimpl_data["formsemestre_id"]} - )[0] - if inscr["etat"] != "D": - nom = ident["nom"].upper() - prenom = ident["prenom"].lower().capitalize() - listetud.append((nom, prenom)) - random.shuffle(listetud) - return listetud - - -class DistributeurContinu: +class _DistributeurContinu: """Distribue les places selon un ordre numérique.""" def __init(self): @@ -419,7 +152,7 @@ class DistributeurContinu: return retour -class Distributeur2D: +class _Distributeur2D: """Distribue les places selon des coordonnées sur nb_rangs.""" def __init__(self, nb_rangs): @@ -436,19 +169,140 @@ class Distributeur2D: return retour -def affectation_places(d): +def placement_eval_selectetuds(evaluation_id): + """Creation de l'écran de placement""" + form = PlacementForm( + request.form, + data={"evaluation_id": int(evaluation_id), "groups": PlacementForm.TOUS}, + ) + form.set_evaluation_infos(evaluation_id) + if form.validate_on_submit(): + return _exec_placement(form) # calcul et generation du fichier + # return flask.redirect(url_for("scodoc.index")) + H = [html_sco_header.sco_header(init_jquery_ui=True)] + H.append(sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)) + H.append("

Placement et émargement des étudiants

") + H.append(render_template("forms/placement.html", form=form)) + F = html_sco_header.sco_footer() + return "\n".join(H) + "

" + F + + +def _exec_placement(form): + """Calcul et génération du fichier sur la base des données du formulaire""" + d = { + "evaluation_id": form["evaluation_id"].data, + "etiquetage": form["etiquetage"].data, + "surveillants": form["surveillants"].data, + "batiment": form["batiment"].data, + "salle": form["salle"].data, + "nb_rangs": form["nb_rangs"].data, + "groups_ids": form["groups"].data, + } + d["eval_data"] = sco_evaluations.do_evaluation_list( + {"evaluation_id": d["evaluation_id"]} + )[0] + # Check access (admin, respformation, and responsable_id) + d["current_user"] = current_user + d["moduleimpl_id"] = d["eval_data"]["moduleimpl_id"] + if not sco_permissions_check.can_edit_notes(d["current_user"], d["moduleimpl_id"]): + return """

Génération du placement impossible pour %s

+

(vérifiez que le semestre n'est pas verrouillé et que vous +avez l'autorisation d'effectuer cette opération)

+

Continuer

+""" % ( + d["current_user"].user_name, + d["module_id"], + ) + d["cnx"] = ndb.GetDBConnexion() + d["groups"] = sco_groups.listgroups(d["groups_ids"]) + d["gr_title_filename"] = sco_groups.listgroups_filename(d["groups"]) + # gr_title = sco_groups.listgroups_abbrev(d['groups']) + d["moduleimpl_data"] = sco_moduleimpl.do_moduleimpl_list( + moduleimpl_id=d["moduleimpl_id"] + )[0] + d["Mod"] = sco_edit_module.do_module_list( + args={"module_id": d["moduleimpl_data"]["module_id"]} + )[0] + d["sem"] = sco_formsemestre.get_formsemestre( + d["moduleimpl_data"]["formsemestre_id"] + ) + d["evalname"] = "%s-%s" % ( + d["Mod"]["code"], + ndb.DateDMYtoISO(d["eval_data"]["jour"]), + ) + if d["eval_data"]["description"]: + d["evaltitre"] = d["eval_data"]["description"] + else: + d["evaltitre"] = "évaluation du %s" % d["eval_data"]["jour"] + d["desceval"] = [ + ["%s" % d["sem"]["titreannee"]], + ["Module : %s - %s" % (d["Mod"]["code"], d["Mod"]["abbrev"])], + ["Surveillants : %(surveillants)s" % d], + ["Batiment : %(batiment)s - Salle : %(salle)s" % d], + ["Controle : %s (coef. %g)" % (d["evaltitre"], d["eval_data"]["coefficient"])], + ] # une liste de liste de chaines: description de l'evaluation + d["plan"] = _repartition(d) + if form["file_format"].data == "xls": + return _production_xls(d) + else: + return _production_pdf(d) + + +def _repartition(d): + """ + Calcule le placement. retourne une liste de couples ((nom, prenom), position) + """ + # Construit liste des etudiants + d["groups"] = sco_groups.listgroups(d["groups_ids"]) + d["listetud"] = _build_listetud(d) + return _affectation_places(d) + + +def _build_listetud(d): + if None in [g["group_name"] for g in d["groups"]]: # tous les etudiants + getallstudents = True + gr_title_filename = "tous" + else: + getallstudents = False + etudids = sco_groups.do_evaluation_listeetuds_groups( + d["evaluation_id"], + d["groups"], + getallstudents=getallstudents, + include_dems=True, + ) + listetud = [] # liste de couples (nom,prenom) + for etudid in etudids: + # infos identite etudiant (xxx sous-optimal: 1/select par etudiant) + ident = sco_etud.etudident_list(d["cnx"], {"etudid": etudid})[0] + # infos inscription + inscr = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( + { + "etudid": etudid, + "formsemestre_id": d["moduleimpl_data"]["formsemestre_id"], + } + )[0] + if inscr["etat"] != "D": + nom = ident["nom"].upper() + prenom = ident["prenom"].lower().capitalize() + listetud.append((nom, prenom)) + random.shuffle(listetud) + return listetud + + +def _affectation_places(d): plan = [] if d["etiquetage"] == "continu": - distributeur = DistributeurContinu() + distributeur = _DistributeurContinu() else: - distributeur = Distributeur2D(d["nb_rangs"]) + distributeur = _Distributeur2D(d["nb_rangs"]) for etud in d["listetud"]: plan.append((etud, distributeur.suivant())) return plan -def production_xls(d): - filename = f"placement_{evalname}_{gr_title_filename}{scu.XLSX_SUFFIX}" +def _production_xls(d): + breakpoint() + filename = scu.make_filename("placement_%(evalname)s_%(gr_title_filename)s{scu.XLSX_SUFFIX}" % d) xls = _excel_feuille_placement( d["eval_data"], d["desceval"], @@ -458,13 +312,13 @@ def production_xls(d): d["salle"], d["etiquetage"], ) - return sco_excel.send_excel_file(REQUEST, xls, filename) + return sco_excel.send_from_flask(xls, filename) -def production_pdf(d): +def _production_pdf(d): pdf_title = d["desceval"] pdf_title += ( - "Date : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s" % d["eval_data"] + "Date : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s" % d["eval_data"] ) filename = "placement_%(evalname)s_%(gr_title_filename)s.pdf" % d @@ -517,8 +371,8 @@ def production_pdf(d): rows=rows, filename=filename, origin="Généré par %s le " % sco_version.SCONAME - + scu.timedate_human_repr() - + "", + + scu.timedate_human_repr() + + "", pdf_title=pdf_title, # pdf_shorttitle = '', preferences=sco_preferences.SemPreferences(M["formsemestre_id"]), @@ -528,42 +382,6 @@ def production_pdf(d): return t -def placement_eval_selectetuds_old(evaluation_id, REQUEST=None): - """Dialogue placement etudiants: choix methode et localisation""" - evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id}) - if not evals: - raise ScoValueError("invalid evaluation_id") - theeval = evals[0] - - if theeval["description"]: - page_title = 'Placement "%s"' % theeval["description"] - else: - page_title = "Placement des étudiants" - H = [html_sco_header.sco_header(page_title=page_title)] - - formid = "placementfile" - if not REQUEST.form.get("%s-submitted" % formid, False): - # not submitted, choix groupe - r = do_placement_selectetuds() - if r: - if isinstance(r, str): - H.append(r) - elif isinstance(r, Response): - H.append(r.get_data().decode("utf-8")) - H.append( - """

Explications

""" - ) - H.append(html_sco_header.sco_footer()) - return "\n".join(H) - - def _one_header(ws, numbering, styles): cells = [] if numbering == "coordinate": @@ -691,16 +509,16 @@ def _titres(ws, description, evaluation, building, room, styles): def _feuille0( - ws0, - description, - evaluation, - styles, - numbering, - listetud, - nbcolumns, - building, - room, - space, + ws0, + description, + evaluation, + styles, + numbering, + listetud, + nbcolumns, + building, + room, + space, ): _titres(ws0, description, evaluation, building, room, styles) # entetes colonnes - feuille0 @@ -788,16 +606,16 @@ def _next_page(ws): def _feuille1( - ws, - description, - evaluation, - styles, - numbering, - maxlines, - nbcolumns, - building, - room, - listetud, + ws, + description, + evaluation, + styles, + numbering, + maxlines, + nbcolumns, + building, + room, + listetud, ): # etudiants - feuille1 # structuration: @@ -854,15 +672,13 @@ def _feuille1( def _excel_feuille_placement( - evaluation, - description, - listetud, - columns, - space, - maxlines, - building, - room, - numbering, + evaluation, + description, + listetud, + columns, + building, + room, + numbering, ): """Genere feuille excel pour placement des etudiants. E: evaluation (dict) @@ -887,7 +703,7 @@ def _excel_feuille_placement( ws0.set_column_dimension_width("A", 750 * column_width_ratio) for col in range(nbcolumns): ws0.set_column_dimension_width( - "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[col + 1 : col + 2], width + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[col + 1: col + 2], width ) SheetName1 = "Positions"