1
0
forked from ScoDoc/ScoDoc

fin fusion

This commit is contained in:
Jean-Marie Place 2021-09-05 14:50:35 +02:00
parent dccae56fe7
commit 770ccb4d6e
4 changed files with 612 additions and 473 deletions

View File

@ -40,10 +40,15 @@ from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
from openpyxl.comments import Comment
from openpyxl import Workbook, load_workbook
from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
import app.scodoc.sco_utils as scu
from app.scodoc import notesdb
from app.scodoc import sco_preferences
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import sco_preferences
from app import log
from app.scodoc.sco_exceptions import ScoValueError
@ -79,52 +84,14 @@ def send_excel_file(request, data, filename, mime=scu.XLSX_MIMETYPE):
# font, border, number_format, fill, .. (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
# (stolen from xlrd)
# Convert an Excel number (presumed to represent a date, a datetime or a time) into
# a Python datetime.datetime
# @param xldate The Excel number
# @param datemode 0: 1900-based, 1: 1904-based.
# @return a datetime.datetime object, to the nearest_second.
# <br>Special case: if 0.0 <= xldate < 1.0, it is assumed to represent a time;
# a datetime.time object will be returned.
# <br>Note: 1904-01-01 is not regarded as a valid date in the datemode 1 system; its "serial number"
# is zero.
#
# _XLDAYS_TOO_LARGE = (2958466, 2958466 - 1462) # This is equivalent to 10000-01-01
#
def xldate_as_datetime(xldate, datemode=0):
return openpyxl.utils.datetime.from_ISO8601(xldate)
# if datemode not in (0, 1):
# raise ValueError("invalid mode %s" % datemode)
# if xldate == 0.00:
# return datetime.time(0, 0, 0)
# if xldate < 0.00:
# raise ValueError("invalid date code %s" % xldate)
# xldays = int(xldate)
# frac = xldate - xldays
# seconds = int(round(frac * 86400.0))
# assert 0 <= seconds <= 86400
# if seconds == 86400:
# seconds = 0
# xldays += 1
# if xldays >= _XLDAYS_TOO_LARGE[datemode]:
# raise ValueError("date too large %s" % xldate)
#
# if xldays == 0:
# # second = seconds % 60; minutes = seconds // 60
# minutes, second = divmod(seconds, 60)
# # minute = minutes % 60; hour = minutes // 60
# hour, minute = divmod(minutes, 60)
# return datetime.time(hour, minute, second)
#
# if xldays < 61 and datemode == 0:
# raise ValueError("ambiguous date %s" % xldate)
#
# return datetime.datetime.fromordinal(
# xldays + 693594 + 1462 * datemode
# ) + datetime.timedelta(seconds=seconds)
def adjust_sheetname(sheet_name):
# Le nom de la feuille ne peut faire plus de 31 caractères.
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
return sheet_name[:31]
class ScoExcelBook:
@ -139,13 +106,16 @@ class ScoExcelBook:
def __init__(self):
self.sheets = [] # list of sheets
self.wb = Workbook(write_only=True)
def create_sheet(self, sheet_name="feuille", default_style=None):
"""Crée une nouvelle feuille dans ce classeur
sheet_name -- le nom de la feuille
default_style -- le style par défaut
"""
sheet = ScoExcelSheet(sheet_name, default_style)
sheet_name = adjust_sheetname(sheet_name)
ws = self.wb.create_sheet(sheet_name)
sheet = ScoExcelSheet(sheet_name, default_style, ws)
self.sheets.append(sheet)
return sheet
@ -153,12 +123,11 @@ class ScoExcelBook:
"""génération d'un stream binaire représentant la totalité du classeur.
retourne le flux
"""
wb = Workbook(write_only=True)
for sheet in self.sheets:
sheet.generate(self)
sheet.prepare()
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
with NamedTemporaryFile() as tmp:
wb.save(tmp.name)
self.wb.save(tmp.name)
tmp.seek(0)
return tmp.read()
@ -166,6 +135,7 @@ class ScoExcelBook:
def excel_make_style(
bold=False,
italic=False,
outline=False,
color: COLORS = COLORS.BLACK,
bgcolor: COLORS = None,
halign=None,
@ -186,7 +156,14 @@ def excel_make_style(
size -- taille de police
"""
style = {}
font = Font(name=font_name, bold=bold, italic=italic, color=color.value, size=size)
font = Font(
name=font_name,
bold=bold,
italic=italic,
outline=outline,
color=color.value,
size=size,
)
style["font"] = font
if bgcolor:
style["fill"] = PatternFill(fill_type="solid", fgColor=bgcolor.value)
@ -222,42 +199,94 @@ class ScoExcelSheet:
* pour finit appel de la méthode de génération
"""
def __init__(self, sheet_name="feuille", default_style=None, wb=None):
"""Création de la feuille.
sheet_name -- le nom de la feuille
default_style -- le style par défaut des cellules
wb -- le WorkBook dans laquelle se trouve la feuille. Si wb est None (cas d'un classeur mono-feuille),
un workbook est crée et associé à cette feuille.
def __init__(self, sheet_name="feuille", default_style=None, ws=None):
"""Création de la feuille. sheet_name
-- le nom de la feuille default_style
-- le style par défaut des cellules ws
-- None si la feuille est autonome (dans ce cas ell crée son propre wb), sinon c'est la worksheet
créée par le workbook propriétaire un workbook est crée et associé à cette feuille.
"""
# Le nom de la feuille ne peut faire plus de 31 caractères.
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
self.sheet_name = sheet_name[
:31
] # if len(sheet_name) > 31: sheet_name = 'Feuille' ?
self.rows = [] # list of list of cells
# self.cells_styles_lico = {} # { (li,co) : style }
# self.cells_styles_li = {} # { li : style }
# self.cells_styles_co = {} # { co : style }
self.sheet_name = adjust_sheetname(sheet_name)
if default_style is None:
default_style = excel_make_style()
self.default_style = default_style
self.wb = wb or Workbook(write_only=True) # Création de workbook si nécessaire
self.ws = self.wb.create_sheet(title=self.sheet_name)
if ws is None:
self.wb = Workbook()
self.ws = self.wb.active
self.ws.title = self.sheet_name
else:
self.wb = None
self.ws = ws
# internal data
self.rows = [] # list of list of cells
self.column_dimensions = {}
self.row_dimensions = {}
def set_column_dimension_width(self, cle, value):
"""Détermine la largeur d'une colonne.
cle -- identifie la colonne ("A"n "B", ...)
value -- la dimension (unité : 7 pixels comme affiché dans Excel)
def excel_make_composite_style(
self,
alignment=None,
border=None,
fill=None,
number_format=None,
font=None,
):
style = {}
if font is not None:
style["font"] = font
if alignment is not None:
style["alignment"] = alignment
if border is not None:
style["border"] = border
if fill is not None:
style["fill"] = fill
if number_format is None:
style["number_format"] = FORMAT_GENERAL
else:
style["number_format"] = number_format
return style
@staticmethod
def i2col(idx):
if idx < 26: # one letter key
return chr(idx + 65)
else: # two letters AA..ZZ
first = (idx // 26) + 66
second = (idx % 26) + 65
return "" + chr(first) + chr(second)
def set_column_dimension_width(self, cle=None, value=21):
"""Détermine la largeur d'une colonne. cle -- identifie la colonne ("A" "B", ... ou 0, 1, 2, ...) si None,
value donne la liste des largeurs de colonnes depuis A, B, C, ... value -- la dimension (unité : 7 pixels
comme affiché dans Excel)
"""
self.ws.column_dimensions[cle].width = value
if cle is None:
for i, val in enumerate(value):
self.ws.column_dimensions[self.i2col(i)].width = val
# No keys: value is a list of widths
elif type(cle) == str: # accepts set_column_with("D", ...)
self.ws.column_dimensions[cle].width = value
else:
self.ws.column_dimensions[self.i2col(cle)].width = value
def set_column_dimension_hidden(self, cle, value):
"""Masque ou affiche une colonne.
cle -- identifie la colonne ("A"n "B", ...)
def set_row_dimension_height(self, cle=None, value=21):
"""Détermine la hauteur d'une ligne. cle -- identifie la ligne (1, 2, ...) si None,
value donne la liste des hauteurs de colonnes depuis 1, 2, 3, ... value -- la dimension
"""
if cle is None:
for i, val in enumerate(value, start=1):
self.ws.row_dimensions[i].height = val
# No keys: value is a list of widths
else:
self.ws.row_dimensions[cle].height = value
def set_row_dimension_hidden(self, cle, value):
"""Masque ou affiche une ligne.
cle -- identifie la colonne (1...)
value -- boolean (vrai = colonne cachée)
"""
self.ws.column_dimensions[cle].hidden = value
self.ws.row_dimensions[cle].hidden = value
def make_cell(self, value: any = None, style=None, comment=None):
"""Construit une cellule.
@ -271,8 +300,12 @@ class ScoExcelSheet:
style = self.default_style
if "font" in style:
cell.font = style["font"]
if "alignment" in style:
cell.alignment = style["alignment"]
if "border" in style:
cell.border = style["border"]
if "fill" in style:
cell.fill = style["fill"]
if "number_format" in style:
cell.number_format = style["number_format"]
if "fill" in style:
@ -311,73 +344,31 @@ class ScoExcelSheet:
"""ajoute une ligne déjà construite à la feuille."""
self.rows.append(row)
# def set_style(self, style=None, li=None, co=None):
# if li is not None and co is not None:
# self.cells_styles_lico[(li, co)] = style
# elif li is None:
# self.cells_styles_li[li] = style
# elif co is None:
# self.cells_styles_co[co] = style
#
# def get_cell_style(self, li, co):
# """Get style for specified cell"""
# return (
# self.cells_styles_lico.get((li, co), None)
# or self.cells_styles_li.get(li, None)
# or self.cells_styles_co.get(co, None)
# or self.default_style
# )
def _generate_ws(self):
def prepare(self):
"""génére un flux décrivant la feuille.
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
ou pour la génération d'un classeur multi-feuilles
"""
for col in self.column_dimensions.keys():
self.ws.column_dimensions[col] = self.column_dimensions[col]
for col in self.row_dimensions.keys():
self.ws.row_dimensions[col] = self.row_dimensions[col]
for row in self.rows:
self.ws.append(row)
def generate_standalone(self):
def generate(self):
"""génération d'un classeur mono-feuille"""
self._generate_ws()
# this method makes sense only if it is a standalone worksheet (else call workbook.generate()
if self.wb is None: # embeded sheet
raise ScoValueError("can't generate a single sheet from a ScoWorkbook")
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
self.prepare()
with NamedTemporaryFile() as tmp:
self.wb.save(tmp.name)
tmp.seek(0)
return tmp.read()
def generate_embeded(self):
"""generation d'une feuille include dans un classeur multi-feuilles"""
self._generate_ws()
def gen_workbook(self, wb=None):
"""TODO: à remplacer"""
"""Generates and returns a workbook from stored data.
If wb, add a sheet (tab) to the existing workbook (in this case, returns None).
"""
if wb is None:
wb = Workbook() # Création du fichier
sauvegarde = True
else:
sauvegarde = False
ws0 = wb.add_sheet(self.sheet_name)
li = 0
for row in self.rows:
co = 0
for c in row:
# safety net: allow only str, int and float
# #py3 #sco8 A revoir lors de la ré-écriture de ce module
# XXX if type(c) not in (IntType, FloatType):
# c = str(c).decode(scu.SCO_ENCODING)
ws0.write(li, co, c, self.get_cell_style(li, co))
co += 1
li += 1
if sauvegarde:
return wb.savetostr()
else:
return None
def excel_simple_table(
titles=None, lines=None, sheet_name=b"feuille", titles_styles=None, comments=None
@ -416,7 +407,7 @@ def excel_simple_table(
cell_style = text_style
cells.append(ws.make_cell(it, cell_style))
ws.append_row(cells)
return ws.generate_standalone()
return ws.generate()
def excel_feuille_saisie(e, titreannee, description, lines):
@ -577,7 +568,7 @@ def excel_feuille_saisie(e, titreannee, description, lines):
ws.make_cell("cellule vide -> note non modifiée", style_expl),
]
)
return ws.generate_standalone()
return ws.generate()
def excel_bytes_to_list(bytes_content):
@ -797,4 +788,4 @@ def excel_feuille_listeappel(
cell_2 = ws.make_cell(("Liste éditée le " + dt), style1i)
ws.append_row([None, cell_2])
return ws.generate_standalone()
return ws.generate()

View File

@ -33,13 +33,30 @@ 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 import log
from app.scodoc import html_sco_header
from app.scodoc import sco_edit_module
from app.scodoc import sco_evaluations
@ -57,23 +74,116 @@ 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 = _
def do_placement_selectetuds(REQUEST):
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.
"""
evaluation_id = int(REQUEST.form["evaluation_id"])
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
if not E:
raise ScoValueError("invalid evaluation_id")
E = E[0]
# M = sco_moduleimpl.do_moduleimpl_list( moduleimpl_id=E["moduleimpl_id"])[0]
# groupes
groups = sco_groups.do_evaluation_listegroupes(evaluation_id, include_default=True)
grlabs = [g["group_name"] or "tous" for g in groups] # legendes des boutons
grnams = [g["group_id"] for g in groups] # noms des checkbox
no_groups = (len(groups) == 1) and groups[0]["group_name"] is None
# description de l'evaluation
H = [
sco_evaluations.evaluation_describe(
@ -247,7 +357,6 @@ def do_placement(REQUEST):
"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"]):
@ -321,7 +430,7 @@ def do_placement(REQUEST):
if placement_method == "xls":
filename = f"placement_{evalname}_{gr_title_filename}{scu.XLSX_SUFFIX}"
xls = Excel_feuille_placement(
xls = _excel_feuille_placement(
E, desceval, listetud, columns, space, maxlines, building, room, numbering
)
return sco_excel.send_excel_file(REQUEST, xls, filename)
@ -399,7 +508,7 @@ def do_placement(REQUEST):
return t
def placement_eval_selectetuds(evaluation_id, REQUEST=None):
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:
@ -415,298 +524,170 @@ def placement_eval_selectetuds(evaluation_id, REQUEST=None):
formid = "placementfile"
if not REQUEST.form.get("%s-submitted" % formid, False):
# not submitted, choix groupe
r = do_placement_selectetuds(REQUEST)
r = do_placement_selectetuds()
if r:
H.append(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>
"""
"""<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 Excel_feuille_placement(
E, 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)
wb = Workbook()
SheetName0 = "Emargement"
ws0 = wb.add_sheet(SheetName0.decode(scu.SCO_ENCODING))
# ajuste largeurs colonnes (unite inconnue, empirique)
width = 4500
if nbcolumns > 5:
width = 22500 // nbcolumns
for col in range(nbcolumns):
ws0.col(col + 1).width = width
ws0.col(0).width = 750
SheetName1 = "Positions"
ws1 = wb.add_sheet(SheetName1.decode(scu.SCO_ENCODING))
def _one_header(ws, numbering, styles):
cells = []
if numbering == "coordinate":
ws1.col(0).width = 4000
ws1.col(1).width = 4500
ws1.col(2).width = 1500
ws1.col(3).width = 1500
ws1.col(4).width = 500
ws1.col(5).width = 4000
ws1.col(6).width = 4500
ws1.col(7).width = 1500
ws1.col(8).width = 1500
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:
ws1.col(0).width = 4000
ws1.col(1).width = 4500
ws1.col(2).width = 3000
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
ws1.col(3).width = 500
ws1.col(4).width = 4000
ws1.col(5).width = 4500
ws1.col(6).width = 3000
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
font0 = Font()
font0.name = "Arial"
font0.bold = True
font0.height = 12 * 0x14
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
font1b = Font()
font1b.name = "Arial"
font1b.bold = True
font1b.height = 9 * 0x14
font1i = Font()
font1i.name = "Arial"
font1i.height = 10 * 0x14
font1i.italic = True
def _init_lines(maxlines):
return [
[] for _ in range(maxlines)
] # lines[no_ligne] -> liste des cellules de la ligne (no_lignes de 1..maxlines
font1o = Font()
font1o.name = "Arial"
font1o.height = 10 * 0x14
font1o.outline = True
font2bi = Font()
font2bi.name = "Arial"
font2bi.height = 8 * 0x14
font2bi.bold = True
font2bi.italic = True
def _write_lines(ws, lines):
for line in lines:
ws.append_row(line)
font2 = Font()
font2.name = "Arial"
font2.height = 10 * 0x14
style_titres = XFStyle()
style_titres.font = font0
style1t = XFStyle()
style1t.font = font1b
alignment = Alignment()
alignment.horz = Alignment.HORZ_CENTER
alignment.vert = Alignment.VERT_CENTER
style1t.alignment = alignment
borders = Borders()
borders.left = Borders.DOUBLE
borders.top = Borders.DOUBLE
borders.bottom = Borders.NO_LINE
borders.right = Borders.DOUBLE
style1t.borders = borders
style1m = XFStyle()
style1m.font = font1b
alignment = Alignment()
alignment.horz = Alignment.HORZ_CENTER
alignment.vert = Alignment.VERT_CENTER
style1m.alignment = alignment
borders = Borders()
borders.left = Borders.DOUBLE
borders.top = Borders.NO_LINE
borders.bottom = Borders.THIN
borders.right = Borders.DOUBLE
style1m.borders = borders
style1bm = XFStyle()
borders = Borders()
borders.left = Borders.DOUBLE
borders.top = Borders.NO_LINE
borders.bottom = Borders.NO_LINE
borders.right = Borders.DOUBLE
style1bm.borders = borders
style1bb = XFStyle()
style1bb.font = font1o
alignment = Alignment()
alignment.horz = Alignment.HORZ_RIGHT
alignment.vert = Alignment.VERT_BOTTOM
style1bb.alignment = alignment
borders = Borders()
borders.left = Borders.DOUBLE
borders.top = Borders.NO_LINE
borders.bottom = Borders.DOUBLE
borders.right = Borders.DOUBLE
style1bb.borders = borders
style2b = XFStyle()
style2b.font = font1i
alignment = Alignment()
alignment.horz = Alignment.HORZ_CENTER
alignment.vert = Alignment.VERT_CENTER
style2b.alignment = alignment
borders = Borders()
borders.left = Borders.THIN
borders.top = Borders.THIN
borders.bottom = Borders.THIN
borders.right = Borders.THIN
style2b.borders = borders
style2bi = XFStyle()
style2bi.font = font2bi
alignment = Alignment()
alignment.horz = Alignment.HORZ_CENTER
alignment.vert = Alignment.VERT_CENTER
style2bi.alignment = alignment
borders = Borders()
borders.left = Borders.THIN
borders.top = Borders.THIN
borders.bottom = Borders.THIN
borders.right = Borders.THIN
style2bi.borders = borders
pattern = Pattern()
pattern.pattern = Pattern.SOLID_PATTERN
pattern._pattern_back_colour = "gray"
style2bi.pattern = pattern
style2l = XFStyle()
style2l.font = font2
alignment = Alignment()
alignment.horz = Alignment.HORZ_LEFT
alignment.vert = Alignment.VERT_CENTER
style2l.alignment = alignment
borders = Borders()
borders.left = Borders.THIN
borders.top = Borders.THIN
borders.bottom = Borders.THIN
borders.right = Borders.NO_LINE
style2l.borders = borders
style2m1 = XFStyle()
style2m1.font = font2
alignment = Alignment()
alignment.horz = Alignment.HORZ_LEFT
alignment.vert = Alignment.VERT_CENTER
style2m1.alignment = alignment
borders = Borders()
borders.left = Borders.NO_LINE
borders.top = Borders.THIN
borders.bottom = Borders.THIN
borders.right = Borders.NO_LINE
style2m1.borders = borders
style2m2 = XFStyle()
style2l.font = font2
alignment = Alignment()
alignment.horz = Alignment.HORZ_RIGHT
alignment.vert = Alignment.VERT_CENTER
style2m2.alignment = alignment
borders = Borders()
borders.left = Borders.NO_LINE
borders.top = Borders.THIN
borders.bottom = Borders.THIN
borders.right = Borders.NO_LINE
style2m2.borders = borders
style2r = XFStyle()
style2l.font = font2
alignment = Alignment()
alignment.horz = Alignment.HORZ_RIGHT
alignment.vert = Alignment.VERT_CENTER
style2r.alignment = alignment
borders = Borders()
borders.left = Borders.NO_LINE
borders.top = Borders.THIN
borders.bottom = Borders.THIN
borders.right = Borders.THIN
style2r.borders = borders
# ligne de titres
li = 0
line = 0
def _titres(ws, description, evaluation, building, room, styles):
dt = time.strftime("%d/%m/%Y a %Hh%M")
ws0.write(li, 0, "Feuille placement etudiants éditée le %s" % dt, style_titres)
ws1.write(li, 0, "Feuille placement etudiants éditée le %s" % dt, style_titres)
for desceval in description:
if line % 2 == 0:
li += 2
else:
li += 1
line += 1
ws0.write(li, 0, desceval[0].decode(scu.SCO_ENCODING), style_titres)
ws1.write(li, 0, desceval[0].decode(scu.SCO_ENCODING), style_titres)
li += 1
ws0.write(
li,
0,
"Date : %s - Horaire : %s à %s" % (E["jour"], E["heure_debut"], E["heure_fin"]),
style_titres,
ws.append_single_cell_row(
"Feuille placement etudiants éditée le %s" % dt, styles["titres"]
)
ws1.write(
li,
0,
"Date : %s - Horaire : %s à %s" % (E["jour"], E["heure_debut"], E["heure_fin"]),
style_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"],
)
li += 1
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):
ws0.write(li, col + 1, "colonne %s" % (col + 1), style2b)
# entetes colonnes - feuille1
if numbering == "coordinate":
ws1.write(li, 0, "Nom", style2bi)
ws1.write(li, 1, "Prénom", style2bi)
ws1.write(li, 2, "Colonne", style2bi)
ws1.write(li, 3, "Ligne", style2bi)
ws1.write(li, 5, "Nom", style2bi)
ws1.write(li, 6, "Prénom", style2bi)
ws1.write(li, 7, "Colonne", style2bi)
ws1.write(li, 8, "Ligne", style2bi)
else:
ws1.write(li, 0, "Nom", style2bi)
ws1.write(li, 1, "Prénom", style2bi)
ws1.write(li, 2, "Place", style2bi)
ws1.write(li, 4, "Nom", style2bi)
ws1.write(li, 5, "Prénom", style2bi)
ws1.write(li, 6, "Place", style2bi)
cells.append(ws0.make_cell("colonne %s" % (col + 1), styles["2b"]))
ws0.append_row(cells)
# etudiants
line = 1
col = 1
@ -730,78 +711,188 @@ def Excel_feuille_placement(
placementetud.append(linetud)
# etudiants - feuille0
line = 0
li0 = li
for linetud in placementetud:
li0 += 1
line += 1
ws0.write(li0, 0, line, style2b)
col = 1
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:
ws0.write(li0, col, (etudid[0]).decode(scu.SCO_ENCODING), style1t)
ws0.write(li0 + 1, col, (etudid[1]).decode(scu.SCO_ENCODING), style1m)
ws0.row(li0 + 2).height = space
cells_a.append(ws0.make_cell(etudid[0], styles["1t"]))
cells_b.append(ws0.make_cell(etudid[1], styles["1m"]))
if numbering == "coordinate":
ws0.write(li0 + 2, col, " ", style1bb)
cell_c = ws0.make_cell("", styles["1bb"])
else:
ws0.write(
li0 + 2, col, "place %s" % (col + (line - 1) * nbcolumns), style1bb
)
# ws0.write(li+3,col, ' ', style1bm )
# ws0.write(li+4,col, ' ', style1bb )
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:
col = 0
li0 += 2
col += 1
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)
# etudiants - feuille1
if numbering == "coordinate":
coloffset = 5
else:
coloffset = 4
line = 0
li1 = li
nbcol = 0
col = 0
orderetud.sort()
for etudid in orderetud:
li1 += 1
line += 1
ws1.write(li1, col, (etudid[0]).decode(scu.SCO_ENCODING), style2l)
ws1.write(li1, col + 1, (etudid[1]).decode(scu.SCO_ENCODING), style2m1)
def _compute_ordretud(listetud, nbcolumns, numbering):
orderetud = []
line = 1
col = 1
for etudid in listetud:
if numbering == "coordinate":
ws1.write(li1, col + 2, etudid[2], style2m2)
ws1.write(li1, col + 3, etudid[3], style2r)
orderetud.append((etudid[0], etudid[1], col, line))
else:
ws1.write(li1, col + 2, etudid[2], style2r)
orderetud.append(
(etudid[0], etudid[1], "%s" % (col + (line - 1) * nbcolumns))
)
col += 1
if col > nbcolumns:
col = 1
line += 1
orderetud.sort()
return orderetud
if line == maxlines:
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
li1 = li
nbcol = nbcol + 1
col = col + coloffset
if nbcol == 2:
li = li + maxlines + 2
li1 = li
nbcol = 0
if col >= maxlistes: # fin de page
_write_lines(ws, lines)
lines = _init_lines(maxlines)
col = 0
if numbering == "coordinate":
ws1.write(li, 0, "Nom", style2bi)
ws1.write(li, 1, "Prénom", style2bi)
ws1.write(li, 2, "Colonne", style2bi)
ws1.write(li, 3, "Ligne", style2bi)
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)
ws1.write(li, 5, "Nom", style2bi)
ws1.write(li, 6, "Prénom", style2bi)
ws1.write(li, 7, "Colonne", style2bi)
ws1.write(li, 8, "Ligne", style2bi)
else:
ws1.write(li, 0, "Nom", style2bi)
ws1.write(li, 1, "Prénom", style2bi)
ws1.write(li, 2, "Place", style2bi)
ws1.write(li, 4, "Nom", style2bi)
ws1.write(li, 5, "Prénom", style2bi)
ws1.write(li, 6, "Place", style2bi)
return wb.savetostr()
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()

View File

@ -0,0 +1,56 @@
{% import 'bootstrap/wtf.html' as wtf %}
{% macro render_field(field) %}
<tr>
<td class="wtf-field">{{ field.label }}</td>
<td class="wtf-field">{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</td>
</tr>
{% endmacro %}
<div class="saisienote_etape1 form_placement">
<form method=post>
{{ form.evaluation_id }}
<table class="tf">
<tbody>
{{ render_field(form.surveillants) }}
{{ render_field(form.batiment) }}
{{ render_field(form.salle) }}
{{ render_field(form.nb_rangs) }}
{{ render_field(form.etiquetage) }}
{% if form.has_groups %}
{{ render_field(form.groups) }}
<!--
{% for partition in form.groups_tree %}
<tr>
{% if partition == 'Tous' %}
<td rowspan="{{ form.groups_tree_length }}">Groupes</td>
{% endif %}
<td>{{ partition }}</td>
<td>
{% for groupe in form.groups_tree[partition] %}
{{ groupe }}{{ form[form.groups_tree[partition][groupe]] }}
{% endfor %}
</td>
</tr>
{% endfor %}
-->
{% endif %}
{{ render_field(form.file_format) }}
</tbody>
</table>
<p>
<input id="gr_submit" type=submit value="Ok">
<input id="gr_cancel" type=submit value="Annuler">
</script>
</form>
</div>

View File

@ -1611,6 +1611,7 @@ sco_publish(
"/placement_eval_selectetuds",
sco_placement.placement_eval_selectetuds,
Permission.ScoEnsView,
methods=["GET", "POST"],
)
sco_publish("/do_placement", sco_placement.do_placement, Permission.ScoEnsView)