diff --git a/sco_pdf.py b/sco_pdf.py
index 18d8af29..90dc0763 100644
--- a/sco_pdf.py
+++ b/sco_pdf.py
@@ -69,7 +69,7 @@ def SU(s):
# Remplace caractères composés
# 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
- # 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"))
return u.encode("utf8")
@@ -200,9 +200,10 @@ class ScolarsPageTemplate(PageTemplate):
minute: Minute as a decimal number [00,59].
server_url: URL du serveur ScoDoc
-
+
"""
canvas.saveState()
+ # ---- Logo: a small image, positionned at top left of the page
if self.logo is not None:
# draws the logo if it exists
((width, height), image) = self.logo
diff --git a/sco_preferences.py b/sco_preferences.py
index cff9a667..f28b0103 100644
--- a/sco_preferences.py
+++ b/sco_preferences.py
@@ -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",
},
),
+ (
+ "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",
{
@@ -872,11 +943,21 @@ s'est réuni le %(date_jury)s.
"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",
{
"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",
"labels": ["non", "oui"],
"category": "pvpdf",
@@ -886,7 +967,7 @@ s'est réuni le %(date_jury)s.
"PV_LETTER_WITH_FOOTER",
{
"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",
"labels": ["non", "oui"],
"category": "pvpdf",
@@ -1633,8 +1714,7 @@ class sco_base_preferences:
self.load()
def load(self):
- """Load all preferences from db
- """
+ """Load all preferences from db"""
log("loading preferences")
try:
GSL.acquire()
@@ -2063,12 +2143,14 @@ def doc_preferences(context):
L = []
for cat, cat_descr in PREF_CATEGORIES:
L.append([""])
- L.append(["## " + cat_descr.get("title", "") ])
+ L.append(["## " + cat_descr.get("title", "")])
L.append([""])
- L.append( ["Nom", " ", " " ] )
- L.append( ["----", "----", "----"] )
+ L.append(["Nom", " ", " "])
+ L.append(["----", "----", "----"])
for pref_name, pref in PREFS:
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])
diff --git a/sco_pvpdf.py b/sco_pvpdf.py
index 92531c4c..b38d5041 100644
--- a/sco_pvpdf.py
+++ b/sco_pvpdf.py
@@ -94,7 +94,9 @@ def pageFooter(canvas, doc, logo, preferences, with_page_numbers=True):
("RIGHTPADDING", (-1, 0), (-1, 0), 1 * cm),
]
)
- elems = [p, logo]
+ elems = [p]
+ if logo:
+ elems.append(logo)
colWidths = [None, LOGO_FOOTER_WIDTH + 2 * mm]
if with_page_numbers:
elems.append(np)
@@ -124,14 +126,14 @@ def pageHeader(canvas, doc, logo, preferences, only_on_first_page=False):
id="monheader",
showBoundary=0,
)
- canvas.saveState() # is it necessary ?
- head.addFromList([logo], canvas)
- canvas.restoreState()
+ if logo:
+ canvas.saveState() # is it necessary ?
+ head.addFromList([logo], canvas)
+ canvas.restoreState()
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__(
self,
@@ -156,6 +158,13 @@ class CourrierIndividuelTemplate(PageTemplate):
self.preferences = preferences
self.force_header = force_header
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.header_only_on_first_page = False
# Our doc is made of a single frame
@@ -175,16 +184,32 @@ class CourrierIndividuelTemplate(PageTemplate):
PageTemplate.__init__(self, template_name, [content])
- self.logo_footer = Image(
- image_dir + "/logo_footer.jpg",
- height=LOGO_FOOTER_HEIGHT,
- width=LOGO_FOOTER_WIDTH,
- )
- self.logo_header = Image(
- image_dir + "/logo_header.jpg",
- height=LOGO_HEADER_HEIGHT,
- width=LOGO_HEADER_WIDTH,
- )
+ 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(
+ fn,
+ height=LOGO_FOOTER_HEIGHT,
+ width=LOGO_FOOTER_WIDTH,
+ )
+
+ fn = image_dir + "/logo_header" + "." + suffix
+ if os.path.exists(fn):
+ self.logo_header = Image(
+ fn,
+ height=LOGO_HEADER_HEIGHT,
+ width=LOGO_HEADER_WIDTH,
+ )
def beforeDrawPage(self, canvas, doc):
"""Draws a logo and an contribution message on each page."""
@@ -201,8 +226,15 @@ class CourrierIndividuelTemplate(PageTemplate):
txt = SU(bm)
canvas.bookmarkPage(key)
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(
canvas,
doc,
@@ -210,8 +242,7 @@ class CourrierIndividuelTemplate(PageTemplate):
self.preferences,
self.header_only_on_first_page,
)
- if self.force_footer or self.preferences["PV_LETTER_WITH_FOOTER"]:
- # --- Add footer
+ if self.with_footer:
pageFooter(
canvas,
doc,
@@ -222,8 +253,7 @@ class CourrierIndividuelTemplate(PageTemplate):
class PVTemplate(CourrierIndividuelTemplate):
- """Template pour les pages des PV de jury
- """
+ """Template pour les pages des PV de jury"""
def __init__(
self,
@@ -231,10 +261,17 @@ class PVTemplate(CourrierIndividuelTemplate):
author=None,
title=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="",
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__(
self,
document,
@@ -250,6 +287,9 @@ class PVTemplate(CourrierIndividuelTemplate):
)
self.with_page_numbers = 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):
"""Called after all flowables have been drawn on a page"""
@@ -331,7 +371,7 @@ def pdf_lettres_individuelles(
document = BaseDocTemplate(report)
image_dir = SCODOC_LOGOS_DIR + "/logos_" + context.DeptId() + "/"
if not os.path.exists(image_dir):
- image_dir = SCODOC_LOGOS_DIR + "/" # use global logos
+ image_dir = SCODOC_LOGOS_DIR + "/" # use global logos
document.addPageTemplates(
CourrierIndividuelTemplate(
document,
@@ -437,9 +477,13 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None, context
s = "s"
else:
s = ""
- params["autorisations_txt"] = (
- """Vous êtes autorisé%s à continuer dans le%s semestre%s : %s"""
- % (etud["ne"], s, s, decision["autorisations_descr"])
+ params[
+ "autorisations_txt"
+ ] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : %s""" % (
+ etud["ne"],
+ s,
+ s,
+ decision["autorisations_descr"],
)
else:
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(
context,
dpv,
@@ -554,6 +595,7 @@ def pvjury_pdf(
anonymous=False,
):
"""Doc PDF récapitulant les décisions de jury
+ (tableau en format paysage)
dpv: result of dict_pvjury
"""
if not dpv:
@@ -602,7 +644,7 @@ def pvjury_pdf(
document.pagesize = landscape(A4)
image_dir = SCODOC_LOGOS_DIR + "/logos_" + context.DeptId() + "/"
if not os.path.exists(image_dir):
- image_dir = SCODOC_LOGOS_DIR + "/" # use global logos
+ image_dir = SCODOC_LOGOS_DIR + "/" # use global logos
document.addPageTemplates(
PVTemplate(
document,
diff --git a/sco_utils.py b/sco_utils.py
index 9e9da47d..596e0069 100644
--- a/sco_utils.py
+++ b/sco_utils.py
@@ -156,15 +156,14 @@ def fmt_note(val, note_max=None, keep_numeric=False):
def fmt_coef(val):
- """Conversion valeur coefficient (float) en chaine
- """
+ """Conversion valeur coefficient (float) en chaine"""
if val < 0.01:
return "%g" % val # unusually small value
return "%g" % round(val, 2)
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
"""
return "%s / %s" % (val[0], val[1])
@@ -194,7 +193,7 @@ GSL = thread.allocate_lock() # Global ScoDoc Lock
if "INSTANCE_HOME" in os.environ:
# ----- 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
SCODOC_VERSION_DIR = os.path.join(SCODOC_VAR_DIR, "config", "version")
# ----- Repertoire tmp
@@ -203,13 +202,12 @@ if "INSTANCE_HOME" in os.environ:
os.mkdir(SCO_TMPDIR, 0o755)
# ----- Les logos: /opt/scodoc/var/scodoc/config/logos
SCODOC_LOGOS_DIR = os.path.join(SCODOC_VAR_DIR, "config", "logos")
-
+
# ----- Repertoire "config" (devrait s'appeler "tools"...)
SCO_CONFIG_DIR = os.path.join(
os.environ["INSTANCE_HOME"], "Products", "ScoDoc", "config"
)
-
# ----- Lecture du fichier de configuration
SCO_SRCDIR = os.path.split(VERSION.__file__)[0]
@@ -231,7 +229,9 @@ except:
raise
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:
@@ -270,7 +270,7 @@ SCO_DEFAULT_SQL_USERS_CNX = "dbname=SCOUSERS port=%s" % SCO_DEFAULT_SQL_PORT
# Valeurs utilisées pour affichage seulement, pas de requetes ni de mails envoyés:
SCO_WEBSITE = "https://scodoc.org"
-SCO_USER_MANUAL = "https://scodoc.org/GuideUtilisateur"
+SCO_USER_MANUAL = "https://scodoc.org/GuideUtilisateur"
SCO_ANNONCES_WEBSITE = "https://listes.univ-paris13.fr/mailman/listinfo/scodoc-annonces"
SCO_DEVEL_LIST = "scodoc-devel@listes.univ-paris13.fr"
SCO_USERS_LIST = "notes@listes.univ-paris13.fr"
@@ -296,6 +296,8 @@ PDF_MIMETYPE = "application/pdf"
XML_MIMETYPE = "text/xml"
JSON_MIMETYPE = "application/json"
+LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "png") # remind that PIL does not read pdf
+
class DictDefault(dict): # obsolete, use collections.defaultdict
"""A dictionnary with default value for all keys
@@ -421,7 +423,7 @@ def simple_dictlist2xml(dictlist, doc=None, tagname=None, quote=False):
-
+
"""
if not tagname:
raise ValueError("invalid empty tagname !")
@@ -462,8 +464,7 @@ ALPHANUM_EXP = re.compile(r"^[\w-]+$", re.UNICODE)
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:
return False
return re.match(r"^[0-9]{6,32}$", s)
@@ -492,7 +493,7 @@ def suppress_accents(s):
def sanitize_string(s):
"""s is an ordinary string, encoding given by SCO_ENCODING"
- suppress accents and chars interpreted in XML
+ suppress accents and chars interpreted in XML
Irreversible (not a quote)
For ids and some filenames
@@ -747,8 +748,7 @@ from sgmllib import SGMLParser
class html2txt_parser(SGMLParser):
- """html2txt()
- """
+ """html2txt()"""
def reset(self):
"""reset() --> initialize the parser"""
@@ -757,15 +757,15 @@ class html2txt_parser(SGMLParser):
def handle_data(self, text):
"""handle_data(text) --> appends the pieces to self.pieces
- handles all normal data not between brackets "<>"
- """
+ handles all normal data not between brackets "<>"
+ """
self.pieces.append(text)
def handle_entityref(self, ref):
"""called for each entity reference, e.g. for "©", ref will be
- "copy"
- Reconstruct the original entity reference.
- """
+ "copy"
+ Reconstruct the original entity reference.
+ """
if ref == "amp":
self.pieces.append("&")
@@ -794,7 +794,7 @@ def icontag(name, file_format="png", **attrs):
"""tag HTML pour un icone.
(dans les versions anterieures on utilisait Zope)
Les icones sont des fichiers PNG dans .../static/icons
- Si la taille (width et height) n'est pas spécifiée, lit l'image
+ Si la taille (width et height) n'est pas spécifiée, lit l'image
pour la mesurer (et cache le résultat).
"""
if ("width" not in attrs) or ("height" not in attrs):