1
0
forked from ScoDoc/ScoDoc

Work in progress: new updater and code cleaning

This commit is contained in:
viennet 2020-12-15 08:35:44 +01:00
parent 1c718dffde
commit 415810496f
20 changed files with 460 additions and 170 deletions

View File

@ -93,7 +93,7 @@ ADMISSION_MODIFIABLE_FIELDS = (
def sco_import_format(with_codesemestre=True): def sco_import_format(with_codesemestre=True):
"returns tuples (Attribut, Type, Table, AllowNulls, Description)" "returns tuples (Attribut, Type, Table, AllowNulls, Description)"
r = [] r = []
for l in open(SCO_SRCDIR + "/" + FORMAT_FILE): for l in open(SCO_SRC_DIR + "/" + FORMAT_FILE):
l = l.strip() l = l.strip()
if l and l[0] != "#": if l and l[0] != "#":
fs = l.split(";") fs = l.split(";")

View File

@ -33,6 +33,7 @@
import time, string, glob, re, inspect import time, string, glob, re, inspect
import urllib, urllib2, cgi, xml import urllib, urllib2, cgi, xml
import datetime
try: try:
from cStringIO import StringIO from cStringIO import StringIO
@ -42,13 +43,22 @@ from zipfile import ZipFile
import os.path, glob import os.path, glob
import traceback import traceback
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.MIMEBase import MIMEBase from email.mime.base import MIMEBase
from email.Header import Header from email.header import Header
from email import Encoders
from sco_zope import * from sco_zope import (
ObjectManager,
PropertyManager,
RoleManager,
Item,
Persistent,
Implicit,
ClassSecurityInfo,
DTMLFile,
Globals,
)
# #
try: try:
@ -56,7 +66,21 @@ try:
except: except:
import ZPsycopgDA.DA as ZopeDA # interp.py import ZPsycopgDA.DA as ZopeDA # interp.py
from sco_utils import * from sco_utils import (
SCO_DEFAULT_SQL_USERS_CNX,
SCO_ENCODING,
CUSTOM_HTML_HEADER_CNX,
SCO_USERS_LIST,
SCO_WEBSITE,
SCO_EXC_MAIL,
SCO_DEV_MAIL,
scodoc_html2txt,
get_svn_version,
VERSION,
SCODOC_CFG_DIR,
)
from sco_permissions import ScoView, ScoSuperAdmin
from sco_exceptions import AccessDenied
from notes_log import log from notes_log import log
import sco_find_etud import sco_find_etud
from ZScoUsers import pwdFascistCheck from ZScoUsers import pwdFascistCheck
@ -111,7 +135,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
def _check_users_folder(self, REQUEST=None): def _check_users_folder(self, REQUEST=None):
"""Vérifie UserFolder et le crée s'il le faut""" """Vérifie UserFolder et le crée s'il le faut"""
try: try:
udb = self.UsersDB self.UsersDB
return "<!-- uf ok -->" return "<!-- uf ok -->"
except: except:
e = self._check_admin_perm(REQUEST) e = self._check_admin_perm(REQUEST)
@ -164,11 +188,11 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
pass pass
# add missing getAuthFailedMessage (bug in exUserFolder ?) # add missing getAuthFailedMessage (bug in exUserFolder ?)
try: try:
x = self.getAuthFailedMessage self.getAuthFailedMessage
except: except:
log("adding getAuthFailedMessage to Zope install") log("adding getAuthFailedMessage to Zope install")
parent = self.aq_parent parent = self.aq_parent
from OFS.DTMLMethod import addDTMLMethod from OFS.DTMLMethod import addDTMLMethod # pylint: disable=import-error
addDTMLMethod(parent, "getAuthFailedMessage", file="Identification") addDTMLMethod(parent, "getAuthFailedMessage", file="Identification")
@ -264,7 +288,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
r = [] r = []
for folder in folders: for folder in folders:
try: try:
s = folder.Scolarite _ = folder.Scolarite
r.append(folder) r.append(folder)
except: except:
pass pass
@ -646,8 +670,8 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
**kv **kv
): ):
"Recuperation des exceptions Zope" "Recuperation des exceptions Zope"
sco_exc_mail = SCO_EXC_MAIL sco_exc_mail = SCO_EXC_MAIL # pylint: disable=unused-variable
sco_dev_mail = SCO_DEV_MAIL sco_dev_mail = SCO_DEV_MAIL # pylint: disable=unused-variable
# neat (or should I say dirty ?) hack to get REQUEST # neat (or should I say dirty ?) hack to get REQUEST
# in fact, our caller (probably SimpleItem.py) has the REQUEST variable # in fact, our caller (probably SimpleItem.py) has the REQUEST variable
# that we'd like to use for our logs, but does not pass it as an argument. # that we'd like to use for our logs, but does not pass it as an argument.
@ -714,6 +738,7 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
) )
# display error traceback (? may open a security risk via xss attack ?) # display error traceback (? may open a security risk via xss attack ?)
# log('exc B') # log('exc B')
# pylint: disable=unused-variable
txt_html = self._report_request(REQUEST, fmt="html") txt_html = self._report_request(REQUEST, fmt="html")
H.append( H.append(
"""<h4 class="scodoc">Zope Traceback (à envoyer par mail à <a href="mailto:%(sco_dev_mail)s">%(sco_dev_mail)s</a>)</h4><div style="background-color: rgb(153,153,204); border: 1px;"> """<h4 class="scodoc">Zope Traceback (à envoyer par mail à <a href="mailto:%(sco_dev_mail)s">%(sco_dev_mail)s</a>)</h4><div style="background-color: rgb(153,153,204); border: 1px;">
@ -734,6 +759,7 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
pass pass
# --- Mail: # --- Mail:
# pylint: disable=unused-variable
error_traceback_txt = scodoc_html2txt(error_tb) error_traceback_txt = scodoc_html2txt(error_tb)
txt = ( txt = (
""" """
@ -752,6 +778,7 @@ ErrorType: %(error_type)s
def _report_request(self, REQUEST, fmt="txt"): def _report_request(self, REQUEST, fmt="txt"):
"""string describing current request for bug reports""" """string describing current request for bug reports"""
# pylint: disable=unused-variable
AUTHENTICATED_USER = REQUEST.get("AUTHENTICATED_USER", "") AUTHENTICATED_USER = REQUEST.get("AUTHENTICATED_USER", "")
dt = time.asctime() dt = time.asctime()
URL = REQUEST.get("URL", "") URL = REQUEST.get("URL", "")
@ -887,7 +914,7 @@ subversion: %(svn_version)s
"""Liste de id de departements definis par create_dept.sh """Liste de id de departements definis par create_dept.sh
(fichiers depts/*.cfg) (fichiers depts/*.cfg)
""" """
filenames = glob.glob(SCODOC_VAR_DIR + "/config/depts/*.cfg") filenames = glob.glob(SCODOC_CFG_DIR + "/config/depts/*.cfg")
ids = [os.path.split(os.path.splitext(f)[0])[1] for f in filenames] ids = [os.path.split(os.path.splitext(f)[0])[1] for f in filenames]
return ids return ids

View File

@ -0,0 +1,128 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
#
# Configuration globale de ScoDoc (version juin 2009)
# Ce fichier est copié dans /opt/scodoc/var/scodoc/config
# par les scripts d'installation/mise à jour.
# La plupart des réglages sont stoqués en base de donnée et accessibles via le web
# (pages de paramètres ou préférences).
# Les valeurs indiquées ici sont les valeurs initiales que prendront
# les paramètres lors de la création d'un nouveau département,
# elles ne sont plus utilisées ensuite.
# Nota: il y a aussi des réglages dans sco_utils.py, mais ils nécessitent
# souvent de comprendre le code qui les utilise pour ne pas faire d'erreur: attention.
class CFG:
pass
CONFIG = CFG()
# set to 1 if you want to require INE:
# CONFIG.always_require_ine = 0
# The base URL, use only if you are behind a proxy
# eg "https://scodoc.example.net/ScoDoc"
# CONFIG.ABSOLUTE_URL = ""
# -----------------------------------------------------
# -------------- Documents PDF
# -----------------------------------------------------
# Taille du l'image logo: largeur/hauteur (ne pas oublier le . !!!)
# W/H XXX provisoire: utilisera PIL pour connaitre la taille de l'image
# CONFIG.LOGO_FOOTER_ASPECT = 326 / 96.0
# Taille dans le document en millimetres
# CONFIG.LOGO_FOOTER_HEIGHT = 10
# Proportions logo (donné ici pour IUTV)
# CONFIG.LOGO_HEADER_ASPECT = 549 / 346.0
# Taille verticale dans le document en millimetres
# CONFIG.LOGO_HEADER_HEIGHT = 28
# Pied de page PDF : un format Python, %(xxx)s est remplacé par la variable xxx.
# Les variables définies sont:
# day : Day of the month as a decimal number [01,31]
# month : Month as a decimal number [01,12].
# year : Year without century as a decimal number [00,99].
# Year : Year with century as a decimal number.
# hour : Hour (24-hour clock) as a decimal number [00,23].
# minute: Minute as a decimal number [00,59].
#
# server_url: URL du serveur ScoDoc
# scodoc_name: le nom du logiciel (ScoDoc actuellement, voir VERSION.py)
# CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(month)s/%(year)s à %(hour)sh%(minute)s sur %(server_url)s"
#
# ------------- Calcul bonus modules optionnels (sport, culture...) -------------
#
# CONFIG.compute_bonus = bonus_sport.bonus_iutv
# Mettre "bonus_demo" pour logguer des informations utiles au developpement...
#
# ------------- Capitalisation des UEs -------------
# Deux écoles:
# - règle "DUT": capitalisation des UE obtenues avec moyenne UE >= 10 ET de toutes les UE
# des semestres validés (ADM, ADC, AJ). (conforme à l'arrêté d'août 2005)
#
# - règle "LMD": capitalisation uniquement des UE avec moy. > 10
# Si vrai, capitalise toutes les UE des semestres validés (règle "DUT").
# CONFIG.CAPITALIZE_ALL_UES = True
#
# -----------------------------------------------------
#
# -------------- Personnalisation des pages
#
# -----------------------------------------------------
# Nom (chemin complet) d'un fichier .html à inclure juste après le <body>
# le <body> des pages ScoDoc
# CONFIG.CUSTOM_HTML_HEADER = ""
# Fichier html a inclure en fin des pages (juste avant le </body>)
# CONFIG.CUSTOM_HTML_FOOTER = ""
# Fichier .html à inclure dans la pages connexion/déconnexion (accueil)
# si on veut que ce soit différent (par défaut la même chose)
# CONFIG.CUSTOM_HTML_HEADER_CNX = CONFIG.CUSTOM_HTML_HEADER
# CONFIG.CUSTOM_HTML_FOOTER_CNX = CONFIG.CUSTOM_HTML_FOOTER
# -----------------------------------------------------
# -------------- Noms de Lycées
# -----------------------------------------------------
# Fichier de correspondance codelycee -> noms
# (chemin relatif au repertoire d'install des sources)
# CONFIG.ETABL_FILENAME = "config/etablissements.csv"
# ----------------------------------------------------
# -------------- Divers:
# ----------------------------------------------------
# True for UCAC (étudiants camerounais sans prénoms)
# CONFIG.ALLOW_NULL_PRENOM = False
# Taille max des fichiers archive etudiants (en octets)
# CONFIG.ETUD_MAX_FILE_SIZE = 10 * 1024 * 1024
# Si pas de photo et portail, publie l'url (était vrai jusqu'en oct 2016)
# CONFIG.PUBLISH_PORTAL_PHOTO_URL = False
# Si > 0: longueur minimale requise des nouveaux mots de passe
# (le test cracklib.FascistCheck s'appliquera dans tous les cas)
# CONFIG.MIN_PASSWORD_LENGTH = 0
# Ce dictionnaire est fusionné à celui de sco_codes_parcours
# pour définir les codes jury et explications associées
# CONFIG.CODES_EXPL = {
# # AJ : 'Ajourné (échec)',
# }

View File

@ -118,6 +118,19 @@ if [ $? -ne 0 ]
then then
/opt/zope213/bin/pip install requests /opt/zope213/bin/pip install requests
fi fi
/opt/zope213/bin/python -c "import attrdict" >& /dev/null
if [ $? -ne 0 ]
then
/opt/zope213/bin/pip install attrdict
fi
# Check that local configuration file is installed
LOCAL_CONFIG_FILENAME="/opt/scodoc/var/scodoc/config/scodoc_local.py"
if [ ! -e "$LOCAL_CONFIG_FILENAME" ]
then
cp "$SCODOC_DIR"/config/scodoc_config_tmpl.py "$LOCAL_CONFIG_FILENAME"
chmod 600 "$LOCAL_CONFIG_FILENAME"
fi
# Ensure www-data can duplicate databases (for dumps) # Ensure www-data can duplicate databases (for dumps)
su -c $'psql -c \'alter role "www-data" with CREATEDB;\'' "$POSTGRES_SUPERUSER" su -c $'psql -c \'alter role "www-data" with CREATEDB;\'' "$POSTGRES_SUPERUSER"

View File

@ -2,10 +2,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pdb, os, sys, time, re, inspect import pdb, os, sys, time, re, inspect
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.Header import Header
import traceback import traceback
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.header import Header
# Simple & stupid file logguer, used only to debug # Simple & stupid file logguer, used only to debug
# (logging to SQL is done in scolog) # (logging to SQL is done in scolog)
@ -88,7 +89,7 @@ def retreive_dept():
return "" return ""
try: try:
url = REQUEST.URL url = REQUEST.URL
m = re.match("^.*ScoDoc/(\w+).*$", url) m = re.match(r"^.*ScoDoc/(\w+).*$", url)
return m.group(1) return m.group(1)
except: except:
return "" return ""

View File

@ -50,7 +50,7 @@ DONNEE_MANQUANTE = (
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def get_code_latex_from_modele(fichier): def get_code_latex_from_modele(fichier):
"""Lit le code latex à partir d'un modèle. Renvoie une chaine unicode. """Lit le code latex à partir d'un modèle. Renvoie une chaine unicode.
Le fichier doit contenir le chemin relatif Le fichier doit contenir le chemin relatif
vers le modele : attention pas de vérification du format d'encodage vers le modele : attention pas de vérification du format d'encodage
Le fichier doit donc etre enregistré avec le même codage que ScoDoc (utf-8) Le fichier doit donc etre enregistré avec le même codage que ScoDoc (utf-8)
@ -85,7 +85,7 @@ def get_tags_latex(code_latex):
à la lecture d'un modèle d'avis pe). à la lecture d'un modèle d'avis pe).
Ces tags sont répérés par les balises **, débutant et finissant le tag Ces tags sont répérés par les balises **, débutant et finissant le tag
et sont renvoyés sous la forme d'une liste. et sont renvoyés sous la forme d'une liste.
result: liste de chaines unicode result: liste de chaines unicode
""" """
if code_latex: if code_latex:
@ -144,7 +144,7 @@ def comp_latex_parcourstimeline(etudiant, promo, taille=17):
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def interprete_tag_latex(tag): def interprete_tag_latex(tag):
"""Découpe les tags latex de la forme S1:groupe:dut:min et renvoie si possible """Découpe les tags latex de la forme S1:groupe:dut:min et renvoie si possible
le résultat sous la forme d'un quadruplet. le résultat sous la forme d'un quadruplet.
""" """
infotag = tag.split(":") infotag = tag.split(":")
@ -164,7 +164,7 @@ def get_code_latex_avis_etudiant(
donnees_etudiant, un_avis_latex, annotationPE, footer_latex, prefs donnees_etudiant, un_avis_latex, annotationPE, footer_latex, prefs
): ):
""" """
Renvoie le code latex permettant de générer l'avis d'un étudiant en utilisant ses Renvoie le code latex permettant de générer l'avis d'un étudiant en utilisant ses
donnees_etudiant contenu dans le dictionnaire de synthèse du jury PE et en suivant un donnees_etudiant contenu dans le dictionnaire de synthèse du jury PE et en suivant un
fichier modele donné fichier modele donné
@ -228,8 +228,8 @@ def get_code_latex_avis_etudiant(
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def get_annotation_PE(context, etudid, tag_annotation_pe): def get_annotation_PE(context, etudid, tag_annotation_pe):
"""Renvoie l'annotation PE dans la liste de ces annotations ; """Renvoie l'annotation PE dans la liste de ces annotations ;
Cette annotation est reconnue par la présence d'un tag **PE** Cette annotation est reconnue par la présence d'un tag **PE**
(cf. context.get_preferences -> pe_tag_annotation_avis_latex). (cf. context.get_preferences -> pe_tag_annotation_avis_latex).
Result: chaine unicode Result: chaine unicode
@ -269,8 +269,8 @@ def get_annotation_PE(context, etudid, tag_annotation_pe):
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ): def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ):
"""Extrait du dictionnaire de synthèse du juryPE pour un étudiant donnée, """Extrait du dictionnaire de synthèse du juryPE pour un étudiant donnée,
une valeur indiquée par un champ ; une valeur indiquée par un champ ;
si champ est une liste, renvoie la liste des valeurs extraites. si champ est une liste, renvoie la liste des valeurs extraites.
Result: chaine unicode ou liste de chaines unicode Result: chaine unicode ou liste de chaines unicode
@ -322,7 +322,7 @@ def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ)
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def get_bilanParTag(donnees_etudiant, groupe="groupe"): def get_bilanParTag(donnees_etudiant, groupe="groupe"):
"""Renvoie le code latex d'un tableau récapitulant, pour tous les tags trouvés dans """Renvoie le code latex d'un tableau récapitulant, pour tous les tags trouvés dans
les données étudiants, ses résultats. les données étudiants, ses résultats.
result: chaine unicode result: chaine unicode
""" """
@ -460,11 +460,11 @@ def get_templates_from_distrib(template="avis"):
if template in ["avis", "footer"]: if template in ["avis", "footer"]:
# pas de preference pour le template: utilise fichier du serveur # pas de preference pour le template: utilise fichier du serveur
p = os.path.join(SCO_SRCDIR, pe_local_tmpl) p = os.path.join(SCO_SRC_DIR, pe_local_tmpl)
if os.path.exists(p): if os.path.exists(p):
template_latex = get_code_latex_from_modele(p) template_latex = get_code_latex_from_modele(p)
else: else:
p = os.path.join(SCO_SRCDIR, pe_default_tmpl) p = os.path.join(SCO_SRC_DIR, pe_default_tmpl)
if os.path.exists(p): if os.path.exists(p):
template_latex = get_code_latex_from_modele(p) template_latex = get_code_latex_from_modele(p)
else: else:
@ -474,8 +474,7 @@ def get_templates_from_distrib(template="avis"):
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def table_syntheseAnnotationPE(context, syntheseJury, tag_annotation_pe): def table_syntheseAnnotationPE(context, syntheseJury, tag_annotation_pe):
"""Génère un fichier excel synthétisant les annotations PE telles qu'inscrites dans les fiches de chaque étudiant """Génère un fichier excel synthétisant les annotations PE telles qu'inscrites dans les fiches de chaque étudiant"""
"""
sT = SeqGenTable() # le fichier excel à générer sT = SeqGenTable() # le fichier excel à générer
# Les etudids des étudiants à afficher, triés par ordre alphabétiques de nom+prénom # Les etudids des étudiants à afficher, triés par ordre alphabétiques de nom+prénom

View File

@ -97,7 +97,7 @@ def print_semestres_description(sems, avec_affichage_debug=False):
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def calcul_age(born): def calcul_age(born):
"""Calcule l'age à partir de la date de naissance sous forme d'une chaine de caractère 'jj/mm/aaaa'. """Calcule l'age à partir de la date de naissance sous forme d'une chaine de caractère 'jj/mm/aaaa'.
Aucun test de validité sur le format de la date n'est fait. Aucun test de validité sur le format de la date n'est fait.
""" """
if not isinstance(born, str) or born == "": if not isinstance(born, str) or born == "":
@ -122,8 +122,7 @@ def remove_accents(input_unicode_str):
def escape_for_latex(s): def escape_for_latex(s):
"""Protège les caractères pour inclusion dans du source LaTeX """Protège les caractères pour inclusion dans du source LaTeX"""
"""
if not s: if not s:
return "" return ""
conv = { conv = {
@ -162,8 +161,7 @@ def list_directory_filenames(path):
def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip): def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip):
"""Read pathname server file and add content to zip under path_in_zip """Read pathname server file and add content to zip under path_in_zip"""
"""
rooted_path_in_zip = os.path.join(ziproot, path_in_zip) rooted_path_in_zip = os.path.join(ziproot, path_in_zip)
data = open(pathname).read() data = open(pathname).read()
zipfile.writestr(rooted_path_in_zip, data) zipfile.writestr(rooted_path_in_zip, data)
@ -177,7 +175,7 @@ def add_pe_stuff_to_zip(context, zipfile, ziproot):
Also copy logos Also copy logos
""" """
PE_AUX_DIR = os.path.join(SCO_SRCDIR, "config/doc_poursuites_etudes") PE_AUX_DIR = os.path.join(SCO_SRC_DIR, "config/doc_poursuites_etudes")
distrib_dir = os.path.join(PE_AUX_DIR, "distrib") distrib_dir = os.path.join(PE_AUX_DIR, "distrib")
distrib_pathnames = list_directory_filenames( distrib_pathnames = list_directory_filenames(
distrib_dir distrib_dir

View File

@ -32,10 +32,9 @@
Il suffit d'appeler abs_notify() après chaque ajout d'absence. Il suffit d'appeler abs_notify() après chaque ajout d'absence.
""" """
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.Header import Header from email.header import Header
from email import Encoders
from notesdb import * from notesdb import *
from sco_utils import * from sco_utils import *
@ -47,7 +46,7 @@ import sco_formsemestre
def abs_notify(context, etudid, date): def abs_notify(context, etudid, date):
"""Check if notifications are requested and send them """Check if notifications are requested and send them
Considère le nombre d'absence dans le semestre courant Considère le nombre d'absence dans le semestre courant
(s'il n'y a pas de semestre courant, ne fait rien, (s'il n'y a pas de semestre courant, ne fait rien,
car l'etudiant n'est pas inscrit au moment de l'absence!). car l'etudiant n'est pas inscrit au moment de l'absence!).
""" """
@ -64,8 +63,7 @@ def abs_notify(context, etudid, date):
def do_abs_notify(context, sem, etudid, date, nbabs, nbabsjust): def do_abs_notify(context, sem, etudid, date, nbabs, nbabsjust):
"""Given new counts of absences, check if notifications are requested and send them. """Given new counts of absences, check if notifications are requested and send them."""
"""
# prefs fallback to global pref if sem is None: # prefs fallback to global pref if sem is None:
if sem: if sem:
formsemestre_id = sem["formsemestre_id"] formsemestre_id = sem["formsemestre_id"]
@ -131,8 +129,7 @@ def abs_notify_send(
def abs_notify_get_destinations(context, sem, prefs, etudid, date, nbabs, nbabsjust): def abs_notify_get_destinations(context, sem, prefs, etudid, date, nbabs, nbabsjust):
"""Returns set of destination emails to be notified """Returns set of destination emails to be notified"""
"""
formsemestre_id = sem["formsemestre_id"] formsemestre_id = sem["formsemestre_id"]
destinations = [] # list of email address to notify destinations = [] # list of email address to notify
@ -176,8 +173,8 @@ def abs_notify_is_above_threshold(context, etudid, nbabs, nbabsjust, formsemestr
nbabs: nombre d'absence (de tous types, unité de compte = demi-journée) nbabs: nombre d'absence (de tous types, unité de compte = demi-journée)
nbabsjust: nombre d'absences justifiées nbabsjust: nombre d'absences justifiées
(nbabs > abs_notify_abs_threshold) (nbabs > abs_notify_abs_threshold)
(nbabs - nbabs_last_notified) > abs_notify_abs_increment (nbabs - nbabs_last_notified) > abs_notify_abs_increment
""" """
abs_notify_abs_threshold = context.get_preference( abs_notify_abs_threshold = context.get_preference(
@ -282,8 +279,7 @@ def retreive_current_formsemestre(context, etudid, cur_date):
def mod_with_evals_at_date(context, date_abs, etudid): def mod_with_evals_at_date(context, date_abs, etudid):
"""Liste des moduleimpls avec des evaluations a la date indiquée """Liste des moduleimpls avec des evaluations a la date indiquée"""
"""
req = """SELECT m.* FROM notes_moduleimpl m, notes_evaluation e, notes_moduleimpl_inscription i req = """SELECT m.* FROM notes_moduleimpl m, notes_evaluation e, notes_moduleimpl_inscription i
WHERE m.moduleimpl_id = e.moduleimpl_id AND e.moduleimpl_id = i.moduleimpl_id WHERE m.moduleimpl_id = e.moduleimpl_id AND e.moduleimpl_id = i.moduleimpl_id
AND i.etudid = %(etudid)s AND e.jour = %(date_abs)s""" AND i.etudid = %(etudid)s AND e.jour = %(date_abs)s"""

View File

@ -28,11 +28,11 @@
"""Génération des bulletins de notes """Génération des bulletins de notes
""" """
from email.MIMEMultipart import MIMEMultipart import email
from email.MIMEText import MIMEText from email.mime.multipart import MIMEMultipart
from email.MIMEBase import MIMEBase from email.mime.text import MIMEText
from email.Header import Header from email.mime.base import MIMEBase
from email import Encoders from email.header import Header
import htmlutils, time import htmlutils, time
from reportlab.lib.colors import Color from reportlab.lib.colors import Color
@ -331,7 +331,7 @@ def formsemestre_bulletinetud_dict(
context, ue_status["formsemestre_id"] context, ue_status["formsemestre_id"]
) # > toutes notes ) # > toutes notes
u["modules_capitalized"], junk = _ue_mod_bulletin( u["modules_capitalized"], _ = _ue_mod_bulletin(
context, context,
etudid, etudid,
formsemestre_id, formsemestre_id,
@ -990,7 +990,7 @@ def mail_bulletin(context, formsemestre_id, I, pdfdata, filename, recipient_addr
att = MIMEBase("application", "pdf") att = MIMEBase("application", "pdf")
att.add_header("Content-Disposition", "attachment", filename=filename) att.add_header("Content-Disposition", "attachment", filename=filename)
att.set_payload(pdfdata) att.set_payload(pdfdata)
Encoders.encode_base64(att) email.encoders.encode_base64(att)
msg.attach(att) msg.attach(att)
log("mail bulletin a %s" % msg["To"]) log("mail bulletin a %s" % msg["To"])
context.sendEmail(msg) context.sendEmail(msg)

View File

@ -116,7 +116,8 @@ CODES_EXPL = {
RAT: "En attente d'un rattrapage", RAT: "En attente d'un rattrapage",
DEF: "Défaillant", DEF: "Défaillant",
} }
# Nota: ces explications sont personnalisables via le fichier de config scodoc_config.py # Nota: ces explications sont personnalisables via le fichier
# de config locale /opt/scodoc/var/scodoc/config/scodoc_local.py
# variable: CONFIG.CODES_EXP # variable: CONFIG.CODES_EXP
CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé

109
sco_config.py Normal file
View File

@ -0,0 +1,109 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Configuration de ScoDoc (version 2020)
NE PAS MODIFIER localement ce fichier !
mais éditer /opt/scodoc/var/scodoc/config/scodoc_local.py
"""
from attrdict import AttrDict
import bonus_sport
CONFIG = AttrDict()
# set to 1 if you want to require INE:
CONFIG.always_require_ine = 0
# The base URL, use only if you are behind a proxy
# eg "https://scodoc.example.net/ScoDoc"
CONFIG.ABSOLUTE_URL = ""
# -----------------------------------------------------
# -------------- Documents PDF
# -----------------------------------------------------
# Taille du l'image logo: largeur/hauteur (ne pas oublier le . !!!)
# W/H XXX provisoire: utilisera PIL pour connaitre la taille de l'image
CONFIG.LOGO_FOOTER_ASPECT = 326 / 96.0
# Taille dans le document en millimetres
CONFIG.LOGO_FOOTER_HEIGHT = 10
# Proportions logo (donné ici pour IUTV)
CONFIG.LOGO_HEADER_ASPECT = 549 / 346.0
# Taille verticale dans le document en millimetres
CONFIG.LOGO_HEADER_HEIGHT = 28
# Pied de page PDF : un format Python, %(xxx)s est remplacé par la variable xxx.
# Les variables définies sont:
# day : Day of the month as a decimal number [01,31]
# month : Month as a decimal number [01,12].
# year : Year without century as a decimal number [00,99].
# Year : Year with century as a decimal number.
# hour : Hour (24-hour clock) as a decimal number [00,23].
# minute: Minute as a decimal number [00,59].
#
# server_url: URL du serveur ScoDoc
# scodoc_name: le nom du logiciel (ScoDoc actuellement, voir VERSION.py)
CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(month)s/%(year)s à %(hour)sh%(minute)s sur %(server_url)s"
#
# ------------- Calcul bonus modules optionnels (sport, culture...) -------------
#
CONFIG.compute_bonus = bonus_sport.bonus_iutv
# Mettre "bonus_demo" pour logguer des informations utiles au developpement...
# ------------- Capitalisation des UEs -------------
# Deux écoles:
# - règle "DUT": capitalisation des UE obtenues avec moyenne UE >= 10 ET de toutes les UE
# des semestres validés (ADM, ADC, AJ). (conforme à l'arrêté d'août 2005)
#
# - règle "LMD": capitalisation uniquement des UE avec moy. > 10
# Si vrai, capitalise toutes les UE des semestres validés (règle "DUT").
# CONFIG.CAPITALIZE_ALL_UES = True
# -----------------------------------------------------
# -------------- Personnalisation des pages
# -----------------------------------------------------
# Nom (chemin complet) d'un fichier .html à inclure juste après le <body>
# le <body> des pages ScoDoc
CONFIG.CUSTOM_HTML_HEADER = ""
# Fichier html a inclure en fin des pages (juste avant le </body>)
CONFIG.CUSTOM_HTML_FOOTER = ""
# Fichier .html à inclure dans la pages connexion/déconnexion (accueil)
# si on veut que ce soit différent (par défaut la même chose)
CONFIG.CUSTOM_HTML_HEADER_CNX = CONFIG.CUSTOM_HTML_HEADER
CONFIG.CUSTOM_HTML_FOOTER_CNX = CONFIG.CUSTOM_HTML_FOOTER
# -----------------------------------------------------
# -------------- Noms de Lycées
# -----------------------------------------------------
# Fichier de correspondance codelycee -> noms
# (chemin relatif au repertoire d'install des sources)
CONFIG.ETABL_FILENAME = "config/etablissements.csv"
# ----------------------------------------------------
# -------------- Divers:
# ----------------------------------------------------
# True for UCAC (étudiants camerounais sans prénoms)
CONFIG.ALLOW_NULL_PRENOM = False
# Taille max des fichiers archive etudiants (en octets)
# CONFIG.ETUD_MAX_FILE_SIZE = 10 * 1024 * 1024
# Si pas de photo et portail, publie l'url (était vrai jusqu'en oct 2016)
CONFIG.PUBLISH_PORTAL_PHOTO_URL = False
# Si > 0: longueur minimale requise des nouveaux mots de passe
# (le test cracklib.FascistCheck s'appliquera dans tous les cas)
CONFIG.MIN_PASSWORD_LENGTH = 0
# Ce dictionnaire est fusionné à celui de sco_codes_parcours
# pour définir les codes jury et explications associées
CONFIG.CODES_EXPL = {
# AJ : 'Ajourné (échec)',
}

44
sco_config_load.py Normal file
View File

@ -0,0 +1,44 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Chargement de la configuration locale
"""
import os
import sys
import sco_utils
from sco_utils import log, SCODOC_CFG_DIR
import sco_config
# scodoc_local defines a CONFIG object
# here we check if there is a local config file
def load_local_configuration():
"""Load local configuration file (if exists)
and merge it with CONFIG.
"""
# this path should be synced with upgrade.sh
LOCAL_CONFIG_FILENAME = os.path.join(SCODOC_CFG_DIR, "scodoc_local.py")
LOCAL_CONFIG = None
if os.path.exists(LOCAL_CONFIG_FILENAME):
if not SCODOC_CFG_DIR in sys.path:
sys.path.insert(1, SCODOC_CFG_DIR)
try:
from scodoc_local import CONFIG as LOCAL_CONFIG
log("imported %s" % LOCAL_CONFIG_FILENAME)
except ImportError:
log("Error: can't import %s" % LOCAL_CONFIG_FILENAME)
del sys.path[1]
if LOCAL_CONFIG is None:
return
# Now merges local config in our CONFIG
for x in [x for x in dir(LOCAL_CONFIG) if x[0] != "_"]:
v = getattr(LOCAL_CONFIG, x)
if not v in sco_config.CONFIG:
log("Warning: local config setting unused parameter %s (skipped)" % x)
else:
if v != sco_config.CONFIG[x]:
log("Setting parameter %s from %s" % (x, LOCAL_CONFIG_FILENAME))
sco_config.CONFIG[x] = v

View File

@ -50,11 +50,10 @@ pg_dump SCORT | psql ANORT
import fcntl import fcntl
import subprocess import subprocess
import requests import requests
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.MIMEBase import MIMEBase from email.mime.base import MIMEBase
from email.Header import Header from email.header import Header
from email import Encoders
from notesdb import * from notesdb import *
from sco_utils import * from sco_utils import *
@ -64,8 +63,7 @@ SCO_DUMP_LOCK = "/tmp/scodump.lock"
def sco_dump_and_send_db(context, REQUEST=None): def sco_dump_and_send_db(context, REQUEST=None):
"""Dump base de données du département courant et l'envoie anonymisée pour debug """Dump base de données du département courant et l'envoie anonymisée pour debug"""
"""
H = [context.sco_header(REQUEST, page_title="Assistance technique")] H = [context.sco_header(REQUEST, page_title="Assistance technique")]
# get currect (dept) DB name: # get currect (dept) DB name:
cursor = SimpleQuery(context, "SELECT current_database()", {}) cursor = SimpleQuery(context, "SELECT current_database()", {})
@ -150,9 +148,8 @@ def _duplicate_db(db_name, ano_db_name):
def _anonymize_db(ano_db_name): def _anonymize_db(ano_db_name):
"""Anonymize a departement database """Anonymize a departement database"""
""" cmd = os.path.join(SCO_TOOLS_DIR, "anonymize_db.py")
cmd = os.path.join(SCO_CONFIG_DIR, "anonymize_db.py")
log("_anonymize_db: {}".format(cmd)) log("_anonymize_db: {}".format(cmd))
try: try:
out = subprocess.check_output([cmd, ano_db_name]) out = subprocess.check_output([cmd, ano_db_name])
@ -171,8 +168,7 @@ def _get_scodoc_serial(context):
def _send_db(context, REQUEST, ano_db_name): def _send_db(context, REQUEST, ano_db_name):
"""Dump this (anonymized) database and send it to tech support """Dump this (anonymized) database and send it to tech support"""
"""
log("dumping anonymized database {}".format(ano_db_name)) log("dumping anonymized database {}".format(ano_db_name))
try: try:
data = subprocess.check_output("pg_dump {} | gzip".format(ano_db_name), shell=1) data = subprocess.check_output("pg_dump {} | gzip".format(ano_db_name), shell=1)
@ -195,7 +191,7 @@ def _send_db(context, REQUEST, ano_db_name):
"nomcomplet" "nomcomplet"
], ],
"sco_version": SCOVERSION, "sco_version": SCOVERSION,
"sco_subversion": get_svn_version(SCO_CONFIG_DIR), "sco_subversion": get_svn_version(SCO_TOOLS_DIR),
}, },
) )
return r return r

View File

@ -33,11 +33,9 @@ from cStringIO import StringIO
import datetime, re import datetime, re
import time import time
from stripogram import html2text, html2safehtml from stripogram import html2text, html2safehtml
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.Header import Header from email.header import Header
from email import Encoders
from notesdb import * from notesdb import *
from notes_log import log from notes_log import log
@ -252,8 +250,7 @@ def scolar_news_summary_rss(context, title, sco_url, n=5):
def _send_news_by_mail(context, n): def _send_news_by_mail(context, n):
"""Notify by email """Notify by email"""
"""
infos = _get_formsemestre_infos_from_news(context, n) infos = _get_formsemestre_infos_from_news(context, n)
formsemestre_id = infos.get("formsemestre_id", None) formsemestre_id = infos.get("formsemestre_id", None)
prefs = context.get_preferences(formsemestre_id=formsemestre_id) prefs = context.get_preferences(formsemestre_id=formsemestre_id)

View File

@ -53,7 +53,7 @@ from PIL import Image as PILImage
from cStringIO import StringIO from cStringIO import StringIO
import glob import glob
from sco_utils import CONFIG, SCO_SRCDIR from sco_utils import CONFIG, SCO_SRC_DIR
from notes_log import log from notes_log import log
import scolars import scolars
@ -62,7 +62,7 @@ from scolog import logdb
# Full paths on server's filesystem. Something like "/opt/scodoc/var/scodoc/photos" # Full paths on server's filesystem. Something like "/opt/scodoc/var/scodoc/photos"
PHOTO_DIR = os.path.join(os.environ["INSTANCE_HOME"], "var", "scodoc", "photos") PHOTO_DIR = os.path.join(os.environ["INSTANCE_HOME"], "var", "scodoc", "photos")
ICONS_DIR = os.path.join(SCO_SRCDIR, "static", "icons") ICONS_DIR = os.path.join(SCO_SRC_DIR, "static", "icons")
UNKNOWN_IMAGE_PATH = os.path.join(ICONS_DIR, "unknown.jpg") UNKNOWN_IMAGE_PATH = os.path.join(ICONS_DIR, "unknown.jpg")
UNKNOWN_IMAGE_URL = "get_photo_image?etudid=" # with empty etudid => unknown face image UNKNOWN_IMAGE_URL = "get_photo_image?etudid=" # with empty etudid => unknown face image
IMAGE_EXT = ".jpg" IMAGE_EXT = ".jpg"

View File

@ -37,9 +37,9 @@ import datetime
import sco_utils import sco_utils
from sco_utils import ScoEtudInscrit, log, ScoValueError, DictDefault from sco_utils import ScoEtudInscrit, log, ScoValueError, DictDefault
from sco_utils import SCO_TMPDIR, SCO_ENCODING from sco_utils import SCO_TMP_DIR, SCO_ENCODING
SCO_CACHE_ETAPE_FILENAME = os.path.join(SCO_TMPDIR, "last_etapes.xml") SCO_CACHE_ETAPE_FILENAME = os.path.join(SCO_TMP_DIR, "last_etapes.xml")
def has_portal(context): def has_portal(context):

View File

@ -49,7 +49,7 @@ Chaque parametre est défini dans la base de données SQL par:
Au niveau du code interface, on défini pour chaque préférence: Au niveau du code interface, on défini pour chaque préférence:
- name (clé) - name (clé)
- title : titre en français - title : titre en français
- initvalue : valeur initiale, chargée depuis config/scodoc_config.py - initvalue : valeur initiale
- explanation: explication en français - explanation: explication en français
- size: longueur du chap texte - size: longueur du chap texte
- input_type: textarea,separator,... type de widget TrivialFormulator a utiliser - input_type: textarea,separator,... type de widget TrivialFormulator a utiliser
@ -90,13 +90,6 @@ sinon, elle ne concerne que le semestre indiqué.
- editer les preferences d'un semestre: - editer les preferences d'un semestre:
sem_preferences(context,formsemestre_id).edit() sem_preferences(context,formsemestre_id).edit()
* Valeurs par défaut:
On a deux valeurs par défaut possibles:
- via le fichier scodoc_config.py, qui peut être modifié localement.
- si rien dans scodoc_config.py, la valeur définie par
sco_preferences.py est utilisée (ne pas modifier ce fichier).
* Implémentation: sco_preferences.py * Implémentation: sco_preferences.py
PREF_CATEGORIES : définition des catégories de préférences (pour PREF_CATEGORIES : définition des catégories de préférences (pour

View File

@ -186,53 +186,84 @@ def get_mention(moy):
return NOTES_MENTIONS_LABS[bisect.bisect_right(NOTES_MENTIONS_TH, moy)] return NOTES_MENTIONS_LABS[bisect.bisect_right(NOTES_MENTIONS_TH, moy)]
class DictDefault(dict): # obsolete, use collections.defaultdict
"""A dictionnary with default value for all keys
Each time a non existent key is requested, it is added to the dict.
(used in python 2.4, can't use new __missing__ method)
"""
defaultvalue = 0
def __init__(self, defaultvalue=0, kv_dict={}):
dict.__init__(self)
self.defaultvalue = defaultvalue
self.update(kv_dict)
def __getitem__(self, k):
if self.has_key(k):
return self.get(k)
value = copy.copy(self.defaultvalue)
self[k] = value
return value
class WrapDict:
"""Wrap a dict so that getitem returns '' when values are None"""
def __init__(self, adict, NoneValue=""):
self.dict = adict
self.NoneValue = NoneValue
def __getitem__(self, key):
value = self.dict[key]
if value is None:
return self.NoneValue
else:
return value
def group_by_key(d, key):
g = DictDefault(defaultvalue=[])
for e in d:
g[e[key]].append(e)
return g
# ----- Global lock for critical sections (except notes_tables caches) # ----- Global lock for critical sections (except notes_tables caches)
GSL = thread.allocate_lock() # Global ScoDoc Lock 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")
# ----- Repertoire "config" modifiable
# /opt/scodoc/var/scodoc/config
SCODOC_CFG_DIR = os.path.join(SCODOC_VAR_DIR, "config")
# ----- Version information # ----- Version information
SCODOC_VERSION_DIR = os.path.join(SCODOC_VAR_DIR, "config", "version") SCODOC_VERSION_DIR = os.path.join(SCODOC_CFG_DIR, "version")
# ----- Repertoire tmp # ----- Repertoire tmp
SCO_TMPDIR = os.path.join(SCODOC_VAR_DIR, "tmp") SCO_TMP_DIR = os.path.join(SCODOC_VAR_DIR, "tmp")
if not os.path.exists(SCO_TMPDIR): if not os.path.exists(SCO_TMP_DIR):
os.mkdir(SCO_TMPDIR, 0o755) os.mkdir(SCO_TMP_DIR, 0o755)
# ----- Les logos: /opt/scodoc/var/scodoc/config/logos # ----- Les logos: /opt/scodoc/var/scodoc/config/logos
SCODOC_LOGOS_DIR = os.path.join(SCODOC_VAR_DIR, "config", "logos") SCODOC_LOGOS_DIR = os.path.join(SCODOC_CFG_DIR, "logos")
# ----- Repertoire "config" (devrait s'appeler "tools"...) # Dans les sources:
SCO_CONFIG_DIR = os.path.join( SCO_SRC_DIR = os.path.join(os.environ["INSTANCE_HOME"], "Products", "ScoDoc")
os.environ["INSTANCE_HOME"], "Products", "ScoDoc", "config" # - Les outils distribués
) SCO_TOOLS_DIR = os.path.join(SCO_SRC_DIR, "config")
# ----- Lecture du fichier de configuration # ----- Lecture du fichier de configuration
SCO_SRCDIR = os.path.split(VERSION.__file__)[0] import sco_config
if SCO_SRCDIR: import sco_config_load
SCO_SRCDIR += "/"
else:
SCO_SRCDIR = "/opt/scodoc/Products/ScoDoc/" # debug mode
CONFIG = None
try:
_config_filename = SCO_SRCDIR + "config/scodoc_config.py"
_config_text = open(_config_filename).read()
except:
sys.stderr.write("sco_utils: cannot open configuration file %s" % _config_filename)
raise
try:
exec(_config_text)
except:
sys.stderr.write("sco_utils: error in configuration file %s" % _config_filename)
raise
sco_config_load.load_local_configuration()
CONFIG = sco_config.CONFIG
if hasattr(CONFIG, "CODES_EXPL"): if hasattr(CONFIG, "CODES_EXPL"):
CODES_EXPL.update( CODES_EXPL.update(
CONFIG.CODES_EXPL CONFIG.CODES_EXPL
) # permet de customiser les explications de codes ) # permet de customiser les explications de codes
if CONFIG.CUSTOM_HTML_HEADER: if CONFIG.CUSTOM_HTML_HEADER:
CUSTOM_HTML_HEADER = open(CONFIG.CUSTOM_HTML_HEADER).read() CUSTOM_HTML_HEADER = open(CONFIG.CUSTOM_HTML_HEADER).read()
else: else:
@ -297,50 +328,6 @@ JSON_MIMETYPE = "application/json"
LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "png") # remind that PIL does not read pdf 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
Each time a non existent key is requested, it is added to the dict.
(used in python 2.4, can't use new __missing__ method)
"""
defaultvalue = 0
def __init__(self, defaultvalue=0, kv_dict={}):
dict.__init__(self)
self.defaultvalue = defaultvalue
self.update(kv_dict)
def __getitem__(self, k):
if self.has_key(k):
return self.get(k)
value = copy.copy(self.defaultvalue)
self[k] = value
return value
class WrapDict:
"""Wrap a dict so that getitem returns '' when values are None"""
def __init__(self, adict, NoneValue=""):
self.dict = adict
self.NoneValue = NoneValue
def __getitem__(self, key):
value = self.dict[key]
if value is None:
return self.NoneValue
else:
return value
def group_by_key(d, key):
g = DictDefault(defaultvalue=[])
for e in d:
g[e[key]].append(e)
return g
# Admissions des étudiants # Admissions des étudiants
# Différents types de voies d'admission: # Différents types de voies d'admission:
# (stocké en texte libre dans la base, mais saisie par menus pour harmoniser) # (stocké en texte libre dans la base, mais saisie par menus pour harmoniser)
@ -791,7 +778,7 @@ def icontag(name, file_format="png", **attrs):
""" """
if ("width" not in attrs) or ("height" not in attrs): if ("width" not in attrs) or ("height" not in attrs):
if name not in ICONSIZES: if name not in ICONSIZES:
img_file = SCO_SRCDIR + "/static/icons/%s.%s" % (name, file_format) img_file = SCO_SRC_DIR + "/static/icons/%s.%s" % (name, file_format)
im = PILImage.open(img_file) im = PILImage.open(img_file)
width, height = im.size[0], im.size[1] width, height = im.size[0], im.size[1]
ICONSIZES[name] = (width, height) # cache ICONSIZES[name] = (width, height) # cache

View File

@ -28,6 +28,8 @@
"""Imports et configuration des composants Zope """Imports et configuration des composants Zope
""" """
# Allows code linting on platforms without Zope:
# pylint: disable=import-error
from OFS.SimpleItem import Item # Basic zope object from OFS.SimpleItem import Item # Basic zope object
from OFS.PropertyManager import PropertyManager # provide the 'Properties' tab with the from OFS.PropertyManager import PropertyManager # provide the 'Properties' tab with the

View File

@ -42,11 +42,10 @@ import locale
locale.setlocale(locale.LC_ALL, ("en_US", SCO_ENCODING)) locale.setlocale(locale.LC_ALL, ("en_US", SCO_ENCODING))
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.MIMEBase import MIMEBase from email.header import Header
from email.Header import Header from email.mime.base import MIMEBase
from email import Encoders
abbrvmonthsnames = [ abbrvmonthsnames = [
"Jan ", "Jan ",
@ -713,7 +712,7 @@ appreciations_edit = _appreciationsEditor.edit
# -------- Noms des Lycées à partir du code # -------- Noms des Lycées à partir du code
def read_etablissements(): def read_etablissements():
filename = SCO_SRCDIR + "/" + CONFIG.ETABL_FILENAME filename = SCO_SRC_DIR + "/" + CONFIG.ETABL_FILENAME
log("reading %s" % filename) log("reading %s" % filename)
f = open(filename) f = open(filename)
L = [x[:-1].split(";") for x in f] L = [x[:-1].split(";") for x in f]