forked from ScoDoc/ScoDoc
Contrib JM Place i/o Excel
This commit is contained in:
commit
0cb9d18344
@ -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 où 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(
|
sco_preferences.get_preference("DeptName", formsemestre_id),
|
||||||
li,
|
notesdb.unquote(sem["titre_num"]),
|
||||||
1,
|
sem["date_debut"],
|
||||||
(
|
sem["date_fin"],
|
||||||
"%s %s (%s - %s)"
|
|
||||||
% (
|
|
||||||
sco_preferences.get_preference("DeptName", formsemestre_id),
|
|
||||||
notesdb.unquote(sem["titre_num"]),
|
|
||||||
sem["date_debut"],
|
|
||||||
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()
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user