Contrib JM Place i/o Excel

This commit is contained in:
Emmanuel Viennet 2021-08-02 09:52:07 +03:00
commit 0cb9d18344
4 changed files with 1715 additions and 1740 deletions

View File

@ -40,7 +40,10 @@ from app.scodoc.scolog import logdb
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
import six import six
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment, Protection
from openpyxl.cell import WriteOnlyCell
from tempfile import NamedTemporaryFile
# colors, voir exemple format.py # colors, voir exemple format.py
COLOR_CODES = { COLOR_CODES = {
@ -54,7 +57,7 @@ COLOR_CODES = {
} }
def sendExcelFile(REQUEST, data, filename): def send_excel_file(REQUEST, data, filename):
"""publication fichier. """publication fichier.
(on ne doit rien avoir émis avant, car ici sont générés les entetes) (on ne doit rien avoir émis avant, car ici sont générés les entetes)
""" """
@ -63,7 +66,7 @@ def sendExcelFile(REQUEST, data, filename):
.replace("&", "") .replace("&", "")
.replace(" ", "_") .replace(" ", "_")
) )
REQUEST.RESPONSE.setHeader("content-type", scu.XLS_MIMETYPE) REQUEST.RESPONSE.setHeader("content-type", scu.XLSX_MIMETYPE)
REQUEST.RESPONSE.setHeader( REQUEST.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename "content-disposition", 'attachment; filename="%s"' % filename
) )
@ -220,7 +223,7 @@ class ScoExcelSheet(object):
sauvegarde = True sauvegarde = True
else: else:
sauvegarde = False sauvegarde = False
ws0 = wb.add_sheet(self.sheet_name.decode(scu.SCO_ENCODING)) ws0 = wb.add_sheet(self.sheet_name)
li = 0 li = 0
for l in self.cells: for l in self.cells:
co = 0 co = 0
@ -476,145 +479,127 @@ def Excel_to_list(data, convert_to_string=str): # we may need 'encoding' argume
# #
return diag, M return diag, M
# Un style est enregistré comme un dictionnaire qui précise la valeur d'un attributdans la liste suivante:
# font, border, .. (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
#
def Excel_feuille_listeappel( def __make_cell(ws, value: any = u"", style=None):
context, """Contruit/retourne une cellule en spécifiant contenu et style.
sem,
groupname, ws -- La feuille sera intégrée la cellule
lines, value -- le contenu de la cellule (texte)
partitions=[], # partitions a montrer (colonnes) style -- le style de la cellule
with_codes=False, # indique codes etuds """
with_paiement=False, # indique si etudiant a paye inscription cell = WriteOnlyCell(ws, value)
server_name=None, if "font" in style:
): cell.font = style["font"]
"generation feuille appel" if "border" in style:
cell.border = style["border"]
return cell
def excel_feuille_listeappel(sem, groupname, lines, partitions=None, with_codes=False, with_paiement=False,
server_name=None):
"""generation feuille appel"""
if partitions is None:
partitions = []
formsemestre_id = sem["formsemestre_id"] formsemestre_id = sem["formsemestre_id"]
SheetName = "Liste " + groupname sheet_name = "Liste " + groupname
wb = Workbook() wb = Workbook(write_only=True)
ws0 = wb.add_sheet(SheetName.decode(scu.SCO_ENCODING)) ws = wb.create_sheet(title=sheet_name)
ws.column_dimensions["A"].width = 3
ws.column_dimensions["B"].width = 35
ws.column_dimensions["C"].width = 12
font1 = Font() font1 = Font(name="Arial", size=11)
font1.name = "Arial" font1i = Font(name="Arial", size=10, italic=True)
font1.height = 10 * 0x14 font1b = Font(name="Arial", size=11, bold=True)
font1i = Font() side_thin = Side(border_style="thin", color="FF000000")
font1i.name = "Arial"
font1i.height = 10 * 0x14
font1i.italic = True
style1i = XFStyle() border_tbl = Border(top=side_thin, bottom=side_thin, left=side_thin)
style1i.font = font1i border_tblr = Border(
top=side_thin, bottom=side_thin, left=side_thin, right=side_thin
)
style1b = XFStyle() style1i = {
style1b.font = font1 "font": font1i,
borders = Borders() }
borders.left = 1
borders.top = 1
borders.bottom = 1
style1b.borders = borders
style2 = XFStyle() style1b = {
font2 = Font() "font": font1,
font2.name = "Arial" "border": border_tbl,
font2.height = 14 * 0x14 }
style2.font = font2
style2b = XFStyle() style2 = {
style2b.font = font1i "font": Font(name="Arial", size=14),
borders = Borders() }
borders.left = 1
borders.top = 1
borders.bottom = 1
borders.right = 1
style2b.borders = borders
style2tb = XFStyle() style2b = {
borders = Borders() "font": font1i,
borders.top = 1 "border": border_tblr,
borders.bottom = 1 }
style2tb.borders = borders
style2tb.font = Font()
style2tb.font.height = 16 * 0x14 # -> ligne hautes
style2t3 = XFStyle() style2t3 = {
borders = Borders() "border": border_tblr,
borders.top = 1 }
borders.bottom = 1
borders.left = 1
style2t3.borders = borders
style2t3bold = XFStyle() style2t3bold = {
borders = Borders() "font": font1b,
borders.top = 1 "border": border_tblr,
borders.bottom = 1 }
borders.left = 1
style2t3bold.borders = borders
fontb = Font()
fontb.bold = True
style2t3bold.font = fontb
style3 = XFStyle() style3 = {
font3 = Font() "font": Font(name="Arial", bold=True, size=14),
font3.name = "Arial" }
font3.bold = True
font3.height = 14 * 0x14
style3.font = font3
NbWeeks = 4 # nombre de colonnes pour remplir absences nb_weeks = 4 # nombre de colonnes pour remplir absences
# ligne 1 # ligne 1
li = 0 title = "%s %s (%s - %s)" % (
ws0.write(
li,
1,
(
"%s %s (%s - %s)"
% (
sco_preferences.get_preference("DeptName", formsemestre_id), sco_preferences.get_preference("DeptName", formsemestre_id),
notesdb.unquote(sem["titre_num"]), notesdb.unquote(sem["titre_num"]),
sem["date_debut"], sem["date_debut"],
sem["date_fin"], sem["date_fin"],
) )
).decode(scu.SCO_ENCODING),
style2,
)
# ligne 2
li += 1
ws0.write(li, 1, u"Discipline :", style2)
# ligne 3
li += 1
ws0.write(li, 1, u"Enseignant :", style2)
ws0.write(li, 5, ("Groupe %s" % groupname).decode(scu.SCO_ENCODING), style3)
# Avertissement pour ne pas confondre avec listes notes
ws0.write(
li + 1, 2, u"Ne pas utiliser cette feuille pour saisir les notes !", style1i
)
#
li += 2
li += 1
ws0.write(li, 1, u"Nom", style3)
co = 2
for partition in partitions:
if partition["partition_name"]:
ws0.write(
li, co, partition["partition_name"].decode(scu.SCO_ENCODING), style3
)
co += 1
if with_codes:
coc = co
ws0.write(li, coc, u"etudid", style3)
ws0.write(li, coc + 1, u"code_nip", style3)
ws0.write(li, coc + 2, u"code_ine", style3)
co += 3
for i in range(NbWeeks): cell_2 = __make_cell(ws, title, style2)
ws0.write(li, co + i, "", style2b) ws.append([None, cell_2])
# ligne 2
cell_2 = __make_cell(ws, u"Discipline :", style2)
ws.append([None, cell_2])
# ligne 3
cell_2 = __make_cell(ws, u"Enseignant :", style2)
cell_6 = __make_cell(ws, ("Groupe %s" % groupname), style3)
ws.append([None, cell_2, None, None, None, None, cell_6])
# ligne 4: Avertissement pour ne pas confondre avec listes notes
cell_2 = __make_cell(ws, u"Ne pas utiliser cette feuille pour saisir les notes !", style1i)
ws.append([None, None, cell_2])
ws.append([None])
ws.append([None])
# ligne 7: Entête (contruction dans une liste cells)
cells = [None] # passe la première colonne
cell_2 = __make_cell(ws, u"Nom", style3)
cells.append(cell_2)
for partition in partitions:
cells.append(__make_cell(ws, partition["partition_name"], style3))
if with_codes:
cells.append(__make_cell(ws, u"etudid", style3))
cells.append(__make_cell(ws, u"code_nip", style3))
cells.append(__make_cell(ws, u"code_ine", style3))
for i in range(nb_weeks):
cells.append(__make_cell(ws, "", style2b))
ws.append(cells)
n = 0 n = 0
# pour chaque étudiant
for t in lines: for t in lines:
n += 1 n += 1
li += 1
ws0.write(li, 0, n, style1b)
nomprenom = ( nomprenom = (
t["civilite_str"] t["civilite_str"]
+ " " + " "
@ -631,45 +616,39 @@ def Excel_feuille_listeappel(
elif not paie: elif not paie:
nomprenom += " (non paiement)" nomprenom += " (non paiement)"
style_nom = style2t3bold style_nom = style2t3bold
ws0.write(li, 1, nomprenom.decode(scu.SCO_ENCODING), style_nom) cell_1 = __make_cell(ws, n, style1b)
co = 2 cell_2 = __make_cell(ws, nomprenom, style_nom)
cells = [cell_1, cell_2]
for partition in partitions: for partition in partitions:
if partition["partition_name"]: if partition["partition_name"]:
ws0.write( cells.append(
li, __make_cell(ws, t.get(partition["partition_id"], u""), style2t3)
co,
t.get(partition["partition_id"], "").decode(scu.SCO_ENCODING),
style2t3,
) )
co += 1
if with_codes: if with_codes:
ws0.write(li, coc, t["etudid"].decode(scu.SCO_ENCODING), style2t3) cells.append(__make_cell(ws, t["etudid"], style2t3))
if t["code_nip"]: code_nip = t.get("code_nip", u"")
code_nip = t["code_nip"].decode(scu.SCO_ENCODING) cells.append(__make_cell(ws, code_nip, style2t3))
else: code_ine = t.get("code_ine", u"")
code_nip = u"" cells.append(__make_cell(ws, code_ine, style2t3))
ws0.write(li, coc + 1, code_nip, style2t3) cells.append(__make_cell(ws, t.get("etath", ""), style2b))
if t["code_ine"]: for i in range(1, nb_weeks):
code_ine = t["code_ine"].decode(scu.SCO_ENCODING) cells.append(__make_cell(ws, style=style2t3))
else: # ws0.row(li).height = 850 # sans effet ?
code_ine = u"" # (openpyxl: en mode optimisé, les hauteurs de lignes doivent être spécifiées avant toutes les cellules)
ws0.write(li, coc + 2, code_ine, style2t3) ws.append(cells)
if t["etath"]:
etath = t["etath"].decode(scu.SCO_ENCODING) ws.append([None])
else:
etath = u"" # bas de page (date, serveur)
ws0.write(li, co, etath, style2b) # etat
for i in range(1, NbWeeks):
ws0.write(li, co + i, u"", style2b) # cellules vides
ws0.row(li).height = 850 # sans effet ?
#
li += 2
dt = time.strftime("%d/%m/%Y à %Hh%M") dt = time.strftime("%d/%m/%Y à %Hh%M")
if server_name: if server_name:
dt += " sur " + server_name dt += " sur " + server_name
ws0.write(li, 1, ("Liste éditée le " + dt).decode(scu.SCO_ENCODING), style1i) cell_2 = __make_cell(ws, ("Liste éditée le " + dt), style1i)
# ws.append([None, cell_2])
ws0.col(0).width = 850
ws0.col(1).width = 9000
return wb.savetostr() # construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
with NamedTemporaryFile() as tmp:
wb.save(tmp.name)
tmp.seek(0)
return tmp.read()

View File

@ -724,18 +724,12 @@ def groups_table(
return tab.make_page(context, format=format, REQUEST=REQUEST) return tab.make_page(context, format=format, REQUEST=REQUEST)
elif format == "xlsappel": elif format == "xlsappel":
xls = sco_excel.Excel_feuille_listeappel( xls = sco_excel.excel_feuille_listeappel(groups_infos.formsemestre, groups_infos.groups_titles,
context, groups_infos.members, partitions=groups_infos.partitions,
groups_infos.formsemestre, # le 1er semestre, serait à modifier si plusieurs with_codes=with_codes, with_paiement=with_paiement,
groups_infos.groups_titles, server_name=REQUEST.BASE0)
groups_infos.members, filename = "liste_%s" % groups_infos.groups_filename + ".xlsx"
partitions=groups_infos.partitions, return sco_excel.send_excel_file(REQUEST, xls, filename)
with_codes=with_codes,
with_paiement=with_paiement,
server_name=REQUEST.BASE0,
)
filename = "liste_%s" % groups_infos.groups_filename + ".xls"
return sco_excel.sendExcelFile(REQUEST, xls, filename)
elif format == "allxls": elif format == "allxls":
# feuille Excel avec toutes les infos etudiants # feuille Excel avec toutes les infos etudiants
if not groups_infos.members: if not groups_infos.members:
@ -806,7 +800,7 @@ def groups_table(
title = "etudiants_%s" % groups_infos.groups_filename title = "etudiants_%s" % groups_infos.groups_filename
xls = sco_excel.Excel_SimpleTable(titles=titles, lines=L, SheetName=title) xls = sco_excel.Excel_SimpleTable(titles=titles, lines=L, SheetName=title)
filename = title + ".xls" filename = title + ".xls"
return sco_excel.sendExcelFile(REQUEST, xls, filename) return sco_excel.send_excel_file(REQUEST, xls, filename)
else: else:
raise ValueError("unsupported format") raise ValueError("unsupported format")

View File

@ -301,6 +301,7 @@ CSV_FIELDSEP = ";"
CSV_LINESEP = "\n" CSV_LINESEP = "\n"
CSV_MIMETYPE = "text/comma-separated-values" CSV_MIMETYPE = "text/comma-separated-values"
XLS_MIMETYPE = "application/vnd.ms-excel" XLS_MIMETYPE = "application/vnd.ms-excel"
XLSX_MIMETYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
PDF_MIMETYPE = "application/pdf" PDF_MIMETYPE = "application/pdf"
XML_MIMETYPE = "text/xml" XML_MIMETYPE = "text/xml"
JSON_MIMETYPE = "application/json" JSON_MIMETYPE = "application/json"

View File

@ -1,3 +1,4 @@
openpyxl
alembic==1.6.5 alembic==1.6.5
attrs==21.2.0 attrs==21.2.0
Babel==2.9.1 Babel==2.9.1