before refactoring

This commit is contained in:
Jean-Marie Place 2021-09-12 07:04:05 +02:00
parent ed07e42222
commit 7f63ab222b
2 changed files with 197 additions and 373 deletions

View File

@ -35,6 +35,7 @@ from enum import Enum
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
import openpyxl.utils.datetime import openpyxl.utils.datetime
from flask import make_response
from openpyxl import Workbook, load_workbook from openpyxl import Workbook, load_workbook
from openpyxl.cell import WriteOnlyCell from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
@ -64,14 +65,21 @@ class COLORS(Enum):
LIGHT_YELLOW = "FFFFFF99" 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): def send_excel_file(request, data, filename, mime=scu.XLSX_MIMETYPE):
"""publication fichier. """publication fichier.
(on ne doit rien avoir émis avant, car ici sont générés les entetes) (on ne doit rien avoir émis avant, car ici sont générés les entetes)
""" """
filename = ( filename = (
scu.unescape_html(scu.suppress_accents(filename)) scu.unescape_html(scu.suppress_accents(filename))
.replace("&", "") .replace("&", "")
.replace(" ", "_") .replace(" ", "_")
) )
request.RESPONSE.setHeader("content-type", mime) request.RESPONSE.setHeader("content-type", mime)
request.RESPONSE.setHeader( request.RESPONSE.setHeader(
@ -136,16 +144,16 @@ class ScoExcelBook:
def excel_make_style( def excel_make_style(
bold=False, bold=False,
italic=False, italic=False,
outline=False, outline=False,
color: COLORS = COLORS.BLACK, color: COLORS = COLORS.BLACK,
bgcolor: COLORS = None, bgcolor: COLORS = None,
halign=None, halign=None,
valign=None, valign=None,
number_format=None, number_format=None,
font_name="Arial", font_name="Arial",
size=10, size=10,
): ):
"""Contruit un style. """Contruit un style.
Les couleurs peuvent être spécfiées soit par une valeur de COLORS, Les couleurs peuvent être spécfiées soit par une valeur de COLORS,
@ -228,12 +236,12 @@ class ScoExcelSheet:
self.row_dimensions = {} self.row_dimensions = {}
def excel_make_composite_style( def excel_make_composite_style(
self, self,
alignment=None, alignment=None,
border=None, border=None,
fill=None, fill=None,
number_format=None, number_format=None,
font=None, font=None,
): ):
style = {} style = {}
if font is not None: if font is not None:
@ -374,7 +382,7 @@ class ScoExcelSheet:
def excel_simple_table( 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""" """Export simple type 'CSV': 1ere ligne en gras, le reste tel quel"""
ws = ScoExcelSheet(sheet_name) ws = ScoExcelSheet(sheet_name)
@ -643,13 +651,13 @@ def _excel_to_list(filelike): # we may need 'encoding' argument ?
def excel_feuille_listeappel( def excel_feuille_listeappel(
sem, sem,
groupname, groupname,
lines, lines,
partitions=None, partitions=None,
with_codes=False, with_codes=False,
with_paiement=False, with_paiement=False,
server_name=None, server_name=None,
): ):
"""generation feuille appel""" """generation feuille appel"""
if partitions is None: if partitions is None:
@ -751,7 +759,7 @@ def excel_feuille_listeappel(
for t in lines: for t in lines:
n += 1 n += 1
nomprenom = ( nomprenom = (
t["civilite_str"] + " " + t["nom"] + " " + t["prenom"].lower().capitalize() t["civilite_str"] + " " + t["nom"] + " " + t["prenom"].lower().capitalize()
) )
style_nom = style2t3 style_nom = style2t3
if with_paiement: if with_paiement:

View File

@ -35,9 +35,8 @@ import random
import time import time
from copy import copy from copy import copy
import flask
import wtforms.validators import wtforms.validators
from flask import request, render_template from flask import request, render_template, url_for
from flask_login import current_user from flask_login import current_user
from werkzeug import Response from werkzeug import Response
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
@ -141,273 +140,7 @@ class PlacementForm(FlaskForm):
self.groups.choices = choices self.groups.choices = choices
def placement_eval_selectetuds(evaluation_id): class _DistributeurContinu:
"""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("<h3>Placement et émargement des étudiants</h3>")
H.append(render_template("forms/placement.html", form=form))
F = html_sco_header.sco_footer()
return "\n".join(H) + "<p>" + 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),
# "<h3>Placement et émargement des étudiants</h3>",
# ]
# #
# 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é
# """<script type="text/javascript">
# function gr_change(e) {
# var boxes = document.getElementsByName("group_ids:list");
# var nbchecked = 0;
# for (var i=0; i < boxes.length; i++) {
# if (boxes[i].checked)
# nbchecked++;
# }
# if (nbchecked > 0) {
# document.getElementsByName('gr_submit')[0].disabled=false;
# } else {
# document.getElementsByName('gr_submit')[0].disabled=true;
# }
# }
# </script>
# """
# )
#
# tf = TrivialFormulator(
# REQUEST.URL0,
# REQUEST.form,
# descr,
# cancelbutton="Annuler",
# submitbuttonattributes=submitbuttonattributes,
# submitlabel="OK",
# formid="gr",
# )
# if tf[0] == 0:
# # H.append( """<div class="saisienote_etape1">
# # <span class="titredivplacementetudiants">Choix du groupe et de la localisation</span>
# # """)
# H.append("""<div class="saisienote_etape1">""")
# return "\n".join(H) + "\n" + tf[1] + "\n</div>"
# 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 (
"""<h2>Génération du placement impossible pour %(current_user)s</h2>
<p>(vérifiez que le semestre n'est pas verrouillé et que vous
avez l'autorisation d'effectuer cette opération)</p>
<p><a href="moduleimpl_status?moduleimpl_id=%(module_id)s">Continuer</a></p>
"""
% 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:
"""Distribue les places selon un ordre numérique.""" """Distribue les places selon un ordre numérique."""
def __init(self): def __init(self):
@ -419,7 +152,7 @@ class DistributeurContinu:
return retour return retour
class Distributeur2D: class _Distributeur2D:
"""Distribue les places selon des coordonnées sur nb_rangs.""" """Distribue les places selon des coordonnées sur nb_rangs."""
def __init__(self, nb_rangs): def __init__(self, nb_rangs):
@ -436,19 +169,140 @@ class Distributeur2D:
return retour 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("<h3>Placement et émargement des étudiants</h3>")
H.append(render_template("forms/placement.html", form=form))
F = html_sco_header.sco_footer()
return "\n".join(H) + "<p>" + 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 """<h2>Génération du placement impossible pour %s</h2>
<p>(vérifiez que le semestre n'est pas verrouillé et que vous
avez l'autorisation d'effectuer cette opération)</p>
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
""" % (
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 = [] plan = []
if d["etiquetage"] == "continu": if d["etiquetage"] == "continu":
distributeur = DistributeurContinu() distributeur = _DistributeurContinu()
else: else:
distributeur = Distributeur2D(d["nb_rangs"]) distributeur = _Distributeur2D(d["nb_rangs"])
for etud in d["listetud"]: for etud in d["listetud"]:
plan.append((etud, distributeur.suivant())) plan.append((etud, distributeur.suivant()))
return plan return plan
def production_xls(d): def _production_xls(d):
filename = f"placement_{evalname}_{gr_title_filename}{scu.XLSX_SUFFIX}" breakpoint()
filename = scu.make_filename("placement_%(evalname)s_%(gr_title_filename)s{scu.XLSX_SUFFIX}" % d)
xls = _excel_feuille_placement( xls = _excel_feuille_placement(
d["eval_data"], d["eval_data"],
d["desceval"], d["desceval"],
@ -458,13 +312,13 @@ def production_xls(d):
d["salle"], d["salle"],
d["etiquetage"], 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 = d["desceval"]
pdf_title += ( 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 filename = "placement_%(evalname)s_%(gr_title_filename)s.pdf" % d
@ -517,8 +371,8 @@ def production_pdf(d):
rows=rows, rows=rows,
filename=filename, filename=filename,
origin="Généré par %s le " % sco_version.SCONAME origin="Généré par %s le " % sco_version.SCONAME
+ scu.timedate_human_repr() + scu.timedate_human_repr()
+ "", + "",
pdf_title=pdf_title, pdf_title=pdf_title,
# pdf_shorttitle = '', # pdf_shorttitle = '',
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]), preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
@ -528,42 +382,6 @@ def production_pdf(d):
return t 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(
"""<h3>Explications</h3> <ul> <li>Choisir le format du fichier résultat :</li> <ul> <li>le format pdf
consiste en un tableau précisant pour chaque étudiant la localisation de sa table;</li> <li>le format xls
produit un classeur avec deux onglets</li> <ul> <li>le premier onglet donne une vue de la salle avec la
localisation des étudiants et peut servir de feuille d'émargement;</li> <li>le second onglet est un tableau
similaire à celui du fichier pdf;</li> </ul> </ul> <li>préciser les surveillants et la localisation (bâtiment
et salle) et indiquer le nombre de colonnes;</li> <li>deux types de placements sont possibles :</li> <ul>
<li>continue suppose que les tables ont toutes un numéro unique;</li> <li>coordonnées localise chaque table
via un numéro de colonne et un numéro de ligne (ou rangée).</li> </ul> </ul> """
)
H.append(html_sco_header.sco_footer())
return "\n".join(H)
def _one_header(ws, numbering, styles): def _one_header(ws, numbering, styles):
cells = [] cells = []
if numbering == "coordinate": if numbering == "coordinate":
@ -691,16 +509,16 @@ def _titres(ws, description, evaluation, building, room, styles):
def _feuille0( def _feuille0(
ws0, ws0,
description, description,
evaluation, evaluation,
styles, styles,
numbering, numbering,
listetud, listetud,
nbcolumns, nbcolumns,
building, building,
room, room,
space, space,
): ):
_titres(ws0, description, evaluation, building, room, styles) _titres(ws0, description, evaluation, building, room, styles)
# entetes colonnes - feuille0 # entetes colonnes - feuille0
@ -788,16 +606,16 @@ def _next_page(ws):
def _feuille1( def _feuille1(
ws, ws,
description, description,
evaluation, evaluation,
styles, styles,
numbering, numbering,
maxlines, maxlines,
nbcolumns, nbcolumns,
building, building,
room, room,
listetud, listetud,
): ):
# etudiants - feuille1 # etudiants - feuille1
# structuration: # structuration:
@ -854,15 +672,13 @@ def _feuille1(
def _excel_feuille_placement( def _excel_feuille_placement(
evaluation, evaluation,
description, description,
listetud, listetud,
columns, columns,
space, building,
maxlines, room,
building, numbering,
room,
numbering,
): ):
"""Genere feuille excel pour placement des etudiants. """Genere feuille excel pour placement des etudiants.
E: evaluation (dict) E: evaluation (dict)
@ -887,7 +703,7 @@ def _excel_feuille_placement(
ws0.set_column_dimension_width("A", 750 * column_width_ratio) ws0.set_column_dimension_width("A", 750 * column_width_ratio)
for col in range(nbcolumns): for col in range(nbcolumns):
ws0.set_column_dimension_width( ws0.set_column_dimension_width(
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"[col + 1 : col + 2], width "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[col + 1: col + 2], width
) )
SheetName1 = "Positions" SheetName1 = "Positions"