forked from ScoDoc/DocScoDoc
899 lines
31 KiB
Python
899 lines
31 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2021 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 M. Salomon, UFC / IUT DE BELFORT-MONTBÉLIARD, 2016
|
|
|
|
"""
|
|
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
|
import random
|
|
import time
|
|
from copy import copy
|
|
|
|
import flask
|
|
import wtforms.validators
|
|
from flask import request, render_template
|
|
from flask_login import current_user
|
|
from werkzeug import Response
|
|
from flask_wtf import FlaskForm
|
|
from wtforms import (
|
|
StringField,
|
|
PasswordField,
|
|
BooleanField,
|
|
SubmitField,
|
|
SelectField,
|
|
RadioField,
|
|
HiddenField,
|
|
SelectMultipleField,
|
|
validators,
|
|
)
|
|
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
|
|
|
from app.scodoc.sco_exceptions import ScoValueError
|
|
import app.scodoc.sco_utils as scu
|
|
import app.scodoc.notesdb as ndb
|
|
from app.scodoc import html_sco_header
|
|
from app.scodoc import sco_edit_module
|
|
from app.scodoc import sco_evaluations
|
|
from app.scodoc import sco_excel
|
|
from app.scodoc import sco_formsemestre
|
|
from app.scodoc import sco_formsemestre_inscriptions
|
|
from app.scodoc import sco_groups
|
|
from app.scodoc import sco_moduleimpl
|
|
from app.scodoc import sco_permissions_check
|
|
from app.scodoc import sco_preferences
|
|
from app.scodoc import sco_saisie_notes
|
|
from app.scodoc import sco_etud
|
|
import sco_version
|
|
from app.scodoc.gen_tables import GenTable
|
|
from app.scodoc.sco_excel import * # XXX à vérifier
|
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
|
|
|
_ = lambda x: x # sans babel
|
|
_l = _
|
|
|
|
|
|
class PlacementForm(FlaskForm):
|
|
TOUS = "Tous"
|
|
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=[wtforms.validators.DataRequired("Test")]
|
|
)
|
|
batiment = StringField("Batiment")
|
|
salle = StringField("Salle")
|
|
nb_rangs = SelectField(
|
|
"nb_rangs", coerce=int, choices=[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
|
)
|
|
etiquetage = RadioField(
|
|
"Numérotation",
|
|
choices=["Continue", "Coordonnées"],
|
|
validators=[
|
|
wtforms.validators.DataRequired("indiquez le style de numérotation"),
|
|
],
|
|
)
|
|
groups = SelectMultipleField(
|
|
"Groupe(s)",
|
|
validators=[
|
|
wtforms.validators.DataRequired("indiquez au moins un groupe"),
|
|
],
|
|
)
|
|
submit = SubmitField("OK")
|
|
|
|
def _set_evaluation_infos(self, evaluation_id):
|
|
eval_data = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
|
if not eval_data:
|
|
raise ScoValueError("invalid evaluation_id")
|
|
eval_data = eval_data[0]
|
|
# groupes
|
|
groups = sco_groups.do_evaluation_listegroupes(
|
|
evaluation_id, include_default=True
|
|
)
|
|
self.groups_tree = {}
|
|
self.has_groups = False
|
|
for group in groups:
|
|
partition = group["partition_name"] or self.TOUS # TODO check required
|
|
group_id = group["group_id"]
|
|
group_name = group["group_name"] or self.TOUS
|
|
if partition not in self.groups_tree:
|
|
self.groups_tree[partition] = {}
|
|
self.groups_tree[partition][group_name] = group_id
|
|
if partition != self.TOUS:
|
|
self.has_groups = True
|
|
self.groups_tree_length = len(self.groups_tree)
|
|
if self.has_groups:
|
|
choices = []
|
|
for partition in self.groups_tree:
|
|
for groupe in self.groups_tree[partition]:
|
|
id = str(self.groups_tree[partition][groupe])
|
|
choices.append((id, "%s (%s)" % (str(groupe), partition)))
|
|
self.groups.choices = choices
|
|
|
|
|
|
def placement_eval_selectetuds(evaluation_id, REQUEST=None):
|
|
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)
|
|
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))
|
|
H.append(
|
|
"""<h3>Explications</h3>
|
|
<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 :
|
|
<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></li>
|
|
<li>Choisir le format du fichier résultat :
|
|
<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:
|
|
<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></li>
|
|
</ul> </li>
|
|
</ul> """
|
|
)
|
|
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, REQUEST=REQUEST
|
|
),
|
|
"<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 do_placement(REQUEST):
|
|
"""
|
|
Choisi le placement
|
|
"""
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
authusername = str(authuser)
|
|
try:
|
|
evaluation_id = int(REQUEST.form["evaluation_id"])
|
|
except:
|
|
raise ScoValueError(
|
|
"Formulaire incomplet ! Vous avez sans doute attendu trop longtemps, veuillez vous reconnecter. Si le problème persiste, contacter l'administrateur. Merci."
|
|
)
|
|
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
|
# Check access
|
|
# (admin, respformation, and responsable_id)
|
|
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
|
|
return (
|
|
"<h2>Génération du placement impossible pour %s</h2>" % authusername
|
|
+ """<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>
|
|
"""
|
|
% E["moduleimpl_id"]
|
|
)
|
|
cnx = ndb.GetDBConnexion()
|
|
# Infos transmises
|
|
placement_method = REQUEST.form["placement_method"]
|
|
teachers = REQUEST.form["teachers"]
|
|
building = REQUEST.form["building"]
|
|
room = REQUEST.form["room"]
|
|
columns = REQUEST.form["columns"]
|
|
numbering = REQUEST.form["numbering"]
|
|
|
|
# Construit liste des etudiants
|
|
group_ids = REQUEST.form.get("group_ids", [])
|
|
groups = sco_groups.listgroups(group_ids)
|
|
gr_title_filename = sco_groups.listgroups_filename(groups)
|
|
# gr_title = sco_groups.listgroups_abbrev(groups)
|
|
|
|
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
|
|
)
|
|
if not etudids:
|
|
return "<p>Aucun groupe sélectionné !</p>"
|
|
|
|
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
|
Mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
|
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
|
evalname = "%s-%s" % (Mod["code"], ndb.DateDMYtoISO(E["jour"]))
|
|
if E["description"]:
|
|
evaltitre = E["description"]
|
|
else:
|
|
evaltitre = "évaluation du %s" % E["jour"]
|
|
|
|
desceval = [] # une liste de liste de chaines: description de l'evaluation
|
|
desceval.append(["%s" % sem["titreannee"]])
|
|
desceval.append(["Module : %s - %s" % (Mod["code"], Mod["abbrev"])])
|
|
desceval.append(["Surveillants : %s" % teachers])
|
|
desceval.append(["Batiment : %s - Salle : %s" % (building, room)])
|
|
desceval.append(["Controle : %s (coef. %g)" % (evaltitre, E["coefficient"])])
|
|
|
|
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": M["formsemestre_id"]}
|
|
)[0]
|
|
if inscr["etat"] != "D":
|
|
nom = ident["nom"].upper()
|
|
prenom = ident["prenom"].lower().capitalize()
|
|
listetud.append((nom, prenom))
|
|
random.shuffle(listetud)
|
|
|
|
sem_preferences = sco_preferences.SemPreferences()
|
|
space = sem_preferences.get("feuille_placement_emargement")
|
|
maxlines = sem_preferences.get("feuille_placement_positions")
|
|
|
|
if placement_method == "xls":
|
|
filename = f"placement_{evalname}_{gr_title_filename}{scu.XLSX_SUFFIX}"
|
|
xls = _excel_feuille_placement(
|
|
E, desceval, listetud, columns, space, maxlines, building, room, numbering
|
|
)
|
|
return sco_excel.send_excel_file(REQUEST, xls, filename)
|
|
else:
|
|
nbcolumns = int(columns)
|
|
|
|
pdf_title = "%s<br/>" % sem["titreannee"]
|
|
pdf_title += "Module : %s - %s<br/>" % (Mod["code"], Mod["abbrev"])
|
|
pdf_title += "Surveillants : %s<br/>" % teachers
|
|
pdf_title += "Batiment : %s - Salle : %s<br/>" % (building, room)
|
|
pdf_title += "Controle : %s (coef. %g)<br/>" % (evaltitre, E["coefficient"])
|
|
pdf_title += "Date : %s - Horaire : %s à %s" % (
|
|
E["jour"],
|
|
E["heure_debut"],
|
|
E["heure_fin"],
|
|
)
|
|
|
|
filename = "placement_%s_%s.pdf" % (evalname, gr_title_filename)
|
|
titles = {
|
|
"nom": "Nom",
|
|
"prenom": "Prenom",
|
|
"colonne": "Colonne",
|
|
"ligne": "Ligne",
|
|
"place": "Place",
|
|
}
|
|
if numbering == "coordinate":
|
|
columns_ids = ["nom", "prenom", "colonne", "ligne"]
|
|
else:
|
|
columns_ids = ["nom", "prenom", "place"]
|
|
|
|
# etudiants
|
|
line = 1
|
|
col = 1
|
|
orderetud = []
|
|
for etudid in listetud:
|
|
if numbering == "coordinate":
|
|
orderetud.append((etudid[0], etudid[1], col, line))
|
|
else:
|
|
orderetud.append((etudid[0], etudid[1], col + (line - 1) * nbcolumns))
|
|
|
|
if col == nbcolumns:
|
|
col = 0
|
|
line += 1
|
|
col += 1
|
|
|
|
rows = []
|
|
orderetud.sort()
|
|
for etudid in orderetud:
|
|
if numbering == "coordinate":
|
|
rows.append(
|
|
{
|
|
"nom": etudid[0],
|
|
"prenom": etudid[1],
|
|
"colonne": etudid[2],
|
|
"ligne": etudid[3],
|
|
}
|
|
)
|
|
else:
|
|
rows.append({"nom": etudid[0], "prenom": etudid[1], "place": etudid[2]})
|
|
|
|
tab = GenTable(
|
|
titles=titles,
|
|
columns_ids=columns_ids,
|
|
rows=rows,
|
|
filename=filename,
|
|
origin="Généré par %s le " % sco_version.SCONAME
|
|
+ scu.timedate_human_repr()
|
|
+ "",
|
|
pdf_title=pdf_title,
|
|
# pdf_shorttitle = '',
|
|
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
|
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
|
)
|
|
t = tab.make_page(format="pdf", with_html_headers=False, REQUEST=REQUEST)
|
|
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):
|
|
cells = []
|
|
if numbering == "coordinate":
|
|
cells.append(ws.make_cell("Nom", styles["2bi"]))
|
|
cells.append(ws.make_cell("Prénom", styles["2bi"]))
|
|
cells.append(ws.make_cell("Colonne", styles["2bi"]))
|
|
cells.append(ws.make_cell("Ligne", styles["2bi"]))
|
|
else:
|
|
cells.append(ws.make_cell("Nom", styles["2bi"]))
|
|
cells.append(ws.make_cell("Prénom", styles["2bi"]))
|
|
cells.append(ws.make_cell("Place", styles["2bi"]))
|
|
return cells
|
|
|
|
|
|
def _headers(ws, numbering, styles, nb_listes):
|
|
cells = []
|
|
for _ in range(nb_listes):
|
|
cells += _one_header(ws, numbering, styles)
|
|
cells.append(ws.make_cell(""))
|
|
ws.append_row(cells)
|
|
|
|
|
|
def _make_styles(ws0, ws1):
|
|
# polices
|
|
font0 = Font(name="Calibri", bold=True, size=12)
|
|
font1b = copy(font0)
|
|
font1b.size = 9
|
|
font1i = Font(name="Arial", italic=True, size=10)
|
|
font1o = Font(name="Arial", outline=True, size=10)
|
|
font2bi = Font(name="Arial", bold=True, italic=True, size=8)
|
|
font2 = Font(name="Arial", size=10)
|
|
|
|
# bordures
|
|
side_double = Side(border_style="double", color=COLORS.BLACK.value)
|
|
side_thin = Side(border_style="thin", color=COLORS.BLACK.value)
|
|
|
|
# bordures
|
|
border1t = Border(left=side_double, top=side_double, right=side_double)
|
|
border1bb = Border(left=side_double, bottom=side_double, right=side_double)
|
|
border1bm = Border(left=side_double, right=side_double)
|
|
border1m = Border(left=side_double, bottom=side_thin, right=side_double)
|
|
border2m = Border(top=side_thin, bottom=side_thin)
|
|
border2r = Border(top=side_thin, bottom=side_thin, right=side_thin)
|
|
border2l = Border(left=side_thin, top=side_thin, bottom=side_thin)
|
|
border2b = Border(left=side_thin, top=side_thin, bottom=side_thin, right=side_thin)
|
|
|
|
# alignements
|
|
align_center_center = Alignment(horizontal="center", vertical="center")
|
|
align_right_bottom = Alignment(horizontal="right", vertical="bottom")
|
|
align_left_center = Alignment(horizontal="left", vertical="center")
|
|
align_right_center = Alignment(horizontal="right", vertical="center")
|
|
|
|
# patterns
|
|
pattern = PatternFill(
|
|
fill_type="solid", fgColor=sco_excel.COLORS.LIGHT_YELLOW.value
|
|
)
|
|
|
|
# styles
|
|
styles = {
|
|
"titres": sco_excel.excel_make_style(font_name="Arial", bold=True, size=12),
|
|
"1t": ws0.excel_make_composite_style(
|
|
font=font0, alignment=align_center_center, border=border1t
|
|
),
|
|
"1m": ws0.excel_make_composite_style(
|
|
font=font1b, alignment=align_center_center, border=border1m
|
|
),
|
|
"1bm": ws0.excel_make_composite_style(
|
|
font=font1b, alignment=align_center_center, border=border1bm
|
|
),
|
|
"1bb": ws0.excel_make_composite_style(
|
|
font=font1o, alignment=align_right_bottom, border=border1bb
|
|
),
|
|
"2b": ws1.excel_make_composite_style(
|
|
font=font1i, alignment=align_center_center, border=border2b
|
|
),
|
|
"2bi": ws1.excel_make_composite_style(
|
|
font=font2bi, alignment=align_center_center, border=border2b, fill=pattern
|
|
),
|
|
"2l": ws1.excel_make_composite_style(
|
|
font=font2, alignment=align_left_center, border=border2l
|
|
),
|
|
"2m1": ws1.excel_make_composite_style(
|
|
font=font2, alignment=align_left_center, border=border2m
|
|
),
|
|
"2m2": ws1.excel_make_composite_style(
|
|
font=font2, alignment=align_right_center, border=border2m
|
|
),
|
|
"2r": ws1.excel_make_composite_style(
|
|
font=font2, alignment=align_right_center, border=border2r
|
|
),
|
|
}
|
|
return styles
|
|
|
|
|
|
def _init_lines(maxlines):
|
|
return [
|
|
[] for _ in range(maxlines)
|
|
] # lines[no_ligne] -> liste des cellules de la ligne (no_lignes de 1..maxlines
|
|
|
|
|
|
def _write_lines(ws, lines):
|
|
for line in lines:
|
|
ws.append_row(line)
|
|
|
|
|
|
def _titres(ws, description, evaluation, building, room, styles):
|
|
dt = time.strftime("%d/%m/%Y a %Hh%M")
|
|
ws.append_single_cell_row(
|
|
"Feuille placement etudiants éditée le %s" % dt, styles["titres"]
|
|
)
|
|
for line, desceval in enumerate(description):
|
|
if line in [1, 4, 7]:
|
|
ws.append_blank_row()
|
|
ws.append_single_cell_row(desceval[0], styles["titres"])
|
|
ws.append_single_cell_row(
|
|
"Date : %s - Horaire : %s à %s"
|
|
% (evaluation["jour"], evaluation["heure_debut"], evaluation["heure_fin"]),
|
|
styles["titres"],
|
|
)
|
|
ws.append_single_cell_row(
|
|
"Date : %s - Horaire : %s à %s"
|
|
% (evaluation["jour"], evaluation["heure_debut"], evaluation["heure_fin"]),
|
|
styles["titres"],
|
|
)
|
|
|
|
|
|
def _feuille0(
|
|
ws0,
|
|
description,
|
|
evaluation,
|
|
styles,
|
|
numbering,
|
|
listetud,
|
|
nbcolumns,
|
|
building,
|
|
room,
|
|
space,
|
|
):
|
|
_titres(ws0, description, evaluation, building, room, styles)
|
|
# entetes colonnes - feuille0
|
|
cells = [ws0.make_cell()]
|
|
for col in range(nbcolumns):
|
|
cells.append(ws0.make_cell("colonne %s" % (col + 1), styles["2b"]))
|
|
ws0.append_row(cells)
|
|
# etudiants
|
|
line = 1
|
|
col = 1
|
|
linetud = []
|
|
orderetud = []
|
|
placementetud = []
|
|
for etudid in listetud:
|
|
linetud.append(etudid)
|
|
if numbering == "coordinate":
|
|
orderetud.append((etudid[0], etudid[1], col, line))
|
|
else:
|
|
orderetud.append((etudid[0], etudid[1], col + (line - 1) * nbcolumns))
|
|
|
|
if col == nbcolumns:
|
|
placementetud.append(linetud)
|
|
linetud = []
|
|
col = 0
|
|
line += 1
|
|
col += 1
|
|
if len(linetud) > 0:
|
|
placementetud.append(linetud)
|
|
|
|
# etudiants - feuille0
|
|
place = 1
|
|
for rang, linetud in enumerate(placementetud, start=1):
|
|
# Chaque rang est affiché sur 3 lignes xlsx (notées A, B, C)
|
|
# ligne A: le nom, ligne B: le prénom, ligne C: un espace ou la place
|
|
cells_a = [ws0.make_cell(rang, styles["2b"])]
|
|
cells_b = [ws0.make_cell("", styles["2b"])]
|
|
cells_c = [ws0.make_cell("", styles["2b"])]
|
|
row = 14 # premieère ligne de signature
|
|
for etudid in linetud:
|
|
cells_a.append(ws0.make_cell(etudid[0], styles["1t"]))
|
|
cells_b.append(ws0.make_cell(etudid[1], styles["1m"]))
|
|
if numbering == "coordinate":
|
|
cell_c = ws0.make_cell("", styles["1bb"])
|
|
else:
|
|
cell_c = ws0.make_cell("place %s" % place, styles["1bb"])
|
|
cells_c.append(cell_c)
|
|
ws0.set_row_dimension_height(row, space / 25)
|
|
row += 3
|
|
place = place + 1
|
|
if col == nbcolumns:
|
|
ws0.append_row(cells_a)
|
|
ws0.append_row(cells_b)
|
|
ws0.append_row(cells_c)
|
|
cells_a = [ws0.make_cell(rang, styles["2b"])]
|
|
cells_b = [ws0.make_cell("", styles["2b"])]
|
|
cells_c = [ws0.make_cell("", styles["2b"])]
|
|
# publication du rang final incomplet
|
|
ws0.append_row(cells_a)
|
|
ws0.append_row(cells_b)
|
|
ws0.append_row(cells_c)
|
|
ws0.set_row_dimension_height(row, space / 25)
|
|
|
|
|
|
def _compute_ordretud(listetud, nbcolumns, numbering):
|
|
orderetud = []
|
|
line = 1
|
|
col = 1
|
|
for etudid in listetud:
|
|
if numbering == "coordinate":
|
|
orderetud.append((etudid[0], etudid[1], col, line))
|
|
else:
|
|
orderetud.append(
|
|
(etudid[0], etudid[1], "%s" % (col + (line - 1) * nbcolumns))
|
|
)
|
|
col += 1
|
|
if col > nbcolumns:
|
|
col = 1
|
|
line += 1
|
|
orderetud.sort()
|
|
return orderetud
|
|
|
|
|
|
def _next_page(ws):
|
|
pass
|
|
|
|
|
|
def _feuille1(
|
|
ws,
|
|
description,
|
|
evaluation,
|
|
styles,
|
|
numbering,
|
|
maxlines,
|
|
nbcolumns,
|
|
building,
|
|
room,
|
|
listetud,
|
|
):
|
|
# etudiants - feuille1
|
|
# structuration:
|
|
# 1 page = maxlistes listes
|
|
# 1 liste = 3 ou 4 colonnes(excel) (selon numbering) et (maximum maxlines) lignes
|
|
maxlistes = 2 # nombre de listes par page
|
|
# computes excel columns widths
|
|
if numbering == "coordinate":
|
|
gabarit = [16, 18, 6, 6, 2]
|
|
else:
|
|
gabarit = [16, 18, 12, 2]
|
|
widths = []
|
|
for _ in range(maxlistes):
|
|
widths += gabarit
|
|
ws.set_column_dimension_width(value=widths)
|
|
nb_etu_restant = len(listetud)
|
|
_titres(ws, description, evaluation, building, room, styles)
|
|
nb_listes = min(
|
|
maxlistes, nb_etu_restant // maxlines + 1
|
|
) # nombre de colonnes dans la page
|
|
_headers(ws, numbering, styles, nb_listes)
|
|
# construction liste alphabétique
|
|
# Affichage
|
|
lines = _init_lines(maxlines)
|
|
orderetud = _compute_ordretud(listetud, nbcolumns, numbering)
|
|
line = 0
|
|
col = 0
|
|
for etudid in orderetud:
|
|
# check for skip of list or page
|
|
if col > 0: # add a empty cell between lists
|
|
lines[line].append(ws.make_cell())
|
|
lines[line].append(ws.make_cell(etudid[0], styles["2l"]))
|
|
lines[line].append(ws.make_cell(etudid[1], styles["2m1"]))
|
|
if numbering == "coordinate":
|
|
lines[line].append(ws.make_cell(etudid[2], styles["2m2"]))
|
|
lines[line].append(ws.make_cell(etudid[3], styles["2r"]))
|
|
else:
|
|
lines[line].append(ws.make_cell(etudid[2], styles["2r"]))
|
|
line = line + 1
|
|
if line >= maxlines: # fin de liste
|
|
col = col + 1
|
|
line = 0
|
|
if col >= maxlistes: # fin de page
|
|
_write_lines(ws, lines)
|
|
lines = _init_lines(maxlines)
|
|
col = 0
|
|
ws.append_blank_row()
|
|
nb_etu_restant -= maxlistes * maxlines
|
|
nb_listes = min(
|
|
maxlistes, nb_etu_restant // maxlines + 1
|
|
) # nombre de colonnes dans la page
|
|
_headers(ws, numbering, styles, nb_listes)
|
|
_write_lines(ws, lines)
|
|
|
|
|
|
def _excel_feuille_placement(
|
|
evaluation,
|
|
description,
|
|
listetud,
|
|
columns,
|
|
space,
|
|
maxlines,
|
|
building,
|
|
room,
|
|
numbering,
|
|
):
|
|
"""Genere feuille excel pour placement des etudiants.
|
|
E: evaluation (dict)
|
|
lines: liste de tuples
|
|
(etudid, nom, prenom, etat, groupe, val, explanation)
|
|
"""
|
|
nbcolumns = int(columns)
|
|
column_width_ratio = 1 / 250 # changement d unités entre pyExcelerator et openpyxl
|
|
|
|
wb = ScoExcelBook()
|
|
|
|
SheetName0 = "Emargement"
|
|
ws0 = wb.create_sheet(SheetName0)
|
|
# ajuste largeurs colonnes (unite inconnue, empirique)
|
|
width = 4500 * column_width_ratio
|
|
if nbcolumns > 5:
|
|
width = 22500 * column_width_ratio // nbcolumns
|
|
|
|
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
|
|
)
|
|
|
|
SheetName1 = "Positions"
|
|
ws1 = wb.create_sheet(SheetName1)
|
|
|
|
styles = _make_styles(ws0, ws1)
|
|
_feuille0(
|
|
ws0,
|
|
description,
|
|
evaluation,
|
|
styles,
|
|
numbering,
|
|
listetud,
|
|
nbcolumns,
|
|
building,
|
|
room,
|
|
space,
|
|
)
|
|
_feuille1(
|
|
ws1,
|
|
description,
|
|
evaluation,
|
|
styles,
|
|
numbering,
|
|
maxlines,
|
|
nbcolumns,
|
|
building,
|
|
room,
|
|
listetud,
|
|
)
|
|
return wb.generate()
|