forked from ScoDoc/ScoDoc
fin fusion
This commit is contained in:
parent
dccae56fe7
commit
770ccb4d6e
@ -40,10 +40,15 @@ from openpyxl.cell import WriteOnlyCell
|
|||||||
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
||||||
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
|
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
|
||||||
from openpyxl.comments import Comment
|
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
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import notesdb
|
from app.scodoc import notesdb
|
||||||
from app.scodoc import sco_preferences
|
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 import log
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
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)
|
# 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):
|
def xldate_as_datetime(xldate, datemode=0):
|
||||||
return openpyxl.utils.datetime.from_ISO8601(xldate)
|
return openpyxl.utils.datetime.from_ISO8601(xldate)
|
||||||
# if datemode not in (0, 1):
|
|
||||||
# raise ValueError("invalid mode %s" % datemode)
|
|
||||||
# if xldate == 0.00:
|
def adjust_sheetname(sheet_name):
|
||||||
# return datetime.time(0, 0, 0)
|
# Le nom de la feuille ne peut faire plus de 31 caractères.
|
||||||
# if xldate < 0.00:
|
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
|
||||||
# raise ValueError("invalid date code %s" % xldate)
|
return sheet_name[:31]
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
|
||||||
class ScoExcelBook:
|
class ScoExcelBook:
|
||||||
@ -139,13 +106,16 @@ class ScoExcelBook:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sheets = [] # list of sheets
|
self.sheets = [] # list of sheets
|
||||||
|
self.wb = Workbook(write_only=True)
|
||||||
|
|
||||||
def create_sheet(self, sheet_name="feuille", default_style=None):
|
def create_sheet(self, sheet_name="feuille", default_style=None):
|
||||||
"""Crée une nouvelle feuille dans ce classeur
|
"""Crée une nouvelle feuille dans ce classeur
|
||||||
sheet_name -- le nom de la feuille
|
sheet_name -- le nom de la feuille
|
||||||
default_style -- le style par défaut
|
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)
|
self.sheets.append(sheet)
|
||||||
return sheet
|
return sheet
|
||||||
|
|
||||||
@ -153,12 +123,11 @@ class ScoExcelBook:
|
|||||||
"""génération d'un stream binaire représentant la totalité du classeur.
|
"""génération d'un stream binaire représentant la totalité du classeur.
|
||||||
retourne le flux
|
retourne le flux
|
||||||
"""
|
"""
|
||||||
wb = Workbook(write_only=True)
|
|
||||||
for sheet in self.sheets:
|
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)
|
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
|
||||||
with NamedTemporaryFile() as tmp:
|
with NamedTemporaryFile() as tmp:
|
||||||
wb.save(tmp.name)
|
self.wb.save(tmp.name)
|
||||||
tmp.seek(0)
|
tmp.seek(0)
|
||||||
return tmp.read()
|
return tmp.read()
|
||||||
|
|
||||||
@ -166,6 +135,7 @@ class ScoExcelBook:
|
|||||||
def excel_make_style(
|
def excel_make_style(
|
||||||
bold=False,
|
bold=False,
|
||||||
italic=False,
|
italic=False,
|
||||||
|
outline=False,
|
||||||
color: COLORS = COLORS.BLACK,
|
color: COLORS = COLORS.BLACK,
|
||||||
bgcolor: COLORS = None,
|
bgcolor: COLORS = None,
|
||||||
halign=None,
|
halign=None,
|
||||||
@ -186,7 +156,14 @@ def excel_make_style(
|
|||||||
size -- taille de police
|
size -- taille de police
|
||||||
"""
|
"""
|
||||||
style = {}
|
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
|
style["font"] = font
|
||||||
if bgcolor:
|
if bgcolor:
|
||||||
style["fill"] = PatternFill(fill_type="solid", fgColor=bgcolor.value)
|
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
|
* pour finit appel de la méthode de génération
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sheet_name="feuille", default_style=None, wb=None):
|
def __init__(self, sheet_name="feuille", default_style=None, ws=None):
|
||||||
"""Création de la feuille.
|
"""Création de la feuille. sheet_name
|
||||||
sheet_name -- le nom de la feuille
|
-- le nom de la feuille default_style
|
||||||
default_style -- le style par défaut des cellules
|
-- le style par défaut des cellules ws
|
||||||
wb -- le WorkBook dans laquelle se trouve la feuille. Si wb est None (cas d'un classeur mono-feuille),
|
-- None si la feuille est autonome (dans ce cas ell crée son propre wb), sinon c'est la worksheet
|
||||||
un workbook est crée et associé à cette feuille.
|
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.
|
# 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' ?)
|
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
|
||||||
self.sheet_name = sheet_name[
|
self.sheet_name = adjust_sheetname(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 }
|
|
||||||
if default_style is None:
|
if default_style is None:
|
||||||
default_style = excel_make_style()
|
default_style = excel_make_style()
|
||||||
self.default_style = default_style
|
self.default_style = default_style
|
||||||
self.wb = wb or Workbook(write_only=True) # Création de workbook si nécessaire
|
if ws is None:
|
||||||
self.ws = self.wb.create_sheet(title=self.sheet_name)
|
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.column_dimensions = {}
|
||||||
|
self.row_dimensions = {}
|
||||||
|
|
||||||
def set_column_dimension_width(self, cle, value):
|
def excel_make_composite_style(
|
||||||
"""Détermine la largeur d'une colonne.
|
self,
|
||||||
cle -- identifie la colonne ("A"n "B", ...)
|
alignment=None,
|
||||||
value -- la dimension (unité : 7 pixels comme affiché dans Excel)
|
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):
|
def set_row_dimension_height(self, cle=None, value=21):
|
||||||
"""Masque ou affiche une colonne.
|
"""Détermine la hauteur d'une ligne. cle -- identifie la ligne (1, 2, ...) si None,
|
||||||
cle -- identifie la colonne ("A"n "B", ...)
|
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)
|
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):
|
def make_cell(self, value: any = None, style=None, comment=None):
|
||||||
"""Construit une cellule.
|
"""Construit une cellule.
|
||||||
@ -271,8 +300,12 @@ class ScoExcelSheet:
|
|||||||
style = self.default_style
|
style = self.default_style
|
||||||
if "font" in style:
|
if "font" in style:
|
||||||
cell.font = style["font"]
|
cell.font = style["font"]
|
||||||
|
if "alignment" in style:
|
||||||
|
cell.alignment = style["alignment"]
|
||||||
if "border" in style:
|
if "border" in style:
|
||||||
cell.border = style["border"]
|
cell.border = style["border"]
|
||||||
|
if "fill" in style:
|
||||||
|
cell.fill = style["fill"]
|
||||||
if "number_format" in style:
|
if "number_format" in style:
|
||||||
cell.number_format = style["number_format"]
|
cell.number_format = style["number_format"]
|
||||||
if "fill" in style:
|
if "fill" in style:
|
||||||
@ -311,73 +344,31 @@ class ScoExcelSheet:
|
|||||||
"""ajoute une ligne déjà construite à la feuille."""
|
"""ajoute une ligne déjà construite à la feuille."""
|
||||||
self.rows.append(row)
|
self.rows.append(row)
|
||||||
|
|
||||||
# def set_style(self, style=None, li=None, co=None):
|
def prepare(self):
|
||||||
# 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):
|
|
||||||
"""génére un flux décrivant la feuille.
|
"""génére un flux décrivant la feuille.
|
||||||
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
|
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
|
||||||
ou pour la génération d'un classeur multi-feuilles
|
ou pour la génération d'un classeur multi-feuilles
|
||||||
"""
|
"""
|
||||||
for col in self.column_dimensions.keys():
|
for col in self.column_dimensions.keys():
|
||||||
self.ws.column_dimensions[col] = self.column_dimensions[col]
|
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:
|
for row in self.rows:
|
||||||
self.ws.append(row)
|
self.ws.append(row)
|
||||||
|
|
||||||
def generate_standalone(self):
|
def generate(self):
|
||||||
"""génération d'un classeur mono-feuille"""
|
"""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)
|
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
|
||||||
|
self.prepare()
|
||||||
with NamedTemporaryFile() as tmp:
|
with NamedTemporaryFile() as tmp:
|
||||||
self.wb.save(tmp.name)
|
self.wb.save(tmp.name)
|
||||||
tmp.seek(0)
|
tmp.seek(0)
|
||||||
return tmp.read()
|
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(
|
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
|
||||||
@ -416,7 +407,7 @@ def excel_simple_table(
|
|||||||
cell_style = text_style
|
cell_style = text_style
|
||||||
cells.append(ws.make_cell(it, cell_style))
|
cells.append(ws.make_cell(it, cell_style))
|
||||||
ws.append_row(cells)
|
ws.append_row(cells)
|
||||||
return ws.generate_standalone()
|
return ws.generate()
|
||||||
|
|
||||||
|
|
||||||
def excel_feuille_saisie(e, titreannee, description, lines):
|
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),
|
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):
|
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)
|
cell_2 = ws.make_cell(("Liste éditée le " + dt), style1i)
|
||||||
ws.append_row([None, cell_2])
|
ws.append_row([None, cell_2])
|
||||||
|
|
||||||
return ws.generate_standalone()
|
return ws.generate()
|
||||||
|
@ -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 six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
from copy import copy
|
||||||
|
|
||||||
import flask
|
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
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
from app.scodoc import sco_evaluations
|
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.sco_excel import * # XXX à vérifier
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
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.
|
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]
|
# 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
|
# description de l'evaluation
|
||||||
H = [
|
H = [
|
||||||
sco_evaluations.evaluation_describe(
|
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."
|
"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]
|
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||||
|
|
||||||
# Check access
|
# Check access
|
||||||
# (admin, respformation, and responsable_id)
|
# (admin, respformation, and responsable_id)
|
||||||
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_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":
|
if placement_method == "xls":
|
||||||
filename = f"placement_{evalname}_{gr_title_filename}{scu.XLSX_SUFFIX}"
|
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
|
E, desceval, listetud, columns, space, maxlines, building, room, numbering
|
||||||
)
|
)
|
||||||
return sco_excel.send_excel_file(REQUEST, xls, filename)
|
return sco_excel.send_excel_file(REQUEST, xls, filename)
|
||||||
@ -399,7 +508,7 @@ def do_placement(REQUEST):
|
|||||||
return t
|
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"""
|
"""Dialogue placement etudiants: choix methode et localisation"""
|
||||||
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||||
if not evals:
|
if not evals:
|
||||||
@ -415,298 +524,170 @@ def placement_eval_selectetuds(evaluation_id, REQUEST=None):
|
|||||||
formid = "placementfile"
|
formid = "placementfile"
|
||||||
if not REQUEST.form.get("%s-submitted" % formid, False):
|
if not REQUEST.form.get("%s-submitted" % formid, False):
|
||||||
# not submitted, choix groupe
|
# not submitted, choix groupe
|
||||||
r = do_placement_selectetuds(REQUEST)
|
r = do_placement_selectetuds()
|
||||||
if r:
|
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(
|
H.append(
|
||||||
"""<h3>Explications</h3>
|
"""<h3>Explications</h3> <ul> <li>Choisir le format du fichier résultat :</li> <ul> <li>le format pdf
|
||||||
<ul>
|
consiste en un tableau précisant pour chaque étudiant la localisation de sa table;</li> <li>le format xls
|
||||||
<li>Choisir le format du fichier résultat :</li>
|
produit un classeur avec deux onglets</li> <ul> <li>le premier onglet donne une vue de la salle avec la
|
||||||
<ul>
|
localisation des étudiants et peut servir de feuille d'émargement;</li> <li>le second onglet est un tableau
|
||||||
<li>le format pdf consiste en un tableau précisant pour chaque étudiant la localisation de sa table;</li>
|
similaire à celui du fichier pdf;</li> </ul> </ul> <li>préciser les surveillants et la localisation (bâtiment
|
||||||
<li>le format xls produit un classeur avec deux onglets</li>
|
et salle) et indiquer le nombre de colonnes;</li> <li>deux types de placements sont possibles :</li> <ul>
|
||||||
<ul>
|
<li>continue suppose que les tables ont toutes un numéro unique;</li> <li>coordonnées localise chaque table
|
||||||
<li>le premier onglet donne une vue de la salle avec la localisation des étudiants et peut servir de feuille d'émargement;</li>
|
via un numéro de colonne et un numéro de ligne (ou rangée).</li> </ul> </ul> """
|
||||||
<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())
|
H.append(html_sco_header.sco_footer())
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def Excel_feuille_placement(
|
def _one_header(ws, numbering, styles):
|
||||||
E, description, listetud, columns, space, maxlines, building, room, numbering
|
cells = []
|
||||||
):
|
|
||||||
"""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))
|
|
||||||
if numbering == "coordinate":
|
if numbering == "coordinate":
|
||||||
ws1.col(0).width = 4000
|
cells.append(ws.make_cell("Nom", styles["2bi"]))
|
||||||
ws1.col(1).width = 4500
|
cells.append(ws.make_cell("Prénom", styles["2bi"]))
|
||||||
ws1.col(2).width = 1500
|
cells.append(ws.make_cell("Colonne", styles["2bi"]))
|
||||||
ws1.col(3).width = 1500
|
cells.append(ws.make_cell("Ligne", styles["2bi"]))
|
||||||
|
|
||||||
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
|
|
||||||
else:
|
else:
|
||||||
ws1.col(0).width = 4000
|
cells.append(ws.make_cell("Nom", styles["2bi"]))
|
||||||
ws1.col(1).width = 4500
|
cells.append(ws.make_cell("Prénom", styles["2bi"]))
|
||||||
ws1.col(2).width = 3000
|
cells.append(ws.make_cell("Place", styles["2bi"]))
|
||||||
|
return cells
|
||||||
|
|
||||||
ws1.col(3).width = 500
|
|
||||||
|
|
||||||
ws1.col(4).width = 4000
|
def _headers(ws, numbering, styles, nb_listes):
|
||||||
ws1.col(5).width = 4500
|
cells = []
|
||||||
ws1.col(6).width = 3000
|
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
|
||||||
font0 = Font()
|
styles = {
|
||||||
font0.name = "Arial"
|
"titres": sco_excel.excel_make_style(font_name="Arial", bold=True, size=12),
|
||||||
font0.bold = True
|
"1t": ws0.excel_make_composite_style(
|
||||||
font0.height = 12 * 0x14
|
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()
|
def _init_lines(maxlines):
|
||||||
font1i.name = "Arial"
|
return [
|
||||||
font1i.height = 10 * 0x14
|
[] for _ in range(maxlines)
|
||||||
font1i.italic = True
|
] # 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()
|
def _write_lines(ws, lines):
|
||||||
font2bi.name = "Arial"
|
for line in lines:
|
||||||
font2bi.height = 8 * 0x14
|
ws.append_row(line)
|
||||||
font2bi.bold = True
|
|
||||||
font2bi.italic = True
|
|
||||||
|
|
||||||
font2 = Font()
|
|
||||||
font2.name = "Arial"
|
|
||||||
font2.height = 10 * 0x14
|
|
||||||
|
|
||||||
style_titres = XFStyle()
|
def _titres(ws, description, evaluation, building, room, styles):
|
||||||
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
|
|
||||||
dt = time.strftime("%d/%m/%Y a %Hh%M")
|
dt = time.strftime("%d/%m/%Y a %Hh%M")
|
||||||
ws0.write(li, 0, "Feuille placement etudiants éditée le %s" % dt, style_titres)
|
ws.append_single_cell_row(
|
||||||
ws1.write(li, 0, "Feuille placement etudiants éditée le %s" % dt, style_titres)
|
"Feuille placement etudiants éditée le %s" % dt, styles["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,
|
|
||||||
)
|
)
|
||||||
ws1.write(
|
for line, desceval in enumerate(description):
|
||||||
li,
|
if line in [1, 4, 7]:
|
||||||
0,
|
ws.append_blank_row()
|
||||||
"Date : %s - Horaire : %s à %s" % (E["jour"], E["heure_debut"], E["heure_fin"]),
|
ws.append_single_cell_row(desceval[0], styles["titres"])
|
||||||
style_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
|
# entetes colonnes - feuille0
|
||||||
|
cells = [ws0.make_cell()]
|
||||||
for col in range(nbcolumns):
|
for col in range(nbcolumns):
|
||||||
ws0.write(li, col + 1, "colonne %s" % (col + 1), style2b)
|
cells.append(ws0.make_cell("colonne %s" % (col + 1), styles["2b"]))
|
||||||
# entetes colonnes - feuille1
|
ws0.append_row(cells)
|
||||||
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)
|
|
||||||
|
|
||||||
# etudiants
|
# etudiants
|
||||||
line = 1
|
line = 1
|
||||||
col = 1
|
col = 1
|
||||||
@ -730,78 +711,188 @@ def Excel_feuille_placement(
|
|||||||
placementetud.append(linetud)
|
placementetud.append(linetud)
|
||||||
|
|
||||||
# etudiants - feuille0
|
# etudiants - feuille0
|
||||||
line = 0
|
place = 1
|
||||||
li0 = li
|
for rang, linetud in enumerate(placementetud, start=1):
|
||||||
for linetud in placementetud:
|
# Chaque rang est affiché sur 3 lignes xlsx (notées A, B, C)
|
||||||
li0 += 1
|
# ligne A: le nom, ligne B: le prénom, ligne C: un espace ou la place
|
||||||
line += 1
|
cells_a = [ws0.make_cell(rang, styles["2b"])]
|
||||||
ws0.write(li0, 0, line, style2b)
|
cells_b = [ws0.make_cell("", styles["2b"])]
|
||||||
col = 1
|
cells_c = [ws0.make_cell("", styles["2b"])]
|
||||||
|
row = 14 # premieère ligne de signature
|
||||||
for etudid in linetud:
|
for etudid in linetud:
|
||||||
ws0.write(li0, col, (etudid[0]).decode(scu.SCO_ENCODING), style1t)
|
cells_a.append(ws0.make_cell(etudid[0], styles["1t"]))
|
||||||
ws0.write(li0 + 1, col, (etudid[1]).decode(scu.SCO_ENCODING), style1m)
|
cells_b.append(ws0.make_cell(etudid[1], styles["1m"]))
|
||||||
ws0.row(li0 + 2).height = space
|
|
||||||
if numbering == "coordinate":
|
if numbering == "coordinate":
|
||||||
ws0.write(li0 + 2, col, " ", style1bb)
|
cell_c = ws0.make_cell("", styles["1bb"])
|
||||||
else:
|
else:
|
||||||
ws0.write(
|
cell_c = ws0.make_cell("place %s" % place, styles["1bb"])
|
||||||
li0 + 2, col, "place %s" % (col + (line - 1) * nbcolumns), style1bb
|
cells_c.append(cell_c)
|
||||||
)
|
ws0.set_row_dimension_height(row, space / 25)
|
||||||
# ws0.write(li+3,col, ' ', style1bm )
|
row += 3
|
||||||
# ws0.write(li+4,col, ' ', style1bb )
|
place = place + 1
|
||||||
|
|
||||||
if col == nbcolumns:
|
if col == nbcolumns:
|
||||||
col = 0
|
ws0.append_row(cells_a)
|
||||||
li0 += 2
|
ws0.append_row(cells_b)
|
||||||
col += 1
|
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":
|
def _compute_ordretud(listetud, nbcolumns, numbering):
|
||||||
coloffset = 5
|
orderetud = []
|
||||||
else:
|
line = 1
|
||||||
coloffset = 4
|
col = 1
|
||||||
line = 0
|
for etudid in listetud:
|
||||||
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)
|
|
||||||
if numbering == "coordinate":
|
if numbering == "coordinate":
|
||||||
ws1.write(li1, col + 2, etudid[2], style2m2)
|
orderetud.append((etudid[0], etudid[1], col, line))
|
||||||
ws1.write(li1, col + 3, etudid[3], style2r)
|
|
||||||
else:
|
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
|
line = 0
|
||||||
li1 = li
|
if col >= maxlistes: # fin de page
|
||||||
nbcol = nbcol + 1
|
_write_lines(ws, lines)
|
||||||
col = col + coloffset
|
lines = _init_lines(maxlines)
|
||||||
if nbcol == 2:
|
|
||||||
li = li + maxlines + 2
|
|
||||||
li1 = li
|
|
||||||
nbcol = 0
|
|
||||||
col = 0
|
col = 0
|
||||||
if numbering == "coordinate":
|
ws.append_blank_row()
|
||||||
ws1.write(li, 0, "Nom", style2bi)
|
nb_etu_restant -= maxlistes * maxlines
|
||||||
ws1.write(li, 1, "Prénom", style2bi)
|
nb_listes = min(
|
||||||
ws1.write(li, 2, "Colonne", style2bi)
|
maxlistes, nb_etu_restant // maxlines + 1
|
||||||
ws1.write(li, 3, "Ligne", style2bi)
|
) # 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)
|
def _excel_feuille_placement(
|
||||||
ws1.write(li, 5, "Prénom", style2bi)
|
evaluation,
|
||||||
ws1.write(li, 6, "Place", style2bi)
|
description,
|
||||||
return wb.savetostr()
|
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()
|
||||||
|
56
app/templates/forms/placement.html
Normal file
56
app/templates/forms/placement.html
Normal 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>
|
||||||
|
|
||||||
|
|
@ -1611,6 +1611,7 @@ sco_publish(
|
|||||||
"/placement_eval_selectetuds",
|
"/placement_eval_selectetuds",
|
||||||
sco_placement.placement_eval_selectetuds,
|
sco_placement.placement_eval_selectetuds,
|
||||||
Permission.ScoEnsView,
|
Permission.ScoEnsView,
|
||||||
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
sco_publish("/do_placement", sco_placement.do_placement, Permission.ScoEnsView)
|
sco_publish("/do_placement", sco_placement.do_placement, Permission.ScoEnsView)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user