Ajout image de fond sur PV jury et lettres individuelles de décision.

This commit is contained in:
viennet 2020-10-13 15:41:04 +02:00
parent 439571862b
commit 6160d48e9e
4 changed files with 187 additions and 62 deletions

View File

@ -69,7 +69,7 @@ def SU(s):
# Remplace caractères composés # Remplace caractères composés
# eg 'e\xcc\x81' COMBINING ACUTE ACCENT par '\xc3\xa9' LATIN SMALL LETTER E WITH ACUTE # eg 'e\xcc\x81' COMBINING ACUTE ACCENT par '\xc3\xa9' LATIN SMALL LETTER E WITH ACUTE
# car les "combining accents" ne sont pas traités par ReportLab mais peuvent # car les "combining accents" ne sont pas traités par ReportLab mais peuvent
# nous être envoyés par certains navigaters ou imports # nous être envoyés par certains navigateurs ou imports
u = unicodedata.normalize("NFC", unicode(s, SCO_ENCODING, "replace")) u = unicodedata.normalize("NFC", unicode(s, SCO_ENCODING, "replace"))
return u.encode("utf8") return u.encode("utf8")
@ -203,6 +203,7 @@ class ScolarsPageTemplate(PageTemplate):
""" """
canvas.saveState() canvas.saveState()
# ---- Logo: a small image, positionned at top left of the page
if self.logo is not None: if self.logo is not None:
# draws the logo if it exists # draws the logo if it exists
((width, height), image) = self.logo ((width, height), image) = self.logo

View File

