forked from ScoDoc/ScoDoc
Ajout image de fond sur PV jury et lettres individuelles de décision.
This commit is contained in:
parent
439571862b
commit
6160d48e9e
@ -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
|
||||
|
@ -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])
|
||||
|
104
sco_pvpdf.py
104
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 : <b>%s</b>"""
|
||||
% (etud["ne"], s, s, decision["autorisations_descr"])
|
||||
params[
|
||||
"autorisations_txt"
|
||||
] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>""" % (
|
||||
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,
|
||||
|
40
sco_utils.py
40
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):
|
||||
<ues note="10" />
|
||||
<ues />
|
||||
</infos>
|
||||
|
||||
|
||||
"""
|
||||
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):
|
||||
|
Loading…
Reference in New Issue
Block a user