@ -786,6 +786,77 @@ vu la délibération de la commission %(Type)s en date du %(Date)s présidée pa
"category": "pvpdf", "category": "pvpdf",
}, },
), ),
(
"PV_WITH_BACKGROUND",
{
"initvalue": 0,
"title": "Mettre l'image de fond sur les PV de jury (paysage)",
"input_type": "boolcheckbox",
"labels": ["non", "oui"],
"category": "pvpdf",
},
),
(
"PV_WITH_HEADER",
{
"initvalue": 1, # legacy
"title": "Ajouter l'en-tête sur les PV (paysage)",
"input_type": "boolcheckbox",
"labels": ["non", "oui"],
"category": "pvpdf",
},
),
(
"PV_WITH_FOOTER",
{
"initvalue": 1, # legacy
"title": "Ajouter le pied de page sur les PV (paysage)",
"input_type": "boolcheckbox",
"labels": ["non", "oui"],
"category": "pvpdf",
},
),
# marges PV paysages (en millimètres)
(
"pv_left_margin",
{
"initvalue": 0,
"size": 10,
"title": "Marge gauche PV en mm",
"type": "float",
"category": "pvpdf",
},
),
(
"pv_top_margin",
{
"initvalue": 23,
"size": 10,
"title": "Marge haute PV",
"type": "float",
"category": "pvpdf",
},
),
(
"pv_right_margin",
{
"initvalue": 0,
"size": 10,
"title": "Marge droite PV",
"type": "float",
"category": "pvpdf",
},
),
(
"pv_bottom_margin",
{
"initvalue": 5,
"size": 10,
"title": "Marge basse PV",
"type": "float",
"category": "pvpdf",
},
),
( (
"PV_LETTER_DIPLOMA_SIGNATURE", "PV_LETTER_DIPLOMA_SIGNATURE",
{ {
@ -872,11 +943,21 @@ s'est réuni le %(date_jury)s.
"category": "pvpdf", "category": "pvpdf",
}, },
), ),
(
"PV_LETTER_WITH_BACKGROUND",
{
"initvalue": 0,
"title": "Mettre l'image de fond sur les lettres individuelles de décision",
"input_type": "boolcheckbox",
"labels": ["non", "oui"],
"category": "pvpdf",
},
),
( (
"PV_LETTER_WITH_HEADER", "PV_LETTER_WITH_HEADER",
{ {
"initvalue": 0, "initvalue": 0,
"title": "Imprimer le logo en tête des lettres individuelles de décision", "title": "Ajouter l'en-tête sur les lettres individuelles de décision",
"input_type": "boolcheckbox", "input_type": "boolcheckbox",
"labels": ["non", "oui"], "labels": ["non", "oui"],
"category": "pvpdf", "category": "pvpdf",
@ -886,7 +967,7 @@ s'est réuni le %(date_jury)s.
"PV_LETTER_WITH_FOOTER", "PV_LETTER_WITH_FOOTER",
{ {
"initvalue": 0, "initvalue": 0,
"title": "Imprimer le pied de page sur les lettres individuelles de décision", "title": "Ajouter le pied de page sur les lettres individuelles de décision",
"input_type": "boolcheckbox", "input_type": "boolcheckbox",
"labels": ["non", "oui"], "labels": ["non", "oui"],
"category": "pvpdf", "category": "pvpdf",
@ -1633,8 +1714,7 @@ class sco_base_preferences:
self.load() self.load()
def load(self): def load(self):
"""Load all preferences from db """Load all preferences from db"""
"""
log("loading preferences") log("loading preferences")
try: try:
GSL.acquire() GSL.acquire()
@ -2063,12 +2143,14 @@ def doc_preferences(context):
L = [] L = []
for cat, cat_descr in PREF_CATEGORIES: for cat, cat_descr in PREF_CATEGORIES:
L.append([""]) L.append([""])
L.append(["## " + cat_descr.get("title", "") ]) L.append(["## " + cat_descr.get("title", "")])
L.append([""]) L.append([""])
L.append( ["Nom", " ", " " ] ) L.append(["Nom", " ", " "])
L.append( ["----", "----", "----"] ) L.append(["----", "----", "----"])
for pref_name, pref in PREFS: for pref_name, pref in PREFS:
if pref["category"] == cat: if pref["category"] == cat:
L.append(['`'+pref_name+'`', pref["title"], pref.get("explanation", "")]) L.append(
["`" + pref_name + "`", pref["title"], pref.get("explanation", "")]
)
return "\n".join([" | ".join(x) for x in L]) return "\n".join([" | ".join(x) for x in L])

View File

@ -94,7 +94,9 @@ def pageFooter(canvas, doc, logo, preferences, with_page_numbers=True):
("RIGHTPADDING", (-1, 0), (-1, 0), 1 * cm), ("RIGHTPADDING", (-1, 0), (-1, 0), 1 * cm),
] ]
) )
elems = [p, logo] elems = [p]
if logo:
elems.append(logo)
colWidths = [None, LOGO_FOOTER_WIDTH + 2 * mm] colWidths = [None, LOGO_FOOTER_WIDTH + 2 * mm]
if with_page_numbers: if with_page_numbers:
elems.append(np) elems.append(np)
@ -124,14 +126,14 @@ def pageHeader(canvas, doc, logo, preferences, only_on_first_page=False):
id="monheader", id="monheader",
showBoundary=0, showBoundary=0,
) )
if logo:
canvas.saveState() # is it necessary ? canvas.saveState() # is it necessary ?
head.addFromList([logo], canvas) head.addFromList([logo], canvas)
canvas.restoreState() canvas.restoreState()
class CourrierIndividuelTemplate(PageTemplate): class CourrierIndividuelTemplate(PageTemplate):
"""Template pour courrier avisant des decisions de jury (1 page /etudiant) """Template pour courrier avisant des decisions de jury (1 page /etudiant)"""
"""
def __init__( def __init__(
self, self,
@ -156,6 +158,13 @@ class CourrierIndividuelTemplate(PageTemplate):
self.preferences = preferences self.preferences = preferences
self.force_header = force_header self.force_header = force_header
self.force_footer = force_footer self.force_footer = force_footer
self.with_footer = (
self.force_footer or self.preferences["PV_LETTER_WITH_HEADER"]
)
self.with_header = (
self.force_header or self.preferences["PV_LETTER_WITH_FOOTER"]
)
self.with_page_background = self.preferences["PV_LETTER_WITH_BACKGROUND"]
self.with_page_numbers = False self.with_page_numbers = False
self.header_only_on_first_page = False self.header_only_on_first_page = False
# Our doc is made of a single frame # Our doc is made of a single frame
@ -175,13 +184,29 @@ class CourrierIndividuelTemplate(PageTemplate):
PageTemplate.__init__(self, template_name, [content]) PageTemplate.__init__(self, template_name, [content])
self.background_image_filename = None
self.logo_footer = None
self.logo_header = None
for suffix in LOGOS_IMAGES_ALLOWED_TYPES:
if template_name == "PVJuryTemplate":
fn = image_dir + "/pvjury_background" + "." + suffix
else:
fn = image_dir + "/letter_background" + "." + suffix
if os.path.exists(fn):
self.background_image_filename = fn
fn = image_dir + "/logo_footer" + "." + suffix
if os.path.exists(fn):
self.logo_footer = Image( self.logo_footer = Image(
image_dir + "/logo_footer.jpg", fn,
height=LOGO_FOOTER_HEIGHT, height=LOGO_FOOTER_HEIGHT,
width=LOGO_FOOTER_WIDTH, width=LOGO_FOOTER_WIDTH,
) )
fn = image_dir + "/logo_header" + "." + suffix
if os.path.exists(fn):
self.logo_header = Image( self.logo_header = Image(
image_dir + "/logo_header.jpg", fn,
height=LOGO_HEADER_HEIGHT, height=LOGO_HEADER_HEIGHT,
width=LOGO_HEADER_WIDTH, width=LOGO_HEADER_WIDTH,
) )
@ -201,8 +226,15 @@ class CourrierIndividuelTemplate(PageTemplate):
txt = SU(bm) txt = SU(bm)
canvas.bookmarkPage(key) canvas.bookmarkPage(key)
canvas.addOutlineEntry(txt, bm) canvas.addOutlineEntry(txt, bm)
if self.force_footer or self.preferences["PV_LETTER_WITH_HEADER"]:
# --- Add header # ---- Background image
if self.background_image_filename and self.with_page_background:
canvas.drawImage(
self.background_image_filename, 0, 0, doc.pagesize[0], doc.pagesize[1]
)
# ---- Header/Footer
if self.with_header:
pageHeader( pageHeader(
canvas, canvas,
doc, doc,
@ -210,8 +242,7 @@ class CourrierIndividuelTemplate(PageTemplate):
self.preferences, self.preferences,
self.header_only_on_first_page, self.header_only_on_first_page,
) )
if self.force_footer or self.preferences["PV_LETTER_WITH_FOOTER"]: if self.with_footer:
# --- Add footer
pageFooter( pageFooter(
canvas, canvas,
doc, doc,
@ -222,8 +253,7 @@ class CourrierIndividuelTemplate(PageTemplate):
class PVTemplate(CourrierIndividuelTemplate): class PVTemplate(CourrierIndividuelTemplate):
"""Template pour les pages des PV de jury """Template pour les pages des PV de jury"""
"""
def __init__( def __init__(
self, self,
@ -231,10 +261,17 @@ class PVTemplate(CourrierIndividuelTemplate):
author=None, author=None,
title=None, title=None,
subject=None, subject=None,
margins=(0, 23, 0, 5), # additional margins in mm (left,top,right, bottom) margins=None, # additional margins in mm (left,top,right, bottom)
image_dir="", image_dir="",
preferences=None, # dictionnary with preferences, required preferences=None, # dictionnary with preferences, required
): ):
if margins is None:
margins = (
preferences["pv_left_margin"],
preferences["pv_top_margin"],
preferences["pv_right_margin"],
preferences["pv_bottom_margin"],
)
CourrierIndividuelTemplate.__init__( CourrierIndividuelTemplate.__init__(
self, self,
document, document,
@ -250,6 +287,9 @@ class PVTemplate(CourrierIndividuelTemplate):
) )
self.with_page_numbers = True self.with_page_numbers = True
self.header_only_on_first_page = True self.header_only_on_first_page = True
self.with_header = self.preferences["PV_WITH_HEADER"]
self.with_footer = self.preferences["PV_WITH_FOOTER"]
self.with_page_background = self.preferences["PV_WITH_BACKGROUND"]
def afterDrawPage(self, canvas, doc): def afterDrawPage(self, canvas, doc):
"""Called after all flowables have been drawn on a page""" """Called after all flowables have been drawn on a page"""
@ -437,9 +477,13 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None, context
s = "s" s = "s"
else: else:
s = "" s = ""
params["autorisations_txt"] = ( params[
"""Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>""" "autorisations_txt"
% (etud["ne"], s, s, decision["autorisations_descr"]) ] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>""" % (
etud["ne"],
s,
s,
decision["autorisations_descr"],
) )
else: else:
params["autorisations_txt"] = "" params["autorisations_txt"] = ""
@ -538,9 +582,6 @@ def _make_signature_image(signature, leftindent, formsemestre_id, context=None):
# ---------------------------------------------- # ----------------------------------------------
# PV complet, tableau en format paysage
def pvjury_pdf( def pvjury_pdf(
context, context,
dpv, dpv,
@ -554,6 +595,7 @@ def pvjury_pdf(
anonymous=False, anonymous=False,
): ):
"""Doc PDF récapitulant les décisions de jury """Doc PDF récapitulant les décisions de jury
(tableau en format paysage)
dpv: result of dict_pvjury dpv: result of dict_pvjury
""" """
if not dpv: if not dpv:

View File

@ -156,15 +156,14 @@ def fmt_note(val, note_max=None, keep_numeric=False):
def fmt_coef(val): def fmt_coef(val):
"""Conversion valeur coefficient (float) en chaine """Conversion valeur coefficient (float) en chaine"""
"""
if val < 0.01: if val < 0.01:
return "%g" % val # unusually small value return "%g" % val # unusually small value
return "%g" % round(val, 2) return "%g" % round(val, 2)
def fmt_abs(val): def fmt_abs(val):
""" Conversion absences en chaine. val est une list [nb_abs_total, nb_abs_justifiees """Conversion absences en chaine. val est une list [nb_abs_total, nb_abs_justifiees
=> NbAbs / Nb_justifiees => NbAbs / Nb_justifiees
""" """
return "%s / %s" % (val[0], val[1]) return "%s / %s" % (val[0], val[1])
@ -194,7 +193,7 @@ GSL = thread.allocate_lock() # Global ScoDoc Lock
if "INSTANCE_HOME" in os.environ: if "INSTANCE_HOME" in os.environ:
# ----- Repertoire "var" (local) # ----- Repertoire "var" (local)
SCODOC_VAR_DIR = os.path.join( os.environ["INSTANCE_HOME"], "var", "scodoc" ) SCODOC_VAR_DIR = os.path.join(os.environ["INSTANCE_HOME"], "var", "scodoc")
# ----- Version information # ----- Version information
SCODOC_VERSION_DIR = os.path.join(SCODOC_VAR_DIR, "config", "version") SCODOC_VERSION_DIR = os.path.join(SCODOC_VAR_DIR, "config", "version")
# ----- Repertoire tmp # ----- Repertoire tmp
@ -210,7 +209,6 @@ if "INSTANCE_HOME" in os.environ:
) )
# ----- Lecture du fichier de configuration # ----- Lecture du fichier de configuration
SCO_SRCDIR = os.path.split(VERSION.__file__)[0] SCO_SRCDIR = os.path.split(VERSION.__file__)[0]
if SCO_SRCDIR: if SCO_SRCDIR:
@ -231,7 +229,9 @@ except:
raise raise
if hasattr(CONFIG, "CODES_EXPL"): if hasattr(CONFIG, "CODES_EXPL"):
CODES_EXPL.update(CONFIG.CODES_EXPL) # permet de customiser les explications de codes CODES_EXPL.update(
CONFIG.CODES_EXPL
) # permet de customiser les explications de codes
if CONFIG.CUSTOM_HTML_HEADER: if CONFIG.CUSTOM_HTML_HEADER:
@ -296,6 +296,8 @@ PDF_MIMETYPE = "application/pdf"
XML_MIMETYPE = "text/xml" XML_MIMETYPE = "text/xml"
JSON_MIMETYPE = "application/json" JSON_MIMETYPE = "application/json"
LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "png") # remind that PIL does not read pdf
class DictDefault(dict): # obsolete, use collections.defaultdict class DictDefault(dict): # obsolete, use collections.defaultdict
"""A dictionnary with default value for all keys """A dictionnary with default value for all keys
@ -462,8 +464,7 @@ ALPHANUM_EXP = re.compile(r"^[\w-]+$", re.UNICODE)
def is_valid_code_nip(s): def is_valid_code_nip(s):
"""True si s peut être un code NIP: au moins 6 chiffres décimaux """True si s peut être un code NIP: au moins 6 chiffres décimaux"""
"""
if not s: if not s:
return False return False
return re.match(r"^[0-9]{6,32}$", s) return re.match(r"^[0-9]{6,32}$", s)
@ -747,8 +748,7 @@ from sgmllib import SGMLParser
class html2txt_parser(SGMLParser): class html2txt_parser(SGMLParser):
"""html2txt() """html2txt()"""
"""
def reset(self): def reset(self):
"""reset() --> initialize the parser""" """reset() --> initialize the parser"""