Merge branch 'master' into code_apogee_multiples

This commit is contained in:
Jean-Marie Place 2021-06-24 20:24:30 +02:00
commit 891b87185e
194 changed files with 13012 additions and 5432 deletions

View File

@ -28,10 +28,16 @@
""" Importation des etudiants à partir de fichiers CSV
"""
import os, sys, time, pdb
import os
import sys
import time
import pdb
import collections
import types
import re
from sco_utils import *
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
import scolars
import sco_formsemestre
@ -42,6 +48,15 @@ import sco_news
from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC
from sco_formsemestre_inscriptions import do_formsemestre_inscription_with_modules
from gen_tables import GenTable
from sco_exceptions import (
AccessDenied,
FormatError,
ScoException,
ScoValueError,
ScoInvalidDateError,
ScoLockedFormError,
ScoGenError,
)
# format description (relative to Product directory))
FORMAT_FILE = "misc/format_import_etudiants.txt"
@ -93,7 +108,7 @@ ADMISSION_MODIFIABLE_FIELDS = (
def sco_import_format(with_codesemestre=True):
"returns tuples (Attribut, Type, Table, AllowNulls, Description)"
r = []
for l in open(SCO_SRCDIR + "/" + FORMAT_FILE):
for l in open(scu.SCO_SRCDIR + "/" + FORMAT_FILE):
l = l.strip()
if l and l[0] != "#":
fs = l.split(";")
@ -152,10 +167,10 @@ def sco_import_generate_excel_sample(
titles = []
titlesStyles = []
for l in fmt:
name = strlower(l[0])
name = scu.strlower(l[0])
if (not with_codesemestre) and name == "codesemestre":
continue # pas de colonne codesemestre
if only_tables is not None and strlower(l[2]) not in only_tables:
if only_tables is not None and scu.strlower(l[2]) not in only_tables:
continue # table non demandée
if name in exclude_cols:
continue # colonne exclue
@ -192,7 +207,7 @@ def sco_import_generate_excel_sample(
)
l.append(etud["partitionsgroupes"])
else:
key = strlower(field).split()[0]
key = scu.strlower(field).split()[0]
l.append(etud.get(key, ""))
lines.append(l)
else:
@ -224,7 +239,7 @@ def students_import_excel(
if formsemestre_id:
dest = "formsemestre_status?formsemestre_id=%s" % formsemestre_id
else:
dest = REQUEST.URL1
dest = context.NotesURL()
H = [context.sco_header(REQUEST, page_title="Import etudiants")]
H.append("<ul>")
for d in diag:
@ -249,7 +264,7 @@ def scolars_import_excel_file(
"""
log("scolars_import_excel_file: formsemestre_id=%s" % formsemestre_id)
cnx = context.GetDBConnexion(autocommit=False)
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
annee_courante = time.localtime()[0]
always_require_ine = context.get_preference("always_require_ine")
exceldata = datafile.read()
@ -265,7 +280,7 @@ def scolars_import_excel_file(
titles = {}
fmt = sco_import_format()
for l in fmt:
tit = strlower(l[0]).split()[0] # titles in lowercase, and take 1st word
tit = scu.strlower(l[0]).split()[0] # titles in lowercase, and take 1st word
if (
(not formsemestre_id) or (tit != "codesemestre")
) and tit not in exclude_cols:
@ -274,7 +289,7 @@ def scolars_import_excel_file(
# log("titles=%s" % titles)
# remove quotes, downcase and keep only 1st word
try:
fs = [strlower(stripquotes(s)).split()[0] for s in data[0]]
fs = [scu.strlower(scu.stripquotes(s)).split()[0] for s in data[0]]
except:
raise ScoValueError("Titres de colonnes invalides (ou vides ?)")
# log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
@ -351,22 +366,22 @@ def scolars_import_excel_file(
% (val, linenum, titleslist[i])
)
# xxx Ad-hoc checks (should be in format description)
if strlower(titleslist[i]) == "sexe":
if scu.strlower(titleslist[i]) == "sexe":
try:
val = scolars.normalize_sexe(val)
val = scolars.input_civilite(val)
except:
raise ScoValueError(
"valeur invalide pour 'SEXE' (doit etre 'M' ou 'MME' ou 'H' ou 'F', pas '%s') ligne %d, colonne %s"
"valeur invalide pour 'SEXE' (doit etre 'M', 'F', ou 'MME', 'H', 'X' ou vide, mais pas '%s') ligne %d, colonne %s"
% (val, linenum, titleslist[i])
)
# Excel date conversion:
if strlower(titleslist[i]) == "date_naissance":
if scu.strlower(titleslist[i]) == "date_naissance":
if val:
if re.match("^[0-9]*\.?[0-9]*$", str(val)):
if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
val = sco_excel.xldate_as_datetime(float(val))
# INE
if (
strlower(titleslist[i]) == "code_ine"
scu.strlower(titleslist[i]) == "code_ine"
and always_require_ine
and not val
):
@ -423,7 +438,7 @@ def scolars_import_excel_file(
log("scolars_import_excel_file: aborting transaction !")
# Nota: db transaction is sometimes partly commited...
# here we try to remove all created students
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
for etudid in created_etudids:
log("scolars_import_excel_file: deleting etudid=%s" % etudid)
cursor.execute(
@ -500,11 +515,11 @@ def _import_one_student(
# Admissions
args["etudid"] = etudid
args["annee"] = annee_courante
adm_id = scolars.admission_create(cnx, args)
_ = scolars.admission_create(cnx, args)
# Adresse
args["typeadresse"] = "domicile"
args["description"] = "(infos admission)"
adresse_id = scolars.adresse_create(cnx, args)
_ = scolars.adresse_create(cnx, args)
# Inscription au semestre
args["etat"] = "I" # etat insc. semestre
if formsemestre_id:
@ -689,7 +704,7 @@ _ADM_PATTERN = re.compile(r"[\W]+", re.UNICODE) # supprime tout sauf alphanum
def adm_normalize_string(s): # normalize unicode title
return suppression_diacritics(_ADM_PATTERN.sub("", s.strip().lower())).replace(
return scu.suppression_diacritics(_ADM_PATTERN.sub("", s.strip().lower())).replace(
"_", ""
)
@ -729,19 +744,19 @@ def adm_get_fields(titles, formsemestre_id):
def adm_convert_text(v):
if type(v) == FloatType:
if type(v) == types.FloatType:
return "{:g}".format(v) # evite "1.0"
return v
def adm_convert_int(v):
if type(v) != IntType and not v:
if type(v) != types.IntType and not v:
return None
return int(float(v)) # accept "10.0"
def adm_convert_real(v):
if type(v) != FloatType and not v:
if type(v) != types.FloatType and not v:
return None
return float(v)

View File

@ -9,7 +9,7 @@
v 1.2
"""
from types import *
from types import BooleanType, StringType
def TrivialFormulator(
@ -368,9 +368,6 @@ class TF:
idx = 0
for idx in range(len(self.formdescription)):
(field, descr) = self.formdescription[idx]
nextitemname = None
if idx < len(self.formdescription) - 2:
nextitemname = self.formdescription[idx + 1][0]
if descr.get("readonly", False):
R.append(self._ReadOnlyElement(field, descr))
continue
@ -682,7 +679,6 @@ class TF:
"Generate HTML for an element, read-only"
R = []
title = descr.get("title", field.capitalize())
withcheckbox = descr.get("withcheckbox", False)
input_type = descr.get("input_type", "text")
klass = descr.get("cssclass", "")
klass = " " + klass
@ -764,6 +760,6 @@ def tf_error_message(msg):
if type(msg) == StringType:
msg = [msg]
return (
'<ul class="tf-msg"><li class="tf-msg">%s</li></ul>'
'<ul class="tf-msg"><li id="errorMessage" class="tf-msg">%s</li></ul>'
% '</li><li class="tf-msg">'.join(msg)
)

View File

@ -1,15 +1,23 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "7.20a"
SCOVERSION = "7.25m"
SCONAME = "ScoDoc"
SCONEWS = """
<h4>Année 2021</h4>
<ul>
<li>Version mobile (en test)</li>
<li>Évaluations de type "deuxième session"</li>
<li>Gestion du genre neutre (pas d'affichage de la civilité)</li>
<li>Diverses corrections (PV de jurys, ...)</li>
<li>Modernisation du code Python</li>
</ul>
<h4>Année 2020</h4>
<ul>
<li>Corrections d'erreurs, améliorations saise absences< et affichage bulletins</li>
<li>Nouveau site <a href="https://scodoc.org">scodoc.org</a> pour la documentation.</li>
<li>Corrections d'erreurs, améliorations saisie absences et affichage bulletins</li>
<li>Nouveau site <a href="https://scodoc.org">scodoc.org</a> pour la documentation</li>
<li>Enregistrement de semestres extérieurs</li>
<li>Améliorations PV de Jury</li>
<li>Contributions J.-M. Place: aide au diagnostic problèmes export Apogée

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

679
ZNotes.py

File diff suppressed because it is too large Load Diff

View File

@ -31,35 +31,59 @@
Chaque departement est géré par un ZScolar sous ZScoDoc.
"""
import time, string, glob, re, inspect
import urllib, urllib2, cgi, xml
import time
import datetime
import string
import glob
import re
import inspect
import urllib
import urllib2
import cgi
import xml
try:
from cStringIO import StringIO
except:
from StringIO import StringIO
from zipfile import ZipFile
import os.path, glob
import os.path
import traceback
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email.Header import Header
from email import Encoders
from email.MIMEMultipart import ( # pylint: disable=no-name-in-module,import-error
MIMEMultipart,
)
from email.MIMEText import MIMEText # pylint: disable=no-name-in-module,import-error
from email.MIMEBase import MIMEBase # pylint: disable=no-name-in-module,import-error
from email.Header import Header # pylint: disable=no-name-in-module,import-error
from email import Encoders # pylint: disable=no-name-in-module,import-error
from sco_zope import *
from sco_zope import * # pylint: disable=unused-wildcard-import
#
try:
import Products.ZPsycopgDA.DA as ZopeDA
except:
import ZPsycopgDA.DA as ZopeDA # interp.py
from sco_utils import *
import sco_utils as scu
import VERSION
from notes_log import log
import sco_find_etud
from ZScoUsers import pwdFascistCheck
import sco_users
from sco_permissions import (
ScoView,
ScoEnsView,
ScoImplement,
ScoChangeFormation,
ScoObservateur,
ScoEtudInscrit,
ScoEtudChangeGroups,
ScoEtudChangeAdr,
ScoEtudSupprAnnotations,
ScoEditAllEvals,
ScoEditAllNotes,
ScoEditFormationTags,
ScoEditApo,
ScoSuperAdmin,
)
from sco_exceptions import ScoValueError, ScoLockedFormError, ScoGenError, AccessDenied
class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit):
@ -77,13 +101,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
manage_options = (
({"label": "Contents", "action": "manage_main"},)
+ PropertyManager.manage_options # add the 'Properties' tab
+ (
# this line is kept as an example with the files :
# dtml/manage_editZScolarForm.dtml
# html/ZScolar-edit.stx
# {'label': 'Properties', 'action': 'manage_editForm',},
{"label": "View", "action": "index_html"},
)
+ ({"label": "View", "action": "index_html"},)
+ Item.manage_options # add the 'Undo' & 'Owner' tab
+ RoleManager.manage_options # add the 'Security' tab
)
@ -111,7 +129,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
def _check_users_folder(self, REQUEST=None):
"""Vérifie UserFolder et le crée s'il le faut"""
try:
udb = self.UsersDB
_ = self.UsersDB
return "<!-- uf ok -->"
except:
e = self._check_admin_perm(REQUEST)
@ -164,11 +182,11 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
pass
# add missing getAuthFailedMessage (bug in exUserFolder ?)
try:
x = self.getAuthFailedMessage
_ = self.getAuthFailedMessage
except:
log("adding getAuthFailedMessage to Zope install")
parent = self.aq_parent
from OFS.DTMLMethod import addDTMLMethod
from OFS.DTMLMethod import addDTMLMethod # pylint: disable=import-error
addDTMLMethod(parent, "getAuthFailedMessage", file="Identification")
@ -187,7 +205,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
da = ZopeDA.Connection(
oid,
"Cnx bd utilisateurs",
SCO_DEFAULT_SQL_USERS_CNX,
scu.SCO_DEFAULT_SQL_USERS_CNX,
False,
check=1,
tilevel=2,
@ -207,7 +225,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
log("trying to change admin password")
# 1-- check strong password
if pwdFascistCheck(password) != None:
if not sco_users.is_valid_password(password):
log("refusing weak password")
return REQUEST.RESPONSE.redirect(
"change_admin_user_form?message=Mot%20de%20passe%20trop%20simple,%20recommencez"
@ -255,16 +273,32 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
security.declareProtected("View", "list_depts")
def list_depts(self, REQUEST=None):
"""List departments folders
(returns a list of Zope folders containing a ZScolar instance)
def list_depts(self, viewable=True, format=None, REQUEST=None):
"""List departments
If viewable, list only depts viewable the current user.
"""
authuser = REQUEST.AUTHENTICATED_USER
viewable = int(viewable)
return scu.sendResult(
REQUEST,
[
d.id
for d in self._list_depts()
if (not viewable) or authuser.has_permission(ScoView, d.Scolarite)
],
name="depts",
format=format,
)
def _list_depts(self, REQUEST=None): # not published
# List departments folders
# (returns a list of Zope folders containing a ZScolar instance)
folders = self.objectValues("Folder")
# select folders with Scolarite object:
r = []
for folder in folders:
try:
s = folder.Scolarite
_ = folder.Scolarite
r.append(folder)
except:
pass
@ -309,7 +343,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
if e:
return e
if not force and DeptId not in [x.id for x in self.list_depts()]:
if not force and DeptId not in [x.id for x in self._list_depts()]:
raise ValueError("nom de departement invalide")
self.manage_delObjects(ids=[DeptId])
@ -317,8 +351,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
return (
"<p>Département "
+ DeptId
+ """ supprimé du serveur web (la base de données n'est pas affectée)!</p><p><a href="%s">Continuer</a></p>"""
% REQUEST.URL1
+ """ supprimé du serveur web (la base de données n'est pas affectée)!</p><p><a href="/ScoDoc">Continuer</a></p>"""
)
_top_level_css = """
@ -362,10 +395,10 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
def scodoc_top_html_header(self, REQUEST, page_title="ScoDoc"):
H = [
self._html_begin
% {"page_title": "ScoDoc: bienvenue", "encoding": SCO_ENCODING},
% {"page_title": "ScoDoc: bienvenue", "encoding": scu.SCO_ENCODING},
self._top_level_css,
"""</head><body class="gtrcontent" id="gtrcontent">""",
CUSTOM_HTML_HEADER_CNX,
scu.CUSTOM_HTML_HEADER_CNX,
]
return "\n".join(H)
@ -374,7 +407,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
def index_html(self, REQUEST=None, message=None):
"""Top level page for ScoDoc"""
authuser = REQUEST.AUTHENTICATED_USER
deptList = self.list_depts()
deptList = self._list_depts()
self._fix_users_folder() # fix our exUserFolder
isAdmin = not self._check_admin_perm(REQUEST)
try:
@ -382,14 +415,14 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
except:
admin_password_initialized = "0"
if isAdmin and admin_password_initialized != "1":
REQUEST.RESPONSE.redirect(
return REQUEST.RESPONSE.redirect(
"ScoDoc/change_admin_user_form?message=Le%20mot%20de%20passe%20administrateur%20doit%20etre%20change%20!"
)
# Si l'URL indique que l'on est dans un folder, affiche page login du departement
try:
deptfoldername = REQUEST.URL0.split("ScoDoc")[1].split("/")[1]
if deptfoldername in [x.id for x in self.list_depts()]:
if deptfoldername in [x.id for x in self._list_depts()]:
return self.index_dept(deptfoldername=deptfoldername, REQUEST=REQUEST)
except:
pass
@ -436,7 +469,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
dest_folder = "/Scolarite"
else:
dest_folder = ""
for deptFolder in self.list_depts():
for deptFolder in self._list_depts():
if authuser.has_permission(ScoView, deptFolder.Scolarite):
link_cls = "link_accessible"
else:
@ -471,6 +504,11 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
% REQUEST.BASE0
)
# Lien expérimental temporaire:
H.append(
'<p><a href="/ScoDoc/static/mobile">Version mobile (expérimentale, à vos risques et périls)</a></p>'
)
H.append(
"""
<div id="scodoc_attribution">
@ -478,7 +516,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
E. Viennet (Université Paris 13).</p>
</div>
</div>"""
% (SCO_WEBSITE,)
% (scu.SCO_WEBSITE,)
)
H.append("""</body></html>""")
@ -591,20 +629,21 @@ E. Viennet (Université Paris 13).</p>
<link HREF="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css"/>
</head><body>%s""" % (
SCO_ENCODING,
CUSTOM_HTML_HEADER_CNX,
scu.SCO_ENCODING,
scu.CUSTOM_HTML_HEADER_CNX,
)
security.declareProtected("View", "standard_html_footer")
def standard_html_footer(self, REQUEST=None):
"""Le pied de page HTML de la page d'accueil."""
return """<p class="footer">
Problème de connexion (identifiant, mot de passe): <em>contacter votre responsable ou chef de département</em>.</p>
<p>Probl&egrave;mes et suggestions sur le logiciel: <a href="mailto:%s">%s</a></p>
<p><em>ScoDoc est un logiciel libre développé par Emmanuel Viennet.</em></p>
</body></html>""" % (
SCO_USERS_LIST,
SCO_USERS_LIST,
scu.SCO_USERS_LIST,
scu.SCO_USERS_LIST,
)
# sendEmail is not used through the web
@ -642,15 +681,13 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
def standard_error_message(
self,
error_value=None,
error_message=None,
error_message=None, # unused ?
error_type=None,
error_traceback=None,
error_tb=None,
**kv
):
"Recuperation des exceptions Zope"
sco_exc_mail = SCO_EXC_MAIL
sco_dev_mail = SCO_DEV_MAIL
# neat (or should I say dirty ?) hack to get REQUEST
# 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.
@ -663,9 +700,6 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
# Authentication uses exceptions, pass them up
HTTP_X_FORWARDED_FOR = REQUEST.get("HTTP_X_FORWARDED_FOR", "")
if error_type == "LoginRequired":
# raise 'LoginRequired', '' # copied from exuserFolder (beurk, old style exception...)
# if REQUEST:
# REQUEST.response.setStatus( 401, "Unauthorized") # ??????
log("LoginRequired from %s" % HTTP_X_FORWARDED_FOR)
self.login_page = error_value
return error_value
@ -676,6 +710,14 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
log("exception caught: %s" % error_type)
log(traceback.format_exc())
params = {
"error_type": error_type,
"error_value": error_value,
"error_tb": error_tb,
"sco_exc_mail": scu.SCO_EXC_MAIL,
"sco_dev_mail": scu.SCO_DEV_MAIL,
}
if error_type == "ScoGenError":
return "<p>" + str(error_value) + "</p>"
elif error_type in ("ScoValueError", "FormatError"):
@ -713,11 +755,11 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
</p>
</td></tr>
</table> """
% vars()
% params
)
# display error traceback (? may open a security risk via xss attack ?)
# log('exc B')
txt_html = self._report_request(REQUEST, fmt="html")
params["txt_html"] = self._report_request(REQUEST, fmt="html")
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;">
%(error_tb)s
@ -728,7 +770,7 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
<p>Merci de votre patience !</p>
"""
% vars()
% params
)
try:
H.append(self.standard_html_footer(REQUEST))
@ -737,14 +779,14 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
pass
# --- Mail:
error_traceback_txt = scodoc_html2txt(error_tb)
params["error_traceback_txt"] = scu.scodoc_html2txt(error_tb)
txt = (
"""
ErrorType: %(error_type)s
%(error_traceback_txt)s
"""
% vars()
% params
)
self.send_debug_alert(txt, REQUEST=REQUEST)
@ -755,25 +797,29 @@ ErrorType: %(error_type)s
def _report_request(self, REQUEST, fmt="txt"):
"""string describing current request for bug reports"""
AUTHENTICATED_USER = REQUEST.get("AUTHENTICATED_USER", "")
dt = time.asctime()
URL = REQUEST.get("URL", "")
QUERY_STRING = REQUEST.get("QUERY_STRING", "")
if QUERY_STRING:
QUERY_STRING = "?" + QUERY_STRING
METHOD = REQUEST.get("REQUEST_METHOD", "")
if fmt == "txt":
REFERER = REQUEST.get("HTTP_REFERER", "")
HTTP_USER_AGENT = REQUEST.get("HTTP_USER_AGENT", "")
else:
REFERER = "na"
HTTP_USER_AGENT = "na"
form = REQUEST.get("form", "")
HTTP_X_FORWARDED_FOR = REQUEST.get("HTTP_X_FORWARDED_FOR", "")
svn_version = get_svn_version(self.file_path)
SCOVERSION = VERSION.SCOVERSION
params = dict(
AUTHENTICATED_USER=REQUEST.get("AUTHENTICATED_USER", ""),
dt=time.asctime(),
URL=REQUEST.get("URL", ""),
QUERY_STRING=QUERY_STRING,
METHOD=REQUEST.get("REQUEST_METHOD", ""),
REFERER=REFERER,
HTTP_USER_AGENT=HTTP_USER_AGENT,
form=REQUEST.get("form", ""),
HTTP_X_FORWARDED_FOR=REQUEST.get("HTTP_X_FORWARDED_FOR", ""),
svn_version=scu.get_svn_version(self.file_path),
SCOVERSION=VERSION.SCOVERSION,
)
txt = (
"""
Version: %(SCOVERSION)s
@ -789,7 +835,7 @@ Agent: %(HTTP_USER_AGENT)s
subversion: %(svn_version)s
"""
% vars()
% params
)
if fmt == "html":
txt = txt.replace("\n", "<br/>")
@ -801,7 +847,7 @@ subversion: %(svn_version)s
def send_debug_alert(self, txt, REQUEST=None):
"""Send an alert email (bug report) to ScoDoc developpers"""
if not SCO_EXC_MAIL:
if not scu.SCO_EXC_MAIL:
log("send_debug_alert: email disabled")
return
if REQUEST:
@ -810,13 +856,13 @@ subversion: %(svn_version)s
else:
URL = "send_debug_alert"
msg = MIMEMultipart()
subj = Header("[scodoc] exc %s" % URL, SCO_ENCODING)
subj = Header("[scodoc] exc %s" % URL, scu.SCO_ENCODING)
msg["Subject"] = subj
recipients = [SCO_EXC_MAIL]
recipients = [scu.SCO_EXC_MAIL]
msg["To"] = " ,".join(recipients)
msg["From"] = "scodoc-alert"
msg.epilogue = ""
msg.attach(MIMEText(txt, "plain", SCO_ENCODING))
msg.attach(MIMEText(txt, "plain", scu.SCO_ENCODING))
self.sendEmailFromException(msg)
log("Sent mail alert:\n" + txt)
@ -843,7 +889,7 @@ subversion: %(svn_version)s
% self.absolute_url(),
]
deptList = [x.id for x in self.list_depts()] # definis dans Zope
deptList = [x.id for x in self._list_depts()] # definis dans Zope
deptIds = set(self._list_depts_ids()) # definis sur le filesystem
existingDepts = set(deptList)
addableDepts = deptIds - existingDepts
@ -852,7 +898,7 @@ subversion: %(svn_version)s
# aucun departement defini: aide utilisateur
H.append("<p>Aucun département à ajouter !</p>")
else:
H.append("""<form action="create_dept"><select name="DeptId"/>""")
H.append("""<form action="create_dept" id="CreateDpt"><select name="DeptId"/>""")
for deptId in addableDepts:
H.append("""<option value="%s">%s</option>""" % (deptId, deptId))
H.append(
@ -869,10 +915,10 @@ subversion: %(svn_version)s
(le site peut donc être recréé sans perte de données).
</p>
<form action="delete_dept">
<select name="DeptId">
<select name="DeptId" id= "DeleteDept">
"""
)
for deptFolder in self.list_depts():
for deptFolder in self._list_depts():
H.append(
'<option value="%s">%s</option>' % (deptFolder.id, deptFolder.id)
)
@ -890,7 +936,7 @@ subversion: %(svn_version)s
"""Liste de id de departements definis par create_dept.sh
(fichiers depts/*.cfg)
"""
filenames = glob.glob(SCODOC_VAR_DIR + "/config/depts/*.cfg")
filenames = glob.glob(scu.SCODOC_VAR_DIR + "/config/depts/*.cfg")
ids = [os.path.split(os.path.splitext(f)[0])[1] for f in filenames]
return ids
@ -907,7 +953,7 @@ subversion: %(svn_version)s
"""Returns the dept id (eg "GEII") of an etud (identified by etudid, INE or NIP in REQUEST).
Warning: This function is inefficient and its result should be cached.
"""
depts = self.list_depts()
depts = self._list_depts()
depts_etud = [] # liste des depts où l'etud est defini
for dept in depts:
etuds = dept.Scolarite.getEtudInfo(REQUEST=REQUEST)
@ -947,5 +993,5 @@ def manage_addZScoDoc(self, id="ScoDoc", title="Site ScoDoc", REQUEST=None):
) # ne cree (presque rien), tout se passe lors du 1er accès
self._setObject(id, zscodoc)
if REQUEST is not None:
REQUEST.RESPONSE.redirect("%s/manage_workspace" % REQUEST.URL1)
REQUEST.RESPONSE.redirect("/ScoDoc/manage_workspace")
return id

View File

@ -27,45 +27,45 @@
""" Gestion des utilisateurs (table SQL pour Zope User Folder)
"""
import string, re
import string
import re
import time
import md5, base64
import md5
import base64
import jaxml
from sco_zope import *
from sco_zope import * # pylint: disable=unused-wildcard-import
# ---------------
import notesdb
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
from scolog import logdb
from sco_utils import *
from scolars import format_prenom, format_nom
import sco_import_users, sco_excel
from TrivialFormulator import TrivialFormulator, TF
import sco_import_users
import sco_excel
from TrivialFormulator import TrivialFormulator, TF, tf_error_message
from gen_tables import GenTable
import scolars
import sco_cache
# ----------------- password checking
import cracklib
def pwdFascistCheck(cleartxt):
"returns None if OK"
if (
hasattr(CONFIG, "MIN_PASSWORD_LENGTH")
and CONFIG.MIN_PASSWORD_LENGTH > 0
and len(cleartxt) < CONFIG.MIN_PASSWORD_LENGTH
):
return True # invalid
try:
x = cracklib.FascistCheck(cleartxt)
return None
except ValueError as e:
return str(e)
import sco_users
from sco_permissions import (
ScoEditAllEvals,
ScoEditAllNotes,
ScoImplement,
ScoSuperAdmin,
ScoUsersAdmin,
ScoUsersView,
ScoView,
)
from sco_exceptions import (
AccessDenied,
ScoException,
ScoValueError,
ScoInvalidDateError,
ScoLockedFormError,
ScoGenError,
)
# ---------------
# cache global: chaque instance, repérée par son URL, a un cache
@ -105,26 +105,12 @@ class ZScoUsers(
self.id = id
self.title = title
# The form used to edit this object
def manage_editZScousers(self, title, RESPONSE=None):
"Changes the instance values"
self.title = title
self._p_changed = 1
RESPONSE.redirect("manage_editForm")
# Ajout (dans l'instance) d'un dtml modifiable par Zope
def defaultDocFile(self, id, title, file):
f = open(file_path + "/dtml-editable/" + file + ".dtml")
file = f.read()
f.close()
self.manage_addDTMLMethod(id, title, file)
# Connexion to SQL database of users:
# Ugly but necessary during transition out of Zope:
_db_cnx_string = "dbname=SCOUSERS port=5432"
security.declareProtected("Change DTML Documents", "GetUsersDBConnexion")
GetUsersDBConnexion = notesdb.GetUsersDBConnexion
GetUsersDBConnexion = ndb.GetUsersDBConnexion
# --------------------------------------------------------------------
#
@ -157,7 +143,7 @@ class ZScoUsers(
if authuser.has_permission(ScoUsersAdmin, self):
H.append(
'<p><a href="create_user_form" class="stdlink">Ajouter un utilisateur</a>'
'<p><a id="creer_Utilisateur" href="create_user_form" class="stdlink">Ajouter un utilisateur</a>'
)
H.append(
'&nbsp;&nbsp; <a href="import_users_form" class="stdlink">Importer des utilisateurs</a></p>'
@ -193,7 +179,7 @@ class ZScoUsers(
F = self.sco_footer(REQUEST)
return "\n".join(H) + F
_userEditor = EditableTable(
_userEditor = ndb.EditableTable(
"sco_users",
"user_id",
(
@ -211,12 +197,12 @@ class ZScoUsers(
"date_expiration",
),
output_formators={
"date_modif_passwd": DateISOtoDMY,
"date_expiration": DateISOtoDMY,
"date_modif_passwd": ndb.DateISOtoDMY,
"date_expiration": ndb.DateISOtoDMY,
},
input_formators={
"date_modif_passwd": DateDMYtoISO,
"date_expiration": DateDMYtoISO,
"date_modif_passwd": ndb.DateDMYtoISO,
"date_expiration": ndb.DateDMYtoISO,
},
sortkey="nom",
filter_nulls=False,
@ -248,12 +234,12 @@ class ZScoUsers(
u = self._user_list(args={"user_name": user_name})[0]
if u["status"] == "old" and u["roles"] and u["roles"][0] != "-":
roles = ["-" + r for r in u["roles"].split(",")]
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
self.acl_users.scodoc_editUser(cursor, user_name, roles=roles)
self.get_userlist_cache().inval_cache()
elif not u["status"] and u["roles"] and u["roles"][0] == "-":
roles = [r[1:] for r in u["roles"].split(",") if (r and r[0] == "-")]
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
self.acl_users.scodoc_editUser(cursor, user_name, roles=roles)
self.get_userlist_cache().inval_cache()
@ -275,7 +261,7 @@ class ZScoUsers(
security.declareProtected(ScoUsersAdmin, "user_info")
def user_info(self, user_name=None, user=None):
def user_info(self, user_name=None, user=None, format=None, REQUEST=None):
"""Donne infos sur l'utilisateur (qui peut ne pas etre dans notre base).
Si user_name est specifie, interroge la BD. Sinon, user doit etre un dict.
"""
@ -299,7 +285,7 @@ class ZScoUsers(
"nom_fmt": user_name,
"nomcomplet": user_name,
"nomplogin": user_name,
"nomnoacc": suppress_accents(user_name),
"nomnoacc": scu.suppress_accents(user_name),
"passwd_temp": 0,
"status": "",
"date_expiration": None,
@ -320,7 +306,7 @@ class ZScoUsers(
else:
n = user_name
prenom_abbrv = abbrev_prenom(p)
prenom_abbrv = scu.abbrev_prenom(p)
# nomprenom est le nom capitalisé suivi de l'initiale du prénom
info["nomprenom"] = (n + " " + prenom_abbrv).strip()
# prenomnom est l'initiale du prénom suivie du nom
@ -332,11 +318,11 @@ class ZScoUsers(
info["nomcomplet"] = info["prenom_fmt"] + " " + info["nom_fmt"]
# nomplogin est le nom en majuscules suivi du prénom et du login
# e.g. Dupont Pierre (dupont)
info["nomplogin"] = "%s %s (%s)" % (strupper(n), p, info["user_name"])
info["nomplogin"] = "%s %s (%s)" % (scu.strupper(n), p, info["user_name"])
# nomnoacc est le nom en minuscules sans accents
info["nomnoacc"] = suppress_accents(strlower(info["nom"]))
info["nomnoacc"] = scu.suppress_accents(scu.strlower(info["nom"]))
return info
return scu.sendResult(REQUEST, info, name="user", format=format)
def _can_handle_passwd(self, authuser, user_name, allow_admindepts=False):
"""true if authuser can see or change passwd of user_name.
@ -372,16 +358,12 @@ class ZScoUsers(
else:
return False
def _is_valid_passwd(self, passwd):
"check if passwd is secure enough"
return not pwdFascistCheck(passwd)
def do_change_password(self, user_name, password):
user = self._user_list(args={"user_name": user_name})
assert len(user) == 1, "database inconsistency: len(r)=%d" % len(r)
assert len(user) == 1, "database inconsistency: len(user)=%d" % len(user)
# should not occur, already tested in _can_handle_passwd
cnx = self.GetUsersDBConnexion() # en mode autocommit
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"update sco_users set date_modif_passwd=now(), passwd_temp=0 where user_name=%(user_name)s",
{"user_name": user_name},
@ -408,7 +390,7 @@ class ZScoUsers(
# access denied
log(
"change_password: access denied (authuser=%s, user_name=%s, ip=%s)"
% (authuser, user_name, REQUEST.REMOTE_ADDR)
% (REQUEST.AUTHENTICATED_USER, user_name, REQUEST.REMOTE_ADDR)
)
raise AccessDenied(
"vous n'avez pas la permission de changer ce mot de passe"
@ -421,7 +403,7 @@ class ZScoUsers(
% user_name
)
else:
if not self._is_valid_passwd(password):
if not sco_users.is_valid_password(password):
H.append(
"""<p><b>ce mot de passe n\'est pas assez compliqué !</b><br/>(oui, il faut un mot de passe vraiment compliqué !)</p>
<p><a href="form_change_password?user_name=%s" class="stdlink">Recommencer</a></p>
@ -452,7 +434,7 @@ class ZScoUsers(
<meta http-equiv="Content-Type" content="text/html; charset=%s" />
<body><h1>Mot de passe changé !</h1>
"""
% (SCO_ENCODING, SCO_ENCODING)
% (scu.SCO_ENCODING, scu.SCO_ENCODING)
+ "\n".join(H)
+ '<a href="%s" class="stdlink">Continuer</a></body></html>'
% self.ScoURL()
@ -541,7 +523,7 @@ class ZScoUsers(
if authuser.has_permission(ScoUsersAdmin, self):
H.append(
"""
<li><a class="stdlink" href="create_user_form?user_name=%(user_name)s&amp;edit=1">modifier/déactiver ce compte</a></li>
<li><a class="stdlink" href="create_user_form?user_name=%(user_name)s&edit=1">modifier/déactiver ce compte</a></li>
<li><a class="stdlink" href="delete_user_form?user_name=%(user_name)s">supprimer cet utilisateur</a> <em>(à n'utiliser qu'en cas d'erreur !)</em></li>
"""
% info[0]
@ -571,7 +553,7 @@ class ZScoUsers(
)
else:
for p in scoperms:
permname, value = p[:2]
permname, _ = p[:2]
if thisuser.has_permission(permname, self):
b = "oui"
else:
@ -810,11 +792,12 @@ class ZScoUsers(
descr,
initvalues=initvalues,
submitlabel=submitlabel,
cancelbutton="Annuler",
)
if tf[0] == 0:
return "\n".join(H) + "\n" + tf[1] + F
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(self.UsersURL())
else:
vals = tf[2]
roles = set(vals["roles"]).intersection(editable_roles)
@ -904,7 +887,7 @@ class ZScoUsers(
"""Les deux mots de passes ne correspondent pas !"""
)
return "\n".join(H) + msg + "\n" + tf[1] + F
if not self._is_valid_passwd(vals["passwd"]):
if not sco_users.is_valid_password(vals["passwd"]):
msg = tf_error_message(
"""Mot de passe trop simple, recommencez !"""
)
@ -938,7 +921,7 @@ class ZScoUsers(
# Des noms/prénoms semblables existent ?
cnx = self.GetUsersDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"select * from sco_users where lower(nom) ~ %(nom)s and lower(prenom) ~ %(prenom)s;",
{"nom": nom.lower().strip(), "prenom": prenom.lower().strip()},
@ -1015,7 +998,7 @@ class ZScoUsers(
if tf[0] == 0:
return "\n".join(H) + tf[1] + "</li></ol>" + help + F
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(self.UsersURL())
else:
# IMPORT
diag = sco_import_users.import_excel_file(
@ -1024,7 +1007,9 @@ class ZScoUsers(
H = [head]
H.append("<p>Import excel: %s</p>" % diag)
H.append("<p>OK, import terminé !</p>")
H.append('<p><a class="stdlink" href="%s">Continuer</a></p>' % REQUEST.URL1)
H.append(
'<p><a class="stdlink" href="%s">Continuer</a></p>' % self.UsersURL()
)
return "\n".join(H) + help + F
security.declareProtected(ScoUsersAdmin, "import_users_generate_excel_sample")
@ -1039,13 +1024,13 @@ class ZScoUsers(
def create_user(self, args, REQUEST=None):
"creation utilisateur zope"
cnx = self.GetUsersDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
passwd = args["passwd"]
args["passwd"] = "undefined"
if "passwd2" in args:
del args["passwd2"]
log("create_user: args=%s" % args) # log apres supr. du mot de passe !
r = self._userEditor.create(cnx, args)
_ = self._userEditor.create(cnx, args)
self.get_userlist_cache().inval_cache() # >
# call exUserFolder to set passwd
@ -1055,7 +1040,7 @@ class ZScoUsers(
)
if REQUEST:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(self.UsersURL())
security.declareProtected(ScoUsersAdmin, "delete_user_form")
@ -1083,11 +1068,15 @@ class ZScoUsers(
% user_name,
dest_url="",
REQUEST=REQUEST,
cancel_url=REQUEST.URL1,
cancel_url=self.UsersURL(),
parameters={"user_name": user_name},
)
self._user_delete(user_name)
REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(
self.UsersURL() + r"?head_message=Utilisateur%20supprimé"
)
security.declareProtected(ScoView, "list_users")
def list_users(
self,
@ -1133,15 +1122,15 @@ class ZScoUsers(
# Convert dates to ISO if XML output
if format == "xml" and u["date_modif_passwd"] != "NA":
u["date_modif_passwd"] = DateDMYtoISO(u["date_modif_passwd"]) or ""
u["date_modif_passwd"] = ndb.DateDMYtoISO(u["date_modif_passwd"]) or ""
# Convert date_expiration and date_modif_passwd to ISO to ease sorting
if u["date_expiration"]:
u["date_expiration_iso"] = DateDMYtoISO(u["date_expiration"])
u["date_expiration_iso"] = ndb.DateDMYtoISO(u["date_expiration"])
else:
u["date_expiration_iso"] = ""
if u["date_modif_passwd"]:
u["date_modif_passwd_iso"] = DateDMYtoISO(u["date_expiration"])
u["date_modif_passwd_iso"] = ndb.DateDMYtoISO(u["date_expiration"])
else:
u["date_modif_passwd_iso"] = ""
@ -1235,13 +1224,13 @@ class ZScoUsers(
"""Returns XML list of users with name (nomplogin) starting with start.
Used for forms auto-completion."""
userlist = self.get_userlist(dept=dept)
start = suppression_diacritics(unicode(start, "utf-8"))
start = strlower(str(start))
start = scu.suppression_diacritics(unicode(start, "utf-8"))
start = scu.strlower(str(start))
userlist = [user for user in userlist if user["nomnoacc"].startswith(start)]
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
doc = jaxml.XML_document(encoding=SCO_ENCODING)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
doc.results()
for user in userlist[:limit]:
doc._push()
@ -1276,8 +1265,8 @@ Il devra ensuite se connecter et le changer.
)
self.reset_password(user_name=user_name, REQUEST=REQUEST)
return REQUEST.RESPONSE.redirect(
REQUEST.URL1
+ "?head_message=mot%20de%20passe%20de%20"
self.UsersURL()
+ r"?head_message=mot%20de%20passe%20de%20"
+ user_name
+ "%20reinitialise"
)
@ -1302,14 +1291,14 @@ Il devra ensuite se connecter et le changer.
log("reset_password: %s" % user_name)
# Check that user has valid mail
info = self.user_info(user_name=user_name)
if not is_valid_mail(info["email"]):
if not scu.is_valid_mail(info["email"]):
raise Exception("pas de mail valide associé à l'utilisateur")
# Generate random password
password = sco_import_users.generate_password()
self.do_change_password(user_name, password)
# Flag it as temporary:
cnx = self.GetUsersDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
ui = {"user_name": user_name}
cursor.execute(
"update sco_users set passwd_temp=1 where user_name='%(user_name)s'" % ui

View File

@ -30,8 +30,15 @@
import sys
import traceback
import time, string, glob, re
import urllib, urllib2, cgi, xml
import time
import string
import glob
import re
import urllib
import urllib2
import cgi
import xml
import jaxml
try:
from cStringIO import StringIO
@ -41,7 +48,7 @@ from zipfile import ZipFile
import thread
import psycopg2
from sco_zope import *
from sco_zope import * # pylint: disable=unused-wildcard-import
# ---------------
@ -53,18 +60,46 @@ log("restarting...")
log("ZScolar home=%s" % file_path)
from sco_utils import *
import notesdb
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from scolog import logdb
from sco_permissions import (
ScoAbsChange,
ScoView,
ScoEnsView,
ScoImplement,
ScoChangeFormation,
ScoChangePreferences,
ScoObservateur,
ScoEtudAddAnnotations,
ScoEtudInscrit,
ScoEtudChangeGroups,
ScoEtudChangeAdr,
ScoEtudSupprAnnotations,
ScoEditAllEvals,
ScoEditAllNotes,
ScoEditFormationTags,
ScoEditApo,
ScoSuperAdmin,
)
import sco_permissions
from sco_exceptions import (
AccessDenied,
ScoException,
ScoValueError,
ScoInvalidDateError,
ScoLockedFormError,
ScoGenError,
)
from TrivialFormulator import TrivialFormulator, tf_error_message
import scolars
import sco_codes_parcours
import sco_preferences
import sco_formations
from scolars import (
format_nom,
format_prenom,
format_sexe,
format_civilite,
format_lycee,
format_lycee_from_code,
)
@ -87,8 +122,10 @@ import ZNotes
import ZAbsences
import ZEntreprises
import ZScoUsers
import sco_modalites
# import sco_modalites
import ImportScolars
import sco_abs
import sco_portal_apogee
import sco_synchro_etuds
import sco_page_etud
@ -108,9 +145,9 @@ import sco_dump_db
from VERSION import SCOVERSION, SCONEWS
if hasattr(CONFIG, "ABSOLUTE_URL") and CONFIG.ABSOLUTE_URL:
log("ScoDoc: ABSOLUTE_URL='%s'" % CONFIG.ABSOLUTE_URL)
log("ScoDoc: using encoding %s" % SCO_ENCODING)
if hasattr(scu.CONFIG, "ABSOLUTE_URL") and scu.CONFIG.ABSOLUTE_URL:
log("ScoDoc: ABSOLUTE_URL='%s'" % scu.CONFIG.ABSOLUTE_URL)
log("ScoDoc: using encoding %s" % scu.SCO_ENCODING)
# import essai_cas
@ -132,13 +169,7 @@ class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
manage_options = (
({"label": "Contents", "action": "manage_main"},)
+ PropertyManager.manage_options # add the 'Properties' tab
+ (
# this line is kept as an example with the files :
# dtml/manage_editZScolarForm.dtml
# html/ZScolar-edit.stx
# {'label': 'Properties', 'action': 'manage_editForm',},
{"label": "View", "action": "index_html"},
)
+ ({"label": "View", "action": "index_html"},)
+ Item.manage_options # add the 'Undo' & 'Owner' tab
+ RoleManager.manage_options # add the 'Security' tab
)
@ -151,17 +182,7 @@ class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
self.title = title
self._db_cnx_string = db_cnx_string
self._cnx = None
# --- add editable DTML documents:
# self.defaultDocFile('sidebar_dept',
# 'barre gauche (partie haute)',
# 'sidebar_dept')
# --- add DB connector
# id = 'DB'
# da = ZopeDA.Connection(
# id, 'DB connector', db_cnx_string, False,
# check=1, tilevel=2, encoding='utf-8')
# self._setObject(id, da)
# --- add Scousers instance
id = "Users"
obj = ZScoUsers.ZScoUsers(id, "Gestion utilisateurs zope")
@ -183,11 +204,13 @@ class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
self.manage_addProperty("roles_initialized", "0", "string")
# The for used to edit this object
def manage_editZScolar(self, title, RESPONSE=None):
security.declareProtected(ScoView, "manage_editZScolar")
def manage_editZScolar(self, title, REQUEST=None):
"Changes the instance values"
self.title = title
self._p_changed = 1
RESPONSE.redirect("manage_editForm")
return REQUEST.RESPONSE.redirect("manage_editForm")
def _setup_initial_roles_and_permissions(self):
"""Initialize roles and permissions
@ -209,8 +232,10 @@ class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
H.append(r)
ok = False
for permission in Sco_Default_Permissions.keys():
roles = [r + DeptId for r in Sco_Default_Permissions[permission]]
for permission in sco_permissions.Sco_Default_Permissions.keys():
roles = [
r + DeptId for r in sco_permissions.Sco_Default_Permissions[permission]
]
roles.append("Manager")
log("granting '%s' to %s" % (permission, roles))
try:
@ -254,7 +279,7 @@ class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Programme DUT R&amp;T</title>
<title>Programme DUT TEST</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="LANG" content="fr" />
@ -269,65 +294,70 @@ class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
</head>
<body>
<p>
UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span class="span_ue_apo" style="display: inline" id="toto">VRTU11</span>)</span>
<span class="locked">[verrouillé]</span>
<tt>
REQUEST.URL=%s<br/>
REQUEST.URL0=%s<br/>
</tt>
</p>
</body>
"""
return (
self.sco_header(REQUEST)
+ """<div class="xp">%s</div>""" % x
+ self.sco_footer(REQUEST)
""" % (
REQUEST.URL,
REQUEST.URL0,
)
b = "<p>Hello, World !</p><br/>"
raise ValueError("essai exception")
# return (
# self.sco_header(REQUEST)
# + """<div class="xp">%s</div>""" % x
# + self.sco_footer(REQUEST)
# )
# b = "<p>Hello, World !</p><br/>"
# raise ValueError("essai exception")
# raise ScoValueError('essai exception !', dest_url='totoro', REQUEST=REQUEST)
# cursor = cnx.cursor(cursor_factory=ScoDocCursor)
# cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
# cursor.execute("select * from notes_formations")
# b += str(cursor.fetchall())
# b = self.Notes.gloups()
# raise NoteProcessError('test exception !')
# essai: liste des permissions
from AccessControl import getSecurityManager
from AccessControl.Permission import Permission
# from AccessControl import getSecurityManager # pylint: disable=import-error
# from AccessControl.Permission import Permission # pylint: disable=import-error
permissions = self.ac_inherited_permissions(1)
scoperms = [p for p in permissions if p[0][:3] == "Sco"]
# H.append( str(self.aq_parent.aq_parent.permission_settings()) )
# H.append('<p>perms: %s</p>'%str(scoperms))
# H.append('<p>valid_roles: %s</p>'%str(self.valid_roles()))
# H.append('<p>ac_inherited_permissions=%s</p>'%str(self.ac_inherited_permissions(1)))
def collect_roles(context, rd):
for p in scoperms:
name, value = p[:2]
P = Permission(name, value, context)
roles = list(P.getRoles())
if rd.has_key(name):
rd[name] += roles
else:
rd[name] = roles
if hasattr(context, "aq_parent"):
collect_roles(context.aq_parent, rd)
# permissions = self.ac_inherited_permissions(1)
# scoperms = [p for p in permissions if p[0][:3] == "Sco"]
# # H.append( str(self.aq_parent.aq_parent.permission_settings()) )
# # H.append('<p>perms: %s</p>'%str(scoperms))
# # H.append('<p>valid_roles: %s</p>'%str(self.valid_roles()))
# # H.append('<p>ac_inherited_permissions=%s</p>'%str(self.ac_inherited_permissions(1)))
# def collect_roles(context, rd):
# for p in scoperms:
# name, value = p[:2]
# P = Permission(name, value, context)
# roles = list(P.getRoles())
# if rd.has_key(name):
# rd[name] += roles
# else:
# rd[name] = roles
# if hasattr(context, "aq_parent"):
# collect_roles(context.aq_parent, rd)
b = ""
rd = {}
collect_roles(self, rd)
b = "<p>" + str(rd) + "</p>"
# b = ""
# rd = {}
# collect_roles(self, rd)
# b = "<p>" + str(rd) + "</p>"
authuser = REQUEST.AUTHENTICATED_USER
for p in scoperms:
permname, value = p[:2]
b += "<p>" + permname + " : "
if authuser.has_permission(permname, self):
b += "yes"
else:
b += "no"
b += "</p>"
b += "<p>xxx</p><hr><p>" + str(self.aq_parent.aq_parent)
# authuser = REQUEST.AUTHENTICATED_USER
# for p in scoperms:
# permname, _ = p[:2]
# b += "<p>" + permname + " : "
# if authuser.has_permission(permname, self):
# b += "yes"
# else:
# b += "no"
# b += "</p>"
# b += "<p>xxx</p><hr><p>" + str(self.aq_parent.aq_parent)
return self.sco_header(REQUEST) + str(b) + self.sco_footer(REQUEST)
# return self.sco_header(REQUEST) + str(b) + self.sco_footer(REQUEST)
# essais calendriers:
security.declareProtected(ScoView, "experimental_calendar")
@ -338,11 +368,43 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
security.declareProtected(ScoView, "ScoURL")
def ScoURL(self):
"base URL for this sco instance"
"""base URL for this sco instance.
e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite
= page accueil département
"""
# absolute_url is the classic Zope method
# The avoid the burden of configuring a proxy zope object, we offer
# a custom configuration via scodoc_config
return CONFIG.ABSOLUTE_URL or self.absolute_url()
# XXX ne devrait pas marcher car l'URL dépend du département !!!
return scu.CONFIG.ABSOLUTE_URL or self.absolute_url()
def NotesURL(self):
"""URL of Notes
e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite/Notes
= url de base des requêtes de ZNotes
et page accueil programmes.
"""
return self.ScoURL() + "/Notes"
def EntreprisesURL(self):
"""URL of Enterprises
e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite/Entreprises
= url de base des requêtes de ZEntreprises
et page accueil Entreprises
"""
return self.ScoURL() + "/Entreprises"
def AbsencesURL(self):
"""URL of Absences"""
return self.ScoURL() + "/Absences"
def UsersURL(self):
"""URL of Users
e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite/Users
= url de base des requêtes ZScoUsers
et page accueil users
"""
return self.ScoURL() + "/Users"
security.declareProtected(ScoView, "sco_header")
sco_header = html_sco_header.sco_header
@ -361,7 +423,7 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
return self._db_cnx_string
security.declareProtected(ScoSuperAdmin, "GetDBConnexion")
GetDBConnexion = notesdb.GetDBConnexion
GetDBConnexion = ndb.GetDBConnexion
# A enlever après re-ecriture de ZEntreprises.py
security.declareProtected(ScoView, "TrivialFormulator")
@ -404,10 +466,10 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
"version info"
H = [
"""<h2>Système de gestion scolarité</h2>
<p>&copy; Emmanuel Viennet 1997-2020</p>
<p>&copy; Emmanuel Viennet 1997-2021</p>
<p>Version %s (subversion %s)</p>
"""
% (SCOVERSION, get_svn_version(file_path))
% (SCOVERSION, scu.get_svn_version(file_path))
]
H.append(
'<p>Logiciel libre écrit en <a href="http://www.python.org">Python</a>.</p><p>Utilise <a href="http://www.reportlab.org/">ReportLab</a> pour générer les documents PDF, et <a href="http://sourceforge.net/projects/pyexcelerator">pyExcelerator</a> pour le traitement des documents Excel.</p>'
@ -415,7 +477,7 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
H.append("<h2>Dernières évolutions</h2>" + SCONEWS)
H.append(
'<div class="about-logo">'
+ icontag("borgne_img")
+ scu.icontag("borgne_img")
+ " <em>Au pays des aveugles...</em></div>"
)
d = ""
@ -429,12 +491,12 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
if format == "html" or format == "pdf":
raise ScoValueError(msg)
elif format == "xml":
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
doc = jaxml.XML_document(encoding=SCO_ENCODING)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
doc.error(msg=msg)
return repr(doc)
elif format == "json":
REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE)
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE)
return "undefined" # XXX voir quoi faire en cas d'erreur json
else:
raise ValueError("ScoErrorResponse: invalid format")
@ -529,7 +591,7 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
page_title="Opérations sur %(nomprenom)s" % etud,
html_title="<h2>Opérations effectuées sur l'étudiant %(nomprenom)s</h2>"
% etud,
filename="log_" + make_filename(etud["nomprenom"]),
filename="log_" + scu.make_filename(etud["nomprenom"]),
html_next_section='<ul><li><a href="ficheEtud?etudid=%(etudid)s">fiche de %(nomprenom)s</a></li></ul>'
% etud,
preferences=self.get_preferences(),
@ -537,10 +599,12 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
return tab.make_page(self, format=format, REQUEST=REQUEST)
security.declareProtected(ScoView, "listScoLog")
def listScoLog(self, etudid):
"liste des operations effectuees sur cet etudiant"
cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"select * from scolog where etudid=%(etudid)s ORDER BY DATE DESC",
{"etudid": etudid},
@ -555,7 +619,7 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
def rssnews(self, REQUEST=None):
"rss feed"
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
return sco_news.scolar_news_summary_rss(
self, "Nouvelles de " + self.get_preference("DeptName"), self.ScoURL()
)
@ -592,7 +656,7 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
# calcule dates 1er jour semaine pour absences
try:
if with_absences:
first_monday = ZAbsences.ddmmyyyy(sem["date_debut"]).prev_monday()
first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday()
FA = [] # formulaire avec menu saisi absences
FA.append(
'<td><form action="Absences/SignaleAbsenceGrSemestre" method="get">'
@ -610,12 +674,12 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
FA.append('<input type="submit" value="Saisir absences du" />')
FA.append('<select name="datedebut" class="noprint">')
date = first_monday
for jour in self.Absences.day_names():
for jour in sco_abs.day_names(self):
FA.append('<option value="%s">%s</option>' % (date, jour))
date = date.next()
FA.append("</select>")
FA.append(
'<a href="Absences/EtatAbsencesGr?group_ids=%%(group_id)s&amp;debut=%(date_debut)s&amp;fin=%(date_fin)s">état</a>'
'<a href="Absences/EtatAbsencesGr?group_ids=%%(group_id)s&debut=%(date_debut)s&fin=%(date_fin)s">état</a>'
% sem
)
FA.append("</form></td>")
@ -651,8 +715,8 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
"""<td>
<a href="%(url)s/groups_view?group_ids=%(group_id)s">%(label)s</a>
</td><td>
(<a href="%(url)s/groups_view?group_ids=%(group_id)s&amp;format=xls">format tableur</a>)
<a href="%(url)s/groups_view?curtab=tab-photos&amp;group_ids=%(group_id)s&amp;etat=I">Photos</a>
(<a href="%(url)s/groups_view?group_ids=%(group_id)s&format=xls">format tableur</a>)
<a href="%(url)s/groups_view?curtab=tab-photos&group_ids=%(group_id)s&etat=I">Photos</a>
</td>"""
% group
)
@ -665,13 +729,13 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
H.append("</table>")
else:
H.append('<p class="help indent">Aucun groupe dans cette partition')
if self.Notes.can_change_groups(REQUEST, formsemestre_id):
if sco_groups.can_change_groups(self, REQUEST, formsemestre_id):
H.append(
' (<a href="affectGroups?partition_id=%s" class="stdlink">créer</a>)'
% partition["partition_id"]
)
H.append("</p>")
if self.Notes.can_change_groups(REQUEST, formsemestre_id):
if sco_groups.can_change_groups(self, REQUEST, formsemestre_id):
H.append(
'<h4><a href="editPartitionForm?formsemestre_id=%s">Ajouter une partition</a></h4>'
% formsemestre_id
@ -716,7 +780,7 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
# -------------------------- INFOS SUR ETUDIANTS --------------------------
security.declareProtected(ScoView, "getEtudInfo")
def getEtudInfo(self, etudid=False, code_nip=False, filled=False, REQUEST=None):
def getEtudInfo(self, etudid=False, code_nip=False, filled=False, REQUEST=None, format=None):
"""infos sur un etudiant pour utilisation en Zope DTML
On peut specifier etudid
ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine
@ -727,9 +791,13 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
cnx = self.GetDBConnexion()
args = make_etud_args(etudid=etudid, code_nip=code_nip, REQUEST=REQUEST)
etud = scolars.etudident_list(cnx, args=args)
if filled:
self.fillEtudsInfo(etud)
if format is None:
return etud
else:
return scu.sendResult(REQUEST, etud, name="etud", format=format)
security.declareProtected(ScoView, "search_etud_in_dept")
search_etud_in_dept = sco_find_etud.search_etud_in_dept
@ -764,14 +832,11 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
# Semestres dans lesquel il est inscrit
ins = self.Notes.do_formsemestre_inscription_list({"etudid": etudid})
etud["ins"] = ins
now = time.strftime("%Y-%m-%d")
sems = []
cursem = None # semestre "courant" ou il est inscrit
for i in ins:
sem = sco_formsemestre.get_formsemestre(self, i["formsemestre_id"])
debut = DateDMYtoISO(sem["date_debut"])
fin = DateDMYtoISO(sem["date_fin"])
if debut <= now and now <= fin:
if sco_formsemestre.sem_est_courant(self, sem):
cursem = sem
curi = i
sem["ins"] = i
@ -859,18 +924,19 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
"nom": "?",
"nom_usuel": "",
"prenom": "?",
"sexe": "?",
"civilite": "?",
"sexe": "?", # for backward compat
"email": "?",
"emailperso": "",
"error": "code etudiant inconnu",
}
return sendResult(
return scu.sendResult(
REQUEST, d, name="etudiant", format=format, force_outer_xml_tag=False
)
d = {}
etud = etuds[0]
self.fillEtudsInfo([etud])
etud["date_naissance_iso"] = DateDMYtoISO(etud["date_naissance"])
etud["date_naissance_iso"] = ndb.DateDMYtoISO(etud["date_naissance"])
for a in (
"etudid",
"code_nip",
@ -878,7 +944,6 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
"nom",
"nom_usuel",
"prenom",
"sexe",
"nomprenom",
"email",
"emailperso",
@ -898,8 +963,12 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
"codelycee",
"date_naissance_iso",
):
d[a] = quote_xml_attr(etud[a])
d["photo_url"] = quote_xml_attr(sco_photos.etud_photo_url(self, etud))
d[a] = scu.quote_xml_attr(etud[a])
d["civilite"] = scu.quote_xml_attr(
etud["civilite_str"]
) # exception: ne sort pas la civilite brute
d["sexe"] = d["civilite"] # backward compat pour anciens clients
d["photo_url"] = scu.quote_xml_attr(sco_photos.etud_photo_url(self, etud))
sem = etud["cursem"]
if sem:
@ -908,10 +977,10 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
{
"current": "1",
"formsemestre_id": sem["formsemestre_id"],
"date_debut": DateDMYtoISO(sem["date_debut"]),
"date_fin": DateDMYtoISO(sem["date_fin"]),
"etat": quote_xml_attr(sem["ins"]["etat"]),
"groupes": quote_xml_attr(
"date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
"date_fin": ndb.DateDMYtoISO(sem["date_fin"]),
"etat": scu.quote_xml_attr(sem["ins"]["etat"]),
"groupes": scu.quote_xml_attr(
etud["groupes"]
), # slt pour semestre courant
}
@ -923,20 +992,22 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
d["insemestre"].append(
{
"formsemestre_id": sem["formsemestre_id"],
"date_debut": DateDMYtoISO(sem["date_debut"]),
"date_fin": DateDMYtoISO(sem["date_fin"]),
"etat": quote_xml_attr(sem["ins"]["etat"]),
"date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
"date_fin": ndb.DateDMYtoISO(sem["date_fin"]),
"etat": scu.quote_xml_attr(sem["ins"]["etat"]),
}
)
log("etud_info (%gs)" % (time.time() - t0))
return sendResult(
return scu.sendResult(
REQUEST, d, name="etudiant", format=format, force_outer_xml_tag=False
)
security.declareProtected(ScoView, "XMLgetEtudInfos")
XMLgetEtudInfos = etud_info # old name, deprecated
security.declareProtected(ScoView, "isPrimoEtud")
def isPrimoEtud(self, etud, sem):
"""Determine si un (filled) etud a ete inscrit avant ce semestre.
Regarde la liste des semestres dans lesquels l'étudiant est inscrit
@ -971,7 +1042,7 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
def _descr_situation_etud(self, etudid, ne=""):
"""chaine decrivant la situation actuelle de l'etudiant"""
cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"select I.formsemestre_id, I.etat from notes_formsemestre_inscription I, notes_formsemestre S where etudid=%(etudid)s and S.formsemestre_id = I.formsemestre_id and date_debut < now() and date_fin > now() order by S.date_debut desc;",
{"etudid": etudid},
@ -1063,7 +1134,7 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
},
)
logdb(REQUEST, cnx, method="addAnnotation", etudid=etudid)
REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid)
return REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid)
security.declareProtected(ScoView, "canSuppressAnnotation")
@ -1102,8 +1173,8 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
logdb(REQUEST, cnx, method="SuppressAnnotation", etudid=etudid)
scolars.etud_annotations_delete(cnx, annotation_id)
REQUEST.RESPONSE.redirect(
"ficheEtud?etudid=%s&amp;head_message=Annotation%%20supprimée" % (etudid)
return REQUEST.RESPONSE.redirect(
"ficheEtud?etudid=%s&head_message=Annotation%%20supprimée" % (etudid)
)
security.declareProtected(ScoEtudChangeAdr, "formChangeCoordonnees")
@ -1161,99 +1232,18 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
initvalues=adr,
submitlabel="Valider le formulaire",
)
dest_url = self.ScoURL() + "/ficheEtud?etudid=" + etudid
if tf[0] == 0:
return header + "\n".join(H) + tf[1] + self.sco_footer(REQUEST)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(dest_url)
else:
if adrs:
scolars.adresse_edit(cnx, args=tf[2], context=self)
else:
scolars.adresse_create(cnx, args=tf[2])
logdb(REQUEST, cnx, method="changeCoordonnees", etudid=etudid)
REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid)
security.declareProtected(ScoView, "formChangeGroup")
def formChangeGroup(self, formsemestre_id, etudid, REQUEST):
"changement groupe etudiant dans semestre"
if not self.Notes.can_change_groups(REQUEST, formsemestre_id):
raise ScoValueError(
"Vous n'avez pas le droit d'effectuer cette opération !"
)
cnx = self.GetDBConnexion()
etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0]
sem = sco_formsemestre.do_formsemestre_list(self, formsemestre_id)
ins = self.Notes.do_formsemestre_inscription_list(
{"etudid": etudid, "formsemestre_id": formsemestre_id}
)[0]
#
# -- check lock
sem = sco_formsemestre.get_formsemestre(self, formsemestre_id)
if sem["etat"] != "1":
raise ScoValueError("Modification impossible: semestre verrouille")
#
etud["semtitre"] = sem["titremois"]
H = [
'<h2><font color="#FF0000">Changement de groupe de</font> %(nomprenom)s (semestre %(semtitre)s)</h2><p>'
% etud
]
header = self.sco_header(
REQUEST, page_title="Changement de groupe de %(nomprenom)s" % etud
)
# Liste des groupes existants
raise NotImplementedError # XXX utiliser form_group_choice ou supprimer completement ?
#
H.append("""<form action="doChangeGroup" method="get" name="cg">""")
H.append(
"""<input type="hidden" name="etudid" value="%s">
<input type="hidden" name="formsemestre_id" value="%s">
<p>
(attention, vérifier que les groupes sont compatibles, selon votre organisation)
</p>
<script type="text/javascript">
function tweakmenu( gname ) {
var gr = document.cg.newgroupname.value;
if (!gr) {
alert("nom de groupe vide !");
return false;
}
var menutd = document.getElementById(gname);
var newopt = document.createElement('option');
newopt.value = gr;
var textopt = document.createTextNode(gr);
newopt.appendChild(textopt);
menutd.appendChild(newopt);
var msg = document.getElementById("groupemsg");
msg.appendChild( document.createTextNode("groupe " + gr + " créé; ") );
document.cg.newgroupname.value = "";
}
</script>
<p>Créer un nouveau groupe:
<input type="text" id="newgroupname" size="8"/>
<input type="button" onClick="tweakmenu( 'groupetd' );" value="créer groupe de %s"/>
<input type="button" onClick="tweakmenu( 'groupeanglais' );" value="créer groupe de %s"/>
<input type="button" onClick="tweakmenu( 'groupetp' );" value="créer groupe de %s"/>
</p>
<p id="groupemsg" style="font-style: italic;"></p>
<input type="submit" value="Changer de groupe">
<input type="button" value="Annuler" onClick="window.location='%s'">
</form>"""
% (
etudid,
formsemestre_id,
sem["nomgroupetd"],
sem["nomgroupeta"],
sem["nomgroupetp"],
REQUEST.URL1,
)
)
return header + "\n".join(H) + self.sco_footer(REQUEST)
return REQUEST.RESPONSE.redirect(dest_url)
# --- Gestion des groupes:
security.declareProtected(ScoView, "affectGroups")
@ -1366,6 +1356,7 @@ function tweakmenu( gname ) {
submitlabel="Valider",
cancelbutton="Annuler",
)
dest_url = self.ScoURL() + "/ficheEtud?etudid=" + etud["etudid"]
if tf[0] == 0:
return (
"\n".join(H)
@ -1375,16 +1366,12 @@ function tweakmenu( gname ) {
+ self.sco_footer(REQUEST)
)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(
REQUEST.URL1 + "/ficheEtud?etudid=" + etud["etudid"]
)
return REQUEST.RESPONSE.redirect(dest_url)
else:
data = tf[2]["photofile"].read()
status, diag = sco_photos.store_photo(self, etud, data, REQUEST=REQUEST)
if status != 0:
return REQUEST.RESPONSE.redirect(
self.ScoURL() + "/ficheEtud?etudid=" + etud["etudid"]
)
return REQUEST.RESPONSE.redirect(dest_url)
else:
H.append('<p class="warning">Erreur:' + diag + "</p>")
return "\n".join(H) + self.sco_footer(REQUEST)
@ -1406,7 +1393,7 @@ function tweakmenu( gname ) {
sco_photos.suppress_photo(self, etud, REQUEST=REQUEST)
return REQUEST.RESPONSE.redirect(
REQUEST.URL1 + "/ficheEtud?etudid=" + etud["etudid"]
self.ScoURL() + "/ficheEtud?etudid=" + etud["etudid"]
)
#
@ -1443,7 +1430,6 @@ function tweakmenu( gname ) {
operation_method="",
):
"Formulaire démission ou défaillance Etudiant"
cnx = self.GetDBConnexion()
etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0]
sem = sco_formsemestre.get_formsemestre(self, formsemestre_id)
if sem["etat"] != "1":
@ -1467,7 +1453,7 @@ function tweakmenu( gname ) {
"""<form action="%s" method="get">
<b>Date de la %s (J/M/AAAA):&nbsp;</b>
"""
% (operation_method, strlower(operation_name))
% (operation_method, scu.strlower(operation_name))
)
H.append(
"""
@ -1503,7 +1489,7 @@ function tweakmenu( gname ) {
etudid,
formsemestre_id,
event_date=event_date,
etat_new=DEF,
etat_new=sco_codes_parcours.DEF,
operation_method="defailleEtudiant",
event_type="DEFAILLANCE",
REQUEST=REQUEST,
@ -1581,7 +1567,7 @@ function tweakmenu( gname ) {
dialog_confirmed=dialog_confirmed,
args=args,
operation_name="défaillance",
etat_current=DEF,
etat_current=sco_codes_parcours.DEF,
etat_new="I",
operation_method="cancelDef",
event_type="DEFAILLANCE",
@ -1637,7 +1623,7 @@ function tweakmenu( gname ) {
args=ins, formsemestre_id=formsemestre_id
)
logdb(REQUEST, cnx, method=operation_method, etudid=etudid)
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"delete from scolar_events where etudid=%(etudid)s and formsemestre_id=%(formsemestre_id)s and event_type='"
+ event_type
@ -1665,7 +1651,6 @@ function tweakmenu( gname ) {
"Le formulaire HTML"
H = [self.sco_header(REQUEST, init_jquery_ui=True)]
F = self.sco_footer(REQUEST)
AUTHENTICATED_USER = REQUEST.AUTHENTICATED_USER
etudid = REQUEST.form.get("etudid", None)
cnx = self.GetDBConnexion()
descr = []
@ -1700,12 +1685,14 @@ function tweakmenu( gname ) {
# recuperation infos Apogee
# Si on a le code NIP, fait juste une requete, sinon tente de rechercher par nom
# (la recherche par nom ne fonctionne plus à Paris 13)
code_nip = initvalues.get("code_nip", "")
if code_nip:
try:
info = sco_portal_apogee.get_etud_apogee(self, code_nip)
except ValueError:
pass # XXX a terminer
# XXX A terminer
# code_nip = initvalues.get("code_nip", "")
# if code_nip:
# try:
# infos = sco_portal_apogee.get_etud_apogee(self, code_nip)
# except ValueError:
# infos = None
# pass # XXX a terminer
nom = REQUEST.form.get("nom", None)
if nom is None:
nom = initvalues.get("nom", None)
@ -1768,15 +1755,19 @@ function tweakmenu( gname ) {
("nom_usuel", {"size": 25, "title": "Nom usuel", "allow_null": True}),
(
"prenom",
{"size": 25, "title": "Prénom", "allow_null": CONFIG.ALLOW_NULL_PRENOM},
{
"size": 25,
"title": "Prénom",
"allow_null": scu.CONFIG.ALLOW_NULL_PRENOM,
},
),
(
"sexe",
"civilite",
{
"input_type": "menu",
"labels": ["H", "F"],
"allowed_values": ["MR", "MME"],
"title": "Genre",
"labels": ["Homme", "Femme", "Autre/neutre"],
"allowed_values": ["M", "F", "X"],
"title": "Civilité",
},
),
(
@ -1869,7 +1860,7 @@ function tweakmenu( gname ) {
{
"input_type": "menu",
"title": "Voie d'admission",
"allowed_values": TYPES_ADMISSION,
"allowed_values": scu.TYPES_ADMISSION,
},
),
(
@ -2102,7 +2093,7 @@ function tweakmenu( gname ) {
"billet_absence",
"identite",
]
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
for table in tables:
cursor.execute("delete from %s where etudid=%%(etudid)s" % table, etud)
cnx.commit()
@ -2110,7 +2101,9 @@ function tweakmenu( gname ) {
to_inval = [s["formsemestre_id"] for s in etud["sems"]]
if to_inval:
self.Notes._inval_cache(formsemestre_id_list=to_inval) # >
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(
self.ScoURL() + r"?head_message=Etudiant%20supprimé"
)
security.declareProtected(ScoEtudInscrit, "check_group_apogee")
@ -2123,7 +2116,7 @@ function tweakmenu( gname ) {
XXX A re-écrire pour API 2: prendre liste dans l'étape et vérifier à partir de cela.
"""
etat = etat or None
members, group, group_tit, sem, nbdem = sco_groups.get_group_infos(
members, group, _, sem, _ = sco_groups.get_group_infos(
self, group_id, etat=etat
)
formsemestre_id = group["formsemestre_id"]
@ -2229,8 +2222,8 @@ function tweakmenu( gname ) {
% (
REQUEST.URL0,
formsemestre_id,
strnone(group_id),
strnone(etat),
scu.strnone(group_id),
scu.strnone(etat),
formsemestre_id,
)
)
@ -2248,8 +2241,8 @@ function tweakmenu( gname ) {
% (
REQUEST.URL0,
formsemestre_id,
strnone(group_id),
strnone(etat),
scu.strnone(group_id),
scu.strnone(etat),
formsemestre_id,
)
)
@ -2262,8 +2255,13 @@ function tweakmenu( gname ) {
"formulaire import xls"
if formsemestre_id:
sem = sco_formsemestre.get_formsemestre(self.Notes, formsemestre_id)
dest_url = (
self.ScoURL()
+ "/formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
else:
sem = None
dest_url = self.ScoURL()
if sem and sem["etat"] != "1":
raise ScoValueError("Modification impossible: semestre verrouille")
H = [
@ -2379,7 +2377,7 @@ Les champs avec un astérisque (*) doivent être présents (nulls non autorisés
if tf[0] == 0:
return "\n".join(H) + tf[1] + "</li></ol>" + "\n".join(S) + F
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(dest_url)
else:
return ImportScolars.students_import_excel(
self,
@ -2428,7 +2426,6 @@ Les champs avec un astérisque (*) doivent être présents (nulls non autorisés
def form_students_import_infos_admissions(self, REQUEST, formsemestre_id=None):
"formulaire import xls"
sem = sco_formsemestre.get_formsemestre(self.Notes, formsemestre_id)
authuser = REQUEST.AUTHENTICATED_USER
F = self.sco_footer(REQUEST)
if not authuser.has_permission(ScoEtudInscrit, self):
@ -2517,7 +2514,11 @@ Les champs avec un astérisque (*) doivent être présents (nulls non autorisés
if tf[0] == 0:
return "\n".join(H) + tf[1] + help_text + F
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(
self.ScoURL()
+ "/formsemestre_status?formsemestre_id="
+ formsemestre_id
)
else:
return self._students_import_admission(
tf[2]["csvfile"],
@ -2598,7 +2599,6 @@ Les champs avec un astérisque (*) doivent être présents (nulls non autorisés
def stat_bac(self, formsemestre_id):
"Renvoie statistisques sur nb d'etudiants par bac"
cnx = self.GetDBConnexion()
sem = sco_formsemestre.get_formsemestre(self, formsemestre_id)
ins = self.Notes.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id}
)
@ -2727,23 +2727,23 @@ def manage_addZScolarForm(context, DeptId, REQUEST=None):
if tf[0] == 0:
return "\n".join(H) + tf[1] + context.standard_html_footer(context)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect("/ScoDoc")
else:
DeptId = tf[2]["DeptId"].strip()
db_cnx_string = tf[2]["db_cnx_string"].strip()
# default connexion string
if not db_cnx_string:
db_name = "SCO" + DeptId.upper()
db_user = SCO_DEFAULT_SQL_USER
db_user = scu.SCO_DEFAULT_SQL_USER
db_cnx_string = "user=%s dbname=%s port=%s" % (
db_user,
db_name,
SCO_DEFAULT_SQL_PORT,
scu.SCO_DEFAULT_SQL_PORT,
)
# vérifie que la bd existe et possede le meme nom de dept.
try:
cnx = psycopg2.connect(db_cnx_string)
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute("select * from sco_prefs where name='DeptName'")
except:
return _simple_error_page(
@ -2776,7 +2776,7 @@ def _simple_error_page(context, msg, DeptId=None):
H = [context.standard_html_header(context), "<h2>Erreur !</h2>", "<p>", msg, "</p>"]
if DeptId:
H.append(
'<p><a href="delete_dept?DeptId=%s&amp;force=1">Supprimer le dossier %s</a>(très recommandé !)</p>'
'<p><a href="delete_dept?DeptId=%s&force=1">Supprimer le dossier %s</a>(très recommandé !)</p>'
% (DeptId, DeptId)
)
H.append(context.standard_html_footer(context))

View File

@ -251,8 +251,8 @@ class exUserFolder(Folder,BasicUserFolder,BasicGroupFolderMixin,
('Manager',)),
('View', ('manage_changePassword',
'manage_forgotPassword', 'docLogin','docLoginRedirect',
'docLogout', 'logout', 'DialogHeader',
'manage_forgotPassword','docLoginRedirect',
'logout', 'DialogHeader',
'DialogFooter', 'manage_signupUser',
'MessageDialog', 'redirectToLogin','manage_changeProps'),
('Anonymous', 'Authenticated', 'Manager')),
@ -269,7 +269,7 @@ class exUserFolder(Folder,BasicUserFolder,BasicGroupFolderMixin,
('Access contents information', ('hasProperty', 'propertyIds',
'propertyValues','propertyItems',
'getProperty', 'getPropertyType',
'propertyMap', 'docLogin','docLoginRedirect',
'propertyMap', 'docLoginRedirect',
'DialogHeader', 'DialogFooter',
'MessageDialog', 'redirectToLogin',),
('Anonymous', 'Authenticated', 'Manager')),

View File

@ -27,8 +27,6 @@
from ZScolar import ZScolar, manage_addZScolarForm, manage_addZScolar
# from ZNotes import ZNotes, manage_addZNotesForm, manage_addZNotes
from ZScoDoc import ZScoDoc, manage_addZScoDoc
# from sco_zope import *
@ -53,23 +51,7 @@ def initialize(context):
icon="static/icons/sco_icon.png",
)
# context.registerHelp()
# context.registerHelpTitle("ZScolar")
# --- ZScoDoc
context.registerClass(
ZScoDoc, constructors=(manage_addZScoDoc,), icon="static/icons/sco_icon.png"
)
# --- ZNotes
# context.registerClass(
# ZNotes,
# constructors = (
# manage_addZNotesForm,
# manage_addZNotes
# ),
# icon = 'static/icons/notes_icon.png'
# )
# context.registerHelp()
# context.registerHelpTitle("ZNotes")

View File

@ -45,9 +45,16 @@ def bonus_iutv(notes_sport, coefs, infos=None):
return bonus
def bonus_iut_stdenis(notes_sport, coefs, infos=None):
"""Semblable à bonus_iutv mais sans coefficients et total limité à 0.5 points.
def bonus_direct(notes_sport, coefs, infos=None):
"""Un bonus direct et sans chichis: les points sont directement ajoutés à la moyenne générale.
Les coefficients sont ignorés: tous les points de bonus sont sommés.
(rappel: la note est ramenée sur 20 avant application).
"""
return sum(notes_sport)
def bonus_iut_stdenis(notes_sport, coefs, infos=None):
"""Semblable à bonus_iutv mais sans coefficients et total limité à 0.5 points."""
points = sum([x - 10 for x in notes_sport if x > 10]) # points au dessus de 10
bonus = points * 0.05 # ou / 20
return min(bonus, 0.5) # bonus limité à 1/2 point
@ -93,35 +100,6 @@ def bonus_iutva(notes_sport, coefs, infos=None):
return 0
# XXX Inutilisé (mai 2020) ? à confirmer avant suppression XXX
# def bonus_iut1grenoble_v0(notes_sport, coefs, infos=None):
# """Calcul bonus sport IUT Grenoble sur la moyenne générale
#
# La note de sport de nos étudiants va de 0 à 5 points.
# Chaque point correspond à un % qui augmente la moyenne de chaque UE et la moyenne générale.
# Par exemple : note de sport 2/5 : chaque UE sera augmentée de 2%, ainsi que la moyenne générale.
#
# Calcul ici du bonus sur moyenne générale et moyennes d'UE non capitalisées.
# """
# # les coefs sont ignorés
# # notes de 0 à 5
# points = sum([x for x in notes_sport])
# factor = (points / 4.0) / 100.0
# bonus = infos["moy"] * factor
# # Modifie les moyennes de toutes les UE:
# for ue_id in infos["moy_ues"]:
# ue_status = infos["moy_ues"][ue_id]
# if ue_status["sum_coefs"] > 0:
# # modifie moyenne UE ds semestre courant
# ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] * (1.0 + factor)
# if not ue_status["is_capitalized"]:
# # si non capitalisee, modifie moyenne prise en compte
# ue_status["moy"] = ue_status["cur_moy_ue"]
#
# # open('/tmp/log','a').write( pprint.pformat(ue_status) + '\n\n' )
# return bonus
def bonus_iut1grenoble_2017(notes_sport, coefs, infos=None):
"""Calcul bonus sport IUT Grenoble sur la moyenne générale (version 2017)

View File

@ -1,8 +1,14 @@
#!/bin/bash
# Version majeure de Debian (..., 9, 10)
debian_version=$(cat /etc/debian_version)
debian_version=${debian_version%%.*}
die() {
echo
echo "Erreur: $1"
echo
exit 1
}
# Fix path
export PATH="${PATH}":/usr/sbin:/sbin
@ -12,7 +18,8 @@ umask 0022
export SCODOC_DIR=/opt/scodoc/Products/ScoDoc
export SCODOC_VAR_DIR=$(realpath "$SCODOC_DIR/../../var/scodoc")
SCODOC_VAR_DIR=$(realpath -L "$SCODOC_DIR/../../var/scodoc") || die "can't set SCODOC_VAR_DIR"
export SCODOC_VAR_DIR
# = /opt/scodoc/var/scodoc
export SCODOC_VERSION_DIR="${SCODOC_VAR_DIR}/config/version"
export SCODOC_LOGOS_DIR="${SCODOC_VAR_DIR}/config/logos"
@ -24,22 +31,24 @@ export POSTGRES_SUPERUSER=postgres
export POSTGRES_USER=www-data
# psql command: if various versions installed, force the one we want:
if [ ${debian_version} = "10" ]
if [ "${debian_version}" = "10" ]
then
PSQL=/usr/lib/postgresql/11/bin/psql
elif [ ${debian_version} = "9" ]
export POSTGRES_SERVICE="postgresql@11-main.service"
elif [ "${debian_version}" = "9" ]
then
PSQL=/usr/lib/postgresql/9.6/bin/psql
elif [ ${debian_version} = "8" ]
export POSTGRES_SERVICE="postgresql"
elif [ "${debian_version}" = "8" ]
then
PSQL=/usr/lib/postgresql/9.4/bin/psql
elif [ ${debian_version} = "7" ]
elif [ "${debian_version}" = "7" ]
then
PSQL=/usr/lib/postgresql/9.1/bin/psql
elif [ ${debian_version} = "5" ]
elif [ "${debian_version}" = "5" ]
then
PSQL=/usr/lib/postgresql/8.3/bin/psql
elif [ ${debian_version} = "6" ]
elif [ "${debian_version}" = "6" ]
then
PSQL=/usr/lib/postgresql/8.4/bin/psql
else
@ -53,7 +62,7 @@ fi
export POSTGRES_PORT=5432
# Utilise par le script de reset du mot de passe:
if [ ${debian_version} -ge "7" ]
if [ "${debian_version}" -ge "7" ]
then
export ZOPE_VERSION=2.13
else

View File

@ -8,7 +8,13 @@ source utils.sh
check_uid_root $0
echo 'Installation du demarrage automatique de ScoDoc (systemd)'
cp $SCODOC_DIR/config/etc/scodoc.service /etc/systemd/system
# La variable POSTGRES_SERVICE doit être positionnée dans config.sh
# suivant la version de Debian et de postgresql
[ -z "${POSTGRES_SERVICE}" ] && die "incompatible Debian version"
cat "$SCODOC_DIR/config/etc/scodoc.service" | sed 's/{{postgresql}}/'"${POSTGRES_SERVICE}"'/g' > /etc/systemd/system/scodoc.service
systemctl enable scodoc.service
echo "A partir de maintenant, utiliser"

View File

@ -9,6 +9,7 @@
# E. Viennet, Juin 2008
#
set -eo pipefail
source config.sh
source utils.sh

View File

@ -4,11 +4,11 @@
# ScoDoc: suppression d'un departement
#
# Ce script supprime la base de donnees ScoDoc d'un departement
# *** le departement doit au prealable avoir <EFBFBD>t<EFBFBD> supprime via l'interface web ! ***
# *** le departement doit au prealable avoir été supprime via l'interface web ! ***
#
# Ne fonctionne que pour les configurations "standards" (dbname=xxx)
#
# Il doit <EFBFBD>tre lanc<6E> par l'utilisateur unix root dans le repertoire .../config
# Il doit être lancé par l'utilisateur unix root dans le repertoire .../config
# ^^^^^^^^^^^^^^^^^^^^^
# E. Viennet, Sept 2008
#
@ -17,7 +17,7 @@
source config.sh
source utils.sh
check_uid_root $0
check_uid_root "$0"
usage() {
echo "$0 [-n DEPT]"
echo "(default to interactive mode)"
@ -59,11 +59,16 @@ then
scodocctl stop
# suppression de la base postgres
db_name=$(cat "$cfg_pathname" | sed '/^dbname=*/!d; s///;q')
echo "suppression de la base postgres $db_name"
db_name=$(sed '/^dbname=*/!d; s///;q' < "$cfg_pathname")
if su -c "psql -lt" "$POSTGRES_SUPERUSER" | cut -d \| -f 1 | grep -wq "$db_name"
then
echo "Suppression de la base postgres $db_name ..."
su -c "dropdb $db_name" "$POSTGRES_SUPERUSER" || terminate "ne peux supprimer base de donnees $db_name"
else
echo "la base postgres $db_name n'existe pas."
fi
# suppression du fichier de config
/bin/rm -f "$cfg_pathname" || terminate "ne peux supprimer $cfg_pathname"
/bin/rm -f "$cfg_pathname" || terminate "Ne peux supprimer $cfg_pathname"
# relance ScoDoc
if [ "$interactive" = 1 ]
then
@ -76,6 +81,8 @@ then
fi
exit 0
else
echo 'Erreur: pas de configuration trouvee pour "'"$DEPT"'"'
exit 1
echo 'Attention: pas de configuration trouvee pour "'"$DEPT"'"'
echo " fichier cherché: $cfg_pathname"
echo " => ne fait rien."
exit 0
fi

View File

@ -1,13 +1,15 @@
# ScoDoc7 service
# Zope based
# Depends on postgresql
# => is restarted when postgresql restarts
# Depends on {{postgresql}} (replaced by installation script by
# postgresql@11-main.service on Debian 10
# postgresql on Debian <= 9
# => is restarted when {{postgresql}} restarts
#
[Unit]
Description=ScoDoc 7 service
After=network.target postgresql@11-main.service
Requires=postgresql@11-main.service
PartOf=postgresql@11-main.service
After=network.target {{postgresql}}
Requires={{postgresql}}
PartOf={{postgresql}}
StartLimitIntervalSec=0
[Service]
@ -21,4 +23,4 @@ ExecStop=/opt/scodoc/bin/zopectl stop
ExecReload=/opt/scodoc/bin/zopectl restart
[Install]
WantedBy=postgresql@11-main.service
WantedBy={{postgresql}}

64
config/fix_bug70_db.py Normal file
View File

@ -0,0 +1,64 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Fix bug #70
Utiliser comme:
scotests/scointeractive.sh DEPT config/fix_bug70_db.py
"""
context = context.Notes # pylint: disable=undefined-variable
REQUEST = REQUEST # pylint: disable=undefined-variable
import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error
import os
import sys
import sco_utils
import notesdb
import sco_formsemestre
import sco_formsemestre_edit
import sco_moduleimpl
G = sco_fake_gen.ScoFake(context.Notes)
def fix_formsemestre_formation_bug70(formsemestre_id):
"""Le bug #70 a pu entrainer des incohérences
lors du clonage avorté de semestres.
Cette fonction réassocie le semestre à la formation
à laquelle appartiennent ses modulesimpls.
2021-04-23
"""
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
cursor = notesdb.SimpleQuery(
context,
"""SELECT m.formation_id
FROM notes_modules m, notes_moduleimpl mi
WHERE mi.module_id = m.module_id
AND mi.formsemestre_id = %(formsemestre_id)s
""",
{"formsemestre_id": formsemestre_id},
)
modimpls_formations = set([x[0] for x in cursor])
if len(modimpls_formations) > 1:
# this is should not occur
G.log(
"Warning: fix_formsemestre_formation_bug70: modules from several formations in sem %s"
% formsemestre_id
)
elif len(modimpls_formations) == 1:
modimpls_formation_id = modimpls_formations.pop()
if modimpls_formation_id != sem["formation_id"]:
# Bug #70: fix
G.log("fix_formsemestre_formation_bug70: fixing %s" % formsemestre_id)
sem["formation_id"] = modimpls_formation_id
context.do_formsemestre_edit(sem, html_quote=False)
formsemestre_ids = [
x[0]
for x in notesdb.SimpleQuery(
context, "SELECT formsemestre_id FROM notes_formsemestre", {}
)
]
for formsemestre_id in formsemestre_ids:
fix_formsemestre_formation_bug70(formsemestre_id)

View File

@ -32,11 +32,13 @@ fi
chgrp www-data $SCODOC_DIR $SCODOC_DIR/ZopeProducts/*
chmod g+w $SCODOC_DIR $SCODOC_DIR/ZopeProducts/*
if [ -d "${SCODOC_VAR_DIR}"/photos ]; then
chgrp -R www-data "${SCODOC_VAR_DIR}"/photos
chmod -R g+w "${SCODOC_VAR_DIR}"/photos
fi
if [ ! -e "${SCODOC_VERSION_DIR}" ]; then
mkdir "${SCODOC_VERSION_DIR}"
mkdir -p "${SCODOC_VERSION_DIR}"
chown www-data.www-data "${SCODOC_VERSION_DIR}"
fi
@ -96,8 +98,6 @@ apt-get -y install python-cracklib # was python-crack
apt-get -y install python-icalendar
apt-get -y install python-requests
apt-get -y install python-egenix-mxtools python-egenix-mxdatetime
# ------------
SVNVERSION=$(cd ..; svnversion)
@ -220,9 +220,7 @@ read ans
if [ "$(norm_ans "$ans")" != 'N' ]
then
# ScoDoc 7.19+ uses systemd
echo 'Installation du demarrage automatique de ScoDoc (systemd)'
cp $SCODOC_DIR/config/etc/scodoc.service /etc/systemd/system
systemctl enable scodoc.service
$SCODOC_DIR/config/configure_systemd.sh
fi

View File

@ -583,27 +583,6 @@ for dept in get_depts():
],
)
# add etape_apo2
check_field(
cnx,
"notes_formsemestre",
"etape_apo2",
["alter table notes_formsemestre add column etape_apo2 text"],
)
# add etape_apo3
check_field(
cnx,
"notes_formsemestre",
"etape_apo3",
["alter table notes_formsemestre add column etape_apo3 text"],
)
# add etape_apo4
check_field(
cnx,
"notes_formsemestre",
"etape_apo4",
["alter table notes_formsemestre add column etape_apo4 text"],
)
# add publish_incomplete
check_field(
cnx,
@ -1070,6 +1049,21 @@ for dept in get_depts():
"etape",
["alter table notes_formsemestre_inscription add column etape text"],
)
# Migre identite.sexe vers identite.civilite: normalise valeurs: M, F, X
check_field(
cnx,
"identite",
"civilite",
[
"UPDATE identite set sexe='M' where upper(sexe) = 'M' or upper(sexe) = 'M.' or upper(sexe) = 'MR' or upper(sexe) = 'H'",
"UPDATE identite set sexe='F' where upper(sexe) = 'F' or upper(sexe) = 'MME' or upper(sexe) = 'MLLE' or upper(sexe) = 'MELLE' or upper(sexe) = 'MLLE.' or upper(sexe) = 'F'",
"UPDATE identite set sexe='X' where sexe is NULL",
"ALTER TABLE identite RENAME COLUMN sexe TO civilite",
"ALTER TABLE identite ALTER COLUMN civilite SET NOT NULL",
"ALTER TABLE identite ADD CONSTRAINT civchk CHECK (civilite IN ('M', 'F', 'X'))",
],
)
# Add here actions to performs after upgrades:
cnx.commit()
cnx.close()

19
config/postupgrade.py Executable file → Normal file
View File

@ -1,4 +1,5 @@
#!/opt/zope213/bin/python
# -*- coding: utf-8 -*-
"""
ScoDoc post-upgrade script.
@ -11,15 +12,16 @@ _before_ upgrading the database.
E. Viennet, June 2008
Mar 2017: suppress upgrade of very old Apache configs
Aug 2020: move photos to .../var/scodoc/
Apr 2021: bug #70
"""
import os
import sys
import glob
import shutil
from scodocutils import log, SCODOC_DIR, SCODOC_VAR_DIR, SCODOC_LOGOS_DIR
from scodocutils import log, SCODOC_DIR, SCODOC_VAR_DIR, SCODOC_LOGOS_DIR, SCO_TMPDIR
if os.getuid() != 0:
log('postupgrade.py: must be run as root')
log("postupgrade.py: must be run as root")
sys.exit(1)
# ---
@ -54,6 +56,19 @@ for d in glob.glob( SCODOC_DIR + "/logos_*" ):
log("Moving %s to %s" % (d, SCODOC_LOGOS_DIR))
shutil.move(d, SCODOC_LOGOS_DIR)
# Fix bug #70
depts = [
os.path.splitext(os.path.basename(f))[0] for f in glob.glob(depts_dir + "/*.cfg")
]
for dept in depts:
fixed_filename = SCO_TMPDIR + "/.%s_bug70_fixed" % dept
if not os.path.exists(fixed_filename):
log("fixing #70 on %s" % dept)
os.system("../scotests/scointeractive.sh -x %s config/fix_bug70_db.py" % dept)
# n'essaie qu'une fois, même en cas d'échec
f = open(fixed_filename, "a")
f.close()
# Continue here...
# ---

View File

@ -51,7 +51,7 @@ then
fi
chown root "$DEST"
# Zope DB and ScoDoc archives:
# Zope DB, ScoDoc archives, configuration, photos, etc.
echo "Copying var/ ..."
cp -rp "$INSTANCE_DIR/var" "$DEST"

8
config/scodocutils.py Normal file → Executable file
View File

@ -9,6 +9,12 @@ import sys, os, psycopg2, glob, subprocess, traceback, time
sys.path.append("..")
# INSTANCE_HOME est nécessaire pour sco_utils.py
# note: avec le python 2.7 de Zope2, l'import de pyscopg2 change
# INSTANCE_HOME dans l'environnement !
# Ici on le fixe à la "bonne" valeur pour ScoDoc7.
os.environ["INSTANCE_HOME"] = "/opt/scodoc"
def log(msg):
sys.stdout.flush()
@ -24,8 +30,10 @@ SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "")
if not SCODOC_VAR_DIR:
log("Error: environment variable SCODOC_VAR_DIR is not defined")
sys.exit(1)
SCO_TMPDIR = os.path.join(SCODOC_VAR_DIR, "tmp")
SCODOC_LOGOS_DIR = os.environ.get("SCODOC_LOGOS_DIR", "")
def get_dept_cnx_str(dept):
"db cnx string for dept"
f = os.path.join(SCODOC_VAR_DIR, "config", "depts", dept + ".cfg")

17
config/upgrade.sh Executable file → Normal file
View File

@ -14,7 +14,7 @@ cd /opt/scodoc/Products/ScoDoc/config
source config.sh
source utils.sh
check_uid_root $0
check_uid_root "$0"
if [ -z "$SCODOC_UPGRADE_RUNNING" ]
then
@ -23,6 +23,7 @@ fi
# Upgrade svn working copy if possible
svnver=$(svn --version --quiet)
# shellcheck disable=SC2072
if [[ ${svnver} > "1.7" ]]
then
(cd "$SCODOC_DIR"; find . -name .svn -type d -exec dirname {} \; | xargs svn upgrade)
@ -63,7 +64,7 @@ CMD="curl --fail --connect-timeout 5 --silent http://scodoc.iutv.univ-paris13.fr
#echo $CMD
SVERSION="$(${CMD})"
if [ $? == 0 ]; then
if [ "$?" == 0 ]; then
#echo "answer=${SVERSION}"
echo "${SVERSION}" > "${SCODOC_VERSION_DIR}"/scodoc.sn
else
@ -72,13 +73,13 @@ fi
# Check that no Zope "access" file has been forgotten in the way:
if [ -e $SCODOC_DIR/../../access ]
if [ -e "$SCODOC_DIR"/../../access ]
then
mv $SCODOC_DIR/../../access $SCODOC_DIR/../../access.bak
mv "$SCODOC_DIR"/../../access "$SCODOC_DIR"/../../access.bak
fi
# Fix some permissions which may have been altered in the way:
chsh -s /bin/sh $POSTGRES_USER # www-data, nologin in Debian 9
chsh -s /bin/sh "$POSTGRES_USER" # www-data, nologin in Debian 9
chown root.www-data "$SCODOC_DIR" # important to create .pyc
chmod 775 "${SCODOC_DIR}"
chmod a+r "$SCODOC_DIR"/*.py
@ -119,6 +120,10 @@ then
/opt/zope213/bin/pip install requests
fi
# upgrade old dateutil (check version manually to speedup)
v=$(/opt/zope213/bin/python -c "import dateutil; print dateutil.__version__")
[[ "$v" < "2.8.1" ]] && /opt/zope213/bin/pip install --upgrade python-dateutil
# Ensure www-data can duplicate databases (for dumps)
su -c $'psql -c \'alter role "www-data" with CREATEDB;\'' "$POSTGRES_SUPERUSER"
#'
@ -128,7 +133,7 @@ echo "Executing post-upgrade script..."
"$SCODOC_DIR"/config/postupgrade.py
echo "Executing post-upgrade database script..."
su -c "$SCODOC_DIR/config/postupgrade-db.py" $POSTGRES_USER
su -c "$SCODOC_DIR/config/postupgrade-db.py" "$POSTGRES_USER"
#
echo

View File

@ -45,7 +45,27 @@ HEAD = """# -*- coding: utf-8 -*-
#
# Command: %s %s
#
from sco_codes_parcours import *
from sco_codes_parcours import (
DUTRule,
ADC,
ADJ,
ADM,
AJ,
ALL,
ATB,
ATJ,
ATT,
CMP,
NAR,
NEXT,
RA_OR_NEXT,
RA_OR_RS,
RAT,
REO,
REDOANNEE,
REDOSEM,
RS_OR_NEXT,
)
rules_source_file='%s'
@ -55,15 +75,15 @@ rules_source_file='%s'
sourcefile,
)
from sco_codes_parcours import *
import sco_utils as scu
def _fmt(s):
if not s:
return None
if strlower(s) in ("ok", "oui", "o", "y", "yes"):
if scu.strlower(s) in ("ok", "oui", "o", "y", "yes"):
return True
if strlower(s) in ("no", "non"):
if scu.strlower(s) in ("no", "non"):
return False
if s == "*":
return ALL

View File

@ -32,10 +32,13 @@ nt = context.Notes._getNotesCache().get_NotesTable(context.Notes, formsemestre_i
"""
import pdb
import pprint
from notesdb import *
import notesdb as ndb
from notesdb import * # pylint: disable=unused-wildcard-import
from notes_log import log
from sco_utils import *
import sco_utils as scu
from sco_utils import * # pylint: disable=unused-wildcard-import
from gen_tables import GenTable
import sco_archives
@ -54,17 +57,23 @@ import sco_bulletins_xml
# Prend le premier departement comme context
def go(app, n=0):
def go(app, n=0, verbose=True):
context = app.ScoDoc.objectValues("Folder")[n].Scolarite
if verbose:
print("context in dept ", context.DeptId())
return context
def go_dept(app, dept):
def go_dept(app, dept, verbose=True):
objs = app.ScoDoc.objectValues("Folder")
for o in objs:
try:
context = o.Scolarite
except AttributeError:
# ignore other folders, like old "icons"
continue
if context.DeptId() == dept:
if verbose:
print("context in dept ", context.DeptId())
return context
raise ValueError("dep %s not found" % dept)
@ -106,7 +115,6 @@ class DummyRequest:
self.AUTHENTICATED_USER = FakeUser("admin")
self.form = {}
self.URL = "http://scodoc/"
self.URL1 = self.URL
self.URL0 = self.URL
self.BASE0 = "localhost"
self.REMOTE_HOST = "localhost"

View File

@ -1,62 +0,0 @@
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<dtml-call "REQUEST.set( 'E', do_entreprise_list( args={ 'entreprise_id' : entreprise_id } )[0])">
<dtml-call "REQUEST.set( 'curtime', ZopeTime().strftime('%d/%m/%Y') )">
<dtml-call "REQUEST.set( 'link_create_corr', make_link_create_corr(entreprise_id) )">
<dtml-call "REQUEST.set( 'correspondants',
do_entreprise_correspondant_listnames( args={ 'entreprise_id' : entreprise_id }) )">
<dtml-if "len(correspondants) == 0">
<dtml-call "REQUEST.set( 'correspondants', [ ('inconnu','') ])">
</dtml-if>
<h2 class="entreprise_contact">Nouveau "contact" avec l'entreprise <dtml-var "E['nom']"></h2>
<dtml-call "REQUEST.set('tf', TrivialFormulator( URL0, REQUEST.form,
(
('entreprise_id', { 'input_type' : 'hidden', 'default' : entreprise_id }),
('type_contact', { 'input_type' : 'menu', 'title' : 'Objet',
'allowed_values' : ('Prospection', 'Stage étudiant',
'Contrat Apprentissage DUT GTR1',
'Contrat Apprentissage DUT GTR2',
'Contrat Apprentissage Licence SQRT',
'Projet', 'Autre' ),
'default' : 'Stage étudiant'
}),
('date', { 'size' : 12, 'title' : 'Date du contact (j/m/a)', 'allow_null' : False,
'default': curtime }),
('entreprise_corresp_id', { 'input_type' : 'menu', 'title' : 'Correspondant entreprise',
'explanation' : link_create_corr, 'allow_null' : True,
'labels' : [ x[0] for x in correspondants ],
'allowed_values' : [ x[1] for x in correspondants ] }),
('etudiant', { 'size' : 16, 'title' : 'Etudiant concerné', 'allow_null' : True,
'explanation' : 'nom (si pas ambigu) ou code' }),
('enseignant', { 'size' : 16, 'title' : 'Enseignant (tuteur)', 'allow_null' : True }),
('description', { 'input_type' : 'textarea', 'rows' : 3, 'cols': 40, 'title' : 'Description' }),
),
cancelbutton='Annuler',
submitlabel = 'Ajouter ce contact', readonly = REQUEST['_read_only']
))">
<dtml-call "REQUEST.set( 'etudok', do_entreprise_check_etudiant(etudiant) )">
<dtml-if "(tf[0] == 0) or (etudok[0] == 0)">
<dtml-if "etudok[0] == 0">
<p class="entreprise_warning"><dtml-var "etudok[1]"></p>
</dtml-if>
<dtml-var "tf[1]">
<dtml-elif "tf[0] == -1">
<dtml-call "RESPONSE.redirect( URL1 )">
<dtml-else>
<dtml-call "tf[2].update( { 'etudid' : etudok[1] })">
<dtml-var "REQUEST.set( 'matiere_id', do_entreprise_contact_create( tf[2] ) )">
<dtml-call "RESPONSE.redirect( URL1 )">
</dtml-if tf>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -1,27 +0,0 @@
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<dtml-call "REQUEST.set( 'F', do_entreprise_contact_list( args={ 'entreprise_contact_id' : entreprise_contact_id } )[0])">
<h2>Suppression du contact</h2>
<dtml-call "REQUEST.set('tf', TrivialFormulator( URL0, REQUEST.form,
( ('entreprise_contact_id', { 'input_type' : 'hidden' }),
),
initvalues = F,
submitlabel = 'Confirmer la suppression',
cancelbutton = 'Annuler', readonly = REQUEST['_read_only']
))">
<dtml-if "tf[0] == 0">
<dtml-var "tf[1]">
<dtml-else>
<dtml-if "tf[0] == -1">
<dtml-call "RESPONSE.redirect( URL1 )">
<dtml-else>
<dtml-call "do_entreprise_contact_delete( F['entreprise_contact_id'] )">
<dtml-call "RESPONSE.redirect(URL1)">
</dtml-if>
</dtml-if>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -1,62 +0,0 @@
<dtml-call "REQUEST.set( 'F', do_entreprise_contact_list( args={ 'entreprise_contact_id' : entreprise_contact_id } )[0])">
<dtml-call "REQUEST.set( 'entreprise_id', F['entreprise_id'])">
<dtml-call "REQUEST.set( 'link_create_corr', make_link_create_corr(F['entreprise_id']) )">
<dtml-call "REQUEST.set( 'E', do_entreprise_list( args={ 'entreprise_id' : F['entreprise_id'] } )[0])">
<dtml-call "REQUEST.set( 'correspondants',
do_entreprise_correspondant_listnames( args={ 'entreprise_id' : F['entreprise_id'] })+ [ ('inconnu','') ])">
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<h2 class="entreprise_contact"><dtml-var title_or_id></h2>
<h3>Contact avec entreprise <dtml-var "E['nom']"></h3>
<dtml-call "REQUEST.set('tf', TrivialFormulator( URL0, REQUEST.form,
( ('entreprise_contact_id', { 'default' : entreprise_contact_id, 'input_type' : 'hidden' }),
('entreprise_id', { 'input_type' : 'hidden', 'default' : F['entreprise_id'] }),
('type_contact', { 'input_type' : 'menu', 'title' : 'Objet',
'allowed_values' : ('Prospection', 'Stage étudiant', 'Contrat Apprentissage', 'Projet', 'Autre' ),
}),
('date', { 'size' : 12, 'title' : 'Date du contact (j/m/a)', 'allow_null' : False }),
('entreprise_corresp_id', { 'input_type' : 'menu', 'title' : 'Correspondant entreprise',
'explanation' : link_create_corr,
'allow_null' : True,
'labels' : [ x[0] for x in correspondants ],
'allowed_values' : [ x[1] for x in correspondants ] }),
('etudiant', { 'size' : 16, 'title' : 'Etudiant concerné', 'allow_null' : True,
'default' : F['etudid'],
'explanation' : 'nom (si pas ambigu) ou code' }),
('enseignant', { 'size' : 16, 'title' : 'Enseignant (tuteur)', 'allow_null' : True }),
('description', { 'input_type' : 'textarea', 'rows' : 3, 'cols': 40, 'title' : 'Description' }),
),
cancelbutton = 'Annuler',
initvalues = F,
submitlabel = 'Modifier les valeurs', readonly = REQUEST['_read_only']
))">
<dtml-call "REQUEST.set( 'etudok', do_entreprise_check_etudiant(etudiant) )">
<dtml-if "(tf[0] == 0) or (etudok[0] == 0)">
<dtml-if "etudok[0] == 0">
<p class="entreprise_warning"><dtml-var "etudok[1]"></p>
</dtml-if>
<dtml-var "tf[1]">
<dtml-unless "REQUEST['_read_only']">
<p class="entreprise_descr"><a class="entreprise_delete" href="entreprise_contact_delete?entreprise_contact_id=<dtml-var entreprise_contact_id>">Supprimer ce contact</a> </p>
</dtml-unless>
<dtml-elif "tf[0] == -1">
<dtml-call "RESPONSE.redirect( URL1 )" >
<dtml-else>
<dtml-call "tf[2].update( { 'etudid' : etudok[1] })">
<dtml-call "do_entreprise_contact_edit( tf[2] )">
<dtml-call "RESPONSE.redirect( URL1 + '/entreprise_contact_list?entreprise_id=' + str(F['entreprise_id']) )" >
</dtml-if>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -1,54 +0,0 @@
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<dtml-if entreprise_id>
<dtml-call "REQUEST.set( 'E', do_entreprise_list( args={ 'entreprise_id' : entreprise_id } )[0])">
<dtml-call "REQUEST.set( 'C', do_entreprise_contact_list( args={ 'entreprise_id' : entreprise_id }) )">
<h2 class="entreprise_contact">Listes des contacts avec l'entreprise <dtml-var "E['nom']"> </h2>
<dtml-else>
<h2 class="entreprise_contact">Listes des contacts</h2>
<dtml-call "REQUEST.set( 'C', do_entreprise_contact_list( args={ }))">
</dtml-if entreprise_id>
<table class="contact_list">
<tr><th>Date</th><th>Objet</th><dtml-unless entreprise_id><th>Entreprise</th></dtml-unless><th>Etudiant</th><th>Description</th></tr>
<dtml-in "C">
<dtml-let F="_['sequence-item']">
<dtml-call "REQUEST.set( 'Ec', do_entreprise_list( args={ 'entreprise_id' : F['entreprise_id'] } )[0])">
<tr class="<dtml-if sequence-odd>contact_list_odd<dtml-else>contact_list_even</dtml-if>">
<td><a class="contact_edit" href="entreprise_contact_edit?entreprise_contact_id=<dtml-var "F['entreprise_contact_id']">">
<dtml-var "F['date']">
</td>
<td class="contact_descr"><dtml-var "F['type_contact']"></td>
<dtml-unless entreprise_id><td class="contact_descr"><dtml-var "Ec['nom']"></td></dtml-unless>
<td class="contact_descr">
<dtml-if "F['etudid']">
<dtml-in "getEtudInfo(etudid=F['etudid'],filled=1)" mapping>
<a href="<dtml-var ScoURL>/ficheEtud?etudid=<dtml-var "F['etudid']">"><dtml-var nomprenom></a>
</dtml-in>
</dtml-if etudid>
</td>
<td class="contact_descr"><dtml-var "F['description']"></td>
</tr>
</dtml-let F>
<dtml-else>
<tr><td colspan="4"><font color="red"><em>Aucun contact !</em></font></td></tr>
</dtml-in>
</table>
<dtml-unless "REQUEST['_read_only']">
<dtml-if entreprise_id>
<p class="entreprise_create">
<a class="entreprise_create" href="entreprise_contact_create?entreprise_id=<dtml-var "E['entreprise_id']">">nouveau "contact"</a>
</p>
</dtml-if>
</dtml-unless>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -1,43 +0,0 @@
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<dtml-call "REQUEST.set( 'UE', do_entreprise_list( args={ 'entreprise_id' : entreprise_id } )[0])">
<h2 class="entreprise_correspondant"><dtml-var title_or_id> dans l'entreprise <dtml-var "UE['nom']"></h2>
<dtml-call "REQUEST.set('tf', TrivialFormulator( URL0, REQUEST.form,
( ('entreprise_id', { 'input_type' : 'hidden', 'default' : entreprise_id }),
('civilite', { 'input_type' : 'menu',
'labels' : [ 'M.', 'Mme' ], 'allowed_values' : [ 'M.', 'Mme' ] }),
('nom', { 'size' : 25, 'title' : 'Nom', 'allow_null' : False }),
('prenom', { 'size' : 25, 'title' : 'Prénom' }),
('fonction', { 'input_type' : 'menu',
'allowed_values' : ('Directeur', 'RH', 'Resp. Administratif', 'Tuteur', 'Autre'),
'default' : 'Tuteur',
'explanation' : 'fonction via à vis de l\'IUT' }),
('phone1', { 'size' : 14, 'title' : 'Téléphone 1', }),
('phone2', { 'size' : 14, 'title' : 'Téléphone 2', }),
('mobile', { 'size' : 14, 'title' : 'Tél. mobile', }),
('fax', { 'size' : 14, 'title' : 'Fax', }),
('mail1', { 'size' : 25, 'title' : 'e-mail', }),
('mail2', { 'size' : 25, 'title' : 'e-mail 2', }),
('note', { 'input_type' : 'textarea', 'rows' : 3, 'cols': 40, 'title' : 'Note' }),
),
cancelbutton='Annuler',
submitlabel = 'Ajouter ce correspondant', readonly = REQUEST['_read_only']
))">
<dtml-if "tf[0] == 0">
<dtml-var "tf[1]">
<dtml-else>
<dtml-var "REQUEST.set( 'matiere_id', do_entreprise_correspondant_create( tf[2] ) )">
<dtml-call "RESPONSE.redirect( URL1 )">
</dtml-if>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -1,27 +0,0 @@
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<dtml-call "REQUEST.set( 'F', do_entreprise_correspondant_list( args={ 'entreprise_corresp_id' : entreprise_corresp_id } )[0])">
<h2>Suppression du correspondant <dtml-var "F['nom']"> <dtml-var "F['prenom']"></h2>
<dtml-call "REQUEST.set('tf', TrivialFormulator( URL0, REQUEST.form,
( ('entreprise_corresp_id', { 'input_type' : 'hidden' }),
),
initvalues = F,
submitlabel = 'Confirmer la suppression',
cancelbutton = 'Annuler', readonly = REQUEST['_read_only']
))">
<dtml-if "tf[0] == 0">
<dtml-var "tf[1]">
<dtml-else>
<dtml-if "tf[0] == -1">
<dtml-call "RESPONSE.redirect( URL1 )">
<dtml-else>
<dtml-call "do_entreprise_correspondant_delete( F['entreprise_corresp_id'] )">
<dtml-call "RESPONSE.redirect(URL1)">
</dtml-if>
</dtml-if>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -1,41 +0,0 @@
<dtml-call "REQUEST.set( 'F', do_entreprise_correspondant_list( args={ 'entreprise_corresp_id' : entreprise_corresp_id } )[0])">
<dtml-call "REQUEST.set( 'entreprise_id', F['entreprise_id'])">
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<h2 class="entreprise_correspondant"><dtml-var title_or_id></h2>
<dtml-call "REQUEST.set('tf', TrivialFormulator( URL0, REQUEST.form,
( ('entreprise_corresp_id', { 'default' : entreprise_corresp_id, 'input_type' : 'hidden' }),
('civilite', { 'input_type' : 'menu',
'labels' : [ 'M.', 'Mme' ], 'allowed_values' : [ 'M.', 'Mme' ] }),
('nom', { 'size' : 25, 'title' : 'Nom', 'allow_null' : False }),
('prenom', { 'size' : 25, 'title' : 'Prénom' }),
('fonction', { 'input_type' : 'menu',
'allowed_values' : ('Directeur', 'RH', 'Resp. Administratif', 'Tuteur', 'Autre'),
'explanation' : 'fonction via à vis de l\'IUT' }),
('phone1', { 'size' : 14, 'title' : 'Téléphone 1', }),
('phone2', { 'size' : 14, 'title' : 'Téléphone 2', }),
('mobile', { 'size' : 14, 'title' : 'Tél. mobile', }),
('fax', { 'size' : 14, 'title' : 'Fax', }),
('mail1', { 'size' : 25, 'title' : 'e-mail', }),
('mail2', { 'size' : 25, 'title' : 'e-mail 2', }),
('note', { 'input_type' : 'textarea', 'rows' : 3, 'cols': 40, 'title' : 'Note' }),
),
cancelbutton = 'Annuler',
initvalues = F,
submitlabel = 'Modifier les valeurs', readonly = REQUEST['_read_only']
))">
<dtml-if "tf[0] == 0">
<dtml-var "tf[1]">
<dtml-elif "tf[0] == -1">
<dtml-call "RESPONSE.redirect( REQUEST.HTTP_REFERER )" >
<dtml-call "RESPONSE.redirect( URL1 + '/entreprise_correspondant_list?entreprise_id=' + str(F['entreprise_id']) )" >
<dtml-else>
<dtml-call "do_entreprise_correspondant_edit( tf[2] )">
<dtml-call "RESPONSE.redirect( URL1 + '/entreprise_correspondant_list?entreprise_id=' + str(F['entreprise_id']) )" >
</dtml-if>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -1,52 +0,0 @@
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<dtml-call "REQUEST.set( 'E', do_entreprise_list( args={ 'entreprise_id' : entreprise_id } )[0])">
<h2>Listes des correspondants dans l'entreprise <dtml-var "E['nom']"> </h2>
<table class="corr_list">
<tr><th>Nom</th><th>Entreprise</th><th>Fonction</th><th>Téléphone</th><th>Mail</th><th>Note</th></tr>
<dtml-in "do_entreprise_correspondant_list( args={ 'entreprise_id' : entreprise_id })">
<dtml-let F="_['sequence-item']">
<tr class="<dtml-if sequence-odd>corr_list_odd<dtml-else>corr_list_even</dtml-if>">
<td><a class="corr_edit" href="entreprise_correspondant_edit?entreprise_corresp_id=<dtml-var "F['entreprise_corresp_id']">">
<dtml-if "F['nom']"><dtml-var "F['nom'].lower().capitalize()"></dtml-if>
<dtml-if "F['prenom']"> <dtml-var "F['prenom'].lower().capitalize()"></dtml-if>
</a>
</td>
<td class="corr_descr"><dtml-var "E['nom']"></td>
<td class="corr_descr"><dtml-var "F['fonction']"></td>
<td class="corr_descr">
<dtml-if "F['phone1']"><dtml-var "F['phone1']"></dtml-if>
<dtml-if "F['phone2']"> / <dtml-var "F['phone2']"></dtml-if>
<dtml-if "F['mobile']"> / <dtml-var "F['mobile']"></dtml-if>
</td>
<td class="corr_descr">
<dtml-if "F['mail1']"> <a href="mailto:<dtml-var "F['mail1']">"><dtml-var "F['mail1']"></a></dtml-if>
<dtml-if "F['mail1']"> <br> <a href="mailto:<dtml-var "F['mail2']">"><dtml-var "F['mail2']"></a></dtml-if>
</td>
<td class="corr_descr"><dtml-var "F['note']"></td>
<td class="corr_descr"><a class="corr_delete" href="entreprise_correspondant_edit?entreprise_corresp_id=<dtml-var "F['entreprise_corresp_id']">">modifier</a> </td>
<td class="corr_descr"><a class="corr_delete" href="entreprise_correspondant_delete?entreprise_corresp_id=<dtml-var "F['entreprise_corresp_id']">">supprimer</a> </td>
</tr>
</dtml-let>
<dtml-else>
<tr><td colspan="4"><font color="red"><em>Aucun correspondant dans cette entreprise !</em></font></td></tr>
</dtml-in>
</table>
<p class="entreprise_create"><a class="entreprise_create" href="<dtml-var "URL1 + '/entreprise_correspondant_create?entreprise_id=' + entreprise_id">">
Ajouter un correspondant dans l'entreprise <dtml-var "E['nom']"> </a><p>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -1,60 +0,0 @@
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<dtml-call "REQUEST.set( 'F', do_entreprise_list( args={ 'entreprise_id' : entreprise_id } )[0])">
<h2>Suppression de l'entreprise <dtml-var "F['nom']"> </h2>
<p class="entreprise_warning">Attention: supression définitive de l'entreprise, de ses correspondants et contacts.
</p>
<dtml-call "REQUEST.set( 'Cl', do_entreprise_correspondant_list( args={ 'entreprise_id' : entreprise_id }))">
<dtml-if "len(Cl)">
<h3>Correspondants dans l'entreprise <dtml-var "F['nom']"> (seront <em>supprimés</em>):</h3>
<ul>
<dtml-in Cl>
<dtml-let c="_['sequence-item']">
<li><dtml-if "c['nom']"><dtml-var "c['nom'].lower().capitalize()"></dtml-if>
<dtml-if "c['prenom']"> <dtml-var "c['prenom'].lower().capitalize()"></dtml-if>
(<dtml-var "c['fonction']">)
</li>
</dtml-let c>
</dtml-in Cl>
</ul>
</dtml-if Cl>
<dtml-call "REQUEST.set( 'Cts', do_entreprise_contact_list( args={ 'entreprise_id' : entreprise_id }))">
<dtml-if "len(Cts)">
<h3>Contacts avec l'entreprise <dtml-var "F['nom']"> (seront <em>supprimés</em>):</h3>
<ul>
<dtml-in Cts>
<dtml-let c="_['sequence-item']">
<li><dtml-if "c['date']"><dtml-var "c['date']"></dtml-if>
<dtml-if "c['description']">(<dtml-var "c['description']">)</dtml-if>
</li>
</dtml-let c>
</dtml-in Cts>
</ul>
</dtml-if Cts>
<dtml-call "REQUEST.set('tf', TrivialFormulator( URL0, REQUEST.form,
( ('entreprise_id', { 'input_type' : 'hidden' }),
),
initvalues = F,
submitlabel = 'Confirmer la suppression',
cancelbutton = 'Annuler', readonly = REQUEST['_read_only']
))">
<dtml-if "tf[0] == 0">
<dtml-var "tf[1]">
<dtml-else>
<dtml-if "tf[0] == -1">
<dtml-call "RESPONSE.redirect( URL1 )">
<dtml-else>
<dtml-call "do_entreprise_delete( F['entreprise_id'] )">
<dtml-call "RESPONSE.redirect(URL1)">
</dtml-if>
</dtml-if>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -1,139 +0,0 @@
<dtml-var "entreprise_header(REQUEST=REQUEST)">
<script type="text/javascript">
function entsub(event,ourform) {
if (event && event.which == 13)
ourform.submit();
else
return true;}
</script>
<dtml-if entreprise_page_size>
<dtml-call "REQUEST.set( 'entreprise_page_size', int(entreprise_page_size))">
<dtml-else>
<dtml-call "REQUEST.set( 'entreprise_page_size', 10 )">
</dtml-if>
<h2><dtml-var title_or_id></h2>
<dtml-comment>
<p class="entreprise_warning">
Attention: version test préliminaire. Signaler les problèmes à <a href="mailto:viennet@lipn.univ-paris13.fr">Emmanuel</a>
</p>
</dtml-comment>
<dtml-unless sort_type><dtml-call "REQUEST.set( 'sort_type', 'nom' )"></dtml-unless>
<dtml-call "REQUEST.set( 'sort_on_contact', False )">
<dtml-if "sort_type=='nom'">
<dtml-call "REQUEST.set( 'sort_key', 'nom' )">
<dtml-elif "sort_type=='datecontact'">
<dtml-call "REQUEST.set( 'sort_on_contact', True )">
<dtml-call "REQUEST.set( 'sort_key', None )">
<dtml-elif "sort_type=='qualite_relation'">
<dtml-call "REQUEST.set( 'sort_key', 'qualite_relation desc, nom asc' )">
<dtml-elif "sort_type=='date_creation'">
<dtml-call "REQUEST.set( 'sort_key', 'date_creation desc, nom asc' )">
<dtml-else>
<p class="entreprise_warning">valeur invalide pour 'sort_type' !</p>
</dtml-if>
<dtml-if etud_nom>
<dtml-call "REQUEST.set( 'Elist', do_entreprise_list_by_etud( args=REQUEST.form, sort_on_contact=sort_on_contact ) )">
<dtml-else>
<dtml-call "REQUEST.set( 'Elist', do_entreprise_list( args=REQUEST.form, test='~*', sort_on_contact=sort_on_contact, sortkey=sort_key ) )">
</dtml-if>
<dtml-unless start><dtml-call "REQUEST.set( 'start', 1)"></dtml-unless>
<form action="" method="GET">
<table><tr>
<th>nom</th><td><input type="text" name="nom" size="12" value="<dtml-if nom><dtml-var nom></dtml-if>" onkeypress="return entsub(event,this.form)"></input></td>
<th>ville</th><td><input type="text" name="ville" size="12" value="<dtml-if ville><dtml-var ville></dtml-if>" onkeypress="return entsub(event,this.form)"></input></td>
<th>étudiant</th><td><input type="text" name="etud_nom" size="12" value="<dtml-if etud_nom><dtml-var etud_nom></dtml-if>" onkeypress="return entsub(event,this.form)"></input></td>
<td><input type="submit" value="rechercher"></input></td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td>Tri par: <select name="sort_type" onchange="this.form.submit()">
<option value="nom" <dtml-if "sort_type=='nom'">selected</dtml-if>>nom entreprise</option>
<option value="datecontact" <dtml-if "sort_type=='datecontact'">selected</dtml-if>>date dernier contact</option>
<option value="qualite_relation" <dtml-if "sort_type=='qualite_relation'">selected</dtml-if>>relation IUT/Entreprise</option>
<option value="date_creation" <dtml-if "sort_type=='date_creation'">selected</dtml-if>>date saisie entreprise</option>
</select></td>
</tr></table>
</form>
<table class="entreprise_list">
<dtml-in Elist size=entreprise_page_size start=start overlap=1>
<dtml-if sequence-start>
<table class="entreprise_list_title">
<tr class="entreprise_list_title"><td class="entreprise_list_title">Entreprises</td>
<td class="entreprise_list_title_res">Résultats <b><dtml-var start></b> - <b><dtml-try><dtml-var "_['next-sequence-start-number']"><dtml-except><dtml-var "len(Elist)"></dtml-try></b> sur <b><dtml-var "len(Elist)"> </b>
</td>
</tr>
</table>
<table class="entreprise_list">
</dtml-if sequence-start>
<dtml-let F="_['sequence-item']">
<dtml-call "REQUEST.set( 'Cl', do_entreprise_correspondant_list( args={ 'entreprise_id' : F['entreprise_id'] }))">
<dtml-call "REQUEST.set( 'Ct', do_entreprise_contact_list( args={ 'entreprise_id' : F['entreprise_id'] }))">
<tr class="<dtml-if sequence-odd>entreprise_list_odd<dtml-else>entreprise_list_even</dtml-if>">
<td colspan="5" class="entreprise_descr_name"><a class="entreprise_edit" href="entreprise_edit?entreprise_id=<dtml-var "F['entreprise_id']">&start=<dtml-var start>"><dtml-var "F['nom']"></a></td>
</tr>
<tr class="<dtml-if sequence-odd>entreprise_list_odd<dtml-else>entreprise_list_even</dtml-if>">
<td>&nbsp;</td>
<td class="entreprise_descr"><dtml-var "str_abbrev(F['ville'], 22)"></td>
<td class="entreprise_descr"><dtml-var "F['secteur']"> </td>
<td class="entreprise_descr_link">
<a class="entreprise_delete" href="entreprise_correspondant_list?entreprise_id=<dtml-var "F['entreprise_id']">"><dtml-var "len(Cl)"> corr.</a>
<dtml-if "sort_type=='qualite_relation'"><b><dtml-var "{100:'TB',75:'B',50:'M',25:'!' }.get(F['qualite_relation'],'?')"></b></dtml-if>
</td>
<td class="entreprise_descr_link"><a class="entreprise_delete" href="entreprise_contact_list?entreprise_id=<dtml-var "F['entreprise_id']">"><dtml-var "len(Ct)"> contacts</a>
<dtml-try>(<dtml-var "F['etud_nom']">)<dtml-except></dtml-try>
<dtml-try>(<dtml-var "F['date']">)<dtml-except></dtml-try>
</td>
</tr>
</dtml-let>
<dtml-if sequence-end>
</table>
</dtml-if sequence-end>
<dtml-else Elist>
<p class="entreprise_warning">Aucune entreprise !</p>
</dtml-in>
<p>
<form action="setPageSizeCookie" method="GET">
<input type="hidden" name="target_url" value="<dtml-var "URL0+'?'+QUERY_STRING">"></input>
<dtml-in Elist size=entreprise_page_size start=start overlap=1>
<dtml-if sequence-start>
<dtml-if previous-sequence>
<a href="<dtml-var URL><dtml-var sequence-query>start=<dtml-var previous-sequence-start-number>">
page précédente</a>&nbsp;&nbsp;&nbsp;&nbsp;
</dtml-if previous-sequence>
</dtml-if sequence-start>
<dtml-if sequence-end>
<dtml-if next-sequence>
<a href="<dtml-var URL><dtml-var sequence-query>start=<dtml-var next-sequence-start-number>">
page suivante</a>
</dtml-if next-sequence>
</dtml-if sequence-end>
</dtml-in Elist>
&nbsp; Résultats par page :
<select name="entreprise_page_size" onchange="this.form.submit()">
<dtml-in "((5,5),(10,10),(15,15),(20,20),(30,30),(50,50),(1000,'Tous'))" prefix="label">
<option value="<dtml-var "label_key">" <dtml-if "label_key == entreprise_page_size">selected</dtml-if>><dtml-var label_item></option>
</dtml-in>
</select>
</form>
</p>
<dtml-var "entreprise_footer(REQUEST=REQUEST)">

View File

@ -123,6 +123,7 @@ class GenTable:
pdf_col_widths=None,
xml_outer_tag="table",
xml_row_tag="row",
text_with_titles=False, # CSV with header line
text_fields_separator="\t",
preferences=None,
):
@ -173,6 +174,7 @@ class GenTable:
self.xml_row_tag = xml_row_tag
# TEXT parameters
self.text_fields_separator = text_fields_separator
self.text_with_titles = text_with_titles
#
if preferences:
self.preferences = preferences
@ -265,8 +267,7 @@ class GenTable:
def get_titles_list(self):
"list of titles"
l = []
return l + [self.titles.get(cid, "") for cid in self.columns_ids]
return [self.titles.get(cid, "") for cid in self.columns_ids]
def gen(self, format="html", columns_ids=None):
"""Build representation of the table in the specified format.
@ -444,14 +445,14 @@ class GenTable:
if self.base_url:
if self.xls_link:
H.append(
' <a href="%s&amp;format=xls">%s</a>'
' <a href="%s&format=xls">%s</a>'
% (self.base_url, scu.ICON_XLS)
)
if self.xls_link and self.pdf_link:
H.append("&nbsp;&nbsp;")
if self.pdf_link:
H.append(
' <a href="%s&amp;format=pdf">%s</a>'
' <a href="%s&format=pdf">%s</a>'
% (self.base_url, scu.ICON_PDF)
)
H.append("</p>")
@ -479,10 +480,14 @@ class GenTable:
def text(self):
"raw text representation of the table"
if self.text_with_titles:
headline = [self.get_titles_list()]
else:
headline = []
return "\n".join(
[
self.text_fields_separator.join([x for x in line])
for line in self.get_data_list()
for line in headline + self.get_data_list()
]
)
@ -534,14 +539,6 @@ class GenTable:
)
]
pdf_style_list += self.pdf_table_style
# log('len(Pt)=%s' % len(Pt))
# log( 'line lens=%s' % [ len(x) for x in Pt ] )
# log( 'style=\n%s' % pdf_style_list)
# col_min = min([x[1][0] for x in pdf_style_list])
# col_max = max([x[2][0] for x in pdf_style_list])
# lin_min = min([x[1][1] for x in pdf_style_list])
# lin_max = max([x[2][1] for x in pdf_style_list])
# log('col_min=%s col_max=%s lin_min=%s lin_max=%s' % (col_min, col_max, lin_min, lin_max))
T = Table(Pt, repeatRows=1, colWidths=self.pdf_col_widths, style=pdf_style_list)
objects = []
@ -693,7 +690,7 @@ class SeqGenTable:
def excel(self):
"""Export des genTables dans un unique fichier excel avec plusieurs feuilles tagguées"""
book = sco_excel.Workbook() # Le fichier xls en devenir
book = sco_excel.Workbook() # pylint: disable=no-member
for (_, gt) in self.genTables.items():
gt.excel(wb=book) # Ecrit dans un fichier excel
return book.savetostr()

View File

@ -25,7 +25,9 @@
#
##############################################################################
from sco_utils import *
import cgi
import sco_utils as scu
from sco_formsemestre_status import formsemestre_page_title
"""
@ -95,7 +97,7 @@ def sco_header(
"page_title": page_title or context.title_or_id(),
"no_side_bar": no_side_bar,
"ScoURL": context.ScoURL(),
"encoding": SCO_ENCODING,
"encoding": scu.SCO_ENCODING,
"titrebandeau_mkup": "<td>" + titrebandeau + "</td>",
"authuser": str(REQUEST.AUTHENTICATED_USER),
}
@ -224,7 +226,7 @@ def sco_header(
# Body et bandeau haut:
H.append("""<body %(bodyOnLoad_mkup)s>""" % params)
H.append(CUSTOM_HTML_HEADER)
H.append(scu.CUSTOM_HTML_HEADER)
#
if not no_side_bar:
H.append(context.sidebar(REQUEST))
@ -258,4 +260,6 @@ def sco_header(
def sco_footer(context, REQUEST=None):
"""Main HTMl pages footer"""
return """</div><!-- /gtrcontent -->""" + CUSTOM_HTML_FOOTER + """</body></html>"""
return (
"""</div><!-- /gtrcontent -->""" + scu.CUSTOM_HTML_FOOTER + """</body></html>"""
)

View File

@ -25,8 +25,14 @@
#
##############################################################################
from sco_utils import *
from ZAbsences import getAbsSemEtud
import sco_utils as scu
from sco_abs import getAbsSemEtud
from sco_permissions import (
ScoUsersAdmin,
ScoUsersView,
ScoChangePreferences,
ScoAbsChange,
)
"""
Génération de la "sidebar" (marge gauche des pages HTML)
@ -36,16 +42,22 @@ Génération de la "sidebar" (marge gauche des pages HTML)
def sidebar_common(context, REQUEST=None):
"partie commune a toutes les sidebar"
authuser = REQUEST.AUTHENTICATED_USER
params = {"ScoURL": context.ScoURL(), "authuser": str(authuser)}
params = {
"ScoURL": context.ScoURL(),
"UsersURL": context.UsersURL(),
"NotesURL": context.NotesURL(),
"AbsencesURL": context.AbsencesURL(),
"authuser": str(authuser),
}
H = [
'<a class="scodoc_title" href="about">ScoDoc</a>',
'<div id="authuser"><a id="authuserlink" href="%(ScoURL)s/Users/userinfo">%(authuser)s</a><br/><a id="deconnectlink" href="%(ScoURL)s/acl_users/logout">déconnexion</a></div>'
% params,
context.sidebar_dept(REQUEST),
"""<h2 class="insidebar">Scolarit&eacute;</h2>
<a href="%(ScoURL)s" class="sidebar">Semestres</a> <br/>
<a href="%(ScoURL)s/Notes" class="sidebar">Programmes</a> <br/>
<a href="%(ScoURL)s/Absences" class="sidebar">Absences</a> <br/>
<a id="SemestresLink" href="%(ScoURL)s" class="sidebar">Semestres</a> <br/>
<a id="ProgrammesLink" href="%(NotesURL)s" class="sidebar">Programmes</a> <br/>
<a id="AbsnecesLink" href="%(AbsencesURL)s" class="sidebar">Absences</a> <br/>
"""
% params,
]
@ -54,19 +66,12 @@ def sidebar_common(context, REQUEST=None):
ScoUsersView, context
):
H.append(
"""<a href="%(ScoURL)s/Users" class="sidebar">Utilisateurs</a> <br/>"""
% params
)
if 0: # XXX experimental
H.append(
"""<a href="%(ScoURL)s/Notes/Services" class="sidebar">Services</a> <br/>"""
% params
"""<a id="utilisateurs_Vue" href="%(UsersURL)s" class="sidebar">Utilisateurs</a> <br/>""" % params
)
if authuser.has_permission(ScoChangePreferences, context):
H.append(
"""<a href="%(ScoURL)s/edit_preferences" class="sidebar">Paramétrage</a> <br/>"""
"""<a id="preferences_Edit" href="%(ScoURL)s/edit_preferences" class="sidebar">Paramétrage</a> <br/>"""
% params
)
@ -76,14 +81,14 @@ def sidebar_common(context, REQUEST=None):
def sidebar(context, REQUEST=None):
"Main HTML page sidebar"
# rewritten from legacy DTML code
params = {"ScoURL": context.ScoURL(), "SCO_USER_MANUAL": SCO_USER_MANUAL}
params = {"ScoURL": context.ScoURL(), "SCO_USER_MANUAL": scu.SCO_USER_MANUAL}
H = ['<div class="sidebar">', sidebar_common(context, REQUEST)]
H.append(
"""<div class="box-chercheetud">Chercher étudiant:<br/>
<form id="form-chercheetud" action="%(ScoURL)s/search_etud_in_dept">
<div><input type="text" size="12" id="in-expnom" name="expnom"></input></div>
<form method="get" id="form-chercheetud" action="%(ScoURL)s/search_etud_in_dept">
<div><input type="text" size="12" id="in-expnom" name="expnom" spellcheck="false"></input></div>
</form></div>
<div class="etud-insidebar">
"""
@ -97,8 +102,9 @@ def sidebar(context, REQUEST=None):
# compte les absences du semestre en cours
H.append(
"""<h2 id="insidebar-etud"><a href="%(ScoURL)s/ficheEtud?etudid=%(etudid)s" class="sidebar">
<font color="#FF0000">%(sexe)s %(nom_disp)s</font></a>
<font color="#FF0000">%(civilite_str)s %(nom_disp)s</font></a>
</h2>
<p id="euid">%(etudid)s</p>
<b>Absences</b>"""
% params
)
@ -118,21 +124,21 @@ def sidebar(context, REQUEST=None):
if REQUEST.AUTHENTICATED_USER.has_permission(ScoAbsChange, context):
H.append(
"""
<li> <a href="%(ScoURL)s/Absences/SignaleAbsenceEtud?etudid=%(etudid)s">Ajouter</a></li>
<li> <a href="%(ScoURL)s/Absences/JustifAbsenceEtud?etudid=%(etudid)s">Justifier</a></li>
<li> <a href="%(ScoURL)s/Absences/AnnuleAbsenceEtud?etudid=%(etudid)s">Supprimer</a></li>
<li> <a id="ajout_Absence" href="%(ScoURL)s/Absences/SignaleAbsenceEtud?etudid=%(etudid)s">Ajouter</a></li>
<li> <a id="justifier_Absence" href="%(ScoURL)s/Absences/JustifAbsenceEtud?etudid=%(etudid)s">Justifier</a></li>
<li> <a id="supprimer_Absence" href="%(ScoURL)s/Absences/AnnuleAbsenceEtud?etudid=%(etudid)s">Supprimer</a></li>
"""
% params
)
if context.get_preference("handle_billets_abs"):
H.append(
"""<li> <a href="%(ScoURL)s/Absences/listeBilletsEtud?etudid=%(etudid)s">Billets</a></li>"""
"""<li> <a id="afficher_Billets" href="%(ScoURL)s/Absences/listeBilletsEtud?etudid=%(etudid)s">Billets</a></li>"""
% params
)
H.append(
"""
<li> <a href="%(ScoURL)s/Absences/CalAbs?etudid=%(etudid)s">Calendrier</a></li>
<li> <a href="%(ScoURL)s/Absences/ListeAbsEtud?etudid=%(etudid)s">Liste</a></li>
<li> <a id="afficher_Calendrier" href="%(ScoURL)s/Absences/CalAbs?etudid=%(etudid)s">Calendrier</a></li>
<li> <a id="afficher_Liste_Absence" href="%(ScoURL)s/Absences/ListeAbsEtud?etudid=%(etudid)s">Liste</a></li>
</ul>
"""
% params
@ -142,7 +148,7 @@ def sidebar(context, REQUEST=None):
# ---------
H.append("</div><br/>&nbsp;") # /etud-insidebar
# Logo
scologo_img = icontag("scologo_img")
scologo_img = scu.icontag("scologo_img")
H.append('<div class="logo-insidebar"><div class="logo-logo">%s<br/>' % scologo_img)
H.append(
"""<a href="%(ScoURL)s/about" class="sidebar">A propos</a><br/>

View File

@ -28,6 +28,8 @@
"""Various HTML generation functions
"""
import listhistogram
def horizontal_bargraph(value, mark):
"""html drawing an horizontal bar and a mark
@ -42,9 +44,6 @@ def horizontal_bargraph(value, mark):
return tmpl % {"value": int(value), "mark": int(mark)}
import listhistogram
def histogram_notes(notes):
"HTML code drawing histogram"
if not notes:
@ -70,3 +69,56 @@ def histogram_notes(notes):
)
D.append("</ul></li></ul>")
return "\n".join(D)
def make_menu(title, items, css_class="", base_url="", alone=False):
"""HTML snippet to render a simple drop down menu.
items is a list of dicts:
{ 'title' :
'url' :
'id' :
'attr' : "" # optionnal html <a> attributes
'enabled' : # True by default
'helpmsg' :
'submenu' : [ list of sub-items ]
}
"""
def gen_menu_items(items):
H.append("<ul>")
for item in items:
if not item.get("enabled", True):
cls = ' class="ui-state-disabled"'
else:
cls = ""
the_id = item.get("id", "")
if the_id:
li_id = 'id="%s" ' % the_id
else:
li_id = ""
if base_url and "url" in item:
item["urlq"] = base_url + item["url"]
else:
item["urlq"] = item.get("url", "#")
item["attr"] = item.get("attr", "")
submenu = item.get("submenu", None)
H.append(
"<li "
+ li_id
+ cls
+ '><a href="%(urlq)s" %(attr)s>%(title)s</a>' % item
)
if submenu:
gen_menu_items(submenu)
H.append("</li>")
H.append("</ul>")
H = []
if alone:
H.append('<ul class="sco_dropdown_menu %s">' % css_class)
H.append("""<li><a href="#">%s</a>""" % title)
gen_menu_items(items)
H.append("</li>")
if alone:
H.append("</ul>")
return "".join(H)

View File

@ -173,17 +173,17 @@ if __name__ == "__main__":
repr(i)
== "{[None, 3] => 'My,', [3, 5] => 'Hello', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
)
i[5.5:6] = "Cruel"
i[5.5:6] = "Cruel" # pylint: disable=invalid-slice-index
assert (
repr(i)
== "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 6] => 'Cruel', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
)
i[6:6.5] = "And Harsh"
i[6:6.5] = "And Harsh" # pylint: disable=invalid-slice-index
assert (
repr(i)
== "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 6] => 'Cruel', [6, 6.5] => 'And Harsh', [6.5, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
)
i[5.9:6.6] = None
i[5.9:6.6] = None # pylint: disable=invalid-slice-index
assert (
repr(i)
== "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 5.9000000000000004] => 'Cruel', [6.5999999999999996, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
@ -222,9 +222,13 @@ if __name__ == "__main__":
print("Test 3 skipped")
else:
i = intervalmap()
i[: datetime(2005, 10, 24)] = "A"
i[datetime(2005, 11, 11) : datetime(2005, 11, 17)] = "B"
i[datetime(2005, 11, 30) :] = "C"
i[: datetime(2005, 10, 24)] = "A" # pylint: disable=invalid-slice-index
i[
datetime(2005, 11, 11) : datetime( # pylint: disable=invalid-slice-index
2005, 11, 17
)
] = "B"
i[datetime(2005, 11, 30) :] = "C" # pylint: disable=invalid-slice-index
assert i[datetime(2005, 9, 25)] == "A"
assert i[datetime(2005, 10, 23)] == "A"
assert i[datetime(2005, 10, 26)] == None

View File

@ -99,8 +99,7 @@ function get_semestre_info($sem, $dept) {
// Renvoi les informations détaillées d'un semestre
// Ne nécessite pas d'authentification avec sco_user et sco_pw - Il est possible de choisir le format XML ou JSON.
// formsemestre_list
// Paramètres (tous optionnels): formsesmestre_id, formation_id, etape_apo, etape_apo2
// Coquille dans la doc : formsesmestre_id
// Paramètres (tous optionnels): formsemestre_id, formation_id, etape_apo
// Résultat: liste des semestres correspondant.
// Exemple: formsemestre_list?format=xml&etape_apo=V1RT
global $sco_pw;

View File

@ -35,6 +35,18 @@ CREATE FUNCTION notes_newid_etud( text ) returns text as '
as result;
' language SQL;
-- Fonction pour anonymisation:
-- inspirée par https://www.simononsoftware.com/random-string-in-postgresql/
CREATE FUNCTION random_text_md5( integer ) returns text
LANGUAGE SQL
AS $$
select upper( substring( (SELECT string_agg(md5(random()::TEXT), '')
FROM generate_series(
1,
CEIL($1 / 32.)::integer)
), 1, $1) );
$$;
-- Preferences
CREATE TABLE sco_prefs (
pref_id text DEFAULT notes_newid('PREF'::text) UNIQUE NOT NULL,
@ -49,7 +61,7 @@ CREATE TABLE identite (
etudid text DEFAULT notes_newid_etud('EID'::text) UNIQUE NOT NULL,
nom text,
prenom text,
sexe text,
civilite text NOT NULL CHECK (civilite IN ('M', 'F', 'X')),
date_naissance date, -- new: date en texte
lieu_naissance text,
dept_naissance text,

View File

@ -8,7 +8,7 @@ Code_INE; text; identite; 1; code INE;INE
nom; text; identite; 0; nom de l'etudiant;
nom_usuel; text; identite; 1; nom usuel (si different);
prenom; text; identite; 0; prenom de l'etudiant
sexe (H ou F); text; identite; 0; sexe ('H' ou 'F');sexe;genre
civilite; text; identite; 1; sexe ('M', 'F', 'X');sexe;genre
date_naissance;text;identite; 1; date de naissance (jj/mm/aaaa)
lieu_naissance;text;identite; 1; lieu de naissance
nationalite; text; identite; 1; nationalite

View File

@ -1,12 +1,22 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
import pdb, os, sys, time, re, inspect
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.Header import Header
import pdb
import sys
import os
import re
import inspect
import time
import traceback
from email.MIMEMultipart import ( # pylint: disable=no-name-in-module,import-error
MIMEMultipart,
)
from email.MIMEText import MIMEText # pylint: disable=no-name-in-module,import-error
from email.MIMEBase import MIMEBase # pylint: disable=no-name-in-module,import-error
from email.Header import Header # pylint: disable=no-name-in-module,import-error
from email import Encoders # pylint: disable=no-name-in-module,import-error
# Simple & stupid file logguer, used only to debug
# (logging to SQL is done in scolog)

View File

@ -27,27 +27,37 @@
"""Calculs sur les notes et cache des resultats
"""
from types import StringType
from types import StringType, FloatType
import time
import pdb
import inspect
import scolars
import sco_groups
from notes_log import log, logCallStack
from sco_utils import *
from notesdb import *
import sco_utils as scu
import notesdb as ndb
import sco_codes_parcours
from sco_codes_parcours import DEF, UE_SPORT, UE_is_fondamentale, UE_is_professionnelle
from sco_parcours_dut import formsemestre_get_etud_capitalisation
from sco_parcours_dut import list_formsemestre_utilisateurs_uecap
import sco_parcours_dut
import sco_formsemestre
from sco_formsemestre_edit import formsemestre_uecoef_list, formsemestre_uecoef_create
from sco_formsemestre import formsemestre_uecoef_list, formsemestre_uecoef_create
import sco_moduleimpl
import sco_evaluations
import sco_compute_moy
from sco_formulas import NoteVector
from sco_exceptions import (
AccessDenied,
NoteProcessError,
ScoException,
ScoValueError,
)
# Support for old user-written "bonus" functions with 2 args:
BONUS_TWO_ARGS = len(inspect.getargspec(CONFIG.compute_bonus)[0]) == 2
BONUS_TWO_ARGS = len(inspect.getargspec(scu.CONFIG.compute_bonus)[0]) == 2
def comp_ranks(T):
@ -85,7 +95,9 @@ def get_sem_ues_modimpls(context, formsemestre_id, modimpls=None):
(utilisé quand on ne peut pas construire nt et faire nt.get_ues())
"""
if modimpls is None:
modimpls = context.do_moduleimpl_list(formsemestre_id=formsemestre_id)
modimpls = sco_moduleimpl.do_moduleimpl_list(
context, formsemestre_id=formsemestre_id
)
uedict = {}
for modimpl in modimpls:
mod = context.do_module_list(args={"module_id": modimpl["module_id"]})[0]
@ -104,7 +116,7 @@ def comp_etud_sum_coef_modules_ue(context, formsemestre_id, etudid, ue_id):
(nécessaire pour éviter appels récursifs de nt, qui peuvent boucler)
"""
infos = SimpleDictFetch(
infos = ndb.SimpleDictFetch(
context,
"""SELECT mod.coefficient
FROM notes_modules mod, notes_moduleimpl mi, notes_moduleimpl_inscription ins
@ -136,7 +148,7 @@ class NotesTable:
- identdict: { etudid : ident }
- sem : le formsemestre
get_table_moyennes_triees: [ (moy_gen, moy_ue1, moy_ue2, ... moy_ues, moy_mod1, ..., moy_modn, etudid) ]
( toutes les valeurs sont soit des nombrs soit des chaines spéciales comme 'NA', 'NI'),
( toutes les valeurs sont soit des nombres soit des chaines spéciales comme 'NA', 'NI'),
incluant les UE de sport
- bonus[etudid] : valeur du bonus "sport".
@ -184,7 +196,7 @@ class NotesTable:
for i in range(len(self.inscrlist)):
rangalpha[self.inscrlist[i]["etudid"]] = i
self.bonus = DictDefault(defaultvalue=0)
self.bonus = scu.DictDefault(defaultvalue=0)
# Notes dans les modules { moduleimpl_id : { etudid: note_moyenne_dans_ce_module } }
(
self._modmoys,
@ -360,29 +372,28 @@ class NotesTable:
def get_sexnom(self, etudid):
"M. DUPONT"
etud = self.identdict[etudid]
return etud["sexe"] + " " + strupper(etud["nom_usuel"] or etud["nom"])
return (
etud["civilite_str"] + " " + scu.strupper(etud["nom_usuel"] or etud["nom"])
)
def get_nom_short(self, etudid):
"formatte nom d'un etud (pour table recap)"
etud = self.identdict[etudid]
# Attention aux caracteres multibytes pour decouper les 2 premiers:
return (
strupper(etud["nom_usuel"] or etud["nom"])
scu.strupper(etud["nom_usuel"] or etud["nom"])
+ " "
+ etud["prenom"].decode(SCO_ENCODING).capitalize()[:2].encode(SCO_ENCODING)
+ etud["prenom"]
.decode(scu.SCO_ENCODING)
.capitalize()[:2]
.encode(scu.SCO_ENCODING)
+ "."
)
def get_nom_long(self, etudid):
"formatte nom d'un etud: M. Pierre DUPONT"
etud = self.identdict[etudid]
return " ".join(
[
scolars.format_sexe(etud["sexe"]),
scolars.format_prenom(etud["prenom"]),
scolars.format_nom(etud["nom_usuel"] or etud["nom"]),
]
)
return scolars.format_nomprenom(etud)
def get_displayed_etud_code(self, etudid):
'code à afficher sur les listings "anonymes"'
@ -613,7 +624,7 @@ class NotesTable:
# si 'NI', etudiant non inscrit a ce module
if val != "NI":
est_inscrit = True
if modimpl["module"]["module_type"] == MODULE_STANDARD:
if modimpl["module"]["module_type"] == scu.MODULE_STANDARD:
coef = modimpl["module"]["coefficient"]
if modimpl["ue"]["type"] != UE_SPORT:
notes.append(val, name=modimpl["module"]["code"])
@ -649,7 +660,7 @@ class NotesTable:
except:
# log('comp_etud_moy_ue: exception: val=%s coef=%s' % (val,coef))
pass
elif modimpl["module"]["module_type"] == MODULE_MALUS:
elif modimpl["module"]["module_type"] == scu.MODULE_MALUS:
try:
ue_malus += val
except:
@ -669,7 +680,7 @@ class NotesTable:
moy = sum_notes / sum_coefs
if ue_malus:
moy -= ue_malus
moy = max(NOTES_MIN, min(moy, 20.0))
moy = max(scu.NOTES_MIN, min(moy, 20.0))
moy_valid = True
else:
moy = "NA"
@ -893,8 +904,10 @@ class NotesTable:
sum_coefs_ue = 0
for mu in moy_ues.values():
# mu["moy"] can be a number, or "NA", or "ERR" (user-defined UE formulas)
if isnumber(mu["moy"]) and (
mu["est_inscrit"] or mu["is_capitalized"]
if (
(mu["ue"]["type"] != UE_SPORT)
and scu.isnumber(mu["moy"])
and (mu["est_inscrit"] or mu["is_capitalized"])
):
coef_ue = mu["ue"]["coefficient"]
sum_moy_ue += mu["moy"] * coef_ue
@ -926,9 +939,11 @@ class NotesTable:
if BONUS_TWO_ARGS:
# backward compat: compute_bonus took only 2 args
bonus = CONFIG.compute_bonus(notes_bonus_gen, coefs_bonus_gen)
bonus = scu.CONFIG.compute_bonus(
notes_bonus_gen, coefs_bonus_gen
)
else:
bonus = CONFIG.compute_bonus(
bonus = scu.CONFIG.compute_bonus(
notes_bonus_gen, coefs_bonus_gen, infos=infos
)
self.bonus[etudid] = bonus
@ -1018,7 +1033,7 @@ class NotesTable:
Si l'étudiant est défaillant, met un code DEF sur toutes les UE
"""
cnx = self.context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"select etudid, code, assidu, compense_formsemestre_id, event_date from scolar_formsemestre_validation where formsemestre_id=%(formsemestre_id)s and ue_id is NULL;",
{"formsemestre_id": self.formsemestre_id},
@ -1035,7 +1050,7 @@ class NotesTable:
"code": code,
"assidu": assidu,
"compense_formsemestre_id": compense_formsemestre_id,
"event_date": DateISOtoDMY(event_date),
"event_date": ndb.DateISOtoDMY(event_date),
}
self.decisions_jury = decisions_jury
@ -1084,7 +1099,7 @@ class NotesTable:
decisions_jury_ues[etudid][ue_id] = {
"code": code,
"ects": ects, # 0. si non UE validée ou si mode de calcul different (?)
"event_date": DateISOtoDMY(event_date),
"event_date": ndb.DateISOtoDMY(event_date),
}
self.decisions_jury_ues = decisions_jury_ues
@ -1117,6 +1132,9 @@ class NotesTable:
def sem_has_decisions(self):
"""True si au moins une decision de jury dans ce semestre"""
if [x for x in self.decisions_jury_ues.values() if x]:
return True
return len([x for x in self.decisions_jury_ues.values() if x]) > 0
def etud_has_decision(self, etudid):
@ -1141,7 +1159,7 @@ class NotesTable:
ue_capitalisees = { etudid :
[{ 'moy':, 'event_date' : ,'formsemestre_id' : }, ...] }
"""
self.ue_capitalisees = DictDefault(defaultvalue=[])
self.ue_capitalisees = scu.DictDefault(defaultvalue=[])
cnx = None
for etudid in self.get_etudids():
capital = formsemestre_get_etud_capitalisation(
@ -1276,13 +1294,13 @@ class NotesTable:
(ne compte que les notes en attente dans des évaluation avec coef. non nul).
"""
cnx = self.context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"select n.* from notes_notes n, notes_evaluation e, notes_moduleimpl m, notes_moduleimpl_inscription i where n.etudid = %(etudid)s and n.value = %(code_attente)s and n.evaluation_id=e.evaluation_id and e.moduleimpl_id=m.moduleimpl_id and m.formsemestre_id=%(formsemestre_id)s and e.coefficient != 0 and m.moduleimpl_id=i.moduleimpl_id and i.etudid=%(etudid)s",
{
"formsemestre_id": self.formsemestre_id,
"etudid": etudid,
"code_attente": NOTES_ATTENTE,
"code_attente": scu.NOTES_ATTENTE,
},
)
return len(cursor.fetchall()) > 0
@ -1326,7 +1344,7 @@ class CacheNotesTable:
# Cache des classeur PDF (bulletins)
self.pdfcache = {} # { formsemestre_id : (filename, pdfdoc) }
# Listeners:
self.listeners = DictDefault(
self.listeners = scu.DictDefault(
defaultvalue={}
) # {formsemestre_id : {listener_id : callback }}

View File

@ -5,10 +5,11 @@ import pdb, os, sys, string
import traceback
import psycopg2
import psycopg2.pool
import psycopg2.extras
import thread
from notes_log import log
from sco_exceptions import *
from types import *
from sco_exceptions import ScoException, ScoValueError, NoteProcessError
from types import StringType
from cgi import escape
import datetime
@ -67,16 +68,17 @@ def GetUsersDBConnexion(context, autocommit=True):
return cnx
# Nota: on pourrait maintenant utiliser psycopg2.extras.DictCursor
class ScoDocCursor(psycopg2.extensions.cursor):
"""A database cursor emulating some methods of psycopg v1 cursors"""
def dictfetchall(cursor):
col_names = [d[0] for d in cursor.description]
return [dict(zip(col_names, row)) for row in cursor.fetchall()]
def dictfetchall(self):
col_names = [d[0] for d in self.description]
return [dict(zip(col_names, row)) for row in self.fetchall()]
def dictfetchone(cursor):
col_names = [d[0] for d in cursor.description]
row = cursor.fetchone()
def dictfetchone(self):
col_names = [d[0] for d in self.description]
row = self.fetchone()
if row:
return dict(zip(col_names, row))
else:
@ -141,6 +143,8 @@ def DBSelectArgs(
distinct=True,
aux_tables=[],
id_name=None,
limit="",
offset="",
):
"""Select * from table where values match dict vals.
Returns cnx, columns_names, list of tuples
@ -155,6 +159,12 @@ def DBSelectArgs(
distinct = " distinct "
else:
distinct = ""
if limit != "":
limit = " LIMIT %d" % limit
if not offset:
offset = ""
if offset != "":
offset = " OFFSET %d" % offset
operator = " " + operator + " "
# liste des tables (apres "from")
tables = [table] + [x[0] for x in aux_tables]
@ -165,7 +175,7 @@ def DBSelectArgs(
cond = ""
i = 1
cl = []
for (aux_tab, aux_id) in aux_tables:
for (_, aux_id) in aux_tables:
cl.append("T0.%s = T%d.%s" % (id_name, i, aux_id))
i = i + 1
cond += " and ".join(cl)
@ -195,7 +205,17 @@ def DBSelectArgs(
if cond:
cond = " where " + cond
#
req = "select " + distinct + ", ".join(what) + " from " + tables + cond + orderby
req = (
"select "
+ distinct
+ ", ".join(what)
+ " from "
+ tables
+ cond
+ orderby
+ limit
+ offset
)
# open('/tmp/select.log','a').write( req % vals + '\n' )
try:
cursor.execute(req, vals)
@ -329,6 +349,8 @@ class EditableTable:
test="=",
sortkey=None,
disable_formatting=False,
limit="",
offset="",
):
"returns list of dicts"
# REQLOG.write('%s: %s by %s (%s) %d\n'%(self.table_name,args,sys._getframe(1).f_code.co_name, sys._getframe(2).f_code.co_name, REQN))
@ -347,6 +369,8 @@ class EditableTable:
operator=operator,
aux_tables=self.aux_tables,
id_name=self.id_name,
limit=limit,
offset=offset,
)
for r in res:
self.format_output(r, disable_formatting=disable_formatting)

View File

@ -30,12 +30,14 @@
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
import os
import codecs
import re
import scolars
import pe_jurype, pe_tagtable, pe_tools
from sco_utils import *
import sco_utils as scu
from notes_log import log
import scolars
import pe_jurype, pe_tagtable, pe_tools
@ -55,7 +57,7 @@ def get_code_latex_from_modele(fichier):
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)
"""
fid_latex = codecs.open(fichier, "r", encoding=SCO_ENCODING)
fid_latex = codecs.open(fichier, "r", encoding=scu.SCO_ENCODING)
un_avis_latex = fid_latex.read()
fid_latex.close()
return un_avis_latex
@ -72,7 +74,7 @@ def get_code_latex_from_scodoc_preference(
template_latex = context.get_preference(champ, formsemestre_id)
# Conversion du template en unicode:
if template_latex:
template_latex = template_latex.decode(SCO_ENCODING)
template_latex = template_latex.decode(scu.SCO_ENCODING)
else:
template_latex = u"" # EV: preference non définie (None)
@ -119,10 +121,14 @@ def comp_latex_parcourstimeline(etudiant, promo, taille=17):
\\end{parcourstimeline}
"""
reslatex = codelatexDebut
reslatex = reslatex.replace("**debut**", etudiant["entree"].decode(SCO_ENCODING))
reslatex = reslatex.replace("**fin**", str(etudiant["promo"]).decode(SCO_ENCODING))
reslatex = reslatex.replace(
"**nbreSemestres**", str(etudiant["nbSemestres"]).decode(SCO_ENCODING)
"**debut**", etudiant["entree"].decode(scu.SCO_ENCODING)
)
reslatex = reslatex.replace(
"**fin**", str(etudiant["promo"]).decode(scu.SCO_ENCODING)
)
reslatex = reslatex.replace(
"**nbreSemestres**", str(etudiant["nbSemestres"]).decode(scu.SCO_ENCODING)
)
# Tri du parcours par ordre croissant : de la forme descr, nom sem date-date
parcours = etudiant["parcours"][::-1] # EV: XXX je ne comprend pas ce commentaire ?
@ -179,7 +185,7 @@ def get_code_latex_avis_etudiant(
# Recherche des tags dans le fichier
tags_latex = get_tags_latex(code)
if DEBUG:
print("Les tags" + str(tags_latex))
log("Les tags" + str(tags_latex))
# Interprète et remplace chaque tags latex par les données numériques de l'étudiant (y compris les
# tags "macros" tels que parcourstimeline
@ -207,17 +213,16 @@ def get_code_latex_avis_etudiant(
elif tag_latex == u"bilanParTag":
valeur = get_bilanParTag(donnees_etudiant)
# Les tags "simples": par ex. nom, prenom, sexe, ...
# Les tags "simples": par ex. nom, prenom, civilite, ...
else:
if tag_latex in donnees_etudiant:
valeur = donnees_etudiant[tag_latex].decode(SCO_ENCODING)
valeur = donnees_etudiant[tag_latex].decode(scu.SCO_ENCODING)
elif tag_latex in prefs: # les champs **NomResponsablePE**, ...
valeur = pe_tools.escape_for_latex(prefs[tag_latex]).decode(
SCO_ENCODING
scu.SCO_ENCODING
)
# Gestion des pb d'encodage XXX debug
# print(tag_latex, valeur)
assert isinstance(tag_latex, unicode)
assert isinstance(valeur, unicode)
@ -244,7 +249,7 @@ def get_annotation_PE(context, etudid, tag_annotation_pe):
exp = re.compile(r"^" + tag_annotation_pe)
for a in annotations:
commentaire = unescape_html(a["comment"]).decode(SCO_ENCODING)
commentaire = scu.unescape_html(a["comment"]).decode(scu.SCO_ENCODING)
if exp.match(commentaire): # tag en début de commentaire ?
a["comment_u"] = commentaire # unicode, HTML non quoté
annotationsPE.append(
@ -282,7 +287,6 @@ def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ)
for chp in champ
]
else: # champ = str à priori
# print(champ)
valeur = DONNEE_MANQUANTE
if (
(aggregat in donnees_etudiant)
@ -316,7 +320,6 @@ def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ)
else:
valeur = u"%s" % donnees_numeriques[indice_champ]
# print(valeur)
return valeur
@ -339,7 +342,7 @@ def get_bilanParTag(donnees_etudiant, groupe="groupe"):
lignes = []
valeurs = {"note": [], "rang": []}
for (indice_aggregat, (aggregat, intitule, ordre)) in enumerate(entete):
for (indice_aggregat, (aggregat, intitule, _)) in enumerate(entete):
# print("> " + aggregat)
# listeTags = jury.get_allTagForAggregat(aggregat) # les tags de l'aggrégat
listeTags = [
@ -366,25 +369,20 @@ def get_bilanParTag(donnees_etudiant, groupe="groupe"):
("\\textit{" + rang + "}") if note else ""
) # rang masqué si pas de notes
# pprint.pprint(valeurs)
# print(len(entete))
code_latex = u"\\begin{tabular}{|c|" + "|c" * (len(entete)) + "|}\n"
code_latex += u"\\hline \n"
code_latex += (
u" & "
+ " & ".join(
["\\textbf{" + intitule + "}" for (agg, intitule, ordre) in entete]
)
+ " & ".join(["\\textbf{" + intitule + "}" for (agg, intitule, _) in entete])
+ " \\\\ \n"
)
code_latex += u"\\hline"
code_latex += u"\\hline \n"
for (i, ligne_val) in enumerate(valeurs["note"]):
titre = lignes[i] # règle le pb d'encodage
# print titre, ligne_val
code_latex += (
u"\\textbf{"
+ titre.decode(SCO_ENCODING)
+ titre.decode(scu.SCO_ENCODING)
+ u"} & "
+ " & ".join(ligne_val)
+ u"\\\\ \n"
@ -412,9 +410,11 @@ def get_avis_poursuite_par_etudiant(
if pe_tools.PE_DEBUG:
pe_tools.pe_print(jury.syntheseJury[etudid]["nom"] + " " + etudid)
sexe = jury.syntheseJury[etudid]["sexe"].decode(SCO_ENCODING)
nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-").decode(SCO_ENCODING)
prenom = jury.syntheseJury[etudid]["prenom"].replace(" ", "-").decode(SCO_ENCODING)
civilite_str = jury.syntheseJury[etudid]["civilite_str"].decode(scu.SCO_ENCODING)
nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-").decode(scu.SCO_ENCODING)
prenom = (
jury.syntheseJury[etudid]["prenom"].replace(" ", "-").decode(scu.SCO_ENCODING)
)
nom_fichier = (
u"avis_poursuite_"
@ -429,7 +429,9 @@ def get_avis_poursuite_par_etudiant(
# Entete (commentaire)
contenu_latex = u"%% ---- Etudiant: " + sexe + " " + nom + " " + prenom + u"\n"
contenu_latex = (
u"%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + u"\n"
)
# les annnotations
annotationPE = get_annotation_PE(
@ -460,11 +462,11 @@ def get_templates_from_distrib(template="avis"):
if template in ["avis", "footer"]:
# pas de preference pour le template: utilise fichier du serveur
p = os.path.join(SCO_SRCDIR, pe_local_tmpl)
p = os.path.join(scu.SCO_SRCDIR, pe_local_tmpl)
if os.path.exists(p):
template_latex = get_code_latex_from_modele(p)
else:
p = os.path.join(SCO_SRCDIR, pe_default_tmpl)
p = os.path.join(scu.SCO_SRCDIR, pe_default_tmpl)
if os.path.exists(p):
template_latex = get_code_latex_from_modele(p)
else:
@ -502,7 +504,7 @@ def table_syntheseAnnotationPE(context, syntheseJury, tag_annotation_pe):
[syntheseJury[etudid]["nbSemestres"] for etudid in etudids]
) # le nombre de semestre le + grand
infos = ["sexe", "nom", "prenom", "age", "nbSemestres"]
infos = ["civilite", "nom", "prenom", "age", "nbSemestres"]
entete = ["etudid"]
entete.extend(infos)
entete.extend(["P%d" % i for i in range(1, maxParcours + 1)]) # ajout du parcours
@ -518,7 +520,7 @@ def table_syntheseAnnotationPE(context, syntheseJury, tag_annotation_pe):
# Les info générales:
row = {
"etudid": etudid,
"sexe": e["sexe"],
"civilite": e["civilite"],
"nom": e["nom"],
"prenom": e["prenom"],
"age": e["age"],
@ -534,7 +536,9 @@ def table_syntheseAnnotationPE(context, syntheseJury, tag_annotation_pe):
annotationPE = get_annotation_PE(
context, etudid, tag_annotation_pe=tag_annotation_pe
)
row["Annotation PE"] = annotationPE.encode(SCO_ENCODING) if annotationPE else ""
row["Annotation PE"] = (
annotationPE.encode(scu.SCO_ENCODING) if annotationPE else ""
)
rows.append(row)
T = GenTable(

View File

@ -42,6 +42,7 @@ Created on Fri Sep 9 09:15:05 2016
# a l'edition d'un jury de poursuites d'etudes
# ----------------------------------------------------------
import os
try:
from cStringIO import StringIO
@ -54,10 +55,9 @@ from zipfile import ZipFile, BadZipfile
import pprint
from gen_tables import GenTable, SeqGenTable
import sco_utils as scu
import sco_codes_parcours # sco_codes_parcours.NEXT -> sem suivant
import sco_report
from sco_utils import *
import pe_tagtable, pe_tools, pe_avislatex, pe_semestretag, pe_settag
@ -85,7 +85,7 @@ class JuryPE:
- context : le contexte Zope
- juryEtudDict : dictionnaire récapitulant les étudiants participant au jury PE (données administratives +
celles des semestres valides à prendre en compte permettant le calcul des moyennes ...
{'etudid : { 'nom', 'prenom', 'sexe', 'diplome', '', }}
{'etudid : { 'nom', 'prenom', 'civilite', 'diplome', '', }}
Rq: il contient à la fois les étudiants qui vont être diplomés à la date prévue
et ceux qui sont éliminés (abandon, redoublement, ...) pour affichage alternatif
@ -450,15 +450,15 @@ class JuryPE:
end="",
)
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2:
print
# if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2:
# print
# ------------------------------------------------------------------------------------------------------------------
def est_un_etudiant_reoriente_ou_demissionnaire(self, etudid):
"""Renvoie True si l'étudiant est réorienté (NAR) ou démissionnaire (DEM)"""
reponse = False
etud = self.get_cache_etudInfo_d_un_etudiant(self.context, etudid)
(code, parcours) = sco_report.get_codeparcoursetud(self.context.Notes, etud)
(_, parcours) = sco_report.get_codeparcoursetud(self.context.Notes, etud)
if (
len(set(sco_codes_parcours.CODES_SEM_REO.keys()) & set(parcours.values()))
> 0
@ -812,7 +812,8 @@ class JuryPE:
self.syntheseJury[etudid] = {
"nom": etudinfo["nom"],
"prenom": etudinfo["prenom"],
"sexe": etudinfo["sexe"],
"civilite": etudinfo["civilite"],
"civilite_str": etudinfo["civilite_str"],
"age": str(pe_tools.calcul_age(etudinfo["date_naissance"])),
"lycee": etudinfo["nomlycee"]
+ (
@ -862,13 +863,13 @@ class JuryPE:
def get_dateEntree(self, etudid):
"""Renvoie l'année d'entrée de l'étudiant à l'IUT"""
etudinfo = self.ETUDINFO_DICT[etudid]
# etudinfo = self.ETUDINFO_DICT[etudid]
semDeb = self.get_semestresDUT_d_un_etudiant(etudid)[-1] # le 1er sem à l'IUT
return semDeb["annee_debut"]
def get_parcoursIUT(self, etudid):
"""Renvoie une liste d'infos sur les semestres du parcours d'un étudiant"""
etudinfo = self.ETUDINFO_DICT[etudid]
# etudinfo = self.ETUDINFO_DICT[etudid]
sems = self.get_semestresDUT_d_un_etudiant(etudid)
infos = []
@ -985,7 +986,7 @@ class JuryPE:
[self.syntheseJury[etudid]["nbSemestres"] for etudid in etudids]
)
infos = ["sexe", "nom", "prenom", "age", "nbSemestres"]
infos = ["civilite", "nom", "prenom", "age", "nbSemestres"]
entete = ["etudid"]
entete.extend(infos)
entete.extend(["P%d" % i for i in range(1, maxParcours + 1)])
@ -1036,7 +1037,7 @@ class JuryPE:
# Les info générales:
row = {
"etudid": etudid,
"sexe": e["sexe"],
"civilite": e["civilite"],
"nom": e["nom"],
"prenom": e["prenom"],
"age": e["age"],
@ -1073,7 +1074,7 @@ class JuryPE:
if mode == "singlesheet"
else "%s " % (sem)
)
row[champ + "note"] = fmt_note(resgroupe[0])
row[champ + "note"] = scu.fmt_note(resgroupe[0])
row[champ + "class groupe"] = "%s / %s" % (
resgroupe[2],
resgroupe[3],
@ -1083,11 +1084,12 @@ class JuryPE:
respromo[3],
)
row[champ + "min/moy/max groupe"] = "%s / %s / %s" % tuple(
fmt_note(x)
scu.fmt_note(x)
for x in (resgroupe[6], resgroupe[4], resgroupe[5])
)
row[champ + "min/moy/max promo"] = "%s / %s / %s" % tuple(
fmt_note(x) for x in (respromo[6], respromo[4], respromo[5])
scu.fmt_note(x)
for x in (respromo[6], respromo[4], respromo[5])
)
rows.append(row)

View File

@ -35,8 +35,10 @@ Created on Fri Sep 9 09:15:05 2016
@author: barasc
"""
import notes_table
import datetime
from notes_log import log
import notes_table
import sco_codes_parcours
import sco_tag_module
import sco_utils
@ -57,7 +59,7 @@ class SemestreTag(pe_tagtable.TableTag):
Attributs supplémentaires :
- inscrlist/identdict: étudiants inscrits hors démissionnaires ou défaillants
- _tagdict : Dictionnaire résumant les tags et les modules du semestre auxquels ils sont liés
- _sum_coeff_semestre : la somme des coeffs du semestre
Attributs hérités de TableTag :
- nom :
@ -111,7 +113,7 @@ class SemestreTag(pe_tagtable.TableTag):
self.modimpls = [
modimpl
for modimpl in self.nt._modimpls
if modimpl["ue"]["type"] == sco_utils.UE_STANDARD
if modimpl["ue"]["type"] == sco_codes_parcours.UE_STANDARD
] # la liste des modules (objet modimpl)
# self._modimpl_ids = [modimpl['moduleimpl_id'] for modimpl in self._modimpls] # la liste de id des modules (modimpl_id)
self.somme_coeffs = sum(
@ -136,8 +138,7 @@ class SemestreTag(pe_tagtable.TableTag):
# -----------------------------------------------------------------------------
def get_etudids(self):
"""Renvoie la liste des etud_id des étudiants inscrits au semestre
"""
"""Renvoie la liste des etud_id des étudiants inscrits au semestre"""
return [etud["etudid"] for etud in self.inscrlist]
# -----------------------------------------------------------------------------
@ -382,7 +383,7 @@ class SemestreTag(pe_tagtable.TableTag):
+ (
"%1.5f" % (coeff * self.somme_coeffs)
if coeff != None and isinstance(coeff, float)
else str(coeff * self._sum_coeff_semestre)
else "???" # str(coeff * self._sum_coeff_semestre) # voir avec Cléo
)
+ delim
)
@ -477,8 +478,9 @@ def get_moduleimpl(nt, modimpl_id):
] # la liste de id des modules (modimpl_id)
if modimpl_id not in modimplids:
if SemestreTag.DEBUG:
print "SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas" % (
modimpl_id
log(
"SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas"
% (modimpl_id)
)
return None
return nt._modimpls[modimplids.index(modimpl_id)]

View File

@ -36,7 +36,7 @@ Created on Fri Sep 9 09:15:05 2016
@author: barasc
"""
from pe_tools import *
from pe_tools import pe_print, PE_DEBUG
import pe_tagtable
import pe_semestretag

View File

@ -210,7 +210,7 @@ class TableTag:
]
nb_notes_valides = len(notes_valides)
if nb_notes_valides > 0:
(moy, coeff) = moyenne_ponderee_terme_a_terme(notes_valides, force=True)
(moy, _) = moyenne_ponderee_terme_a_terme(notes_valides, force=True)
self.statistiques[tag] = (moy, max(notes_valides), min(notes_valides))
# ************************************************************************

View File

@ -37,12 +37,14 @@ Created on Thu Sep 8 09:36:33 2016
"""
from __future__ import print_function
import os
import datetime
import operator
import re
import unicodedata
from sco_utils import *
import sco_utils as scu
from notes_log import log
import notes_table
PE_DEBUG = 0
@ -175,7 +177,7 @@ def add_pe_stuff_to_zip(context, zipfile, ziproot):
Also copy logos
"""
PE_AUX_DIR = os.path.join(SCO_SRCDIR, "config/doc_poursuites_etudes")
PE_AUX_DIR = os.path.join(scu.SCO_SRCDIR, "config/doc_poursuites_etudes")
distrib_dir = os.path.join(PE_AUX_DIR, "distrib")
distrib_pathnames = list_directory_filenames(
distrib_dir
@ -204,7 +206,7 @@ def add_pe_stuff_to_zip(context, zipfile, ziproot):
# Logos: (add to logos/ directory in zip)
logos_names = ["logo_header.jpg", "logo_footer.jpg"]
for f in logos_names:
logo = os.path.join(SCODOC_LOGOS_DIR, f)
logo = os.path.join(scu.SCODOC_LOGOS_DIR, f)
if os.path.isfile(logo):
add_local_file_to_zip(zipfile, ziproot, logo, "avis/logos/" + f)
@ -215,7 +217,7 @@ JURY_SYNTHESE_POUR_DEBUG = {
"EID1810": {
"nom": "ROUX",
"entree": "2016",
"sexe": "M.",
"civilite_str": "M.",
"promo": 2016,
"S2": {
"groupe": {

View File

@ -35,7 +35,7 @@
"""
from sco_utils import *
import sco_utils as scu
from notes_log import log
import sco_formsemestre
import sco_formsemestre_status
@ -44,7 +44,7 @@ from gen_tables import GenTable
import sco_codes_parcours
import pe_tools
from pe_tools import *
from pe_tools import PE_LATEX_ENCODING
import pe_tagtable
import pe_semestretag
import pe_settag
@ -110,7 +110,7 @@ def pe_view_sem_recap(
# template fourni via le formulaire Web
if avis_tmpl_file:
template_latex = avis_tmpl_file.read()
template_latex = template_latex.decode(SCO_ENCODING)
template_latex = template_latex.decode(scu.SCO_ENCODING)
else:
# template indiqué dans préférences ScoDoc ?
template_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
@ -127,7 +127,7 @@ def pe_view_sem_recap(
# template fourni via le formulaire Web
if footer_tmpl_file:
footer_latex = footer_tmpl_file.read()
footer_latex = footer_latex.decode(SCO_ENCODING)
footer_latex = footer_latex.decode(scu.SCO_ENCODING)
else:
footer_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
context, formsemestre_id, champ="pe_avis_latex_footer"

633
sco_abs.py Normal file
View File

@ -0,0 +1,633 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""Fonctions sur les absences
"""
# Anciennement dans ZAbscences.py, séparé pour migration
import string
import datetime
import re
import time
import calendar
import cgi
import notesdb
from sco_exceptions import ScoValueError, ScoInvalidDateError
import sco_formsemestre
import sco_compute_moy
def is_work_saturday(context):
"Vrai si le samedi est travaillé"
return int(context.get_preference("work_saturday"))
def MonthNbDays(month, year):
"returns nb of days in month"
if month > 7:
month = month + 1
if month % 2:
return 31
elif month == 2:
if calendar.isleap(year):
return 29
else:
return 28
else:
return 30
class ddmmyyyy:
"""immutable dates"""
def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False):
self.work_saturday = work_saturday
if date is None:
return
try:
if fmt == "ddmmyyyy":
self.day, self.month, self.year = string.split(date, "/")
elif fmt == "iso":
self.year, self.month, self.day = string.split(date, "-")
else:
raise ValueError("invalid format spec. (%s)" % fmt)
self.year = string.atoi(self.year)
self.month = string.atoi(self.month)
self.day = string.atoi(self.day)
except:
raise ScoValueError("date invalide: %s" % date)
# accept years YYYY or YY, uses 1970 as pivot
if self.year < 1970:
if self.year > 100:
raise ScoInvalidDateError("Année invalide: %s" % self.year)
if self.year < 70:
self.year = self.year + 2000
else:
self.year = self.year + 1900
if self.month < 1 or self.month > 12:
raise ScoInvalidDateError("Mois invalide: %s" % self.month)
if self.day < 1 or self.day > MonthNbDays(self.month, self.year):
raise ScoInvalidDateError("Jour invalide: %s" % self.day)
# weekday in 0-6, where 0 is monday
self.weekday = calendar.weekday(self.year, self.month, self.day)
self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0))
def iswork(self):
"returns true if workable day"
if self.work_saturday:
nbdays = 6
else:
nbdays = 5
if (
self.weekday >= 0 and self.weekday < nbdays
): # monday-friday or monday-saturday
return 1
else:
return 0
def __repr__(self):
return "'%02d/%02d/%04d'" % (self.day, self.month, self.year)
def __str__(self):
return "%02d/%02d/%04d" % (self.day, self.month, self.year)
def ISO(self):
"iso8601 representation of the date"
return "%04d-%02d-%02d" % (self.year, self.month, self.day)
def next(self, days=1):
"date for the next day (nota: may be a non workable day)"
day = self.day + days
month = self.month
year = self.year
while day > MonthNbDays(month, year):
day = day - MonthNbDays(month, year)
month = month + 1
if month > 12:
month = 1
year = year + 1
return self.__class__(
"%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
)
def prev(self, days=1):
"date for previous day"
day = self.day - days
month = self.month
year = self.year
while day <= 0:
month = month - 1
if month == 0:
month = 12
year = year - 1
day = day + MonthNbDays(month, year)
return self.__class__(
"%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
)
def next_monday(self):
"date of next monday"
return self.next((7 - self.weekday) % 7)
def prev_monday(self):
"date of last monday, but on sunday, pick next monday"
if self.weekday == 6:
return self.next_monday()
else:
return self.prev(self.weekday)
def __cmp__(self, other):
"""return a negative integer if self < other,
zero if self == other, a positive integer if self > other"""
return int(self.time - other.time)
def __hash__(self):
"we are immutable !"
return hash(self.time) ^ hash(str(self))
# d = ddmmyyyy( '21/12/99' )
def DateRangeISO(context, date_beg, date_end, workable=1):
"""returns list of dates in [date_beg,date_end]
workable = 1 => keeps only workable days"""
if not date_beg:
raise ScoValueError("pas de date spécifiée !")
if not date_end:
date_end = date_beg
r = []
work_saturday = is_work_saturday(context)
cur = ddmmyyyy(date_beg, work_saturday=work_saturday)
end = ddmmyyyy(date_end, work_saturday=work_saturday)
while cur <= end:
if (not workable) or cur.iswork():
r.append(cur)
cur = cur.next()
return map(lambda x: x.ISO(), r)
def day_names(context):
"""Returns week day names.
If work_saturday property is set, include saturday
"""
if is_work_saturday(context):
return ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"]
else:
return ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi"]
def next_iso_day(context, date):
"return date after date"
d = ddmmyyyy(date, fmt="iso", work_saturday=is_work_saturday(context))
return d.next().ISO()
def YearTable(
context,
year,
events=[],
firstmonth=9,
lastmonth=7,
halfday=0,
dayattributes="",
pad_width=8,
):
"""Generate a calendar table
events = list of tuples (date, text, color, href [,halfday])
where date is a string in ISO format (yyyy-mm-dd)
halfday is boolean (true: morning, false: afternoon)
text = text to put in calendar (must be short, 1-5 cars) (optional)
if halfday, generate 2 cells per day (morning, afternoon)
"""
T = [
'<table id="maincalendar" class="maincalendar" border="3" cellpadding="1" cellspacing="1" frame="box">'
]
T.append("<tr>")
month = firstmonth
while 1:
T.append('<td valign="top">')
T.append(MonthTableHead(month))
T.append(
MonthTableBody(
month,
year,
events,
halfday,
dayattributes,
is_work_saturday(context),
pad_width=pad_width,
)
)
T.append(MonthTableTail())
T.append("</td>")
if month == lastmonth:
break
month = month + 1
if month > 12:
month = 1
year = year + 1
T.append("</table>")
return string.join(T, "\n")
# ---- BILLETS
_billet_absenceEditor = notesdb.EditableTable(
"billet_absence",
"billet_id",
(
"billet_id",
"etudid",
"abs_begin",
"abs_end",
"description",
"etat",
"entry_date",
"justified",
),
sortkey="entry_date desc",
)
billet_absence_create = _billet_absenceEditor.create
billet_absence_delete = _billet_absenceEditor.delete
billet_absence_list = _billet_absenceEditor.list
billet_absence_edit = _billet_absenceEditor.edit
# ------ HTML Calendar functions (see YearTable function)
# MONTH/DAY NAMES:
MONTHNAMES = (
"Janvier",
"F&eacute;vrier",
"Mars",
"Avril",
"Mai",
"Juin",
"Juillet",
"Aout",
"Septembre",
"Octobre",
"Novembre",
"D&eacute;cembre",
)
MONTHNAMES_ABREV = (
"Jan.",
"F&eacute;v.",
"Mars",
"Avr.",
"Mai&nbsp;",
"Juin",
"Juil",
"Aout",
"Sept",
"Oct.",
"Nov.",
"D&eacute;c.",
)
DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D")
# COLORS:
WHITE = "#FFFFFF"
GRAY1 = "#EEEEEE"
GREEN3 = "#99CC99"
WEEKDAYCOLOR = GRAY1
WEEKENDCOLOR = GREEN3
def MonthTableHead(month):
color = WHITE
return """<table class="monthcalendar" border="0" cellpadding="0" cellspacing="0" frame="box">
<tr bgcolor="%s"><td class="calcol" colspan="2" align="center">%s</td></tr>\n""" % (
color,
MONTHNAMES_ABREV[month - 1],
)
def MonthTableTail():
return "</table>\n"
def MonthTableBody(
month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8
):
firstday, nbdays = calendar.monthrange(year, month)
localtime = time.localtime()
current_weeknum = time.strftime("%U", localtime)
current_year = localtime[0]
T = []
# cherche date du lundi de la 1ere semaine de ce mois
monday = ddmmyyyy("1/%d/%d" % (month, year))
while monday.weekday != 0:
monday = monday.prev()
if work_saturday:
weekend = ("D",)
else:
weekend = ("S", "D")
if not halfday:
for d in range(1, nbdays + 1):
weeknum = time.strftime(
"%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
)
day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
if day in weekend:
bgcolor = WEEKENDCOLOR
weekclass = "wkend"
attrs = ""
else:
bgcolor = WEEKDAYCOLOR
weekclass = "wk" + str(monday).replace("/", "_")
attrs = trattributes
color = None
legend = ""
href = ""
descr = ""
# event this day ?
# each event is a tuple (date, text, color, href)
# where date is a string in ISO format (yyyy-mm-dd)
for ev in events:
ev_year = int(ev[0][:4])
ev_month = int(ev[0][5:7])
ev_day = int(ev[0][8:10])
if year == ev_year and month == ev_month and ev_day == d:
if ev[1]:
legend = ev[1]
if ev[2]:
color = ev[2]
if ev[3]:
href = ev[3]
if len(ev) > 4 and ev[4]:
descr = ev[4]
#
cc = []
if color != None:
cc.append('<td bgcolor="%s" class="calcell">' % color)
else:
cc.append('<td class="calcell">')
if href:
href = 'href="%s"' % href
if descr:
descr = 'title="%s"' % cgi.escape(descr, quote=True)
if href or descr:
cc.append("<a %s %s>" % (href, descr))
if legend or d == 1:
if pad_width != None:
n = pad_width - len(legend) # pad to 8 cars
if n > 0:
legend = "&nbsp;" * (n / 2) + legend + "&nbsp;" * ((n + 1) / 2)
else:
legend = "&nbsp;" # empty cell
cc.append(legend)
if href or descr:
cc.append("</a>")
cc.append("</td>")
cell = string.join(cc, "")
if day == "D":
monday = monday.next(7)
if (
weeknum == current_weeknum
and current_year == year
and weekclass != "wkend"
):
weekclass += " currentweek"
T.append(
'<tr bgcolor="%s" class="%s" %s><td class="calday">%d%s</td>%s</tr>'
% (bgcolor, weekclass, attrs, d, day, cell)
)
else:
# Calendar with 2 cells / day
for d in range(1, nbdays + 1):
weeknum = time.strftime(
"%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
)
day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
if day in weekend:
bgcolor = WEEKENDCOLOR
weekclass = "wkend"
attrs = ""
else:
bgcolor = WEEKDAYCOLOR
weekclass = "wk" + str(monday).replace("/", "_")
attrs = trattributes
if (
weeknum == current_weeknum
and current_year == year
and weekclass != "wkend"
):
weeknum += " currentweek"
if day == "D":
monday = monday.next(7)
T.append(
'<tr bgcolor="%s" class="wk%s" %s><td class="calday">%d%s</td>'
% (bgcolor, weekclass, attrs, d, day)
)
cc = []
for morning in (1, 0):
color = None
legend = ""
href = ""
descr = ""
for ev in events:
ev_year = int(ev[0][:4])
ev_month = int(ev[0][5:7])
ev_day = int(ev[0][8:10])
if ev[4] != None:
ev_half = int(ev[4])
else:
ev_half = 0
if (
year == ev_year
and month == ev_month
and ev_day == d
and morning == ev_half
):
if ev[1]:
legend = ev[1]
if ev[2]:
color = ev[2]
if ev[3]:
href = ev[3]
if len(ev) > 5 and ev[5]:
descr = ev[5]
#
if color != None:
cc.append('<td bgcolor="%s" class="calcell">' % (color))
else:
cc.append('<td class="calcell">')
if href:
href = 'href="%s"' % href
if descr:
descr = 'title="%s"' % cgi.escape(descr, quote=True)
if href or descr:
cc.append("<a %s %s>" % (href, descr))
if legend or d == 1:
n = 3 - len(legend) # pad to 3 cars
if n > 0:
legend = "&nbsp;" * (n / 2) + legend + "&nbsp;" * ((n + 1) / 2)
else:
legend = "&nbsp;&nbsp;&nbsp;" # empty cell
cc.append(legend)
if href or descr:
cc.append("</a>")
cc.append("</td>\n")
T.append(string.join(cc, "") + "</tr>")
return string.join(T, "\n")
# --------------------------------------------------------------------
#
# Cache absences
#
# On cache simplement (à la demande) le nombre d'absences de chaque etudiant
# dans un semestre donné.
# Toute modification du semestre (invalidation) invalide le cache
# (simple mécanisme de "listener" sur le cache de semestres)
# Toute modification des absences d'un étudiant invalide les caches des semestres
# concernés à cette date (en général un seul semestre)
#
# On ne cache pas la liste des absences car elle est rarement utilisée (calendrier,
# absences à une date donnée).
#
# --------------------------------------------------------------------
class CAbsSemEtud:
"""Comptes d'absences d'un etudiant dans un semestre"""
def __init__(self, context, sem, etudid):
self.context = context
self.sem = sem
self.etudid = etudid
self._loaded = False
formsemestre_id = sem["formsemestre_id"]
context.Notes._getNotesCache().add_listener(
self.invalidate, formsemestre_id, (etudid, formsemestre_id)
)
def CountAbs(self):
if not self._loaded:
self.load()
return self._CountAbs
def CountAbsJust(self):
if not self._loaded:
self.load()
return self._CountAbsJust
def load(self):
"Load state from DB"
# log('loading CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id']))
# Reload sem, it may have changed
self.sem = sco_formsemestre.get_formsemestre(
self.context, self.sem["formsemestre_id"]
)
debut_sem = notesdb.DateDMYtoISO(self.sem["date_debut"])
fin_sem = notesdb.DateDMYtoISO(self.sem["date_fin"])
self._CountAbs = self.context.Absences.CountAbs(
etudid=self.etudid, debut=debut_sem, fin=fin_sem
)
self._CountAbsJust = self.context.Absences.CountAbsJust(
etudid=self.etudid, debut=debut_sem, fin=fin_sem
)
self._loaded = True
def invalidate(self, args=None):
"Notify me that DB has been modified"
# log('invalidate CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id']))
self._loaded = False
# Accès au cache des absences
ABS_CACHE_INST = {} # { DeptId : { formsemestre_id : { etudid : CAbsEtudSem } } }
def getAbsSemEtud(context, sem, etudid):
AbsSemEtuds = getAbsSemEtuds(context, sem)
if not etudid in AbsSemEtuds:
AbsSemEtuds[etudid] = CAbsSemEtud(context, sem, etudid)
return AbsSemEtuds[etudid]
def getAbsSemEtuds(context, sem):
u = context.GetDBConnexionString() # identifie le dept de facon fiable
if not u in ABS_CACHE_INST:
ABS_CACHE_INST[u] = {}
C = ABS_CACHE_INST[u]
if sem["formsemestre_id"] not in C:
C[sem["formsemestre_id"]] = {}
return C[sem["formsemestre_id"]]
def invalidateAbsEtudDate(context, etudid, date):
"""Doit etre appelé à chaque modification des absences pour cet étudiant et cette date.
Invalide cache absence et PDF bulletins si nécessaire.
date: date au format ISO
"""
# Semestres a cette date:
etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
sems = [
sem
for sem in etud["sems"]
if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date
]
# Invalide les PDF et les abscences:
for sem in sems:
# Inval cache bulletin et/ou note_table
if sco_compute_moy.formsemestre_expressions_use_abscounts(
context, sem["formsemestre_id"]
):
pdfonly = False # seules certaines formules utilisent les absences
else:
pdfonly = (
True # efface toujours le PDF car il affiche en général les absences
)
context.Notes._inval_cache(
pdfonly=pdfonly, formsemestre_id=sem["formsemestre_id"]
)
# Inval cache compteurs absences:
AbsSemEtuds = getAbsSemEtuds(context, sem)
if etudid in AbsSemEtuds:
AbsSemEtuds[etudid].invalidate()

View File

@ -31,14 +31,17 @@
Il suffit d'appeler abs_notify() après chaque ajout d'absence.
"""
import datetime
from email.MIMEMultipart import ( # pylint: disable=no-name-in-module,import-error
MIMEMultipart,
)
from email.MIMEText import MIMEText # pylint: disable=no-name-in-module,import-error
from email.MIMEBase import MIMEBase # pylint: disable=no-name-in-module,import-error
from email.Header import Header # pylint: disable=no-name-in-module,import-error
from email import Encoders # pylint: disable=no-name-in-module,import-error
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.Header import Header
from email import Encoders
from notesdb import *
from sco_utils import *
import notesdb as ndb
import sco_utils as scu
from notes_log import log
from scolog import logdb
import sco_bulletins
@ -55,8 +58,8 @@ def abs_notify(context, etudid, date):
if not sem:
return # non inscrit a la date, pas de notification
debut_sem = DateDMYtoISO(sem["date_debut"])
fin_sem = DateDMYtoISO(sem["date_fin"])
debut_sem = ndb.DateDMYtoISO(sem["date_debut"])
fin_sem = ndb.DateDMYtoISO(sem["date_fin"])
nbabs = context.CountAbs(etudid, debut=debut_sem, fin=fin_sem)
nbabsjust = context.CountAbsJust(etudid, debut=debut_sem, fin=fin_sem)
@ -109,12 +112,12 @@ def abs_notify_send(
"""Actually send the notification by email, and register it in database"""
cnx = context.GetDBConnexion()
log("abs_notify: sending notification to %s" % destinations)
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
for email in destinations:
del msg["To"]
msg["To"] = email
context.sendEmail(msg)
SimpleQuery(
ndb.SimpleQuery(
context,
"""insert into absences_notifications (etudid, email, nbabs, nbabsjust, formsemestre_id) values (%(etudid)s, %(email)s, %(nbabs)s, %(nbabsjust)s, %(formsemestre_id)s)""",
vars(),
@ -201,7 +204,7 @@ def etud_nbabs_last_notified(context, etudid, formsemestre_id=None):
"""nbabs lors de la dernière notification envoyée pour cet étudiant dans ce semestre
ou sans semestre (ce dernier cas est nécessaire pour la transition au nouveau code)"""
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"""select * from absences_notifications where etudid = %(etudid)s and (formsemestre_id = %(formsemestre_id)s or formsemestre_id is NULL) order by notification_date desc""",
vars(),
@ -216,17 +219,15 @@ def etud_nbabs_last_notified(context, etudid, formsemestre_id=None):
def user_nbdays_since_last_notif(context, email_addr, etudid):
"""nb days since last notification to this email, or None if no previous notification"""
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"""select * from absences_notifications where email = %(email_addr)s and etudid=%(etudid)s order by notification_date desc""",
{"email_addr": email_addr, "etudid": etudid},
)
res = cursor.dictfetchone()
if res:
mxd = res["notification_date"] # mx.DateTime instance
lastdate = datetime.datetime(mxd.year, mxd.month, mxd.day)
now = datetime.datetime.now()
return (now - lastdate).days
now = datetime.datetime.now(res["notification_date"].tzinfo)
return (now - res["notification_date"]).days
else:
return None
@ -255,10 +256,10 @@ def abs_notification_message(context, sem, prefs, etudid, nbabs, nbabsjust):
subject = """Trop d'absences pour %(nomprenom)s""" % etud
#
msg = MIMEMultipart()
subj = Header("[ScoDoc] " + subject, SCO_ENCODING)
subj = Header("[ScoDoc] " + subject, scu.SCO_ENCODING)
msg["Subject"] = subj
msg["From"] = prefs["email_from_addr"]
txt = MIMEText(txt, "plain", SCO_ENCODING)
txt = MIMEText(txt, "plain", scu.SCO_ENCODING)
msg.attach(txt)
return msg
@ -271,7 +272,7 @@ def retreive_current_formsemestre(context, etudid, cur_date):
WHERE sem.formsemestre_id = i.formsemestre_id AND i.etudid=%(etudid)s
AND (%(cur_date)s >= sem.date_debut) AND (%(cur_date)s <= sem.date_fin)"""
r = SimpleDictFetch(context, req, {"etudid": etudid, "cur_date": cur_date})
r = ndb.SimpleDictFetch(context, req, {"etudid": etudid, "cur_date": cur_date})
if not r:
return None
# s'il y a plusieurs semestres, prend le premier (rarissime et non significatif):
@ -284,5 +285,5 @@ def mod_with_evals_at_date(context, date_abs, etudid):
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
AND i.etudid = %(etudid)s AND e.jour = %(date_abs)s"""
r = SimpleDictFetch(context, req, {"etudid": etudid, "date_abs": date_abs})
r = ndb.SimpleDictFetch(context, req, {"etudid": etudid, "date_abs": date_abs})
return r

View File

@ -28,19 +28,24 @@
"""Pages HTML gestion absences
(la plupart portées du DTML)
"""
import datetime
from stripogram import html2text, html2safehtml
from gen_tables import GenTable
from notesdb import *
from sco_utils import *
from notesdb import DateISOtoDMY
import sco_utils as scu
from sco_exceptions import ScoValueError
from sco_permissions import ScoAbsChange
from notes_log import log
import sco_groups
import sco_find_etud
import sco_formsemestre
import sco_moduleimpl
import sco_photos
import ZAbsences
import sco_abs
def doSignaleAbsence(
@ -51,17 +56,29 @@ def doSignaleAbsence(
demijournee=2,
estjust=False,
description=None,
etudid=False,
REQUEST=None,
): # etudid implied
"""Signalement d'une absence"""
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
"""Signalement d'une absence.
Args:
datedebut: dd/mm/yyyy
datefin: dd/mm/yyyy (non incluse)
moduleimpl_id: module auquel imputer les absences
demijournee: 2 si journée complète, 1 matin, 0 après-midi
estjust: absence justifiée
description: str
etudid: etudiant concerné. Si non spécifié, cherche dans REQUEST.form
"""
etud = context.getEtudInfo(filled=1, etudid=etudid, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
description_abs = description
dates = context.DateRangeISO(datedebut, datefin)
dates = sco_abs.DateRangeISO(context, datedebut, datefin)
nbadded = 0
demijournee = int(demijournee)
for jour in dates:
if demijournee == "2":
if demijournee == 2:
context._AddAbsence(
etudid, jour, False, estjust, REQUEST, description_abs, moduleimpl_id
)
@ -70,9 +87,8 @@ def doSignaleAbsence(
)
nbadded += 2
else:
matin = int(demijournee)
context._AddAbsence(
etudid, jour, matin, estjust, REQUEST, description_abs, moduleimpl_id
etudid, jour, demijournee, estjust, REQUEST, description_abs, moduleimpl_id
)
nbadded += 1
#
@ -82,7 +98,7 @@ def doSignaleAbsence(
J = "NON "
M = ""
if moduleimpl_id and moduleimpl_id != "NULL":
mod = context.Notes.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
mod = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0]
formsemestre_id = mod["formsemestre_id"]
nt = context.Notes._getNotesCache().get_NotesTable(
context.Notes, formsemestre_id
@ -242,16 +258,32 @@ Raison: <input type="text" name="description" size="42"/> (optionnel)
def doJustifAbsence(
context, datedebut, datefin, demijournee, description=None, REQUEST=None
context,
datedebut,
datefin,
demijournee,
description=None,
etudid=False,
REQUEST=None,
): # etudid implied
"""Justification d'une absence"""
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
"""Justification d'une absence
Args:
datedebut: dd/mm/yyyy
datefin: dd/mm/yyyy (non incluse)
demijournee: 2 si journée complète, 1 matin, 0 après-midi
estjust: absence justifiée
description: str
etudid: etudiant concerné. Si non spécifié, cherche dans REQUEST.form
"""
etud = context.getEtudInfo(filled=1, etudid=etudid, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
description_abs = description
dates = context.DateRangeISO(datedebut, datefin)
dates = sco_abs.DateRangeISO(context, datedebut, datefin)
nbadded = 0
demijournee = int(demijournee)
for jour in dates:
if demijournee == "2":
if demijournee == 2:
context._AddJustif(
etudid=etudid,
jour=jour,
@ -268,11 +300,10 @@ def doJustifAbsence(
)
nbadded += 2
else:
matin = int(demijournee)
context._AddJustif(
etudid=etudid,
jour=jour,
matin=matin,
matin=demijournee,
REQUEST=REQUEST,
description=description_abs,
)
@ -365,22 +396,22 @@ Raison: <input type="text" name="description" size="42"/> (optionnel)
def doAnnuleAbsence(
context, datedebut, datefin, demijournee, REQUEST=None
context, datedebut, datefin, demijournee, etudid=False, REQUEST=None
): # etudid implied
"""Annulation des absences pour une demi journée"""
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etud = context.getEtudInfo(filled=1, etudid=etudid, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
dates = context.DateRangeISO(datedebut, datefin)
dates = sco_abs.DateRangeISO(context, datedebut, datefin)
nbadded = 0
demijournee = int(demijournee)
for jour in dates:
if demijournee == "2":
if demijournee == 2:
context._AnnuleAbsence(etudid, jour, False, REQUEST=REQUEST)
context._AnnuleAbsence(etudid, jour, True, REQUEST=REQUEST)
nbadded += 2
else:
matin = int(demijournee)
context._AnnuleAbsence(etudid, jour, matin, REQUEST=REQUEST)
context._AnnuleAbsence(etudid, jour, demijournee, REQUEST=REQUEST)
nbadded += 1
#
H = [
@ -506,17 +537,17 @@ def doAnnuleJustif(
"""Annulation d'une justification"""
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
dates = context.DateRangeISO(datedebut0, datefin0)
dates = sco_abs.DateRangeISO(context, datedebut0, datefin0)
nbadded = 0
demijournee = int(demijournee)
for jour in dates:
# Attention: supprime matin et après-midi
if demijournee == "2":
if demijournee == 2:
context._AnnuleJustif(etudid, jour, False, REQUEST=REQUEST)
context._AnnuleJustif(etudid, jour, True, REQUEST=REQUEST)
nbadded += 2
else:
matin = int(demijournee)
context._AnnuleJustif(etudid, jour, matin, REQUEST=REQUEST)
context._AnnuleJustif(etudid, jour, demijournee, REQUEST=REQUEST)
nbadded += 1
#
H = [
@ -571,7 +602,7 @@ def EtatAbsences(context, REQUEST=None):
</td></tr></table>
</form>"""
% (AnneeScolaire(REQUEST), datetime.datetime.now().strftime("%d/%m/%Y")),
% (scu.AnneeScolaire(REQUEST), datetime.datetime.now().strftime("%d/%m/%Y")),
context.sco_footer(REQUEST),
]
return "\n".join(H)
@ -610,7 +641,7 @@ def CalAbs(context, REQUEST=None): # etud implied
# crude portage from 1999 DTML
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
anneescolaire = int(AnneeScolaire(REQUEST))
anneescolaire = int(scu.AnneeScolaire(REQUEST))
datedebut = str(anneescolaire) + "-08-31"
datefin = str(anneescolaire + 1) + "-07-31"
nbabs = context.CountAbs(etudid=etudid, debut=datedebut, fin=datefin)
@ -631,7 +662,7 @@ def CalAbs(context, REQUEST=None): # etud implied
events.append(
(str(a["jour"]), "X", "#8EA2C6", "", a["matin"], a["description"])
)
CalHTML = ZAbsences.YearTable(context, anneescolaire, events=events, halfday=1)
CalHTML = sco_abs.YearTable(context, anneescolaire, events=events, halfday=1)
#
H = [
@ -689,11 +720,11 @@ def ListeAbsEtud(
):
"""Liste des absences d'un étudiant sur l'année en cours
En format 'html': page avec deux tableaux (non justifiées et justifiées).
En format xls ou pdf: l'un ou l'autre des table, suivant absjust_only.
En format json, xml, xls ou pdf: l'un ou l'autre des table, suivant absjust_only.
En format 'text': texte avec liste d'absences (pour mails).
"""
absjust_only = int(absjust_only) # si vrai, table absjust seule (export xls ou pdf)
datedebut = "%s-08-31" % AnneeScolaire(REQUEST)
datedebut = "%s-08-31" % scu.AnneeScolaire(REQUEST)
etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
@ -701,10 +732,9 @@ def ListeAbsEtud(
titles, columns_ids, absnonjust, absjust = context.Absences._TablesAbsEtud(
etudid, datedebut, with_evals=with_evals, format=format
)
if REQUEST:
base_url_nj = "%s?etudid=%s&amp;absjust_only=0" % (REQUEST.URL0, etudid)
base_url_j = "%s?etudid=%s&amp;absjust_only=1" % (REQUEST.URL0, etudid)
base_url_nj = "%s?etudid=%s&absjust_only=0" % (REQUEST.URL0, etudid)
base_url_j = "%s?etudid=%s&absjust_only=1" % (REQUEST.URL0, etudid)
else:
base_url_nj = base_url_j = ""
tab_absnonjust = GenTable(
@ -714,7 +744,7 @@ def ListeAbsEtud(
html_class="table_leftalign",
table_id="tab_absnonjust",
base_url=base_url_nj,
filename="abs_" + make_filename(etud["nomprenom"]),
filename="abs_" + scu.make_filename(etud["nomprenom"]),
caption="Absences non justifiées de %(nomprenom)s" % etud,
preferences=context.get_preferences(),
)
@ -725,7 +755,7 @@ def ListeAbsEtud(
html_class="table_leftalign",
table_id="tab_absjust",
base_url=base_url_j,
filename="absjust_" + make_filename(etud["nomprenom"]),
filename="absjust_" + scu.make_filename(etud["nomprenom"]),
caption="Absences justifiées de %(nomprenom)s" % etud,
preferences=context.get_preferences(),
)
@ -830,7 +860,7 @@ def absences_index_html(context, REQUEST=None):
% REQUEST.URL0,
formChoixSemestreGroupe(context),
"</p>",
context.CalSelectWeek(REQUEST=REQUEST),
cal_select_week(context, REQUEST=REQUEST),
"""<p class="help">Sélectionner le groupe d'étudiants, puis cliquez sur une semaine pour
saisir les absences de toute cette semaine.</p>
</form>""",
@ -843,3 +873,16 @@ saisir les absences de toute cette semaine.</p>
H.append(context.sco_footer(REQUEST))
return "\n".join(H)
def cal_select_week(context, year=None, REQUEST=None):
"display calendar allowing week selection"
if not year:
year = scu.AnneeScolaire(REQUEST)
sems = sco_formsemestre.do_formsemestre_list(context)
if not sems:
js = ""
else:
js = 'onmouseover="highlightweek(this);" onmouseout="deselectweeks();" onclick="wclick(this);"'
C = sco_abs.YearTable(context, int(year), dayattributes=js)
return C

View File

@ -46,10 +46,11 @@ Pour chaque étudiant commun:
from collections import OrderedDict
from sco_utils import *
import sco_utils as scu
from notes_log import log
import sco_apogee_csv
from gen_tables import GenTable
from sco_exceptions import ScoValueError
_help_txt = """
<div class="help">

View File

@ -27,9 +27,7 @@
"""Exportation des résultats des étudiants vers Apogée.
EXPERIMENTAL / PRECAUTIONS !
Code inspiré par les travaux de Damien Mascré, scodoc2apogee (en Java).
Ce code a été au départ inspiré par les travaux de Damien Mascré, scodoc2apogee (en Java).
A utiliser en fin de semestre, après les jury.
@ -81,6 +79,13 @@ XXX A vérifier:
AJAC car 1 sem. validé et pas de NAR
"""
import collections
from types import FloatType
import re
import time
import datetime
import os
from cStringIO import StringIO
from zipfile import ZipFile
import pprint
@ -90,6 +95,10 @@ try:
except:
chardet_detect = None
import sco_utils as scu
import notesdb as ndb
from notes_log import log
from sco_exceptions import ScoValueError, FormatError
import sco_formsemestre
from sco_formsemestre import ApoEtapeVDI
import sco_formsemestre_status
@ -98,8 +107,6 @@ import sco_codes_parcours
from sco_codes_parcours import code_semestre_validant
from sco_codes_parcours import ATT, ATB, ADM, ADC, ADJ, ATJ, ATB, AJ, CMP, NAR, RAT, DEF
from gen_tables import GenTable
from notesdb import *
from sco_utils import *
APO_PORTAL_ENCODING = (
"utf8" # encodage du fichier CSV Apogée (était 'ISO-8859-1' avant jul. 2016)
@ -347,10 +354,10 @@ class ApoEtud(dict):
for col_id in apo_data.col_ids[:4]:
self.new_cols[col_id] = self.cols[col_id]
def unassociated_codes(self, apo_data):
"list of apo elements for this student without a value in ScoDoc"
codes = set([apo_data.cols[col_id].code for col_id in apo_data.col_ids])
return codes - set(sco_elts)
# def unassociated_codes(self, apo_data):
# "list of apo elements for this student without a value in ScoDoc"
# codes = set([apo_data.cols[col_id].code for col_id in apo_data.col_ids])
# return codes - set(sco_elts)
def search_elt_in_sem(self, context, code, sem, cur_sem, autre_sem):
"""
@ -749,7 +756,7 @@ class ApoData:
if not data:
raise FormatError("Fichier Apogée vide !")
data_utf8 = data.decode(APO_INPUT_ENCODING).encode(SCO_ENCODING)
data_utf8 = data.decode(APO_INPUT_ENCODING).encode(scu.SCO_ENCODING)
f = StringIOFileLineWrapper(data_utf8) # pour traiter comme un fichier
# check that we are at the begining of Apogee CSV
line = f.readline().strip()
@ -1023,7 +1030,7 @@ def _apo_read_cols(f):
line = f.readline().strip(" " + APO_NEWLINE)
fs = line.split(APO_SEP)
if fs[0] != "apoL_a01_code":
raise FormatError("invalid line: %s (expecting apoL_a01_code)"(line, i))
raise FormatError("invalid line: %s (expecting apoL_a01_code)" % line)
col_keys = fs
while True: # skip premiere partie (apoL_a02_nom, ...)
@ -1046,7 +1053,9 @@ def _apo_read_cols(f):
raise FormatError("duplicate column definition: %s" % col_id)
m = re.match(r"^apoL_c([0-9]{4})$", col_id)
if not m:
raise FormatError("invalid column id: %s (expecting apoL_c%04d)"(line, i))
raise FormatError(
"invalid column id: %s (expecting apoL_c%04d)" % (line, col_id)
)
if int(m.group(1)) != i:
raise FormatError("invalid column id: %s for index %s" % (col_id, i))
@ -1281,7 +1290,7 @@ def export_csv_to_apogee(
)
log(logf.getvalue()) # sortie aussi sur le log ScoDoc
csv_data = f.getvalue().decode(SCO_ENCODING).encode(APO_OUTPUT_ENCODING)
csv_data = f.getvalue().decode(scu.SCO_ENCODING).encode(APO_OUTPUT_ENCODING)
# Write data to ZIP
dest_zip.writestr(csv_filename, csv_data)

View File

@ -45,15 +45,16 @@
qui est une description (humaine, format libre) de l'archive.
"""
from mx.DateTime import DateTime as mxDateTime
import mx.DateTime
import os
import time
import datetime
import re
import shutil
import glob
from sco_utils import *
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
import sco_formsemestre
import sco_pvjury
import sco_excel
@ -62,6 +63,10 @@ import sco_groups
import sco_groups_view
from sco_recapcomplet import make_formsemestre_recapcomplet
import sco_bulletins_pdf
from TrivialFormulator import TrivialFormulator
from sco_exceptions import (
AccessDenied,
)
class BaseArchiver:
@ -75,12 +80,12 @@ class BaseArchiver:
for dir in dirs[1:]:
path = os.path.join(path, dir)
try:
GSL.acquire()
scu.GSL.acquire()
if not os.path.isdir(path):
log("creating directory %s" % path)
os.mkdir(path)
finally:
GSL.release()
scu.GSL.release()
def get_obj_dir(self, context, oid):
"""
@ -89,7 +94,7 @@ class BaseArchiver:
"""
dept_dir = os.path.join(self.root, context.DeptId())
try:
GSL.acquire()
scu.GSL.acquire()
if not os.path.isdir(dept_dir):
log("creating directory %s" % dept_dir)
os.mkdir(dept_dir)
@ -98,7 +103,7 @@ class BaseArchiver:
log("creating directory %s" % obj_dir)
os.mkdir(obj_dir)
finally:
GSL.release()
scu.GSL.release()
return obj_dir
def list_oids(self, context):
@ -126,23 +131,23 @@ class BaseArchiver:
def delete_archive(self, archive_id):
"""Delete (forever) this archive"""
try:
GSL.acquire()
scu.GSL.acquire()
shutil.rmtree(archive_id, ignore_errors=True)
finally:
GSL.release()
scu.GSL.release()
def get_archive_date(self, archive_id):
"""Returns date (as a DateTime object) of an archive"""
dt = [int(x) for x in os.path.split(archive_id)[1].split("-")]
return mxDateTime(*dt)
return datetime.datetime(*dt)
def list_archive(self, archive_id):
"""Return list of filenames (without path) in archive"""
try:
GSL.acquire()
scu.GSL.acquire()
files = os.listdir(archive_id)
finally:
GSL.release()
scu.GSL.release()
files.sort()
return [f for f in files if f and f[0] != "_"]
@ -182,10 +187,10 @@ class BaseArchiver:
)
log("creating archive: %s" % archive_id)
try:
GSL.acquire()
scu.GSL.acquire()
os.mkdir(archive_id) # if exists, raises an OSError
finally:
GSL.release()
scu.GSL.release()
self.store(archive_id, "_description.txt", description)
return archive_id
@ -194,21 +199,21 @@ class BaseArchiver:
Filename may be modified (sanitized): return used filename
The file is created or replaced.
"""
filename = sanitize_filename(filename)
filename = scu.sanitize_filename(filename)
log("storing %s (%d bytes) in %s" % (filename, len(data), archive_id))
try:
GSL.acquire()
scu.GSL.acquire()
fname = os.path.join(archive_id, filename)
f = open(fname, "w")
f.write(data)
f.close()
finally:
GSL.release()
scu.GSL.release()
return filename
def get(self, archive_id, filename):
"""Retreive data"""
if not is_valid_filename(filename):
if not scu.is_valid_filename(filename):
log('Archiver.get: invalid filename "%s"' % filename)
raise ValueError("invalid filename")
fname = os.path.join(archive_id, filename)
@ -220,18 +225,18 @@ class BaseArchiver:
# XXX très incomplet: devrait inférer et assigner un type MIME
archive_id = self.get_id_from_name(context, oid, archive_name)
data = self.get(archive_id, filename)
ext = os.path.splitext(strlower(filename))[1]
ext = os.path.splitext(scu.strlower(filename))[1]
if ext == ".html" or ext == ".htm":
return data
elif ext == ".xml":
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
return data
elif ext == ".xls":
return sco_excel.sendExcelFile(REQUEST, data, filename)
elif ext == ".csv":
return sendCSVFile(REQUEST, data, filename)
return scu.sendCSVFile(REQUEST, data, filename)
elif ext == ".pdf":
return sendPDFFile(REQUEST, data, filename)
return scu.sendPDFFile(REQUEST, data, filename)
return data # should set mimetype...
@ -320,7 +325,7 @@ def do_formsemestre_archive(
if data:
PVArchive.store(archive_id, "Decisions_Jury.xls", data)
# Classeur bulletins (PDF)
data, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
context, formsemestre_id, REQUEST, version=bulVersion
)
if data:
@ -479,7 +484,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
# submitted or cancelled:
return REQUEST.RESPONSE.redirect(
"formsemestre_list_archives?formsemestre_id=%s&amp;head_message=%s"
"formsemestre_list_archives?formsemestre_id=%s&head_message=%s"
% (formsemestre_id, msg)
)
@ -505,7 +510,7 @@ def formsemestre_list_archives(context, REQUEST, formsemestre_id):
for a in L:
archive_name = PVArchive.get_archive_name(a["archive_id"])
H.append(
'<li>%s : <em>%s</em> (<a href="formsemestre_delete_archive?formsemestre_id=%s&amp;archive_name=%s">supprimer</a>)<ul>'
'<li>%s : <em>%s</em> (<a href="formsemestre_delete_archive?formsemestre_id=%s&archive_name=%s">supprimer</a>)<ul>'
% (
a["date"].strftime("%d/%m/%Y %H:%M"),
a["description"],
@ -515,7 +520,7 @@ def formsemestre_list_archives(context, REQUEST, formsemestre_id):
)
for filename in a["content"]:
H.append(
'<li><a href="formsemestre_get_archived_file?formsemestre_id=%s&amp;archive_name=%s&amp;filename=%s">%s</a></li>'
'<li><a href="formsemestre_get_archived_file?formsemestre_id=%s&archive_name=%s&filename=%s">%s</a></li>'
% (formsemestre_id, archive_name, filename, filename)
)
if not a["content"]:
@ -543,7 +548,7 @@ def formsemestre_delete_archive(
raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
sem = sco_formsemestre.get_formsemestre(
_ = sco_formsemestre.get_formsemestre(
context, formsemestre_id
) # check formsemestre_id
archive_id = PVArchive.get_id_from_name(context, formsemestre_id, archive_name)
@ -565,4 +570,4 @@ def formsemestre_delete_archive(
)
PVArchive.delete_archive(archive_id)
return REQUEST.RESPONSE.redirect(dest_url + "&amp;head_message=Archive%20supprimée")
return REQUEST.RESPONSE.redirect(dest_url + "&head_message=Archive%20supprimée")

View File

@ -30,7 +30,7 @@
les dossiers d'admission et autres pièces utiles.
"""
from sco_utils import *
import sco_utils as scu
from notes_log import log
import ImportScolars
import sco_formsemestre
@ -38,6 +38,9 @@ import sco_groups
import sco_trombino
import sco_excel
import sco_archives
from sco_permissions import ScoEtudAddAnnotations
from sco_exceptions import AccessDenied
from TrivialFormulator import TrivialFormulator
class EtudsArchiver(sco_archives.BaseArchiver):
@ -65,10 +68,10 @@ def etud_list_archives_html(context, REQUEST, etudid):
"content": EtudsArchive.list_archive(archive_id),
}
L.append(a)
delete_icon = icontag(
delete_icon = scu.icontag(
"delete_small_img", title="Supprimer fichier", alt="supprimer"
)
delete_disabled_icon = icontag(
delete_disabled_icon = scu.icontag(
"delete_small_dis_img", title="Suppression non autorisée"
)
H = ['<div class="etudarchive"><ul>']
@ -80,14 +83,14 @@ def etud_list_archives_html(context, REQUEST, etudid):
)
for filename in a["content"]:
H.append(
"""<a class="stdlink etudarchive_link" href="etud_get_archived_file?etudid=%s&amp;archive_name=%s&amp;filename=%s">%s</a>"""
"""<a class="stdlink etudarchive_link" href="etud_get_archived_file?etudid=%s&archive_name=%s&filename=%s">%s</a>"""
% (etudid, archive_name, filename, filename)
)
if not a["content"]:
H.append("<em>aucun fichier !</em>")
if can_edit:
H.append(
'<span class="deletudarchive"><a class="smallbutton" href="etud_delete_archive?etudid=%s&amp;archive_name=%s">%s</a></span>'
'<span class="deletudarchive"><a class="smallbutton" href="etud_delete_archive?etudid=%s&archive_name=%s">%s</a></span>'
% (etudid, archive_name, delete_icon)
)
else:
@ -137,7 +140,7 @@ def etud_upload_file_form(context, REQUEST, etudid):
% etud,
"""<p>Le fichier ne doit pas dépasser %sMo.</p>
"""
% (CONFIG.ETUD_MAX_FILE_SIZE / (1024 * 1024)),
% (scu.CONFIG.ETUD_MAX_FILE_SIZE / (1024 * 1024)),
]
tf = TrivialFormulator(
REQUEST.URL0,
@ -161,7 +164,9 @@ def etud_upload_file_form(context, REQUEST, etudid):
if tf[0] == 0:
return "\n".join(H) + tf[1] + context.sco_footer(REQUEST)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1 + "/ficheEtud?etudid=" + etudid)
return REQUEST.RESPONSE.redirect(
context.NotesURL() + "/ficheEtud?etudid=" + etudid
)
else:
data = tf[2]["datafile"].read()
descr = tf[2]["description"]
@ -169,7 +174,9 @@ def etud_upload_file_form(context, REQUEST, etudid):
_store_etud_file_to_new_archive(
context, REQUEST, etudid, data, filename, description=descr
)
return REQUEST.RESPONSE.redirect(REQUEST.URL1 + "/ficheEtud?etudid=" + etudid)
return REQUEST.RESPONSE.redirect(
context.NotesURL() + "/ficheEtud?etudid=" + etudid
)
def _store_etud_file_to_new_archive(
@ -177,7 +184,7 @@ def _store_etud_file_to_new_archive(
):
"""Store data to new archive."""
filesize = len(data)
if filesize < 10 or filesize > CONFIG.ETUD_MAX_FILE_SIZE:
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
return 0, "Fichier image de taille invalide ! (%d)" % filesize
archive_id = EtudsArchive.create_obj_archive(context, etudid, description)
EtudsArchive.store(archive_id, filename, data)
@ -209,7 +216,7 @@ def etud_delete_archive(context, REQUEST, etudid, archive_name, dialog_confirmed
)
EtudsArchive.delete_archive(archive_id)
return REQUEST.RESPONSE.redirect(dest_url + "&amp;head_message=Archive%20supprimée")
return REQUEST.RESPONSE.redirect(dest_url + "&head_message=Archive%20supprimée")
def etud_get_archived_file(context, REQUEST, etudid, archive_name, filename):

View File

@ -28,23 +28,34 @@
"""Génération des bulletins de notes
"""
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email.Header import Header
from email import Encoders
from types import StringType
import pprint
import urllib
from email.MIMEMultipart import ( # pylint: disable=no-name-in-module,import-error
MIMEMultipart,
)
from email.MIMEText import MIMEText # pylint: disable=no-name-in-module,import-error
from email.MIMEBase import MIMEBase # pylint: disable=no-name-in-module,import-error
from email.Header import Header # pylint: disable=no-name-in-module,import-error
from email import Encoders # pylint: disable=no-name-in-module,import-error
import htmlutils, time
import time
import htmlutils
from reportlab.lib.colors import Color
from sco_utils import *
from notes_table import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
import scolars
from sco_permissions import ScoImplement, ScoEtudInscrit
from sco_exceptions import AccessDenied
import sco_codes_parcours
import sco_formsemestre
import sco_groups
import sco_pvjury
import sco_formsemestre_status
import sco_photos
import ZAbsences
import sco_abs
import sco_abs_views
import sco_preferences
import sco_codes_parcours
@ -111,7 +122,7 @@ def formsemestre_bulletinetud_dict(
context, formsemestre_id
) # > toutes notes
I = DictDefault(defaultvalue="")
I = scu.DictDefault(defaultvalue="")
I["etudid"] = etudid
I["formsemestre_id"] = formsemestre_id
I["sem"] = nt.sem
@ -150,7 +161,7 @@ def formsemestre_bulletinetud_dict(
context, pid
)
# --- Absences
AbsSemEtud = ZAbsences.getAbsSemEtud(context, nt.sem, etudid)
AbsSemEtud = sco_abs.getAbsSemEtud(context, nt.sem, etudid)
I["nbabs"] = AbsSemEtud.CountAbs()
I["nbabsjust"] = AbsSemEtud.CountAbsJust()
@ -179,7 +190,7 @@ def formsemestre_bulletinetud_dict(
if I["etud_etat"] == "D":
I["demission"] = "(Démission)"
I["filigranne"] = "Démission"
elif I["etud_etat"] == DEF:
elif I["etud_etat"] == sco_codes_parcours.DEF:
I["demission"] = "(Défaillant)"
I["filigranne"] = "Défaillant"
elif (prefs["bul_show_temporary"] and not I["decision_sem"]) or prefs[
@ -203,16 +214,16 @@ def formsemestre_bulletinetud_dict(
modimpls = nt.get_modimpls()
moy_gen = nt.get_etud_moy_gen(etudid)
I["nb_inscrits"] = len(nt.rangs)
I["moy_gen"] = fmt_note(moy_gen)
I["moy_min"] = fmt_note(nt.moy_min)
I["moy_max"] = fmt_note(nt.moy_max)
I["moy_gen"] = scu.fmt_note(moy_gen)
I["moy_min"] = scu.fmt_note(nt.moy_min)
I["moy_max"] = scu.fmt_note(nt.moy_max)
I["mention"] = ""
if dpv:
decision_sem = dpv["decisions"][0]["decision_sem"]
if decision_sem and sco_codes_parcours.code_semestre_validant(
decision_sem["code"]
):
I["mention"] = get_mention(moy_gen)
I["mention"] = scu.get_mention(moy_gen)
if dpv and dpv["decisions"][0]:
I["sum_ects"] = dpv["decisions"][0]["sum_ects"]
@ -220,7 +231,7 @@ def formsemestre_bulletinetud_dict(
else:
I["sum_ects"] = 0
I["sum_ects_capitalises"] = 0
I["moy_moy"] = fmt_note(nt.moy_moy) # moyenne des moyennes generales
I["moy_moy"] = scu.fmt_note(nt.moy_moy) # moyenne des moyennes generales
if type(moy_gen) != StringType and type(nt.moy_moy) != StringType:
I["moy_gen_bargraph_html"] = "&nbsp;" + htmlutils.horizontal_bargraph(
moy_gen * 5, nt.moy_moy * 5
@ -240,8 +251,8 @@ def formsemestre_bulletinetud_dict(
if nt.get_moduleimpls_attente():
# n'affiche pas le rang sur le bulletin s'il y a des
# notes en attente dans ce semestre
rang = RANG_ATTENTE_STR
rang_gr = DictDefault(defaultvalue=RANG_ATTENTE_STR)
rang = scu.RANG_ATTENTE_STR
rang_gr = scu.DictDefault(defaultvalue=scu.RANG_ATTENTE_STR)
I["rang"] = rang
I["rang_gr"] = rang_gr
I["gr_name"] = gr_name
@ -267,17 +278,17 @@ def formsemestre_bulletinetud_dict(
u = ue.copy()
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...}
if ue["type"] != UE_SPORT:
u["cur_moy_ue_txt"] = fmt_note(ue_status["cur_moy_ue"])
if ue["type"] != sco_codes_parcours.UE_SPORT:
u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"])
else:
x = fmt_note(nt.bonus[etudid], keep_numeric=True)
x = scu.fmt_note(nt.bonus[etudid], keep_numeric=True)
if type(x) == StringType:
u["cur_moy_ue_txt"] = "pas de bonus"
else:
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x
u["moy_ue_txt"] = fmt_note(ue_status["moy"])
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
if ue_status["coef_ue"] != None:
u["coef_ue_txt"] = fmt_coef(ue_status["coef_ue"])
u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"])
else:
# C'est un bug:
log("u=" + pprint.pformat(u))
@ -289,7 +300,7 @@ def formsemestre_bulletinetud_dict(
and ue["ue_id"] in dpv["decisions"][0]["decisions_ue"]
):
u["ects"] = dpv["decisions"][0]["decisions_ue"][ue["ue_id"]]["ects"]
if ue["type"] == UE_ELECTIVE:
if ue["type"] == sco_codes_parcours.UE_ELECTIVE:
u["ects"] = (
"%g+" % u["ects"]
) # ajoute un "+" pour indiquer ECTS d'une UE élective
@ -313,12 +324,12 @@ def formsemestre_bulletinetud_dict(
sem_origin = sco_formsemestre.get_formsemestre(
context, ue_status["formsemestre_id"]
)
u["ue_descr_txt"] = "Capitalisée le %s" % DateISOtoDMY(
u["ue_descr_txt"] = "Capitalisée le %s" % ndb.DateISOtoDMY(
ue_status["event_date"]
)
u[
"ue_descr_html"
] = '<a href="formsemestre_bulletinetud?formsemestre_id=%s&amp;etudid=%s" title="%s" class="bull_link">%s</a>' % (
] = '<a href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s" class="bull_link">%s</a>' % (
sem_origin["formsemestre_id"],
etudid,
sem_origin["titreannee"],
@ -331,7 +342,7 @@ def formsemestre_bulletinetud_dict(
context, ue_status["formsemestre_id"]
) # > toutes notes
u["modules_capitalized"], junk = _ue_mod_bulletin(
u["modules_capitalized"], _ = _ue_mod_bulletin(
context,
etudid,
formsemestre_id,
@ -344,10 +355,10 @@ def formsemestre_bulletinetud_dict(
_sort_mod_by_matiere(u["modules_capitalized"], nt_cap, etudid)
)
else:
if prefs["bul_show_ue_rangs"] and ue["type"] != UE_SPORT:
if prefs["bul_show_ue_rangs"] and ue["type"] != sco_codes_parcours.UE_SPORT:
if ue_attente: # nt.get_moduleimpls_attente():
u["ue_descr_txt"] = "%s/%s" % (
RANG_ATTENTE_STR,
scu.RANG_ATTENTE_STR,
nt.ue_rangs[ue["ue_id"]][1],
)
else:
@ -383,7 +394,7 @@ def _sort_mod_by_matiere(modlist, nt, etudid):
"titre": mod["mat"]["titre"],
"modules": mod,
"moy": moy,
"moy_txt": fmt_note(moy),
"moy_txt": scu.fmt_note(moy),
}
return matmod
@ -399,8 +410,8 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
)
if bul_show_abs_modules:
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
debut_sem = DateDMYtoISO(sem["date_debut"])
fin_sem = DateDMYtoISO(sem["date_fin"])
debut_sem = ndb.DateDMYtoISO(sem["date_debut"])
fin_sem = ndb.DateDMYtoISO(sem["date_fin"])
ue_modimpls = [mod for mod in modimpls if mod["module"]["ue_id"] == ue_id]
mods = [] # result
@ -411,7 +422,7 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
mod_moy = nt.get_etud_mod_moy(
modimpl["moduleimpl_id"], etudid
) # peut etre 'NI'
is_malus = mod["module"]["module_type"] == MODULE_MALUS
is_malus = mod["module"]["module_type"] == scu.MODULE_MALUS
if bul_show_abs_modules:
mod_abs = [
context.Absences.CountAbs(
@ -427,25 +438,25 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
moduleimpl_id=modimpl["moduleimpl_id"],
),
]
mod["mod_abs_txt"] = fmt_abs(mod_abs)
mod["mod_abs_txt"] = scu.fmt_abs(mod_abs)
else:
mod["mod_abs_txt"] = ""
mod["mod_moy_txt"] = fmt_note(mod_moy)
mod["mod_moy_txt"] = scu.fmt_note(mod_moy)
if mod["mod_moy_txt"][:2] == "NA":
mod["mod_moy_txt"] = "-"
if is_malus:
if mod_moy > 0:
mod["mod_moy_txt"] = fmt_note(mod_moy)
mod["mod_moy_txt"] = scu.fmt_note(mod_moy)
mod["mod_coef_txt"] = "Malus"
elif mod_moy < 0:
mod["mod_moy_txt"] = fmt_note(-mod_moy)
mod["mod_moy_txt"] = scu.fmt_note(-mod_moy)
mod["mod_coef_txt"] = "Bonus"
else:
mod["mod_moy_txt"] = "-"
mod["mod_coef_txt"] = "-"
else:
mod["mod_coef_txt"] = fmt_coef(modimpl["module"]["coefficient"])
mod["mod_coef_txt"] = scu.fmt_coef(modimpl["module"]["coefficient"])
if mod["mod_moy_txt"] != "NI": # ne montre pas les modules 'non inscrit'
mods.append(mod)
if is_malus: # n'affiche pas les statistiques sur les modules malus
@ -461,7 +472,7 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
mod["stats"] = nt.get_mod_stats(modimpl["moduleimpl_id"])
mod["mod_descr_txt"] = "Module %s, coef. %s (%s)" % (
modimpl["module"]["titre"],
fmt_coef(modimpl["module"]["coefficient"]),
scu.fmt_coef(modimpl["module"]["coefficient"]),
context.Users.user_info(modimpl["responsable_id"])["nomcomplet"],
)
link_mod = (
@ -480,7 +491,7 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
mod_descr = "Module %s, coef. %s (%s)" % (
modimpl["module"]["titre"],
fmt_coef(modimpl["module"]["coefficient"]),
scu.fmt_coef(modimpl["module"]["coefficient"]),
context.Users.user_info(modimpl["responsable_id"])["nomcomplet"],
)
link_mod = (
@ -511,7 +522,7 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
else:
e["name"] = e["description"] or "le %s" % e["jour"]
e["target_html"] = (
"evaluation_listenotes?evaluation_id=%s&amp;format=html&amp;tf-submitted=1"
"evaluation_listenotes?evaluation_id=%s&format=html&tf-submitted=1"
% e["evaluation_id"]
)
e["name_html"] = '<a class="bull_link" href="%s">%s</a>' % (
@ -524,19 +535,21 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
if val == "NP":
e["note_txt"] = "nd"
e["note_html"] = '<span class="note_nd">nd</span>'
e["coef_txt"] = fmt_coef(e["coefficient"])
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
else:
# (-0.15) s'affiche "bonus de 0.15"
if is_malus:
val = abs(val)
e["note_txt"] = fmt_note(val, note_max=e["note_max"])
e["note_txt"] = scu.fmt_note(val, note_max=e["note_max"])
e["note_html"] = e["note_txt"]
if is_malus:
e["coef_txt"] = ""
else:
e["coef_txt"] = fmt_coef(e["coefficient"])
if e["evaluation_type"] == EVALUATION_RATTRAPAGE:
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
if e["evaluation_type"] == scu.EVALUATION_RATTRAPAGE:
e["coef_txt"] = "rat."
elif e["evaluation_type"] == scu.EVALUATION_SESSION2:
e["coef_txt"] = "sess. 2"
if e["etat"]["evalattente"]:
mod_attente = True # une eval en attente dans ce module
if (not is_malus) or (val != "NP"):
@ -558,7 +571,7 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
mod["evaluations_incompletes"].append(e)
e["name"] = (e["description"] or "") + " (%s)" % e["jour"]
e["target_html"] = (
"evaluation_listenotes?evaluation_id=%s&amp;format=html&amp;tf-submitted=1"
"evaluation_listenotes?evaluation_id=%s&format=html&tf-submitted=1"
% e["evaluation_id"]
)
e["name_html"] = '<a class="bull_link" href="%s">%s</a>' % (
@ -566,12 +579,12 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
e["name"],
)
e["note_txt"] = e["note_html"] = ""
e["coef_txt"] = fmt_coef(e["coefficient"])
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
# Classement
if bul_show_mod_rangs and mod["mod_moy_txt"] != "-" and not is_malus:
rg = nt.mod_rangs[modimpl["moduleimpl_id"]]
if mod_attente: # nt.get_moduleimpls_attente():
mod["mod_rang"] = RANG_ATTENTE_STR
mod["mod_rang"] = scu.RANG_ATTENTE_STR
else:
mod["mod_rang"] = rg[0][etudid]
mod["mod_eff"] = rg[1] # effectif dans ce module
@ -636,7 +649,7 @@ def etud_descr_situation_semestre(
descr_mention : 'Mention Bien', ou vide si pas de mention ou si pas show_mention
"""
cnx = context.GetDBConnexion()
infos = DictDefault(defaultvalue="")
infos = scu.DictDefault(defaultvalue="")
# --- Situation et décisions jury
@ -647,7 +660,6 @@ def etud_descr_situation_semestre(
date_inscr = None
date_dem = None
date_def = None
date_echec = None
for event in events:
event_type = event["event_type"]
if event_type == "INSCRIPTION":
@ -764,7 +776,7 @@ def formsemestre_bulletinetud(
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
except:
return log_unknown_etud(context, REQUEST, format=format)
return scu.log_unknown_etud(context, REQUEST, format=format)
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
@ -804,7 +816,7 @@ def formsemestre_bulletinetud(
if sem["modalite"] == "EXT":
R.append(
"""<p><a
href="formsemestre_ext_edit_ue_validations?formsemestre_id=%s&amp;etudid=%s"
href="formsemestre_ext_edit_ue_validations?formsemestre_id=%s&etudid=%s"
class="stdlink">
Editer les validations d'UE dans ce semestre extérieur
</a></p>"""
@ -886,7 +898,7 @@ def do_formsemestre_bulletinetud(
etud = I["etud"]
if format == "html":
htm, junk = sco_bulletins_generator.make_formsemestre_bulletinetud(
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
context, I, version=version, format="html", REQUEST=REQUEST
)
return htm, I["filigranne"]
@ -902,7 +914,7 @@ def do_formsemestre_bulletinetud(
)
if format == "pdf":
return (
sendPDFFile(REQUEST, bul, filename),
scu.sendPDFFile(REQUEST, bul, filename),
I["filigranne"],
) # unused ret. value
else:
@ -917,7 +929,7 @@ def do_formsemestre_bulletinetud(
if nohtml:
htm = "" # speed up if html version not needed
else:
htm, junk = sco_bulletins_generator.make_formsemestre_bulletinetud(
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
context, I, version=version, format="html", REQUEST=REQUEST
)
@ -961,7 +973,7 @@ def mail_bulletin(context, formsemestre_id, I, pdfdata, filename, recipient_addr
"""
etud = I["etud"]
webmaster = context.get_preference("bul_mail_contact_addr", formsemestre_id)
dept = unescape_html(context.get_preference("DeptName", formsemestre_id))
dept = scu.unescape_html(context.get_preference("DeptName", formsemestre_id))
copy_addr = context.get_preference("email_copy_bulletins", formsemestre_id)
intro_mail = context.get_preference("bul_intro_mail", formsemestre_id)
@ -980,7 +992,7 @@ def mail_bulletin(context, formsemestre_id, I, pdfdata, filename, recipient_addr
)
msg = MIMEMultipart()
subj = Header("Relevé de notes de %s" % etud["nomprenom"], SCO_ENCODING)
subj = Header("Relevé de notes de %s" % etud["nomprenom"], scu.SCO_ENCODING)
recipients = [recipient_addr]
msg["Subject"] = subj
msg["From"] = context.get_preference("email_from_addr", formsemestre_id)
@ -990,7 +1002,7 @@ def mail_bulletin(context, formsemestre_id, I, pdfdata, filename, recipient_addr
# Guarantees the message ends in a newline
msg.epilogue = ""
# Text
txt = MIMEText(hea, "plain", SCO_ENCODING)
txt = MIMEText(hea, "plain", scu.SCO_ENCODING)
# log('hea:\n' + hea)
msg.attach(txt)
# Attach pdf
@ -1064,7 +1076,7 @@ def _formsemestre_bulletinetud_header_html(
menuBul = [
{
"title": "Réglages bulletins",
"url": "formsemestre_edit_options?formsemestre_id=%s&amp;target_url=%s"
"url": "formsemestre_edit_options?formsemestre_id=%s&target_url=%s"
% (formsemestre_id, qurl),
"enabled": (uid in sem["responsables"])
or authuser.has_permission(ScoImplement, context),
@ -1075,13 +1087,13 @@ def _formsemestre_bulletinetud_header_html(
context, formsemestre_id
),
"url": url
+ "?formsemestre_id=%s&amp;etudid=%s&amp;format=pdf&amp;version=%s"
+ "?formsemestre_id=%s&etudid=%s&format=pdf&version=%s"
% (formsemestre_id, etudid, version),
},
{
"title": "Envoi par mail à %s" % etud["email"],
"url": url
+ "?formsemestre_id=%s&amp;etudid=%s&amp;format=pdfmail&amp;version=%s"
+ "?formsemestre_id=%s&etudid=%s&format=pdfmail&version=%s"
% (formsemestre_id, etudid, version),
"enabled": etud["email"]
and can_send_bulletin_by_mail(
@ -1091,7 +1103,7 @@ def _formsemestre_bulletinetud_header_html(
{
"title": "Envoi par mail à %s (adr. personnelle)" % etud["emailperso"],
"url": url
+ "?formsemestre_id=%s&amp;etudid=%s&amp;format=pdfmail&amp;version=%s&amp;prefer_mail_perso=1"
+ "?formsemestre_id=%s&etudid=%s&format=pdfmail&version=%s&prefer_mail_perso=1"
% (formsemestre_id, etudid, version),
"enabled": etud["emailperso"]
and can_send_bulletin_by_mail(
@ -1101,12 +1113,12 @@ def _formsemestre_bulletinetud_header_html(
{
"title": "Version XML",
"url": url
+ "?formsemestre_id=%s&amp;etudid=%s&amp;format=xml&amp;version=%s"
+ "?formsemestre_id=%s&etudid=%s&format=xml&version=%s"
% (formsemestre_id, etudid, version),
},
{
"title": "Ajouter une appréciation",
"url": "appreciation_add_form?etudid=%s&amp;formsemestre_id=%s"
"url": "appreciation_add_form?etudid=%s&formsemestre_id=%s"
% (etudid, formsemestre_id),
"enabled": (
(authuser in sem["responsables"])
@ -1115,46 +1127,46 @@ def _formsemestre_bulletinetud_header_html(
},
{
"title": "Enregistrer un semestre effectué ailleurs",
"url": "formsemestre_ext_create_form?etudid=%s&amp;formsemestre_id=%s"
"url": "formsemestre_ext_create_form?etudid=%s&formsemestre_id=%s"
% (etudid, formsemestre_id),
"enabled": authuser.has_permission(ScoImplement, context),
},
{
"title": "Enregistrer une validation d'UE antérieure",
"url": "formsemestre_validate_previous_ue?etudid=%s&amp;formsemestre_id=%s"
"url": "formsemestre_validate_previous_ue?etudid=%s&formsemestre_id=%s"
% (etudid, formsemestre_id),
"enabled": context._can_validate_sem(REQUEST, formsemestre_id),
},
{
"title": "Enregistrer note d'une UE externe",
"url": "external_ue_create_form?etudid=%s&amp;formsemestre_id=%s"
"url": "external_ue_create_form?etudid=%s&formsemestre_id=%s"
% (etudid, formsemestre_id),
"enabled": context._can_validate_sem(REQUEST, formsemestre_id),
},
{
"title": "Entrer décisions jury",
"url": "formsemestre_validation_etud_form?formsemestre_id=%s&amp;etudid=%s"
"url": "formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s"
% (formsemestre_id, etudid),
"enabled": context._can_validate_sem(REQUEST, formsemestre_id),
},
{
"title": "Editer PV jury",
"url": "formsemestre_pvjury_pdf?formsemestre_id=%s&amp;etudid=%s"
"url": "formsemestre_pvjury_pdf?formsemestre_id=%s&etudid=%s"
% (formsemestre_id, etudid),
"enabled": True,
},
]
H.append("""<td class="bulletin_menubar"><div class="bulletin_menubar">""")
H.append(sco_formsemestre_status.makeMenu("Autres opérations", menuBul, alone=True))
H.append(htmlutils.make_menu("Autres opérations", menuBul, alone=True))
H.append("""</div></td>""")
H.append(
'<td> <a href="%s">%s</a></td>'
% (
url
+ "?formsemestre_id=%s&amp;etudid=%s&amp;format=pdf&amp;version=%s"
+ "?formsemestre_id=%s&etudid=%s&format=pdf&version=%s"
% (formsemestre_id, etudid, version),
ICON_PDF,
scu.ICON_PDF,
)
)
H.append("""</tr></table>""")

View File

@ -28,12 +28,14 @@
"""Generation bulletins de notes: exemple minimal pour les programmeurs
"""
# Quelques modules ScoDoc utiles:
from sco_pdf import *
import VERSION
import sco_utils as scu
import sco_pdf
import sco_preferences
from notes_log import log
import sco_bulletins_generator
import sco_bulletins_standard
from reportlab.platypus import Paragraph
class BulletinGeneratorExample(sco_bulletins_standard.BulletinGeneratorStandard):
@ -59,7 +61,7 @@ class BulletinGeneratorExample(sco_bulletins_standard.BulletinGeneratorStandard)
assert format == "pdf" # garde fou
return [
Paragraph(
SU(
sco_pdf.SU(
"L'étudiant %(nomprenom)s a une moyenne générale de %(moy_gen)s"
% self.infos
),

View File

@ -42,12 +42,22 @@ La préférence 'bul_pdf_class_name' est obsolete (inutilisée).
"""
import time
import cStringIO
import collections
import traceback
import reportlab
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
import sco_utils
import VERSION
from sco_exceptions import NoteProcessError
import sco_preferences
from notes_log import log
import sco_formsemestre
from sco_pdf import *
import sco_pdf
from sco_pdf import PDFLOCK
BULLETIN_CLASSES = (
collections.OrderedDict()
@ -145,7 +155,7 @@ class BulletinGenerator:
dt,
self.infos["etud"]["nom"],
)
filename = unescape_html(filename).replace(" ", "_").replace("&", "")
filename = sco_utils.unescape_html(filename).replace(" ", "_").replace("&", "")
return filename
def generate(self, format="", stand_alone=True):
@ -166,8 +176,10 @@ class BulletinGenerator:
def generate_html(self):
"""Return bulletin as an HTML string"""
H = ['<div class="notes_bulletin">']
H.append(self.bul_table(format="html")) # table des notes
H.append(self.bul_part_below(format="html")) # infos sous la table
# table des notes:
H.append(self.bul_table(format="html")) # pylint: disable=no-member
# infos sous la table:
H.append(self.bul_part_below(format="html")) # pylint: disable=no-member
H.append("</div>")
return "\n".join(H)
@ -179,10 +191,14 @@ class BulletinGenerator:
"""
formsemestre_id = self.infos["formsemestre_id"]
objects = self.bul_title_pdf() # partie haute du bulletin
objects += self.bul_table(format="pdf") # table des notes
objects += self.bul_part_below(format="pdf") # infos sous la table
objects += self.bul_signatures_pdf() # signatures
# partie haute du bulletin
objects = self.bul_title_pdf() # pylint: disable=no-member
# table des notes
objects += self.bul_table(format="pdf") # pylint: disable=no-member
# infos sous la table
objects += self.bul_part_below(format="pdf") # pylint: disable=no-member
# signatures
objects += self.bul_signatures_pdf() # pylint: disable=no-member
# Réduit sur une page
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
@ -194,13 +210,13 @@ class BulletinGenerator:
# Generation du document PDF
sem = sco_formsemestre.get_formsemestre(self.context, formsemestre_id)
report = cStringIO.StringIO() # in-memory document, no disk file
document = BaseDocTemplate(report)
document = sco_pdf.BaseDocTemplate(report)
document.addPageTemplates(
ScolarsPageTemplate(
sco_pdf.ScolarsPageTemplate(
document,
self.context,
context=self.context,
author="%s %s (E. Viennet) [%s]"
% (SCONAME, SCOVERSION, self.description),
% (VERSION.SCONAME, VERSION.SCOVERSION, self.description),
title="Bulletin %s de %s"
% (sem["titremois"], self.infos["etud"]["nomprenom"]),
subject="Bulletin de note",
@ -222,7 +238,9 @@ class BulletinGenerator:
"""
try:
# put each table cell in a Paragraph
Pt = [[Paragraph(SU(x), self.CellStyle) for x in line] for line in P]
Pt = [
[Paragraph(sco_pdf.SU(x), self.CellStyle) for x in line] for line in P
]
except:
# enquête sur exception intermittente...
log("*** bug in PDF buildTableObject:")

View File

@ -28,13 +28,16 @@
"""Génération du bulletin en format JSON (beta, non completement testé)
"""
import datetime
import json
from notes_table import *
import sco_utils as scu
import notesdb as ndb
import scolars
import sco_formsemestre
import sco_groups
import sco_photos
import ZAbsences
import sco_abs
import sco_bulletins
# -------- Bulletin en JSON
@ -62,9 +65,9 @@ def make_json_formsemestre_bulletinetud(
)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE)
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE)
return json.dumps(d, cls=ScoDocJSONEncoder, encoding=SCO_ENCODING)
return json.dumps(d, cls=scu.ScoDocJSONEncoder, encoding=scu.SCO_ENCODING)
# (fonction séparée: n'utilise pas formsemestre_bulletinetud_dict()
@ -119,16 +122,16 @@ def formsemestre_bulletinetud_published_dict(
etudid=etudid,
code_nip=etudinfo["code_nip"],
code_ine=etudinfo["code_ine"],
nom=quote_xml_attr(etudinfo["nom"]),
prenom=quote_xml_attr(etudinfo["prenom"]),
sexe=quote_xml_attr(etudinfo["sexe"]),
photo_url=quote_xml_attr(
nom=scu.quote_xml_attr(etudinfo["nom"]),
prenom=scu.quote_xml_attr(etudinfo["prenom"]),
civilite=scu.quote_xml_attr(etudinfo["civilite_str"]),
photo_url=scu.quote_xml_attr(
sco_photos.etud_photo_url(context, etudinfo, fast=True)
),
email=quote_xml_attr(etudinfo["email"]),
emailperso=quote_xml_attr(etudinfo["emailperso"]),
email=scu.quote_xml_attr(etudinfo["email"]),
emailperso=scu.quote_xml_attr(etudinfo["emailperso"]),
)
d["etudiant"]["sexe"] = d["etudiant"]["civilite"] # backward compat for our clients
# Disponible pour publication ?
if not published:
return d # stop !
@ -150,7 +153,7 @@ def formsemestre_bulletinetud_published_dict(
ues = nt.get_ues()
modimpls = nt.get_modimpls()
nbetuds = len(nt.rangs)
mg = fmt_note(nt.get_etud_moy_gen(etudid))
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
if (
nt.get_moduleimpls_attente()
or context.get_preference("bul_show_rangs", formsemestre_id) == 0
@ -168,9 +171,9 @@ def formsemestre_bulletinetud_published_dict(
d["note"] = dict(
value=mg,
min=fmt_note(nt.moy_min),
max=fmt_note(nt.moy_max),
moy=fmt_note(nt.moy_moy),
min=scu.fmt_note(nt.moy_min),
max=scu.fmt_note(nt.moy_max),
moy=scu.fmt_note(nt.moy_moy),
)
d["rang"] = dict(value=rang, ninscrits=nbetuds)
d["rang_group"] = []
@ -199,25 +202,28 @@ def formsemestre_bulletinetud_published_dict(
ects_txt = ""
u = dict(
id=ue["ue_id"],
numero=quote_xml_attr(ue["numero"]),
acronyme=quote_xml_attr(ue["acronyme"]),
titre=quote_xml_attr(ue["titre"]),
numero=scu.quote_xml_attr(ue["numero"]),
acronyme=scu.quote_xml_attr(ue["acronyme"]),
titre=scu.quote_xml_attr(ue["titre"]),
note=dict(
value=fmt_note(ue_status["cur_moy_ue"]),
min=fmt_note(ue["min"]),
max=fmt_note(ue["max"]),
value=scu.fmt_note(ue_status["cur_moy_ue"]),
min=scu.fmt_note(ue["min"]),
max=scu.fmt_note(ue["max"]),
moy=scu.fmt_note(ue["moy"]), # CM : ajout pour faire apparaitre la moyenne des UE
),
rang=str(nt.ue_rangs[ue["ue_id"]][0][etudid]),
effectif=str(nt.ue_rangs[ue["ue_id"]][1]),
ects=ects_txt,
code_apogee=quote_xml_attr(ue["code_apogee"]),
code_apogee=scu.quote_xml_attr(ue["code_apogee"]),
)
d["ue"].append(u)
u["module"] = []
# Liste les modules de l'UE
ue_modimpls = [mod for mod in modimpls if mod["module"]["ue_id"] == ue["ue_id"]]
for modimpl in ue_modimpls:
mod_moy = fmt_note(nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid))
mod_moy = scu.fmt_note(
nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
)
if mod_moy == "NI": # ne mentionne pas les modules ou n'est pas inscrit
continue
mod = modimpl["module"]
@ -232,15 +238,15 @@ def formsemestre_bulletinetud_published_dict(
code=mod["code"],
coefficient=mod["coefficient"],
numero=mod["numero"],
titre=quote_xml_attr(mod["titre"]),
abbrev=quote_xml_attr(mod["abbrev"]),
titre=scu.quote_xml_attr(mod["titre"]),
abbrev=scu.quote_xml_attr(mod["abbrev"]),
# ects=ects, ects des modules maintenant inutilisés
note=dict(value=mod_moy),
code_apogee=quote_xml_attr(mod["code_apogee"]),
code_apogee=scu.quote_xml_attr(mod["code_apogee"]),
)
m["note"].update(modstat)
for k in ("min", "max", "moy"): # formatte toutes les notes
m["note"][k] = fmt_note(m["note"][k])
m["note"][k] = scu.fmt_note(m["note"][k])
u["module"].append(m)
if context.get_preference("bul_show_mod_rangs", formsemestre_id):
@ -258,19 +264,20 @@ def formsemestre_bulletinetud_published_dict(
val = e["notes"].get(etudid, {"value": "NP"})[
"value"
] # NA si etud demissionnaire
val = fmt_note(val, note_max=e["note_max"])
val = scu.fmt_note(val, note_max=e["note_max"])
m["evaluation"].append(
dict(
jour=DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=TimetoISO8601(
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=TimetoISO8601(
heure_fin=ndb.TimetoISO8601(
e["heure_fin"], null_is_empty=True
),
coefficient=e["coefficient"],
evaluation_type=e["evaluation_type"],
description=quote_xml_attr(e["description"]),
evaluation_id=e["evaluation_id"], # CM : ajout pour permettre de faire le lien sur les bulletins en ligne avec l'évaluation
description=scu.quote_xml_attr(e["description"]),
note=val,
)
)
@ -285,15 +292,17 @@ def formsemestre_bulletinetud_published_dict(
if e["evaluation_id"] not in complete_eval_ids:
m["evaluation"].append(
dict(
jour=DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=TimetoISO8601(
jour=ndb.DateDMYtoISO(
e["jour"], null_is_empty=True
),
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=TimetoISO8601(
heure_fin=ndb.TimetoISO8601(
e["heure_fin"], null_is_empty=True
),
coefficient=e["coefficient"],
description=quote_xml_attr(e["description"]),
description=scu.quote_xml_attr(e["description"]),
incomplete="1",
)
)
@ -307,21 +316,19 @@ def formsemestre_bulletinetud_published_dict(
d["ue_capitalisee"].append(
dict(
id=ue["ue_id"],
numero=quote_xml_attr(ue["numero"]),
acronyme=quote_xml_attr(ue["acronyme"]),
titre=quote_xml_attr(ue["titre"]),
note=fmt_note(ue_status["moy"]),
coefficient_ue=fmt_note(ue_status["coef_ue"]),
date_capitalisation=DateDMYtoISO(ue_status["event_date"]),
numero=scu.quote_xml_attr(ue["numero"]),
acronyme=scu.quote_xml_attr(ue["acronyme"]),
titre=scu.quote_xml_attr(ue["titre"]),
note=scu.fmt_note(ue_status["moy"]),
coefficient_ue=scu.fmt_note(ue_status["coef_ue"]),
date_capitalisation=ndb.DateDMYtoISO(ue_status["event_date"]),
ects=ects_txt,
)
)
# --- Absences
if context.get_preference("bul_show_abs", formsemestre_id):
debut_sem = DateDMYtoISO(sem["date_debut"])
fin_sem = DateDMYtoISO(sem["date_fin"])
AbsEtudSem = ZAbsences.getAbsSemEtud(context, sem, etudid)
AbsEtudSem = sco_abs.getAbsSemEtud(context, sem, etudid)
nbabs = AbsEtudSem.CountAbs()
nbabsjust = AbsEtudSem.CountAbsJust()
@ -339,7 +346,7 @@ def formsemestre_bulletinetud_published_dict(
format="xml",
show_uevalid=context.get_preference("bul_show_uevalid", formsemestre_id),
)
d["situation"] = quote_xml_attr(infos["situation"])
d["situation"] = scu.quote_xml_attr(infos["situation"])
if dpv:
decision = dpv["decisions"][0]
etat = decision["etat"]
@ -366,11 +373,11 @@ def formsemestre_bulletinetud_published_dict(
d["decision_ue"].append(
dict(
ue_id=ue["ue_id"],
numero=quote_xml_attr(ue["numero"]),
acronyme=quote_xml_attr(ue["acronyme"]),
titre=quote_xml_attr(ue["titre"]),
numero=scu.quote_xml_attr(ue["numero"]),
acronyme=scu.quote_xml_attr(ue["acronyme"]),
titre=scu.quote_xml_attr(ue["titre"]),
code=decision["decisions_ue"][ue_id]["code"],
ects=quote_xml_attr(ue["ects"] or ""),
ects=scu.quote_xml_attr(ue["ects"] or ""),
)
)
d["autorisation_inscription"] = []
@ -389,7 +396,10 @@ def formsemestre_bulletinetud_published_dict(
d["appreciation"] = []
for app in apprecs:
d["appreciation"].append(
dict(comment=quote_xml_attr(app["comment"]), date=DateDMYtoISO(app["date"]))
dict(
comment=scu.quote_xml_attr(app["comment"]),
date=ndb.DateDMYtoISO(app["date"]),
)
)
#

View File

@ -37,8 +37,13 @@
import traceback, re
import sco_utils as scu
from sco_permissions import ScoEtudInscrit
import sco_formsemestre
from sco_pdf import *
import sco_pdf
from sco_pdf import Color, Paragraph, Spacer, Table
from sco_pdf import blue, cm, mm
from sco_pdf import SU
import sco_preferences
from notes_log import log
import sco_bulletins_generator
@ -81,9 +86,7 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
def bul_table_html(self):
"""Génère la table centrale du bulletin de notes: chaine HTML"""
format = "html"
I = self.infos
authuser = self.authuser
formsemestre_id = self.infos["formsemestre_id"]
context = self.context
@ -98,8 +101,8 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
bgcolor = "background-color: rgb(255,255,240)"
linktmpl = '<span onclick="toggle_vis_ue(this);" class="toggle_ue">%s</span>'
minuslink = linktmpl % icontag("minus_img", border="0", alt="-")
pluslink = linktmpl % icontag("plus_img", border="0", alt="+")
minuslink = linktmpl % scu.icontag("minus_img", border="0", alt="-")
pluslink = linktmpl % scu.icontag("plus_img", border="0", alt="+")
H = ['<table class="notes_bulletin" style="background-color: %s;">' % bgcolor]
@ -131,8 +134,8 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
if context.get_preference("bul_show_minmax_mod", formsemestre_id):
rang_minmax = '%s <span class="bul_minmax" title="[min, max] UE">[%s, %s]</span>' % (
mod["mod_rang_txt"],
fmt_note(mod["stats"]["min"]),
fmt_note(mod["stats"]["max"]),
scu.fmt_note(mod["stats"]["min"]),
scu.fmt_note(mod["stats"]["max"]),
)
else:
rang_minmax = mod["mod_rang_txt"] # vide si pas option rang
@ -209,9 +212,9 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
moy_txt = (
'%s <span class="bul_minmax" title="[min, max] UE">[%s, %s]</span>'
% (
fmt_note(ue["cur_moy_ue_txt"]),
fmt_note(ue["min"]),
fmt_note(ue["max"]),
scu.fmt_note(ue["cur_moy_ue_txt"]),
scu.fmt_note(ue["min"]),
scu.fmt_note(ue["max"]),
)
)
else:
@ -321,7 +324,7 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
for app in I["appreciations_list"]:
if can_edit_app:
mlink = (
'<a class="stdlink" href="appreciation_add_form?id=%s">modifier</a> <a class="stdlink" href="appreciation_add_form?id=%s&amp;suppress=1">supprimer</a>'
'<a class="stdlink" href="appreciation_add_form?id=%s">modifier</a> <a class="stdlink" href="appreciation_add_form?id=%s&suppress=1">supprimer</a>'
% (app["id"], app["id"])
)
else:
@ -332,7 +335,7 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
)
if can_edit_app:
H.append(
'<p><a class="stdlink" href="appreciation_add_form?etudid=%(etudid)s&amp;formsemestre_id=%(formsemestre_id)s">Ajouter une appréciation</a></p>'
'<p><a class="stdlink" href="appreciation_add_form?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s">Ajouter une appréciation</a></p>'
% self.infos
)
H.append("</div>")
@ -441,7 +444,6 @@ def _bulletin_pdf_table_legacy(context, I, version="long"):
S = BulTableStyle()
P = [] # elems pour gen. pdf
formsemestre_id = I["formsemestre_id"]
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
bul_show_abs_modules = context.get_preference(
"bul_show_abs_modules", formsemestre_id
)
@ -460,7 +462,7 @@ def _bulletin_pdf_table_legacy(context, I, version="long"):
]
if bul_show_abs_modules:
t.append("Abs (J. / N.J.)")
P.append(bold_paras(t))
P.append(sco_pdf.bold_paras(t))
def list_modules(ue_modules, ue_type=None):
"ajoute les lignes decrivant les modules d'une UE, avec eventuellement les évaluations de chacun"
@ -471,8 +473,8 @@ def _bulletin_pdf_table_legacy(context, I, version="long"):
if context.get_preference("bul_show_minmax_mod", formsemestre_id):
rang_minmax = '%s <font size="8">[%s, %s]</font>' % (
mod["mod_rang_txt"],
fmt_note(mod["stats"]["min"]),
fmt_note(mod["stats"]["max"]),
scu.fmt_note(mod["stats"]["min"]),
scu.fmt_note(mod["stats"]["max"]),
)
else:
rang_minmax = mod["mod_rang_txt"] # vide si pas option rang
@ -504,7 +506,7 @@ def _bulletin_pdf_table_legacy(context, I, version="long"):
t = [ue["acronyme"], ue["moy_ue_txt"], ue_descr, "", coef_ue]
if bul_show_abs_modules:
t.append("")
P.append(bold_paras(t))
P.append(sco_pdf.bold_paras(t))
coef_ue = ""
ue_descr = "(en cours, non prise en compte)"
S.ueline()
@ -523,7 +525,7 @@ def _bulletin_pdf_table_legacy(context, I, version="long"):
t = [ue["acronyme"], moy_txt, ue_descr, "", coef_ue]
if bul_show_abs_modules:
t.append("")
P.append(bold_paras(t))
P.append(sco_pdf.bold_paras(t))
S.ueline()
list_modules(ue["modules"], ue_type=ue_type)

View File

@ -50,13 +50,21 @@ Pour définir un nouveau type de bulletin:
Chaque semestre peut si nécessaire utiliser un type de bulletin différent.
"""
import htmlutils, time
import pprint, traceback
import htmlutils
import time
import pprint
import traceback
import re
import cStringIO
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
from notes_table import *
import VERSION
import sco_utils as scu
from notes_log import log
import sco_formsemestre
import sco_bulletins
from sco_pdf import *
import sco_pdf
import os
@ -84,10 +92,10 @@ def pdfassemblebulletins(
report = cStringIO.StringIO() # in-memory document, no disk file
document = BaseDocTemplate(report)
document.addPageTemplates(
ScolarsPageTemplate(
sco_pdf.ScolarsPageTemplate(
document,
context,
author="%s %s (E. Viennet)" % (SCONAME, SCOVERSION),
context=context,
author="%s %s (E. Viennet)" % (VERSION.SCONAME, VERSION.SCOVERSION),
title="Bulletin %s" % bul_title,
subject="Bulletin de note",
server_name=server_name,
@ -117,7 +125,7 @@ def process_field(
If format = 'html', replaces <para> by <p>. HTML does not allow logos.
"""
try:
text = (field or "") % WrapDict(
text = (field or "") % scu.WrapDict(
cdict
) # note that None values are mapped to empty strings
except:
@ -135,9 +143,9 @@ def process_field(
return text
# --- PDF format:
# handle logos:
image_dir = SCODOC_LOGOS_DIR + "/logos_" + context.DeptId() + "/"
image_dir = scu.SCODOC_LOGOS_DIR + "/logos_" + context.DeptId() + "/"
if not os.path.exists(image_dir):
image_dir = SCODOC_LOGOS_DIR + "/" # use global logos
image_dir = scu.SCODOC_LOGOS_DIR + "/" # use global logos
text = re.sub(
r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text
) # remove forbidden src attribute
@ -150,7 +158,7 @@ def process_field(
# tentatives d'acceder à d'autres fichiers !
# log('field: %s' % (text))
return makeParas(text, style, suppress_empty=suppress_empty_pars)
return sco_pdf.makeParas(text, style, suppress_empty=suppress_empty_pars)
def get_formsemestre_bulletins_pdf(
@ -180,7 +188,7 @@ def get_formsemestre_bulletins_pdf(
)
fragments += frag
filigrannes[i] = filigranne
bookmarks[i] = suppress_accents(nt.get_sexnom(etudid))
bookmarks[i] = scu.suppress_accents(nt.get_sexnom(etudid))
i = i + 1
#
infos = {"DeptName": context.get_preference("DeptName", formsemestre_id)}
@ -189,7 +197,7 @@ def get_formsemestre_bulletins_pdf(
else:
server_name = ""
try:
PDFLOCK.acquire()
sco_pdf.PDFLOCK.acquire()
pdfdoc = pdfassemblebulletins(
formsemestre_id,
fragments,
@ -201,11 +209,11 @@ def get_formsemestre_bulletins_pdf(
context=context,
)
finally:
PDFLOCK.release()
sco_pdf.PDFLOCK.release()
#
dt = time.strftime("%Y-%m-%d")
filename = "bul-%s-%s.pdf" % (sem["titre_num"], dt)
filename = unescape_html(filename).replace(" ", "_").replace("&", "")
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
# fill cache
context._getNotesCache().store_bulletins_pdf(
formsemestre_id, version, filename, pdfdoc
@ -239,7 +247,7 @@ def get_etud_bulletins_pdf(context, etudid, REQUEST, version="selectedevals"):
else:
server_name = ""
try:
PDFLOCK.acquire()
sco_pdf.PDFLOCK.acquire()
pdfdoc = pdfassemblebulletins(
None,
fragments,
@ -251,11 +259,11 @@ def get_etud_bulletins_pdf(context, etudid, REQUEST, version="selectedevals"):
context=context,
)
finally:
PDFLOCK.release()
sco_pdf.PDFLOCK.release()
#
filename = "bul-%s" % (etud["nomprenom"])
filename = (
unescape_html(filename).replace(" ", "_").replace("&", "").replace(".", "")
scu.unescape_html(filename).replace(" ", "_").replace("&", "").replace(".", "")
+ ".pdf"
)

View File

@ -53,53 +53,53 @@ Lien sur Scolarite/edit_preferences (sans formsemestre_id)
et sur page "réglages bulletin" (avec formsemestre_id)
"""
import os
# import os
def form_change_bul_sig(context, side, formsemestre_id=None, REQUEST=None):
"""Change pdf signature"""
filename = _get_sig_existing_filename(
context, side, formsemestre_id=formsemestre_id
)
if side == "left":
sidetxt = "gauche"
elif side == "right":
sidetxt = "droite"
else:
raise ValueError("invalid value for 'side' parameter")
signatureloc = get_bul_sig_img()
H = [
self.sco_header(REQUEST, page_title="Changement de signature"),
"""<h2>Changement de la signature bulletin de %(sidetxt)s</h2>
"""
% (sidetxt,),
]
"<p>Photo actuelle (%(signatureloc)s): "
# def form_change_bul_sig(context, side, formsemestre_id=None, REQUEST=None):
# """Change pdf signature"""
# filename = _get_sig_existing_filename(
# context, side, formsemestre_id=formsemestre_id
# )
# if side == "left":
# sidetxt = "gauche"
# elif side == "right":
# sidetxt = "droite"
# else:
# raise ValueError("invalid value for 'side' parameter")
# signatureloc = get_bul_sig_img()
# H = [
# self.sco_header(REQUEST, page_title="Changement de signature"),
# """<h2>Changement de la signature bulletin de %(sidetxt)s</h2>
# """
# % (sidetxt,),
# ]
# "<p>Photo actuelle (%(signatureloc)s): "
def get_bul_sig_img(context, side, formsemestre_id=None):
"send back signature image data"
# slow, not cached, used for unfrequent access (do not bypass python)
# def get_bul_sig_img(context, side, formsemestre_id=None):
# "send back signature image data"
# # slow, not cached, used for unfrequent access (do not bypass python)
def _sig_filename(context, side, formsemestre_id=None):
if not side in ("left", "right"):
raise ValueError("side must be left or right")
dirs = [SCODOC_LOGOS_DIR, context.DeptId()]
if formsemestre_id:
dirs.append(formsemestre_id)
dirs.append("bul_sig_{}".format(side))
return os.path.join(*dirs)
# def _sig_filename(context, side, formsemestre_id=None):
# if not side in ("left", "right"):
# raise ValueError("side must be left or right")
# dirs = [SCODOC_LOGOS_DIR, context.DeptId()]
# if formsemestre_id:
# dirs.append(formsemestre_id)
# dirs.append("bul_sig_{}".format(side))
# return os.path.join(*dirs)
def _get_sig_existing_filename(context, side, formsemestre_id=None):
"full path to signature to use, or None if no signature available"
if formsemestre_id:
filename = _sig_filename(context, side, formsemestre_id=formsemestre_id)
if os.path.exists(filename):
return filename
filename = _sig_filename(context, side)
if os.path.exists(filename):
return filename
else:
return None
# def _get_sig_existing_filename(context, side, formsemestre_id=None):
# "full path to signature to use, or None if no signature available"
# if formsemestre_id:
# filename = _sig_filename(context, side, formsemestre_id=formsemestre_id)
# if os.path.exists(filename):
# return filename
# filename = _sig_filename(context, side)
# if os.path.exists(filename):
# return filename
# else:
# return None

View File

@ -46,7 +46,10 @@ de la forme %(XXX)s sont remplacées par la valeur de XXX, pour XXX dans:
Balises img: actuellement interdites.
"""
import traceback, re
import datetime
import traceback
import re
import jaxml
import sco_utils as scu
import sco_formsemestre
@ -156,7 +159,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
for app in self.infos["appreciations_list"]:
if can_edit_app:
mlink = (
'<a class="stdlink" href="appreciation_add_form?id=%s">modifier</a> <a class="stdlink" href="appreciation_add_form?id=%s&amp;suppress=1">supprimer</a>'
'<a class="stdlink" href="appreciation_add_form?id=%s">modifier</a> <a class="stdlink" href="appreciation_add_form?id=%s&suppress=1">supprimer</a>'
% (app["id"], app["id"])
)
else:
@ -167,7 +170,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
)
if can_edit_app:
H.append(
'<p><a class="stdlink" href="appreciation_add_form?etudid=%(etudid)s&amp;formsemestre_id=%(formsemestre_id)s">Ajouter une appréciation</a></p>'
'<p><a class="stdlink" href="appreciation_add_form?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s">Ajouter une appréciation</a></p>'
% self.infos
)
H.append("</div>")
@ -375,7 +378,8 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
P[-1]["_pdf_style"].append(
("LINEBELOW", (0, 0), (-1, 0), self.PDF_LINEWIDTH, self.PDF_LINECOLOR)
)
# Espacement sous la ligne moyenne générale:
P[-1]["_pdf_style"].append(("BOTTOMPADDING", (0, 1), (-1, 1), 8))
# Moyenne générale:
nbabs = I["nbabs"]
nbabsjust = I["nbabsjust"]
@ -389,8 +393,10 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
"abs": "%s / %s" % (nbabs, nbabsjust),
"_css_row_class": "notes_bulletin_row_gen",
"_titre_colspan": 2,
"_pdf_row_markup": ['font size="12"', "b"], # bold, size 12
"_pdf_style": [("LINEABOVE", (0, 1), (-1, 1), 1, self.PDF_LINECOLOR)],
"_pdf_row_markup": ["b"], # bold. On peut ajouter 'font size="12"'
"_pdf_style": [
("LINEABOVE", (0, 1), (-1, 1), 1, self.PDF_LINECOLOR),
],
}
P.append(t)

View File

@ -34,8 +34,12 @@ E. Viennet, juillet 2011
"""
import traceback
import sco_utils as scu
import sco_formsemestre
from sco_pdf import *
import sco_pdf
from sco_pdf import blue, cm, mm
from sco_pdf import Color, Paragraph, Spacer, Table
import sco_preferences
from notes_log import log
@ -72,7 +76,6 @@ class BulletinGeneratorUCAC(sco_bulletins_standard.BulletinGeneratorStandard):
I = self.infos
context = self.context
formsemestre_id = I["formsemestre_id"]
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
prefs = context.get_preferences(formsemestre_id)
P = [] # elems pour générer table avec gen_table (liste de dicts)
@ -134,7 +137,7 @@ class BulletinGeneratorUCAC(sco_bulletins_standard.BulletinGeneratorStandard):
total_pt_ue_v = (
ue["ue_status"]["cur_moy_ue"] * ue["ue_status"]["coef_ue"]
)
total_pt_ue = fmt_note(total_pt_ue_v)
total_pt_ue = scu.fmt_note(total_pt_ue_v)
# log('total_pt_ue = %s' % total_pt_ue)
except:
# log("ue['ue_status']['cur_moy_ue'] = %s" % ue['ue_status']['cur_moy_ue'] )
@ -212,7 +215,6 @@ class BulletinGeneratorUCAC(sco_bulletins_standard.BulletinGeneratorStandard):
ue_descr = "(en cours, non prise en compte)"
ue_type = "cur"
rowstyle = " bul_ucac_row_ue_cur"
# --- UE ordinaire
pt = list_ue(ue, ue_descr)
@ -228,8 +230,8 @@ class BulletinGeneratorUCAC(sco_bulletins_standard.BulletinGeneratorStandard):
{
"code_ue": "Total",
"moyenne_ue": I["moy_gen"],
"coef": fmt_note(sum_coef_ues),
"total": fmt_note(sum_pt_sem),
"coef": scu.fmt_note(sum_coef_ues),
"total": scu.fmt_note(sum_pt_sem),
"_code_ue_colspan": 4,
"_css_row_class": "bul_ucac_row_total",
"_pdf_row_markup": ["b"],

View File

@ -37,11 +37,18 @@ Malheureusement, le code de génération JSON et XML sont séparés, ce qui est
Je propose de considérer le XMl comme "deprecated" et de ne plus le modifier, sauf nécessité.
"""
from notes_table import *
import datetime
import jaxml
import sco_utils as scu
import notesdb as ndb
from notes_log import log
import scolars
import sco_codes_parcours
import sco_formsemestre
import sco_groups
import sco_photos
import ZAbsences
import sco_abs
import sco_bulletins
# -------- Bulletin en XML
@ -62,9 +69,9 @@ def make_xml_formsemestre_bulletinetud(
"bulletin au format XML"
log("xml_bulletin( formsemestre_id=%s, etudid=%s )" % (formsemestre_id, etudid))
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
if not doc:
doc = jaxml.XML_document(encoding=SCO_ENCODING)
doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
if sem["bul_hide_xml"] == "0" or force_publishing:
@ -98,12 +105,13 @@ def make_xml_formsemestre_bulletinetud(
etudid=etudid,
code_nip=etudinfo["code_nip"],
code_ine=etudinfo["code_ine"],
nom=quote_xml_attr(etudinfo["nom"]),
prenom=quote_xml_attr(etudinfo["prenom"]),
sexe=quote_xml_attr(etudinfo["sexe"]),
photo_url=quote_xml_attr(sco_photos.etud_photo_url(context, etudinfo)),
email=quote_xml_attr(etudinfo["email"]),
emailperso=quote_xml_attr(etudinfo["emailperso"]),
nom=scu.quote_xml_attr(etudinfo["nom"]),
prenom=scu.quote_xml_attr(etudinfo["prenom"]),
civilite=scu.quote_xml_attr(etudinfo["civilite_str"]),
sexe=scu.quote_xml_attr(etudinfo["civilite_str"]), # compat
photo_url=scu.quote_xml_attr(sco_photos.etud_photo_url(context, etudinfo)),
email=scu.quote_xml_attr(etudinfo["email"]),
emailperso=scu.quote_xml_attr(etudinfo["emailperso"]),
)
doc._pop()
@ -128,7 +136,7 @@ def make_xml_formsemestre_bulletinetud(
ues = nt.get_ues()
modimpls = nt.get_modimpls()
nbetuds = len(nt.rangs)
mg = fmt_note(nt.get_etud_moy_gen(etudid))
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
if (
nt.get_moduleimpls_attente()
or context.get_preference("bul_show_rangs", formsemestre_id) == 0
@ -147,9 +155,9 @@ def make_xml_formsemestre_bulletinetud(
doc._push()
doc.note(
value=mg,
min=fmt_note(nt.moy_min),
max=fmt_note(nt.moy_max),
moy=fmt_note(nt.moy_moy),
min=scu.fmt_note(nt.moy_min),
max=scu.fmt_note(nt.moy_max),
moy=scu.fmt_note(nt.moy_moy),
)
doc._pop()
doc._push()
@ -177,17 +185,21 @@ def make_xml_formsemestre_bulletinetud(
doc._push()
doc.ue(
id=ue["ue_id"],
numero=quote_xml_attr(ue["numero"]),
acronyme=quote_xml_attr(ue["acronyme"]),
titre=quote_xml_attr(ue["titre"]),
code_apogee=quote_xml_attr(ue["code_apogee"]),
numero=scu.quote_xml_attr(ue["numero"]),
acronyme=scu.quote_xml_attr(ue["acronyme"]),
titre=scu.quote_xml_attr(ue["titre"]),
code_apogee=scu.quote_xml_attr(ue["code_apogee"]),
)
doc._push()
if ue["type"] != UE_SPORT:
if ue["type"] != sco_codes_parcours.UE_SPORT:
v = ue_status["cur_moy_ue"]
else:
v = nt.bonus[etudid]
doc.note(value=fmt_note(v), min=fmt_note(ue["min"]), max=fmt_note(ue["max"]))
doc.note(
value=scu.fmt_note(v),
min=scu.fmt_note(ue["min"]),
max=scu.fmt_note(ue["max"]),
)
doc._pop()
try:
ects_txt = str(int(ue["ects"]))
@ -205,7 +217,9 @@ def make_xml_formsemestre_bulletinetud(
# Liste les modules de l'UE
ue_modimpls = [mod for mod in modimpls if mod["module"]["ue_id"] == ue["ue_id"]]
for modimpl in ue_modimpls:
mod_moy = fmt_note(nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid))
mod_moy = scu.fmt_note(
nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
)
if mod_moy == "NI": # ne mentionne pas les modules ou n'est pas inscrit
continue
mod = modimpl["module"]
@ -219,17 +233,18 @@ def make_xml_formsemestre_bulletinetud(
code=mod["code"],
coefficient=mod["coefficient"],
numero=mod["numero"],
titre=quote_xml_attr(mod["titre"]),
abbrev=quote_xml_attr(mod["abbrev"]),
code_apogee=quote_xml_attr(mod["code_apogee"])
titre=scu.quote_xml_attr(mod["titre"]),
abbrev=scu.quote_xml_attr(mod["abbrev"]),
code_apogee=scu.quote_xml_attr(mod["code_apogee"])
# ects=ects ects des modules maintenant inutilisés
)
doc._push()
modstat = nt.get_mod_stats(modimpl["moduleimpl_id"])
doc.note(
value=mod_moy,
min=fmt_note(modstat["min"]),
max=fmt_note(modstat["max"]),
min=scu.fmt_note(modstat["min"]),
max=scu.fmt_note(modstat["max"]),
moy=scu.fmt_note(modstat["moy"]),
)
doc._pop()
if context.get_preference("bul_show_mod_rangs", formsemestre_id):
@ -246,14 +261,16 @@ def make_xml_formsemestre_bulletinetud(
if int(e["visibulletin"]) == 1 or version == "long":
doc._push()
doc.evaluation(
jour=DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=TimetoISO8601(
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=TimetoISO8601(e["heure_fin"], null_is_empty=True),
heure_fin=ndb.TimetoISO8601(
e["heure_fin"], null_is_empty=True
),
coefficient=e["coefficient"],
evaluation_type=e["evaluation_type"],
description=quote_xml_attr(e["description"]),
description=scu.quote_xml_attr(e["description"]),
note_max_origin=e[
"note_max"
], # notes envoyées sur 20, ceci juste pour garder trace
@ -261,7 +278,7 @@ def make_xml_formsemestre_bulletinetud(
val = e["notes"].get(etudid, {"value": "NP"})[
"value"
] # NA si etud demissionnaire
val = fmt_note(val, note_max=e["note_max"])
val = scu.fmt_note(val, note_max=e["note_max"])
doc.note(value=val)
doc._pop()
# Evaluations incomplètes ou futures:
@ -275,15 +292,15 @@ def make_xml_formsemestre_bulletinetud(
if e["evaluation_id"] not in complete_eval_ids:
doc._push()
doc.evaluation(
jour=DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=TimetoISO8601(
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=TimetoISO8601(
heure_fin=ndb.TimetoISO8601(
e["heure_fin"], null_is_empty=True
),
coefficient=e["coefficient"],
description=quote_xml_attr(e["description"]),
description=scu.quote_xml_attr(e["description"]),
incomplete="1",
note_max_origin=e[
"note_max"
@ -301,28 +318,26 @@ def make_xml_formsemestre_bulletinetud(
doc._push()
doc.ue_capitalisee(
id=ue["ue_id"],
numero=quote_xml_attr(ue["numero"]),
acronyme=quote_xml_attr(ue["acronyme"]),
titre=quote_xml_attr(ue["titre"]),
numero=scu.quote_xml_attr(ue["numero"]),
acronyme=scu.quote_xml_attr(ue["acronyme"]),
titre=scu.quote_xml_attr(ue["titre"]),
)
doc._push()
doc.note(value=fmt_note(ue_status["moy"]))
doc.note(value=scu.fmt_note(ue_status["moy"]))
doc._pop()
doc._push()
doc.ects(value=ects_txt)
doc._pop()
doc._push()
doc.coefficient_ue(value=fmt_note(ue_status["coef_ue"]))
doc.coefficient_ue(value=scu.fmt_note(ue_status["coef_ue"]))
doc._pop()
doc._push()
doc.date_capitalisation(value=DateDMYtoISO(ue_status["event_date"]))
doc.date_capitalisation(value=ndb.DateDMYtoISO(ue_status["event_date"]))
doc._pop()
doc._pop()
# --- Absences
if context.get_preference("bul_show_abs", formsemestre_id):
debut_sem = DateDMYtoISO(sem["date_debut"])
fin_sem = DateDMYtoISO(sem["date_fin"])
AbsEtudSem = ZAbsences.getAbsSemEtud(context, sem, etudid)
AbsEtudSem = sco_abs.getAbsSemEtud(context, sem, etudid)
nbabs = AbsEtudSem.CountAbs()
nbabsjust = AbsEtudSem.CountAbsJust()
doc._push()
@ -340,7 +355,7 @@ def make_xml_formsemestre_bulletinetud(
format="xml",
show_uevalid=context.get_preference("bul_show_uevalid", formsemestre_id),
)
doc.situation(quote_xml_attr(infos["situation"]))
doc.situation(scu.quote_xml_attr(infos["situation"]))
if dpv:
decision = dpv["decisions"][0]
etat = decision["etat"]
@ -374,9 +389,9 @@ def make_xml_formsemestre_bulletinetud(
doc._push()
doc.decision_ue(
ue_id=ue["ue_id"],
numero=quote_xml_attr(ue["numero"]),
acronyme=quote_xml_attr(ue["acronyme"]),
titre=quote_xml_attr(ue["titre"]),
numero=scu.quote_xml_attr(ue["numero"]),
acronyme=scu.quote_xml_attr(ue["acronyme"]),
titre=scu.quote_xml_attr(ue["titre"]),
code=decision["decisions_ue"][ue_id]["code"],
)
doc._pop()
@ -395,5 +410,7 @@ def make_xml_formsemestre_bulletinetud(
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
)
for app in apprecs:
doc.appreciation(quote_xml_attr(app["comment"]), date=DateDMYtoISO(app["date"]))
doc.appreciation(
scu.quote_xml_attr(app["comment"]), date=ndb.DateDMYtoISO(app["date"])
)
return doc

View File

@ -29,15 +29,26 @@
"""
import traceback
import pprint
from types import FloatType
from sco_utils import *
from notesdb import *
import sco_utils as scu
from sco_utils import (
NOTES_ATTENTE,
NOTES_NEUTRALISE,
EVALUATION_NORMALE,
EVALUATION_RATTRAPAGE,
EVALUATION_SESSION2,
)
from sco_exceptions import ScoException
from notesdb import EditableTable, quote_html
from notes_log import log, sendAlarm
import sco_formsemestre
import sco_moduleimpl
import sco_groups
import sco_evaluations
from sco_formulas import *
import ZAbsences
import sco_formulas
import sco_abs
def moduleimpl_has_expression(context, mod):
@ -67,7 +78,9 @@ def formsemestre_expressions_use_abscounts(context, formsemestre_id):
if expr and expr[0] != "#" and ab in expr:
return True
# 2- moyennes de modules
for mod in context.Notes.do_moduleimpl_list(formsemestre_id=formsemestre_id):
for mod in sco_moduleimpl.do_moduleimpl_list(
context, formsemestre_id=formsemestre_id
):
if moduleimpl_has_expression(context, mod) and ab in mod["computation_expr"]:
return True
return False
@ -124,7 +137,7 @@ def compute_user_formula(
Retourne moy, et en cas d'erreur met à jour diag_info (msg)
"""
if use_abs:
AbsSemEtud = ZAbsences.getAbsSemEtud(context, sem, etudid)
AbsSemEtud = sco_abs.getAbsSemEtud(context, sem, etudid)
nbabs = AbsSemEtud.CountAbs()
nbabs_just = AbsSemEtud.CountAbsJust()
else:
@ -148,7 +161,7 @@ def compute_user_formula(
try:
formula = formula.replace("\n", "").replace("\r", "")
# log('expression : %s\nvariables=%s\n' % (formula, variables)) # XXX debug
user_moy = eval_user_expression(context, formula, variables)
user_moy = sco_formulas.eval_user_expression(context, formula, variables)
# log('user_moy=%s' % user_moy)
if user_moy != "NA0" and user_moy != "NA":
user_moy = float(user_moy)
@ -188,10 +201,10 @@ def do_moduleimpl_moyennes(context, nt, mod):
"""
diag_info = {} # message d'erreur formule
moduleimpl_id = mod["moduleimpl_id"]
is_malus = mod["module"]["module_type"] == MODULE_MALUS
is_malus = mod["module"]["module_type"] == scu.MODULE_MALUS
sem = sco_formsemestre.get_formsemestre(context, mod["formsemestre_id"])
etudids = context.do_moduleimpl_listeetuds(
moduleimpl_id
etudids = sco_moduleimpl.do_moduleimpl_listeetuds(
context, moduleimpl_id
) # tous, y compris demissions
# Inscrits au semestre (pour traiter les demissions):
inssem_set = set(
@ -230,7 +243,10 @@ def do_moduleimpl_moyennes(context, nt, mod):
if e["etat"]["evalattente"]:
attente = True
if e["evaluation_type"] == EVALUATION_RATTRAPAGE:
if (
e["evaluation_type"] == EVALUATION_RATTRAPAGE
or e["evaluation_type"] == EVALUATION_SESSION2
):
if eval_rattr:
# !!! plusieurs rattrapages !
diag_info.update(
@ -256,7 +272,7 @@ def do_moduleimpl_moyennes(context, nt, mod):
]
#
R = {}
formula = unescape_html(mod["computation_expr"])
formula = scu.unescape_html(mod["computation_expr"])
formula_use_abs = "abs" in formula
for etudid in insmod_set: # inscrits au semestre et au module
@ -332,7 +348,7 @@ def do_moduleimpl_moyennes(context, nt, mod):
if diag_info:
diag_info["moduleimpl_id"] = moduleimpl_id
R[etudid] = user_moy
# Note de rattrapage ?
# Note de rattrapage ou deuxième session ?
if eval_rattr:
if eval_rattr["notes"].has_key(etudid):
note = eval_rattr["notes"][etudid]["value"]
@ -341,9 +357,15 @@ def do_moduleimpl_moyennes(context, nt, mod):
R[etudid] = note
else:
note_sur_20 = note * 20.0 / eval_rattr["note_max"]
if eval_rattr["evaluation_type"] == EVALUATION_RATTRAPAGE:
# rattrapage classique: prend la meilleure note entre moyenne
# module et note eval rattrapage
if note_sur_20 > R[etudid]:
# log('note_sur_20=%s' % note_sur_20)
R[etudid] = note_sur_20
elif eval_rattr["evaluation_type"] == EVALUATION_SESSION2:
# rattrapage type "deuxième session": remplace la note moyenne
R[etudid] = note_sur_20
return R, valid_evals, attente, diag_info
@ -353,12 +375,14 @@ def do_formsemestre_moyennes(context, nt, formsemestre_id):
la liste des moduleimpls, la liste des evaluations valides,
liste des moduleimpls avec notes en attente.
"""
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
inscr = context.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id}
# sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
# inscr = context.do_formsemestre_inscription_list(
# args={"formsemestre_id": formsemestre_id}
# )
# etudids = [x["etudid"] for x in inscr]
modimpls = sco_moduleimpl.do_moduleimpl_list(
context, formsemestre_id=formsemestre_id
)
etudids = [x["etudid"] for x in inscr]
modimpls = context.do_moduleimpl_list(formsemestre_id=formsemestre_id)
# recupere les moyennes des etudiants de tous les modules
D = {}
valid_evals = []

View File

@ -30,14 +30,16 @@
(coût théorique en heures équivalent TD)
"""
from notesdb import *
from sco_utils import *
import notesdb as ndb
import sco_utils as scu
from notes_log import log
from gen_tables import GenTable
import sco_excel, sco_pdf
from sco_pdf import SU
import sco_formsemestre
import sco_moduleimpl
import sco_formsemestre_status
import VERSION
def formsemestre_table_estim_cost(
@ -60,7 +62,9 @@ def formsemestre_table_estim_cost(
"""
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
sco_formsemestre_status.fill_formsemestre(context, sem, REQUEST=REQUEST)
Mlist = context.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
context, formsemestre_id=formsemestre_id
)
T = []
for M in Mlist:
Mod = M["module"]
@ -141,7 +145,7 @@ def formsemestre_table_estim_cost(
(dans ce cas, retoucher le tableau excel exporté).
</div>
""",
origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "",
origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "",
filename="EstimCout-S%s" % sem["semestre_id"],
)
return tab
@ -190,7 +194,7 @@ def formsemestre_estim_cost(
)
tab.html_before_table = h
tab.base_url = (
"%s?formsemestre_id=%s&amp;n_group_td=%s&amp;n_group_tp=%s&amp;coef_tp=%s"
"%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s"
% (REQUEST.URL0, formsemestre_id, n_group_td, n_group_tp, coef_tp)
)

View File

@ -115,7 +115,7 @@ def table_debouche_etudids(context, etudids, keep_numeric=True):
)
row = {
"etudid": etudid,
"sexe": etud["sexe"],
"civilite": etud["civilite"],
"nom": etud["nom"],
"prenom": etud["prenom"],
"_nom_target": "ficheEtud?etudid=" + etud["etudid"],
@ -154,7 +154,7 @@ def table_debouche_etudids(context, etudids, keep_numeric=True):
L.sort(key=lambda x: x["sem_ident"])
titles = {
"sexe": "",
"civilite": "",
"nom": "Nom",
"prenom": "Prénom",
"semestre": "Dernier semestre",
@ -170,7 +170,7 @@ def table_debouche_etudids(context, etudids, keep_numeric=True):
"semestre",
"semestre_id",
"periode",
"sexe",
"civilite",
"nom",
"prenom",
"moy",

View File

@ -27,14 +27,15 @@
"""Page accueil département (liste des semestres, etc)
"""
from notesdb import *
from sco_utils import *
import notesdb as ndb
import sco_utils as scu
from notes_log import log
import sco_modalites
import sco_news
import sco_up_to_date
import sco_formsemestre
from gen_tables import GenTable
from sco_permissions import ScoEtudInscrit, ScoEditApo
def index_html(context, REQUEST=None, showcodes=0, showsemtable=0):
@ -44,7 +45,7 @@ def index_html(context, REQUEST=None, showcodes=0, showsemtable=0):
# News:
# 2020-12-30: abandonne l'icon rss
# rssicon = icontag("rssscodoc_img", title="Flux RSS", border="0")
# rssicon = scu.icontag("rssscodoc_img", title="Flux RSS", border="0")
H.append(sco_news.scolar_news_summary_html(context)) # , rssicon=rssicon))
# Avertissement de mise à jour:
@ -55,9 +56,11 @@ def index_html(context, REQUEST=None, showcodes=0, showsemtable=0):
cursems = [] # semestres "courants"
othersems = [] # autres (verrouillés)
# icon image:
groupicon = icontag("groupicon_img", title="Inscrits", border="0")
emptygroupicon = icontag("emptygroupicon_img", title="Pas d'inscrits", border="0")
lockicon = icontag("lock32_img", title="verrouillé", border="0")
groupicon = scu.icontag("groupicon_img", title="Inscrits", border="0")
emptygroupicon = scu.icontag(
"emptygroupicon_img", title="Pas d'inscrits", border="0"
)
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
# Sélection sur l'etat du semestre
for sem in sems:
if sem["etat"] == "1" and sem["modalite"] != "EXT":
@ -126,11 +129,12 @@ def index_html(context, REQUEST=None, showcodes=0, showsemtable=0):
)
H.append(
"""<p><form action="Notes/view_formsemestre_by_etape">
Chercher étape courante: <input name="etape_apo" type="text" size="8"></input>
"""<p><form action="%s/view_formsemestre_by_etape">
Chercher étape courante: <input name="etape_apo" type="text" size="8" spellcheck="false"></input>
</form
</p>
"""
% context.NotesURL()
)
#
authuser = REQUEST.AUTHENTICATED_USER
@ -139,7 +143,7 @@ Chercher étape courante: <input name="etape_apo" type="text" size="8"></input>
"""<hr>
<h3>Gestion des étudiants</h3>
<ul>
<li><a class="stdlink" href="etudident_create_form">créer <em>un</em> nouvel étudiant</a></li>
<li><a class="stdlink" id="link_create_etudident" href="etudident_create_form">créer <em>un</em> nouvel étudiant</a></li>
<li><a class="stdlink" href="form_students_import_excel">importer de nouveaux étudiants</a> (ne pas utiliser sauf cas particulier, utilisez plutôt le lien dans
le tableau de bord semestre si vous souhaitez inscrire les
étudiants importés à un semestre)</li>
@ -152,9 +156,10 @@ Chercher étape courante: <input name="etape_apo" type="text" size="8"></input>
"""<hr>
<h3>Exports Apogée</h3>
<ul>
<li><a class="stdlink" href="Notes/semset_page">Années scolaires / exports Apogée</a></li>
<li><a class="stdlink" href="%s/semset_page">Années scolaires / exports Apogée</a></li>
</ul>
"""
% context.NotesURL()
)
#
H.append(
@ -172,9 +177,9 @@ Chercher étape courante: <input name="etape_apo" type="text" size="8"></input>
def _sem_table(context, sems):
"""Affiche liste des semestres, utilisée pour semestres en cours"""
tmpl = """<tr class="%(trclass)s">%(tmpcode)s
<td class="semicon">%(lockimg)s <a href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
<td class="datesem">%(mois_debut)s</td><td class="datesem"><a title="%(session_id)s">-</a> %(mois_fin)s</td>
<td><a class="stdlink" href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
<td><a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
<span class="respsem">(%(responsable_name)s)</span>
</td>
</tr>
@ -196,6 +201,7 @@ def _sem_table(context, sems):
cur_idx = sem["semestre_id"]
else:
sem["trclass"] = ""
sem["notes_url"] = context.NotesURL()
H.append(tmpl % sem)
H.append("</table>")
return "\n".join(H)
@ -242,14 +248,16 @@ def _sem_table_gt(context, sems, showcodes=False):
def _style_sems(context, sems):
"""ajoute quelques attributs de présentation pour la table"""
for sem in sems:
sem["notes_url"] = context.NotesURL()
sem["_groupicon_target"] = (
"Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s" % sem
"%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s"
% sem
)
sem["_formsemestre_id_class"] = "blacktt"
sem["dash_mois_fin"] = '<a title="%(session_id)s"></a> %(anneescolaire)s' % sem
sem["_dash_mois_fin_class"] = "datesem"
sem["titre_resp"] = (
"""<a class="stdlink" href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
"""<a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
<span class="respsem">(%(responsable_name)s)</span>"""
% sem
)

View File

@ -46,19 +46,24 @@ pg_dump SCORT | psql ANORT
"""
import os
import fcntl
import subprocess
import requests
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email.Header import Header
from email import Encoders
from notesdb import *
from sco_utils import *
from email.MIMEMultipart import ( # pylint: disable=no-name-in-module,import-error
MIMEMultipart,
)
from email.MIMEText import MIMEText # pylint: disable=no-name-in-module,import-error
from email.MIMEBase import MIMEBase # pylint: disable=no-name-in-module,import-error
from email.Header import Header # pylint: disable=no-name-in-module,import-error
from email import Encoders # pylint: disable=no-name-in-module,import-error
import notesdb as ndb
import sco_utils as scu
from notes_log import log
from sco_exceptions import ScoValueError
SCO_DUMP_LOCK = "/tmp/scodump.lock"
@ -67,14 +72,14 @@ 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"""
H = [context.sco_header(REQUEST, page_title="Assistance technique")]
# get currect (dept) DB name:
cursor = SimpleQuery(context, "SELECT current_database()", {})
cursor = ndb.SimpleQuery(context, "SELECT current_database()", {})
db_name = cursor.fetchone()[0]
ano_db_name = "ANO" + db_name
# Lock
try:
x = open(SCO_DUMP_LOCK, "w+")
fcntl.flock(x, fcntl.LOCK_EX | fcntl.LOCK_NB)
except fcntl.BlockingIOError:
except (IOError, OSError): # exception changed from Python 2 to 3
raise ScoValueError(
"Un envoi de la base "
+ db_name
@ -93,22 +98,25 @@ def sco_dump_and_send_db(context, REQUEST=None):
# Send
r = _send_db(context, REQUEST, ano_db_name)
if r.status_code == requests.codes.INSUFFICIENT_STORAGE:
if (
r.status_code
== requests.codes.INSUFFICIENT_STORAGE # pylint: disable=no-member
):
H.append(
"""<p class="warning">
Erreur: espace serveur trop plein.
Merci de contacter <a href="mailto:{0}">{0}</a></p>""".format(
SCO_DEV_MAIL
scu.SCO_DEV_MAIL
)
)
elif r.status_code == requests.codes.OK:
elif r.status_code == requests.codes.OK: # pylint: disable=no-member
H.append("""<p>Opération effectuée.</p>""")
else:
H.append(
"""<p class="warning">
Erreur: code <tt>{0} {1}</tt>
Merci de contacter <a href="mailto:{2}">{2}</a></p>""".format(
r.status_code, r.reason, SCO_DEV_MAIL
r.status_code, r.reason, scu.SCO_DEV_MAIL
)
)
@ -128,7 +136,7 @@ def _duplicate_db(db_name, ano_db_name):
cmd = ["createdb", "-E", "UTF-8", ano_db_name]
log("sco_dump_and_send_db/_duplicate_db: {}".format(cmd))
try:
out = subprocess.check_output(cmd)
_ = subprocess.check_output(cmd)
except subprocess.CalledProcessError as e:
log("sco_dump_and_send_db: exception createdb {}".format(e))
raise ScoValueError(
@ -138,7 +146,7 @@ def _duplicate_db(db_name, ano_db_name):
cmd = "pg_dump {} | psql {}".format(db_name, ano_db_name)
log("sco_dump_and_send_db/_duplicate_db: {}".format(cmd))
try:
out = subprocess.check_output(cmd, shell=1)
_ = subprocess.check_output(cmd, shell=1)
except subprocess.CalledProcessError as e:
log("sco_dump_and_send_db: exception {}".format(e))
raise ScoValueError(
@ -150,10 +158,10 @@ def _duplicate_db(db_name, ano_db_name):
def _anonymize_db(ano_db_name):
"""Anonymize a departement database"""
cmd = os.path.join(SCO_CONFIG_DIR, "anonymize_db.py")
cmd = os.path.join(scu.SCO_CONFIG_DIR, "anonymize_db.py")
log("_anonymize_db: {}".format(cmd))
try:
out = subprocess.check_output([cmd, ano_db_name])
_ = subprocess.check_output([cmd, ano_db_name])
except subprocess.CalledProcessError as e:
log("sco_dump_and_send_db: exception in anonymisation: {}".format(e))
raise ScoValueError(
@ -163,7 +171,7 @@ def _anonymize_db(ano_db_name):
def _get_scodoc_serial(context):
try:
return int(open(os.path.join(SCODOC_VERSION_DIR, "scodoc.sn")).read())
return int(open(os.path.join(scu.SCODOC_VERSION_DIR, "scodoc.sn")).read())
except:
return 0
@ -182,7 +190,7 @@ def _send_db(context, REQUEST, ano_db_name):
log("uploading anonymized dump...")
files = {"file": (ano_db_name + ".gz", data)}
r = requests.post(
SCO_DUMP_UP_URL,
scu.SCO_DUMP_UP_URL,
files=files,
data={
"dept_name": context.get_preference("DeptName"),
@ -191,8 +199,8 @@ def _send_db(context, REQUEST, ano_db_name):
"sent_by": context.Users.user_info(str(REQUEST.AUTHENTICATED_USER))[
"nomcomplet"
],
"sco_version": SCOVERSION,
"sco_subversion": get_svn_version(SCO_CONFIG_DIR),
"sco_version": scu.SCOVERSION,
"sco_subversion": scu.get_svn_version(scu.SCO_CONFIG_DIR),
},
)
return r
@ -210,7 +218,7 @@ def _drop_ano_db(ano_db_name):
cmd = ["dropdb", ano_db_name]
log("sco_dump_and_send_db: {}".format(cmd))
try:
out = subprocess.check_output(cmd)
_ = subprocess.check_output(cmd)
except subprocess.CalledProcessError as e:
log("sco_dump_and_send_db: exception dropdb {}".format(e))
raise ScoValueError(

View File

@ -28,12 +28,13 @@
"""Ajout/Modification/Supression formations
(portage from DTML)
"""
from notesdb import *
from sco_utils import *
import notesdb as ndb
import sco_utils as scu
from notes_log import log
from TrivialFormulator import TrivialFormulator, TF
from TrivialFormulator import TrivialFormulator, TF, tf_error_message
import sco_codes_parcours
import sco_formsemestre
from sco_exceptions import ScoValueError
def formation_delete(context, formation_id=None, dialog_confirmed=False, REQUEST=None):
@ -61,7 +62,7 @@ def formation_delete(context, formation_id=None, dialog_confirmed=False, REQUEST
'<li><a href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titremois)s</a></li>'
% sem
)
H.append('</ul><p><a href="%s">Revenir</a></p>' % REQUEST.URL1)
H.append('</ul><p><a href="%s">Revenir</a></p>' % context.NotesURL())
else:
if not dialog_confirmed:
return context.confirmDialog(
@ -72,7 +73,7 @@ def formation_delete(context, formation_id=None, dialog_confirmed=False, REQUEST
% F,
REQUEST=REQUEST,
OK="Supprimer cette formation",
cancel_url=REQUEST.URL1,
cancel_url=context.NotesURL(),
parameters={"formation_id": formation_id},
)
else:
@ -80,7 +81,7 @@ def formation_delete(context, formation_id=None, dialog_confirmed=False, REQUEST
H.append(
"""<p>OK, formation supprimée.</p>
<p><a class="stdlink" href="%s">continuer</a></p>"""
% REQUEST.URL1
% context.NotesURL()
)
H.append(context.sco_footer(REQUEST))
@ -190,7 +191,7 @@ def formation_edit(context, formation_id=None, create=False, REQUEST=None):
if tf[0] == 0:
return "\n".join(H) + tf[1] + context.sco_footer(REQUEST)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(context.NotesURL())
else:
# check unicity : constraint UNIQUE(acronyme,titre,version)
if create:
@ -202,7 +203,7 @@ def formation_edit(context, formation_id=None, create=False, REQUEST=None):
"titre": tf[2]["titre"],
"version": version,
}
quote_dict(args)
ndb.quote_dict(args)
others = context.formation_list(args=args)
if others and ((len(others) > 1) or others[0]["formation_id"] != formation_id):
return (

View File

@ -28,11 +28,12 @@
"""Ajout/Modification/Supression matieres
(portage from DTML)
"""
from notesdb import *
from sco_utils import *
import notesdb as ndb
import sco_utils as scu
from notes_log import log
from TrivialFormulator import TrivialFormulator, TF
from TrivialFormulator import TrivialFormulator, TF, tf_error_message
import sco_formsemestre
from sco_exceptions import ScoValueError
def matiere_create(context, ue_id=None, REQUEST=None):
@ -74,7 +75,7 @@ associé.
submitlabel="Créer cette matière",
)
dest_url = REQUEST.URL1 + "/ue_list?formation_id=" + UE["formation_id"]
dest_url = context.NotesURL() + "/ue_list?formation_id=" + UE["formation_id"]
if tf[0] == 0:
return "\n".join(H) + tf[1] + context.sco_footer(REQUEST)
@ -90,7 +91,7 @@ associé.
+ tf[1]
+ context.sco_footer(REQUEST)
)
matiere_id = context.do_matiere_create(tf[2], REQUEST)
_ = context.do_matiere_create(tf[2], REQUEST)
return REQUEST.RESPONSE.redirect(dest_url)
@ -103,7 +104,7 @@ def matiere_delete(context, matiere_id=None, REQUEST=None):
"<h2>Suppression de la matière %(titre)s" % M,
" dans l'UE (%(acronyme)s))</h2>" % UE,
]
dest_url = context.NotesURL() + "/ue_list?formation_id=" + str(UE["formation_id"])
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
@ -115,12 +116,10 @@ def matiere_delete(context, matiere_id=None, REQUEST=None):
if tf[0] == 0:
return "\n".join(H) + tf[1] + context.sco_footer(REQUEST)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(dest_url)
else:
context.do_matiere_delete(matiere_id, REQUEST)
return REQUEST.RESPONSE.redirect(
REQUEST.URL1 + "/ue_list?formation_id=" + str(UE["formation_id"])
)
return REQUEST.RESPONSE.redirect(dest_url)
def matiere_edit(context, matiere_id=None, REQUEST=None):
@ -180,7 +179,7 @@ associé.
submitlabel="Modifier les valeurs",
)
dest_url = REQUEST.URL1 + "/ue_list?formation_id=" + U["formation_id"]
dest_url = context.NotesURL() + "/ue_list?formation_id=" + U["formation_id"]
if tf[0] == 0:
return "\n".join(H) + tf[1] + help + context.sco_footer(REQUEST)
@ -202,7 +201,7 @@ associé.
# changement d'UE ?
if tf[2]["ue_id"] != F["ue_id"]:
log("attaching mat %s to new UE %s" % (matiere_id, tf[2]["ue_id"]))
SimpleQuery(
ndb.SimpleQuery(
context,
"UPDATE notes_modules SET ue_id = %(ue_id)s WHERE matiere_id=%(matiere_id)s",
{"ue_id": tf[2]["ue_id"], "matiere_id": matiere_id},

View File

@ -28,14 +28,16 @@
"""Ajout/Modification/Supression UE
(portage from DTML)
"""
from notesdb import *
from sco_utils import *
import notesdb as ndb
import sco_utils as scu
from notes_log import log
import sco_codes_parcours
from TrivialFormulator import TrivialFormulator, TF
import sco_formsemestre
import sco_edit_ue
import sco_tag_module
from sco_permissions import ScoChangeFormation
from sco_exceptions import ScoValueError
_MODULE_HELP = """<p class="help">
Les modules sont décrits dans le programme pédagogique. Un module est pour ce
@ -99,7 +101,7 @@ def module_create(context, matiere_id=None, REQUEST=None):
"title": "Type",
"explanation": "",
"labels": ("Standard", "Malus"),
"allowed_values": (str(MODULE_STANDARD), str(MODULE_MALUS)),
"allowed_values": (str(scu.MODULE_STANDARD), str(scu.MODULE_MALUS)),
},
),
(
@ -140,7 +142,7 @@ def module_create(context, matiere_id=None, REQUEST=None):
{
"input_type": "menu",
"type": "int",
"title": strcapitalize(parcours.SESSION_NAME),
"title": scu.strcapitalize(parcours.SESSION_NAME),
"explanation": "%s de début du module dans la formation standard"
% parcours.SESSION_NAME,
"labels": [str(x) for x in semestres_indices],
@ -170,9 +172,9 @@ def module_create(context, matiere_id=None, REQUEST=None):
if tf[0] == 0:
return "\n".join(H) + tf[1] + context.sco_footer(REQUEST)
else:
moduleid = context.do_module_create(tf[2], REQUEST)
context.do_module_create(tf[2], REQUEST)
return REQUEST.RESPONSE.redirect(
REQUEST.URL1 + "/ue_list?formation_id=" + UE["formation_id"]
context.NotesURL() + "/ue_list?formation_id=" + UE["formation_id"]
)
@ -189,7 +191,7 @@ def module_delete(context, module_id=None, REQUEST=None):
"""<h2>Suppression du module %(titre)s (%(code)s)</h2>""" % Mod,
]
dest_url = REQUEST.URL1 + "/ue_list?formation_id=" + Mod["formation_id"]
dest_url = context.NotesURL() + "/ue_list?formation_id=" + Mod["formation_id"]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
@ -227,7 +229,7 @@ def module_edit(context, module_id=None, REQUEST=None):
unlocked = not context.module_is_locked(module_id)
Fo = context.formation_list(args={"formation_id": Mod["formation_id"]})[0]
parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
M = SimpleDictFetch(
M = ndb.SimpleDictFetch(
context,
"SELECT ue.acronyme, mat.* FROM notes_matieres mat, notes_ue ue WHERE mat.ue_id = ue.ue_id AND ue.formation_id = %(formation_id)s ORDER BY ue.numero, mat.numero",
{"formation_id": Mod["formation_id"]},
@ -238,7 +240,7 @@ def module_edit(context, module_id=None, REQUEST=None):
semestres_indices = range(1, parcours.NB_SEM + 1)
dest_url = REQUEST.URL1 + "/ue_list?formation_id=" + Mod["formation_id"]
dest_url = context.NotesURL() + "/ue_list?formation_id=" + Mod["formation_id"]
H = [
context.sco_header(
@ -286,7 +288,7 @@ def module_edit(context, module_id=None, REQUEST=None):
"title": "Type",
"explanation": "",
"labels": ("Standard", "Malus"),
"allowed_values": (str(MODULE_STANDARD), str(MODULE_MALUS)),
"allowed_values": (str(scu.MODULE_STANDARD), str(scu.MODULE_MALUS)),
"enabled": unlocked,
},
),
@ -395,11 +397,10 @@ def edit_module_set_code_apogee(context, id=None, value=None, REQUEST=None):
modules = context.do_module_list(args={"module_id": module_id})
if not modules:
return "module invalide" # shoud not occur
module = modules[0]
context.do_module_edit({"module_id": module_id, "code_apogee": value})
if not value:
value = APO_MISSING_CODE_STR
value = scu.APO_MISSING_CODE_STR
return value
@ -444,14 +445,14 @@ def formation_add_malus_modules(context, formation_id, titre=None, REQUEST=None)
[
mod
for mod in context.do_module_list(args={"ue_id": ue["ue_id"]})
if mod["module_type"] == MODULE_MALUS
if mod["module_type"] == scu.MODULE_MALUS
]
)
if nb_mod_malus == 0:
ue_add_malus_module(context, ue["ue_id"], titre=titre, REQUEST=REQUEST)
if REQUEST:
REQUEST.RESPONSE.redirect("ue_list?formation_id=" + formation_id)
return REQUEST.RESPONSE.redirect("ue_list?formation_id=" + formation_id)
def ue_add_malus_module(context, ue_id, titre=None, code=None, REQUEST=None):
@ -490,7 +491,7 @@ def ue_add_malus_module(context, ue_id, titre=None, code=None, REQUEST=None):
"matiere_id": matiere_id,
"formation_id": ue["formation_id"],
"semestre_id": semestre_id,
"module_type": MODULE_MALUS,
"module_type": scu.MODULE_MALUS,
},
REQUEST,
)

View File

@ -28,8 +28,8 @@
"""Ajout/Modification/Supression UE
"""
from notesdb import *
from sco_utils import *
import notesdb as ndb
import sco_utils as scu
from notes_log import log
from TrivialFormulator import TrivialFormulator, TF
from gen_tables import GenTable
@ -38,6 +38,8 @@ import sco_formsemestre
import sco_formsemestre_validation
import sco_codes_parcours
import sco_tag_module
from sco_permissions import ScoChangeFormation, ScoEditFormationTags, ScoImplement
from sco_exceptions import ScoValueError, ScoLockedFormError
def ue_create(context, formation_id=None, REQUEST=None):
@ -84,7 +86,7 @@ def ue_edit(context, ue_id=None, create=False, formation_id=None, REQUEST=None):
ue_types = parcours.ALLOWED_UE_TYPES
ue_types.sort()
ue_types_names = [UE_TYPE_NAME[k] for k in ue_types]
ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types]
ue_types = [str(x) for x in ue_types]
fw = [
@ -158,7 +160,7 @@ def ue_edit(context, ue_id=None, create=False, formation_id=None, REQUEST=None):
{
"input_type": "menu",
"type": "int",
"title": strcapitalize(parcours.SESSION_NAME),
"title": scu.strcapitalize(parcours.SESSION_NAME),
"explanation": "%s de début du module dans la formation"
% parcours.SESSION_NAME,
"labels": [str(x) for x in semestres_indices],
@ -204,7 +206,7 @@ def ue_edit(context, ue_id=None, create=False, formation_id=None, REQUEST=None):
)
if parcours.UE_IS_MODULE:
# dans ce mode, crée un (unique) module dans l'UE:
module_id = context.do_module_create(
_ = context.do_module_create(
{
"titre": tf[2]["titre"],
"code": tf[2]["acronyme"],
@ -217,9 +219,9 @@ def ue_edit(context, ue_id=None, create=False, formation_id=None, REQUEST=None):
REQUEST,
)
else:
ue_id = do_ue_edit(context, tf[2])
do_ue_edit(context, tf[2])
return REQUEST.RESPONSE.redirect(
REQUEST.URL1 + "/ue_list?formation_id=" + formation_id
context.NotesURL() + "/ue_list?formation_id=" + formation_id
)
@ -306,15 +308,15 @@ def ue_list(context, formation_id=None, msg="", REQUEST=None):
editable = perm_change
tag_editable = authuser.has_permission(ScoEditFormationTags, context) or perm_change
if locked:
lockicon = icontag("lock32_img", title="verrouillé")
lockicon = scu.icontag("lock32_img", title="verrouillé")
else:
lockicon = ""
arrow_up, arrow_down, arrow_none = sco_groups.getArrowIconsTags(context, REQUEST)
delete_icon = icontag(
delete_icon = scu.icontag(
"delete_small_img", title="Supprimer (module inutilisé)", alt="supprimer"
)
delete_disabled_icon = icontag(
delete_disabled_icon = scu.icontag(
"delete_small_dis_img", title="Suppression impossible (module utilisé)"
)
H = [
@ -415,7 +417,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
klass = ""
UE["code_apogee_str"] = (
""", Apo: <span class="%s" data-url="edit_ue_set_code_apogee" id="%s" data-placeholder="%s">"""
% (klass, UE["ue_id"], APO_MISSING_CODE_STR)
% (klass, UE["ue_id"], scu.APO_MISSING_CODE_STR)
+ (UE["code_apogee"] or "")
+ "</span>"
)
@ -424,7 +426,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
cur_ue_semestre_id = UE["semestre_id"]
if iue > 0:
H.append("</ul>")
if UE["semestre_id"] == UE_SEM_DEFAULT:
if UE["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
lab = "Pas d'indication de semestre:"
else:
lab = "Semestre %s:" % UE["semestre_id"]
@ -433,14 +435,14 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
H.append('<li class="notes_ue_list">')
if iue != 0 and editable:
H.append(
'<a href="ue_move?ue_id=%s&amp;after=0" class="aud">%s</a>'
'<a href="ue_move?ue_id=%s&after=0" class="aud">%s</a>'
% (UE["ue_id"], arrow_up)
)
else:
H.append(arrow_none)
if iue < len(ue_list) - 1 and editable:
H.append(
'<a href="ue_move?ue_id=%s&amp;after=1" class="aud">%s</a>'
'<a href="ue_move?ue_id=%s&after=1" class="aud">%s</a>'
% (UE["ue_id"], arrow_down)
)
else:
@ -456,8 +458,11 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
% UE
)
if UE["type"] != UE_STANDARD:
H.append('<span class="ue_type">%s</span>' % UE_TYPE_NAME[UE["type"]])
if UE["type"] != sco_codes_parcours.UE_STANDARD:
H.append(
'<span class="ue_type">%s</span>'
% sco_codes_parcours.UE_TYPE_NAME[UE["type"]]
)
ue_editable = editable and not context.ue_is_locked(UE["ue_id"])
if ue_editable:
H.append(
@ -488,21 +493,21 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
Mod["module_id"]
)
klass = "notes_module_list"
if Mod["module_type"] == MODULE_MALUS:
if Mod["module_type"] == scu.MODULE_MALUS:
klass += " module_malus"
H.append('<li class="%s">' % klass)
H.append('<span class="notes_module_list_buts">')
if im != 0 and editable:
H.append(
'<a href="module_move?module_id=%s&amp;after=0" class="aud">%s</a>'
'<a href="module_move?module_id=%s&after=0" class="aud">%s</a>'
% (Mod["module_id"], arrow_up)
)
else:
H.append(arrow_none)
if im < len(Modlist) - 1 and editable:
H.append(
'<a href="module_move?module_id=%s&amp;after=1" class="aud">%s</a>'
'<a href="module_move?module_id=%s&after=1" class="aud">%s</a>'
% (Mod["module_id"], arrow_down)
)
else:
@ -527,7 +532,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
)
H.append(
'<span class="formation_module_tit">%s</span>'
% join_words(Mod["code"], Mod["titre"])
% scu.join_words(Mod["code"], Mod["titre"])
)
if mod_editable:
H.append("</a>")
@ -541,7 +546,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
klass = ""
heurescoef += (
', Apo: <span class="%s" data-url="edit_module_set_code_apogee" id="%s" data-placeholder="%s">'
% (klass, Mod["module_id"], APO_MISSING_CODE_STR)
% (klass, Mod["module_id"], scu.APO_MISSING_CODE_STR)
+ (Mod["code_apogee"] or "")
+ "</span>"
)
@ -615,9 +620,9 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
"""
<li><a class="stdlink" href="formation_table_recap?formation_id=%(formation_id)s">Table récapitulative de la formation</a></li>
<li><a class="stdlink" href="formation_export?formation_id=%(formation_id)s&amp;format=xml">Export XML de la formation</a> (permet de la sauvegarder pour l'échanger avec un autre site)</li>
<li><a class="stdlink" href="formation_export?formation_id=%(formation_id)s&format=xml">Export XML de la formation</a> (permet de la sauvegarder pour l'échanger avec un autre site)</li>
<li><a class="stdlink" href="formation_export?formation_id=%(formation_id)s&amp;format=json">Export JSON de la formation</a></li>
<li><a class="stdlink" href="formation_export?formation_id=%(formation_id)s&format=json">Export JSON de la formation</a></li>
<li><a class="stdlink" href="module_list?formation_id=%(formation_id)s">Liste détaillée des modules de la formation</a> (debug) </li>
</ul>
@ -641,7 +646,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
H.append(" [verrouillé]")
else:
H.append(
' <a class="stdlink" href="formsemestre_editwithmodules?formation_id=%(formation_id)s&amp;formsemestre_id=%(formsemestre_id)s">Modifier</a>'
' <a class="stdlink" href="formsemestre_editwithmodules?formation_id=%(formation_id)s&formsemestre_id=%(formsemestre_id)s">Modifier</a>'
% sem
)
H.append("</li>")
@ -650,7 +655,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
if authuser.has_permission(ScoImplement, context):
H.append(
"""<ul>
<li><a class="stdlink" href="formsemestre_createwithmodules?formation_id=%(formation_id)s&amp;semestre_id=1">Mettre en place un nouveau semestre de formation %(acronyme)s</a>
<li><a class="stdlink" href="formsemestre_createwithmodules?formation_id=%(formation_id)s&semestre_id=1">Mettre en place un nouveau semestre de formation %(acronyme)s</a>
</li>
</ul>"""
@ -658,9 +663,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
)
# <li>(debug) <a class="stdlink" href="check_form_integrity?formation_id=%(formation_id)s">Vérifier cohérence</a></li>
warn, ue_multiples = sco_formsemestre_validation.check_formation_ues(
context, formation_id
)
warn, _ = sco_formsemestre_validation.check_formation_ues(context, formation_id)
H.append(warn)
H.append(context.sco_footer(REQUEST))
@ -764,7 +767,6 @@ def edit_ue_set_code_apogee(context, id=None, value=None, REQUEST=None):
ues = context.do_ue_list(args={"ue_id": ue_id})
if not ues:
return "ue invalide"
ue = ues[0]
do_ue_edit(
context,
@ -773,13 +775,13 @@ def edit_ue_set_code_apogee(context, id=None, value=None, REQUEST=None):
dont_invalidate_cache=False,
)
if not value:
value = APO_MISSING_CODE_STR
value = scu.APO_MISSING_CODE_STR
return value
# ---- Table recap formation
def formation_table_recap(context, formation_id, format="html", REQUEST=None):
""""""
"""Table recapitulant formation."""
F = context.formation_list(args={"formation_id": formation_id})
if not F:
raise ScoValueError("invalid formation_id")
@ -845,7 +847,9 @@ def formation_table_recap(context, formation_id, format="html", REQUEST=None):
columns_ids=columns_ids,
rows=T,
titles=titles,
origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "",
origin="Généré par %s le " % scu.VERSION.SCONAME
+ scu.timedate_human_repr()
+ "",
caption=title,
html_caption=title,
html_class="table_leftalign",

View File

@ -36,8 +36,10 @@ XXX incompatible avec les ics HyperPlanning Paris 13 (était pour GPU).
import urllib2
import traceback
import icalendar
import pprint
from sco_utils import *
import sco_utils as scu
from notes_log import log
import sco_formsemestre
import sco_groups
import sco_groups_view
@ -84,10 +86,10 @@ def formsemestre_load_ics(context, sem):
return cal
def formsemestre_edt_groups_used(context, sem):
"""L'ensemble des groupes EDT utilisés dans l'emplois du temps publié"""
cal = formsemestre_load_ics(context, sem)
return {e["X-GROUP-ID"].decode("utf8") for e in events}
# def formsemestre_edt_groups_used(context, sem):
# """L'ensemble des groupes EDT utilisés dans l'emploi du temps publié"""
# cal = formsemestre_load_ics(context, sem)
# return {e["X-GROUP-ID"].decode("utf8") for e in events}
def get_edt_transcodage_groups(context, formsemestre_id):
@ -147,7 +149,7 @@ def group_edt_json(context, group_id, start="", end="", REQUEST=None):
}
J.append(d)
return sendJSON(REQUEST, J)
return scu.sendJSON(REQUEST, J)
"""XXX

326
sco_entreprises.py Normal file
View File

@ -0,0 +1,326 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""Fonctions sur les entreprises
"""
# codes anciens déplacés de ZEntreprise
import datetime
import sco_utils as scu
from notesdb import ScoDocCursor, EditableTable, DateISOtoDMY, DateDMYtoISO
def _format_nom(nom):
"formatte nom (filtre en entree db) d'une entreprise"
if not nom:
return nom
nom = nom.decode(scu.SCO_ENCODING)
return (nom[0].upper() + nom[1:]).encode(scu.SCO_ENCODING)
class EntreprisesEditor(EditableTable):
def delete(self, cnx, oid):
"delete correspondants and contacts, then self"
# first, delete all correspondants and contacts
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute(
"delete from entreprise_contact where entreprise_id=%(entreprise_id)s",
{"entreprise_id": oid},
)
cursor.execute(
"delete from entreprise_correspondant where entreprise_id=%(entreprise_id)s",
{"entreprise_id": oid},
)
cnx.commit()
EditableTable.delete(self, cnx, oid)
def list(
self,
cnx,
args={},
operator="and",
test="=",
sortkey=None,
sort_on_contact=False,
context=None,
limit="",
offset="",
):
# list, then sort on date of last contact
R = EditableTable.list(
self,
cnx,
args=args,
operator=operator,
test=test,
sortkey=sortkey,
limit=limit,
offset=offset,
)
if sort_on_contact:
for r in R:
c = do_entreprise_contact_list(
context,
args={"entreprise_id": r["entreprise_id"]},
disable_formatting=True,
)
if c:
r["date"] = max([x["date"] or datetime.date.min for x in c])
else:
r["date"] = datetime.date.min
# sort
R.sort(lambda r1, r2: cmp(r2["date"], r1["date"]))
for r in R:
r["date"] = DateISOtoDMY(r["date"])
return R
def list_by_etud(
self, cnx, args={}, sort_on_contact=False, disable_formatting=False
):
"cherche rentreprise ayant eu contact avec etudiant"
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute(
"select E.*, I.nom as etud_nom, I.prenom as etud_prenom, C.date from entreprises E, entreprise_contact C, identite I where C.entreprise_id = E.entreprise_id and C.etudid = I.etudid and I.nom ~* %(etud_nom)s ORDER BY E.nom",
args,
)
_, res = [x[0] for x in cursor.description], cursor.dictfetchall()
R = []
for r in res:
r["etud_prenom"] = r["etud_prenom"] or ""
d = {}
for key in r:
v = r[key]
# format value
if not disable_formatting and self.output_formators.has_key(key):
v = self.output_formators[key](v)
d[key] = v
R.append(d)
# sort
if sort_on_contact:
R.sort(
lambda r1, r2: cmp(
r2["date"] or datetime.date.min, r1["date"] or datetime.date.min
)
)
for r in R:
r["date"] = DateISOtoDMY(r["date"] or datetime.date.min)
return R
_entreprisesEditor = EntreprisesEditor(
"entreprises",
"entreprise_id",
(
"entreprise_id",
"nom",
"adresse",
"ville",
"codepostal",
"pays",
"contact_origine",
"secteur",
"privee",
"localisation",
"qualite_relation",
"plus10salaries",
"note",
"date_creation",
),
sortkey="nom",
input_formators={"nom": _format_nom},
)
# ----------- Correspondants
_entreprise_correspEditor = EditableTable(
"entreprise_correspondant",
"entreprise_corresp_id",
(
"entreprise_corresp_id",
"entreprise_id",
"civilite",
"nom",
"prenom",
"fonction",
"phone1",
"phone2",
"mobile",
"fax",
"mail1",
"mail2",
"note",
),
sortkey="nom",
)
# ----------- Contacts
_entreprise_contactEditor = EditableTable(
"entreprise_contact",
"entreprise_contact_id",
(
"entreprise_contact_id",
"date",
"type_contact",
"entreprise_id",
"entreprise_corresp_id",
"etudid",
"description",
"enseignant",
),
sortkey="date",
output_formators={"date": DateISOtoDMY},
input_formators={"date": DateDMYtoISO},
)
def do_entreprise_create(context, args):
"entreprise_create"
cnx = context.GetDBConnexion()
r = _entreprisesEditor.create(cnx, args)
return r
def do_entreprise_delete(context, oid):
"entreprise_delete"
cnx = context.GetDBConnexion()
_entreprisesEditor.delete(cnx, oid)
def do_entreprise_list(context, **kw):
"entreprise_list"
cnx = context.GetDBConnexion()
kw["context"] = context
return _entreprisesEditor.list(cnx, **kw)
def do_entreprise_list_by_etud(context, **kw):
"entreprise_list_by_etud"
cnx = context.GetDBConnexion()
return _entreprisesEditor.list_by_etud(cnx, **kw)
def do_entreprise_edit(context, *args, **kw):
"entreprise_edit"
cnx = context.GetDBConnexion()
_entreprisesEditor.edit(cnx, *args, **kw)
def do_entreprise_correspondant_create(context, args):
"entreprise_correspondant_create"
cnx = context.GetDBConnexion()
r = _entreprise_correspEditor.create(cnx, args)
return r
def do_entreprise_correspondant_delete(context, oid):
"entreprise_correspondant_delete"
cnx = context.GetDBConnexion()
_entreprise_correspEditor.delete(cnx, oid)
def do_entreprise_correspondant_list(context, **kw):
"entreprise_correspondant_list"
cnx = context.GetDBConnexion()
return _entreprise_correspEditor.list(cnx, **kw)
def do_entreprise_correspondant_edit(context, *args, **kw):
"entreprise_correspondant_edit"
cnx = context.GetDBConnexion()
_entreprise_correspEditor.edit(cnx, *args, **kw)
def do_entreprise_correspondant_listnames(context, args={}):
"-> liste des noms des correspondants (pour affichage menu)"
C = do_entreprise_correspondant_list(context, args=args)
return [(x["prenom"] + " " + x["nom"], str(x["entreprise_corresp_id"])) for x in C]
def do_entreprise_contact_delete(context, oid):
"entreprise_contact_delete"
cnx = context.GetDBConnexion()
_entreprise_contactEditor.delete(cnx, oid)
def do_entreprise_contact_list(context, **kw):
"entreprise_contact_list"
cnx = context.GetDBConnexion()
return _entreprise_contactEditor.list(cnx, **kw)
def do_entreprise_contact_edit(context, *args, **kw):
"entreprise_contact_edit"
cnx = context.GetDBConnexion()
_entreprise_contactEditor.edit(cnx, *args, **kw)
def do_entreprise_contact_create(context, args):
"entreprise_contact_create"
cnx = context.GetDBConnexion()
r = _entreprise_contactEditor.create(cnx, args)
return r
def do_entreprise_check_etudiant(context, etudiant):
"""Si etudiant est vide, ou un ETUDID valide, ou un nom unique,
retourne (1, ETUDID).
Sinon, retourne (0, 'message explicatif')
"""
etudiant = etudiant.strip().translate(
None, "'()"
) # suppress parens and quote from name
if not etudiant:
return 1, None
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute(
"select etudid, nom, prenom from identite where upper(nom) ~ upper(%(etudiant)s) or etudid=%(etudiant)s",
{"etudiant": etudiant},
)
r = cursor.fetchall()
if len(r) < 1:
return 0, 'Aucun etudiant ne correspond à "%s"' % etudiant
elif len(r) > 10:
return (
0,
"<b>%d etudiants</b> correspondent à ce nom (utilisez le code)" % len(r),
)
elif len(r) > 1:
e = ['<ul class="entreprise_etud_list">']
for x in r:
e.append(
"<li>%s %s (code %s)</li>"
% (scu.strupper(x[1]), x[2] or "", x[0].strip())
)
e.append("</ul>")
return (
0,
"Les étudiants suivants correspondent: préciser le nom complet ou le code\n"
+ "\n".join(e),
)
else: # une seule reponse !
return 1, r[0][0].strip()

View File

@ -75,7 +75,9 @@
"""
from sco_utils import *
import re
import sco_utils as scu
from notes_log import log
import sco_formsemestre
import notes_table
@ -83,6 +85,7 @@ import sco_groups
import sco_groups_view
import sco_archives
import sco_apogee_csv
from sco_exceptions import ScoValueError
class ApoCSVArchiver(sco_archives.BaseArchiver):
@ -110,7 +113,7 @@ def apo_csv_store(context, csv_data, annee_scolaire, sem_id):
"""
# sanity check
filesize = len(csv_data)
if filesize < 10 or filesize > CONFIG.ETUD_MAX_FILE_SIZE:
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
raise ScoValueError("Fichier csv de taille invalide ! (%d)" % filesize)
if not annee_scolaire:

View File

@ -31,7 +31,7 @@
from cStringIO import StringIO
from zipfile import ZipFile
from sco_utils import *
import sco_utils as scu
from notes_log import log
import sco_formsemestre
import sco_formsemestre_status
@ -43,6 +43,7 @@ import sco_apogee_csv
import sco_portal_apogee
from sco_apogee_csv import APO_PORTAL_ENCODING, APO_INPUT_ENCODING
import sco_archives
from sco_exceptions import ScoValueError
def apo_semset_maq_status(
@ -176,8 +177,8 @@ def apo_semset_maq_status(
H.append("""<li>Il y a plusieurs années scolaires !</li>""")
if nips_no_sco: # seulement un warning
url_list = (
"view_apo_etuds?semset_id=%s&amp;title=Etudiants%%20presents%%20dans%%20maquettes%%20Apogee%%20mais%%20pas%%20dans%%20les%%20semestres%%20ScoDoc:&amp;nips=%s"
% (semset_id, "&amp;nips=".join(nips_no_sco))
"view_apo_etuds?semset_id=%s&title=Etudiants%%20presents%%20dans%%20maquettes%%20Apogee%%20mais%%20pas%%20dans%%20les%%20semestres%%20ScoDoc:&nips=%s"
% (semset_id, "&nips=".join(nips_no_sco))
)
H.append(
'<li class="apo_csv_warning">Attention: il y a <a href="%s">%d étudiant(s)</a> dans les maquettes Apogée chargées non inscrit(s) dans ce semestre ScoDoc;</li>'
@ -195,8 +196,8 @@ def apo_semset_maq_status(
if nips_no_apo:
url_list = (
"view_scodoc_etuds?semset_id=%s&amp;title=Etudiants%%20ScoDoc%%20non%%20listés%%20dans%%20les%%20maquettes%%20Apogée%%20chargées&amp;nips=%s"
% (semset_id, "&amp;nips=".join(nips_no_apo))
"view_scodoc_etuds?semset_id=%s&title=Etudiants%%20ScoDoc%%20non%%20listés%%20dans%%20les%%20maquettes%%20Apogée%%20chargées&nips=%s"
% (semset_id, "&nips=".join(nips_no_apo))
)
H.append(
'<li><a href="%s">%d étudiants</a> dans ce semestre non présents dans les maquettes Apogée chargées</li>'
@ -205,8 +206,8 @@ def apo_semset_maq_status(
if nips_no_sco: # seulement un warning
url_list = (
"view_apo_etuds?semset_id=%s&amp;title=Etudiants%%20presents%%20dans%%20maquettes%%20Apogee%%20mais%%20pas%%20dans%%20les%%20semestres%%20ScoDoc:&amp;nips=%s"
% (semset_id, "&amp;nips=".join(nips_no_sco))
"view_apo_etuds?semset_id=%s&title=Etudiants%%20presents%%20dans%%20maquettes%%20Apogee%%20mais%%20pas%%20dans%%20les%%20semestres%%20ScoDoc:&nips=%s"
% (semset_id, "&nips=".join(nips_no_sco))
)
H.append(
'<li class="apo_csv_warning">Attention: il reste <a href="%s">%d étudiants</a> dans les maquettes Apogée chargées mais pas inscrits dans ce semestre ScoDoc</li>'
@ -215,8 +216,8 @@ def apo_semset_maq_status(
if apo_dups:
url_list = (
"view_apo_etuds?semset_id=%s&amp;title=Doublons%%20Apogee&amp;nips=%s"
% (semset_id, "&amp;nips=".join(apo_dups))
"view_apo_etuds?semset_id=%s&title=Doublons%%20Apogee&nips=%s"
% (semset_id, "&nips=".join(apo_dups))
)
H.append(
'<li><a href="%s">%d étudiants</a> présents dans les <em>plusieurs</em> maquettes Apogée chargées</li>'
@ -447,7 +448,7 @@ def table_apo_csv_list(context, semset, REQUEST=None):
)
t["_filename_target"] = view_link
t["_etape_apo_target"] = view_link
t["suppress"] = icontag(
t["suppress"] = scu.icontag(
"delete_small_img", border="0", alt="supprimer", title="Supprimer"
)
t["_suppress_target"] = "view_apo_csv_delete?etape_apo=%s&semset_id=%s" % (
@ -484,8 +485,8 @@ def view_apo_etuds(context, semset_id, title="", nips=[], format="html", REQUEST
if not semset_id:
raise ValueError("invalid null semset_id")
semset = sco_semset.SemSet(context, semset_id=semset_id)
annee_scolaire = semset["annee_scolaire"]
sem_id = semset["sem_id"]
# annee_scolaire = semset["annee_scolaire"]
# sem_id = semset["sem_id"]
if nips and type(nips) != type([]):
nips = [nips]
@ -672,7 +673,7 @@ def view_apo_csv_delete(
context, etape_apo, semset["annee_scolaire"], semset["sem_id"]
)
sco_etape_apogee.apo_csv_delete(context, info["archive_id"])
return REQUEST.RESPONSE.redirect(dest_url + "&amp;head_message=Archive%20supprimée")
return REQUEST.RESPONSE.redirect(dest_url + "&head_message=Archive%20supprimée")
def view_apo_csv(context, etape_apo="", semset_id="", format="html", REQUEST=None):
@ -686,7 +687,7 @@ def view_apo_csv(context, etape_apo="", semset_id="", format="html", REQUEST=Non
sem_id = semset["sem_id"]
csv_data = sco_etape_apogee.apo_csv_get(context, etape_apo, annee_scolaire, sem_id)
if format == "raw":
return sendCSVFile(REQUEST, csv_data, etape_apo + ".txt")
return scu.sendCSVFile(REQUEST, csv_data, etape_apo + ".txt")
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
(
@ -838,7 +839,7 @@ def apo_csv_export_results(
+ "-%s-" % periode
+ "-".join(etapes_apo)
)
basename = sco_archives.sanitize_filename(unescape_html(basename))
basename = scu.sanitize_filename(scu.unescape_html(basename))
dest_zip.close()
size = data.tell()

View File

@ -90,6 +90,7 @@ l'inscrition de semestres décalés (S1 en septembre, ...).
Le filtrage s'effctue sur la date et non plus sur la parité du semestre (1-3/2-4).
"""
import json
from sco_portal_apogee import get_inscrits_etape
from notes_log import log
@ -540,7 +541,7 @@ class EtapeBilan:
ind_col,
comptage,
"",
self.titres[ind_col].replace("<br/>", " / "),
json.dumps(self.titres[ind_col].replace("<br/>", " / "))[1:-1],
)
elif ind_col == COL_CUMUL:
javascript = "doFiltrage(%s, %s, '.%s', '*', '%s', '%s', '%s');" % (
@ -548,7 +549,7 @@ class EtapeBilan:
self.all_cols_str,
ind_row,
" (%d étudiants)" % count,
self.titres[ind_row],
json.dumps(self.titres[ind_row])[1:-1],
"",
)
else:
@ -558,10 +559,10 @@ class EtapeBilan:
ind_row,
ind_col,
comptage,
self.titres[ind_row],
self.titres[ind_col].replace("<br/>", " / "),
json.dumps(self.titres[ind_row])[1:-1],
json.dumps(self.titres[ind_col].replace("<br/>", " / "))[1:-1],
)
return "<a href='#synthese' onclick=\"%s\">%d</a>" % (javascript, count)
return '<a href="#synthese" onclick="%s">%d</a>' % (javascript, count)
def _diagtable(self):
H = []

View File

@ -41,9 +41,11 @@ from gen_tables import GenTable
from TrivialFormulator import TrivialFormulator
import sco_news
import sco_formsemestre
import sco_moduleimpl
import sco_groups
import ZAbsences
import sco_abs
import sco_evaluations
import sco_saisie_notes
# --------------------------------------------------------------------
#
@ -101,12 +103,14 @@ def do_evaluation_delete(context, REQUEST, evaluation_id):
context._evaluationEditor.delete(cnx, evaluation_id)
# inval cache pour ce semestre
M = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0]
context._inval_cache(formsemestre_id=M["formsemestre_id"]) # > eval delete
# news
mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
mod["moduleimpl_id"] = M["moduleimpl_id"]
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
mod["url"] = (
context.NotesURL() + "/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
)
sco_news.add(
context,
REQUEST,
@ -131,11 +135,6 @@ def do_evaluation_etat(
à ce module ont des notes)
evalattente est vrai s'il ne manque que des notes en attente
"""
# global _DEE_TOT
# t0=time.time()
# if evaluation_id == 'GEAEVAL82883':
# log('do_evaluation_etat: evaluation_id=%s partition_id=%s sfp=%s' % (evaluation_id, partition_id, select_first_partition))
nb_inscrits = len(
sco_groups.do_evaluation_listeetuds_groups(
context, evaluation_id, getallstudents=True
@ -165,7 +164,7 @@ def do_evaluation_etat(
last_modif = None
# ---- Liste des groupes complets et incomplets
E = context.do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=E["moduleimpl_id"])[0]
Mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
is_malus = Mod["module_type"] == scu.MODULE_MALUS # True si module de malus
formsemestre_id = M["formsemestre_id"]
@ -182,7 +181,9 @@ def do_evaluation_etat(
# (pour avoir l'etat et le groupe) et aussi les inscriptions
# au module (pour gerer les modules optionnels correctement)
insem = context.do_formsemestre_inscription_listinscrits(formsemestre_id)
insmod = context.do_moduleimpl_inscription_list(moduleimpl_id=E["moduleimpl_id"])
insmod = sco_moduleimpl.do_moduleimpl_inscription_list(
context, moduleimpl_id=E["moduleimpl_id"]
)
insmodset = set([x["etudid"] for x in insmod])
# retire de insem ceux qui ne sont pas inscrits au module
ins = [i for i in insem if i["etudid"] in insmodset]
@ -228,6 +229,7 @@ def do_evaluation_etat(
if (
(TotalNbMissing > 0)
and (E["evaluation_type"] != scu.EVALUATION_RATTRAPAGE)
and (E["evaluation_type"] != scu.EVALUATION_SESSION2)
and not is_malus
):
complete = False
@ -241,6 +243,9 @@ def do_evaluation_etat(
evalattente = True
else:
evalattente = False
# mais ne met pas en attente les evals immediates sans aucune notes:
if E["publish_incomplete"] != "0" and nb_notes == 0:
evalattente = False
# Calcul moyenne dans chaque groupe de TD
gr_moyennes = [] # group : {moy,median, nb_notes}
@ -264,11 +269,6 @@ def do_evaluation_etat(
}
)
gr_moyennes.sort(key=operator.itemgetter("group_name"))
# log('gr_moyennes=%s' % gr_moyennes)
# _DEE_TOT += (time.time() - t0)
# log('%s\t_DEE_TOT=%f' % (evaluation_id, _DEE_TOT))
# if evaluation_id == 'GEAEVAL82883':
# logCallStack()
# retourne mapping
return {
@ -451,7 +451,9 @@ def formsemestre_evaluations_cal(context, formsemestre_id, REQUEST=None):
if not e["jour"]:
continue
day = e["jour"].strftime("%Y-%m-%d")
mod = context.do_moduleimpl_withmodule_list(moduleimpl_id=e["moduleimpl_id"])[0]
mod = sco_moduleimpl.do_moduleimpl_withmodule_list(
context, moduleimpl_id=e["moduleimpl_id"]
)[0]
txt = mod["module"]["code"] or mod["module"]["abbrev"] or "eval"
if e["heure_debut"]:
debut = e["heure_debut"].strftime("%Hh%M")
@ -487,7 +489,7 @@ def formsemestre_evaluations_cal(context, formsemestre_id, REQUEST=None):
if day > today:
e[2] = color_futur
CalHTML = ZAbsences.YearTable(
CalHTML = sco_abs.YearTable(
context.Absences, year, events=events.values(), halfday=False, pad_width=None
)
@ -524,10 +526,10 @@ def evaluation_date_first_completion(context, evaluation_id):
# (pour avoir l'etat et le groupe) et aussi les inscriptions
# au module (pour gerer les modules optionnels correctement)
# E = context.do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
# M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
# M = sco_moduleimpl.do_moduleimpl_list(context,moduleimpl_id=E["moduleimpl_id"])[0]
# formsemestre_id = M["formsemestre_id"]
# insem = context.do_formsemestre_inscription_listinscrits(formsemestre_id)
# insmod = context.do_moduleimpl_inscription_list(moduleimpl_id=E["moduleimpl_id"])
# insmod = sco_moduleimpl.do_moduleimpl_inscription_list(context,moduleimpl_id=E["moduleimpl_id"])
# insmodset = set([x["etudid"] for x in insmod])
# retire de insem ceux qui ne sont pas inscrits au module
# ins = [i for i in insem if i["etudid"] in insmodset]
@ -566,7 +568,9 @@ def formsemestre_evaluations_delai_correction(
evals = nt.get_sem_evaluation_etat_list()
T = []
for e in evals:
M = context.do_moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0]
M = sco_moduleimpl.do_moduleimpl_list(
context, moduleimpl_id=e["moduleimpl_id"]
)[0]
Mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
if (e["evaluation_type"] != scu.EVALUATION_NORMALE) or (
Mod["module_type"] == scu.MODULE_MALUS
@ -732,14 +736,14 @@ def evaluation_describe(context, evaluation_id="", edit_in_place=True, REQUEST=N
"""
E = context.do_evaluation_list({"evaluation_id": evaluation_id})[0]
moduleimpl_id = E["moduleimpl_id"]
M = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0]
Mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
formsemestre_id = M["formsemestre_id"]
u = context.Users.user_info(M["responsable_id"])
resp = u["prenomnom"]
nomcomplet = u["nomcomplet"]
can_edit = context.can_edit_notes(
REQUEST.AUTHENTICATED_USER, moduleimpl_id, allow_ens=False
can_edit = sco_saisie_notes.can_edit_notes(
context, REQUEST.AUTHENTICATED_USER, moduleimpl_id, allow_ens=False
)
link = (
@ -810,7 +814,9 @@ def evaluation_create_form(
the_eval = context.do_evaluation_list({"evaluation_id": evaluation_id})[0]
moduleimpl_id = the_eval["moduleimpl_id"]
#
M = context.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
M = sco_moduleimpl.do_moduleimpl_withmodule_list(
context, moduleimpl_id=moduleimpl_id
)[0]
is_malus = M["module"]["module_type"] == scu.MODULE_MALUS # True si module de malus
formsemestre_id = M["formsemestre_id"]
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
@ -886,12 +892,20 @@ def evaluation_create_form(
notes, en sus des moyennes de modules. Attention, cette option n'empêche pas la publication sur
les bulletins en version "longue" (la note est donc visible par les étudiants sur le portail).
</p><p class="help">
La modalité "rattrapage" permet de définir une évaluation dont les notes remplaceront les moyennes du modules
si elles sont meilleures que celles calculées. Dans ce cas, le coefficient est ignoré, et toutes les notes n'ont
Les modalités "rattrapage" et "deuxième session" définissent des évaluations prises en compte de
façon spéciale: </p>
<ul>
<li>les notes d'une évaluation de "rattrapage" remplaceront les moyennes du module
<em>si elles sont meilleures que celles calculées</em>.</li>
<li>les notes de "deuxième session" remplacent, lorsqu'elles sont saisies, la moyenne de l'étudiant
à ce module, même si la note de deuxième session est plus faible.</li>
</ul>
<p class="help">
Dans ces deux cas, le coefficient est ignoré, et toutes les notes n'ont
pas besoin d'être rentrées.
</p>
<p class="help">
Les évaluations des modules de type "malus" sont spéciales: le coefficient n'est pas utilisé.
Par ailleurs, les évaluations des modules de type "malus" sont toujours spéciales: le coefficient n'est pas utilisé.
Les notes de malus sont toujours comprises entre -20 et 20. Les points sont soustraits à la moyenne
de l'UE à laquelle appartient le module malus (si la note est négative, la moyenne est donc augmentée).
</p>
@ -1014,9 +1028,17 @@ def evaluation_create_form(
{
"input_type": "menu",
"title": "Modalité",
"allowed_values": (scu.EVALUATION_NORMALE, scu.EVALUATION_RATTRAPAGE),
"allowed_values": (
scu.EVALUATION_NORMALE,
scu.EVALUATION_RATTRAPAGE,
scu.EVALUATION_SESSION2,
),
"type": "int",
"labels": ("Normale", "Rattrapage"),
"labels": (
"Normale",
"Rattrapage (remplace si meilleure note)",
"Deuxième session (remplace toujours)",
),
},
),
]

View File

@ -33,8 +33,9 @@ from pyExcelerator import *
from notes_log import log
from scolog import logdb
from sco_exceptions import *
from sco_utils import *
from sco_exceptions import ScoValueError
import sco_utils as scu
import notesdb
import time, datetime
@ -57,9 +58,11 @@ def sendExcelFile(REQUEST, data, filename):
(on ne doit rien avoir émis avant, car ici sont générés les entetes)
"""
filename = (
unescape_html(suppress_accents(filename)).replace("&", "").replace(" ", "_")
scu.unescape_html(scu.suppress_accents(filename))
.replace("&", "")
.replace(" ", "_")
)
REQUEST.RESPONSE.setHeader("content-type", XLS_MIMETYPE)
REQUEST.RESPONSE.setHeader("content-type", scu.XLS_MIMETYPE)
REQUEST.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename
)
@ -216,7 +219,7 @@ class ScoExcelSheet:
sauvegarde = True
else:
sauvegarde = False
ws0 = wb.add_sheet(self.sheet_name.decode(SCO_ENCODING))
ws0 = wb.add_sheet(self.sheet_name.decode(scu.SCO_ENCODING))
li = 0
for l in self.cells:
co = 0
@ -225,7 +228,7 @@ class ScoExcelSheet:
if type(c) == LongType:
c = int(c) # assume all ScoDoc longs fits in int !
elif type(c) not in (IntType, FloatType):
c = str(c).decode(SCO_ENCODING)
c = str(c).decode(scu.SCO_ENCODING)
ws0.write(li, co, c, self.get_cell_style(li, co))
co += 1
@ -240,14 +243,14 @@ def Excel_SimpleTable(titles=[], lines=[[]], SheetName="feuille", titlesStyles=[
"""Export simple type 'CSV': 1ere ligne en gras, le reste tel quel"""
# XXX devrait maintenant utiliser ScoExcelSheet
wb = Workbook()
ws0 = wb.add_sheet(SheetName.decode(SCO_ENCODING))
ws0 = wb.add_sheet(SheetName.decode(scu.SCO_ENCODING))
if not titlesStyles:
style = Excel_MakeStyle(bold=True)
titlesStyles = [style] * len(titles)
# ligne de titres
col = 0
for it in titles:
ws0.write(0, col, it.decode(SCO_ENCODING), titlesStyles[col])
ws0.write(0, col, it.decode(scu.SCO_ENCODING), titlesStyles[col])
col += 1
# suite
default_style = Excel_MakeStyle()
@ -262,7 +265,7 @@ def Excel_SimpleTable(titles=[], lines=[[]], SheetName="feuille", titlesStyles=[
if type(it) == LongType:
it = int(it) # assume all ScoDoc longs fits in int !
elif type(it) not in (IntType, FloatType):
it = str(it).decode(SCO_ENCODING)
it = str(it).decode(scu.SCO_ENCODING)
cell_style = text_style
ws0.write(li, col, it, cell_style)
col += 1
@ -279,7 +282,7 @@ def Excel_feuille_saisie(E, titreannee, description, lines):
"""
SheetName = "Saisie notes"
wb = Workbook()
ws0 = wb.add_sheet(SheetName.decode(SCO_ENCODING))
ws0 = wb.add_sheet(SheetName.decode(scu.SCO_ENCODING))
# ajuste largeurs colonnes (unite inconnue, empirique)
ws0.col(0).width = 400 # codes
ws0.col(1).width = 6000 # noms
@ -366,10 +369,14 @@ def Excel_feuille_saisie(E, titreannee, description, lines):
ws0.write(li, 0, u"Ne pas modifier les cases en mauve !", style_expl)
li += 1
# Nom du semestre
ws0.write(li, 0, unescape_html(titreannee).decode(SCO_ENCODING), style_titres)
ws0.write(
li, 0, scu.unescape_html(titreannee).decode(scu.SCO_ENCODING), style_titres
)
li += 1
# description evaluation
ws0.write(li, 0, unescape_html(description).decode(SCO_ENCODING), style_titres)
ws0.write(
li, 0, scu.unescape_html(description).decode(scu.SCO_ENCODING), style_titres
)
li += 1
ws0.write(
li, 0, u"Evaluation du %s (coef. %g)" % (E["jour"], E["coefficient"]), style
@ -387,7 +394,7 @@ def Excel_feuille_saisie(E, titreannee, description, lines):
for line in lines:
li += 1
st = style_nom
ws0.write(li, 0, ("!" + line[0]).decode(SCO_ENCODING), style_ro) # code
ws0.write(li, 0, ("!" + line[0]).decode(scu.SCO_ENCODING), style_ro) # code
if line[3] != "I":
st = style_dem
if line[3] == "D": # demissionnaire
@ -396,15 +403,15 @@ def Excel_feuille_saisie(E, titreannee, description, lines):
s = line[3] # etat autre
else:
s = line[4] # groupes TD/TP/...
ws0.write(li, 1, line[1].decode(SCO_ENCODING), st)
ws0.write(li, 2, line[2].decode(SCO_ENCODING), st)
ws0.write(li, 3, s.decode(SCO_ENCODING), st)
ws0.write(li, 1, line[1].decode(scu.SCO_ENCODING), st)
ws0.write(li, 2, line[2].decode(scu.SCO_ENCODING), st)
ws0.write(li, 3, s.decode(scu.SCO_ENCODING), st)
try:
val = float(line[5])
except:
val = line[5].decode(SCO_ENCODING)
val = line[5].decode(scu.SCO_ENCODING)
ws0.write(li, 4, val, style_notes) # note
ws0.write(li, 5, line[6].decode(SCO_ENCODING), style_comment) # comment
ws0.write(li, 5, line[6].decode(scu.SCO_ENCODING), style_comment) # comment
# explication en bas
li += 2
ws0.write(li, 1, u"Code notes", style_titres)
@ -426,7 +433,7 @@ def Excel_to_list(data, convert_to_string=str): # we may need 'encoding' argume
convert_to_string is a conversion function applied to all non-string values (ie numbers)
"""
try:
P = parse_xls("", SCO_ENCODING, doc=data)
P = parse_xls("", scu.SCO_ENCODING, doc=data)
except:
log("Excel_to_list: failure to import document")
open("/tmp/last_scodoc_import_failure.xls", "w").write(data)
@ -443,7 +450,7 @@ def Excel_to_list(data, convert_to_string=str): # we may need 'encoding' argume
diag.append("Attention: n'utilise que la première feuille du classeur !")
# fill matrix
sheet_name, values = P[0]
sheet_name = sheet_name.encode(SCO_ENCODING, "backslashreplace")
sheet_name = sheet_name.encode(scu.SCO_ENCODING, "backslashreplace")
if not values:
diag.append("Aucune valeur trouvée dans le classeur !")
return diag, None
@ -454,13 +461,13 @@ def Excel_to_list(data, convert_to_string=str): # we may need 'encoding' argume
nbcols = max(cols) + 1
nbrows = max(rows) + 1
M = []
for i in range(nbrows):
for _ in range(nbrows):
M.append([""] * nbcols)
for row_idx, col_idx in indexes:
v = values[(row_idx, col_idx)]
if isinstance(v, unicode):
v = v.encode(SCO_ENCODING, "backslashreplace")
v = v.encode(scu.SCO_ENCODING, "backslashreplace")
elif convert_to_string:
v = convert_to_string(v)
M[row_idx][col_idx] = v
@ -485,7 +492,7 @@ def Excel_feuille_listeappel(
formsemestre_id = sem["formsemestre_id"]
SheetName = "Liste " + groupname
wb = Workbook()
ws0 = wb.add_sheet(SheetName.decode(SCO_ENCODING))
ws0 = wb.add_sheet(SheetName.decode(scu.SCO_ENCODING))
font1 = Font()
font1.name = "Arial"
@ -569,7 +576,7 @@ def Excel_feuille_listeappel(
sem["date_debut"],
sem["date_fin"],
)
).decode(SCO_ENCODING),
).decode(scu.SCO_ENCODING),
style2,
)
# ligne 2
@ -578,7 +585,7 @@ def Excel_feuille_listeappel(
# ligne 3
li += 1
ws0.write(li, 1, u"Enseignant :", style2)
ws0.write(li, 5, ("Groupe %s" % groupname).decode(SCO_ENCODING), style3)
ws0.write(li, 5, ("Groupe %s" % groupname).decode(scu.SCO_ENCODING), style3)
# Avertissement pour ne pas confondre avec listes notes
ws0.write(
li + 1, 2, u"Ne pas utiliser cette feuille pour saisir les notes !", style1i
@ -590,7 +597,9 @@ def Excel_feuille_listeappel(
co = 2
for partition in partitions:
if partition["partition_name"]:
ws0.write(li, co, partition["partition_name"].decode(SCO_ENCODING), style3)
ws0.write(
li, co, partition["partition_name"].decode(scu.SCO_ENCODING), style3
)
co += 1
if with_codes:
coc = co
@ -607,7 +616,11 @@ def Excel_feuille_listeappel(
li += 1
ws0.write(li, 0, n, style1b)
nomprenom = (
t["sexe"] + " " + t["nom"] + " " + strcapitalize(strlower(t["prenom"]))
t["civilite_str"]
+ " "
+ t["nom"]
+ " "
+ scu.strcapitalize(scu.strlower(t["prenom"]))
)
style_nom = style2t3
if with_paiement:
@ -618,31 +631,31 @@ def Excel_feuille_listeappel(
elif not paie:
nomprenom += " (non paiement)"
style_nom = style2t3bold
ws0.write(li, 1, nomprenom.decode(SCO_ENCODING), style_nom)
ws0.write(li, 1, nomprenom.decode(scu.SCO_ENCODING), style_nom)
co = 2
for partition in partitions:
if partition["partition_name"]:
ws0.write(
li,
co,
t.get(partition["partition_id"], "").decode(SCO_ENCODING),
t.get(partition["partition_id"], "").decode(scu.SCO_ENCODING),
style2t3,
)
co += 1
if with_codes:
ws0.write(li, coc, t["etudid"].decode(SCO_ENCODING), style2t3)
ws0.write(li, coc, t["etudid"].decode(scu.SCO_ENCODING), style2t3)
if t["code_nip"]:
code_nip = t["code_nip"].decode(SCO_ENCODING)
code_nip = t["code_nip"].decode(scu.SCO_ENCODING)
else:
code_nip = u""
ws0.write(li, coc + 1, code_nip, style2t3)
if t["code_ine"]:
code_ine = t["code_ine"].decode(SCO_ENCODING)
code_ine = t["code_ine"].decode(scu.SCO_ENCODING)
else:
code_ine = u""
ws0.write(li, coc + 2, code_ine, style2t3)
if t["etath"]:
etath = t["etath"].decode(SCO_ENCODING)
etath = t["etath"].decode(scu.SCO_ENCODING)
else:
etath = u""
ws0.write(li, co, etath, style2b) # etat
@ -654,7 +667,7 @@ def Excel_feuille_listeappel(
dt = time.strftime("%d/%m/%Y à %Hh%M")
if server_name:
dt += " sur " + server_name
ws0.write(li, 1, ("Liste éditée le " + dt).decode(SCO_ENCODING), style1i)
ws0.write(li, 1, ("Liste éditée le " + dt).decode(scu.SCO_ENCODING), style1i)
#
ws0.col(0).width = 850
ws0.col(1).width = 9000

View File

@ -27,7 +27,11 @@
"""Export d'une table avec les résultats de tous les étudiants
"""
from types import ListType
import notesdb as ndb
import sco_utils as scu
from notes_log import log
import scolars
import sco_bac
import sco_formsemestre
@ -35,11 +39,10 @@ import sco_parcours_dut
import sco_codes_parcours
from sco_codes_parcours import NO_SEMESTRE_ID
import sco_excel
from notesdb import *
from sco_utils import *
from gen_tables import GenTable
import sco_pvjury
import html_sco_header
import VERSION
def _build_results_table(context, start_date=None, end_date=None, types_parcours=[]):
@ -96,9 +99,9 @@ def _build_results_table(context, start_date=None, end_date=None, types_parcours
rows=rows,
titles=titles,
columns_ids=columns_ids,
filename=make_filename("scodoc-results-%s-%s" % (start_date, end_date)),
filename=scu.make_filename("scodoc-results-%s-%s" % (start_date, end_date)),
caption="Résultats ScoDoc de %s à %s" % (start_date, end_date),
origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "",
origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "",
html_class="table_leftalign",
html_sortable=True,
preferences=context.get_preferences(),
@ -119,7 +122,7 @@ def _build_results_list(context, dpv_by_sem, etuds_infos):
"code_nip": "NIP",
"nom": "Nom",
"prenom": "Prénom",
"sexe": "Civ.",
"civilite_str": "Civ.",
"nom_usuel": "Nom usuel",
"bac": "Bac",
"parcours": "Parcours",
@ -130,7 +133,7 @@ def _build_results_list(context, dpv_by_sem, etuds_infos):
"periode",
"sid",
"code_nip",
"sexe",
"civilite_str",
"nom",
# 'nom_usuel', # inutile ?
"prenom",
@ -168,7 +171,7 @@ def _build_results_list(context, dpv_by_sem, etuds_infos):
"nom": etud["nom"],
"nom_usuel": etud["nom_usuel"],
"prenom": etud["prenom"],
"sexe": etud["sexe"],
"civilite_str": etud["civilite_str"],
"_nom_target": "%s/ficheEtud?etudid=%s" % (context.ScoURL(), etudid),
"_nom_td_attrs": 'id="%s" class="etudinfo"' % etudid,
"bac": bac.abbrev(),
@ -189,7 +192,7 @@ def _build_results_list(context, dpv_by_sem, etuds_infos):
code = "-"
r[sem["semestre_id"]] = code
r["periode"] = sem["periode"]
r["anneescolaire"] = annee_scolaire_debut(
r["anneescolaire"] = scu.annee_scolaire_debut(
int(sem["annee_debut"]), sem["mois_debut_ord"]
)
r["sid"] = "{} {} {}".format(
@ -202,7 +205,7 @@ def _build_results_list(context, dpv_by_sem, etuds_infos):
def get_set_formsemestre_id_dates(context, start_date, end_date):
"""Ensemble des formsemestre_id entre ces dates"""
s = SimpleDictFetch(
s = ndb.SimpleDictFetch(
context,
"SELECT formsemestre_id FROM notes_formsemestre WHERE date_debut >= %(start_date)s AND date_fin <= %(end_date)s",
{"start_date": start_date, "end_date": end_date},
@ -224,20 +227,20 @@ def scodoc_table_results(
if not isinstance(types_parcours, ListType):
types_parcours = [types_parcours]
if start_date:
start_date_iso = DateDMYtoISO(start_date)
start_date_iso = ndb.DateDMYtoISO(start_date)
if end_date:
end_date_iso = DateDMYtoISO(end_date)
end_date_iso = ndb.DateDMYtoISO(end_date)
types_parcours = [int(x) for x in types_parcours if x]
if start_date and end_date:
tab, semlist = _build_results_table(
context, start_date_iso, end_date_iso, types_parcours
)
tab.base_url = "%s?start_date=%s&amp;end_date=%s&amp;types_parcours=%s" % (
tab.base_url = "%s?start_date=%s&end_date=%s&types_parcours=%s" % (
REQUEST.URL0,
start_date,
end_date,
"&amp;types_parcours=".join([str(x) for s in types_parcours]),
"&types_parcours=".join([str(x) for x in types_parcours]),
)
if format != "html":
return tab.make_page(

View File

@ -27,17 +27,19 @@
"""Recherche d'étudiants
"""
from sco_utils import *
from types import ListType
import xml.dom.minidom
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
from gen_tables import GenTable
import scolars
import sco_formsemestre
import sco_groups
from sco_permissions import ScoView
def form_search_etud(
@ -56,7 +58,7 @@ def form_search_etud(
H.append(
"""<form action="search_etud_in_dept" method="POST">
<b>%s</b>
<input type="text" name="expnom" width=12 value="">
<input type="text" name="expnom" id="searchEtud" width="12" spellcheck="false" value="">
<input type="submit" value="Chercher">
<br/>(entrer une partie du nom)
"""
@ -94,71 +96,52 @@ def form_search_etud(
return "\n".join(H)
# was chercheEtud()
def search_etud_in_dept(
context,
expnom=None,
dest_url="ficheEtud",
parameters={},
parameters_keys="",
add_headers=True, # complete page
title=None,
REQUEST=None,
):
"""Page recherche d'un etudiant
expnom est un regexp sur le nom ou un code_nip
dest_url est la page sur laquelle on sera redirigé après choix
parameters spécifie des arguments additionnels à passer à l'URL (en plus de etudid)
def search_etud_in_dept(context, expnom="", REQUEST=None):
"""Page recherche d'un etudiant.
Affiche la fiche de l'étudiant, ou, si la recherche donne plusieurs résultats, la liste des étudianst
correspondants.
Appelée par boite de recherche barre latérale gauche.
Args:
expnom: string, regexp sur le nom ou un code_nip ou un etudid
"""
if type(expnom) == ListType:
expnom = expnom[0]
q = []
if parameters:
for param in parameters.keys():
q.append("%s=%s" % (param, parameters[param]))
elif parameters_keys:
for key in parameters_keys.split(","):
v = REQUEST.form.get(key, False)
if v:
q.append("%s=%s" % (key, v))
query_string = "&amp;".join(q)
no_side_bar = True
H = []
if title:
H.append("<h2>%s</h2>" % title)
if is_valid_code_nip(expnom):
dest_url = "ficheEtud"
if len(expnom) > 1:
etuds = context.getEtudInfo(filled=1, etudid=expnom, REQUEST=REQUEST)
if len(etuds) != 1:
if scu.is_valid_code_nip(expnom):
etuds = search_etuds_infos(context, code_nip=expnom, REQUEST=REQUEST)
elif expnom:
else:
etuds = search_etuds_infos(context, expnom=expnom, REQUEST=REQUEST)
else:
etuds = []
etuds = [] # si expnom est trop court, n'affiche rien
if len(etuds) == 1:
# va directement a la destination
return REQUEST.RESPONSE.redirect(
dest_url + "?etudid=%s&amp;" % etuds[0]["etudid"] + query_string
)
return context.ficheEtud(etudid=etuds[0]["etudid"], REQUEST=REQUEST)
if len(etuds) > 0:
# Choix dans la liste des résultats:
H.append(
H = [
context.sco_header(
page_title="Recherche d'un étudiant",
no_side_bar=True,
init_qtip=True,
javascripts=["js/etud_info.js"],
REQUEST=REQUEST,
),
"""<h2>%d résultats pour "%s": choisissez un étudiant:</h2>"""
% (len(etuds), expnom)
)
H.append(
% (len(etuds), expnom),
form_search_etud(
context,
dest_url=dest_url,
parameters=parameters,
parameters_keys=parameters_keys,
REQUEST=REQUEST,
title="Autre recherche",
)
)
),
]
if len(etuds) > 0:
# Choix dans la liste des résultats:
for e in etuds:
target = dest_url + "?etudid=%s&amp;" % e["etudid"] + query_string
target = dest_url + "?etudid=%s&" % e["etudid"]
e["_nomprenom_target"] = target
e["inscription_target"] = target
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
@ -183,34 +166,16 @@ def search_etud_in_dept(
form_search_etud(
context,
dest_url=dest_url,
parameters=parameters,
parameters_keys=parameters_keys,
REQUEST=REQUEST,
title="Autre recherche",
)
)
else:
H.append('<h2 style="color: red;">Aucun résultat pour "%s".</h2>' % expnom)
add_headers = True
no_side_bar = False
H.append(
"""<p class="help">La recherche porte sur tout ou partie du NOM ou du NIP de l'étudiant</p>"""
)
if add_headers:
return (
context.sco_header(
REQUEST,
page_title="Choix d'un étudiant",
init_qtip=True,
javascripts=["js/etud_info.js"],
no_side_bar=no_side_bar,
)
+ "\n".join(H)
+ context.sco_footer(REQUEST)
)
else:
return "\n".join(H)
return "\n".join(H) + context.sco_footer(REQUEST)
# Was chercheEtudsInfo()
@ -218,10 +183,10 @@ def search_etuds_infos(context, expnom=None, code_nip=None, REQUEST=None):
"""recherche les étudiants correspondants à expnom ou au code_nip
et ramene liste de mappings utilisables en DTML.
"""
may_be_nip = is_valid_code_nip(expnom)
may_be_nip = scu.is_valid_code_nip(expnom)
cnx = context.GetDBConnexion()
if expnom and not may_be_nip:
expnom = strupper(expnom) # les noms dans la BD sont en uppercase
expnom = scu.strupper(expnom) # les noms dans la BD sont en uppercase
etuds = scolars.etudident_list(cnx, args={"nom": expnom}, test="~")
else:
code_nip = code_nip or expnom
@ -238,20 +203,19 @@ def search_etud_by_name(context, term, REQUEST=None):
Accepte aussi un début de code NIP (au moins 6 caractères)
Renvoie une liste de nom en JSON
"""
cnx = context.GetDBConnexion()
may_be_nip = is_valid_code_nip(term)
# term = strupper(term) # conserve les accents
may_be_nip = scu.is_valid_code_nip(term)
# term = scu.strupper(term) # conserve les accents
term = term.upper()
if (
not ALPHANUM_EXP.match(
term.decode(SCO_ENCODING)
not scu.ALPHANUM_EXP.match(
term.decode(scu.SCO_ENCODING)
) # n'autorise pas les caractères spéciaux
and not may_be_nip
):
data = []
else:
if may_be_nip:
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT nom, prenom, code_nip FROM identite WHERE code_nip LIKE %(beginning)s ORDER BY nom",
{"beginning": term + "%"},
@ -265,21 +229,21 @@ def search_etud_by_name(context, term, REQUEST=None):
for x in r
]
else:
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT nom, prenom FROM identite WHERE nom LIKE %(beginning)s ORDER BY nom",
"SELECT etudid, nom, prenom FROM identite WHERE nom LIKE %(beginning)s ORDER BY nom",
{"beginning": term + "%"},
)
data = [
{
"label": "%s %s" % (x["nom"], scolars.format_prenom(x["prenom"])),
"value": x["nom"],
"value": x["etudid"],
}
for x in r
]
# log(data)
return sendJSON(REQUEST, data)
return scu.sendJSON(REQUEST, data)
# ---------- Recherche sur plusieurs département
@ -293,7 +257,7 @@ def form_search_etud_in_accessible_depts(context, REQUEST):
return ""
return """<form action="table_etud_in_accessible_depts" method="POST">
<b>Chercher étudiant:</b>
<input type="text" name="expnom" width=12 value="">
<input type="text" name="expnom" width="12" spellcheck="false" value="">
<input type="submit" value="Chercher">
<br/>(entrer une partie du nom ou le code NIP, cherche dans tous les départements autorisés)
"""
@ -312,7 +276,7 @@ def search_etud_in_accessible_depts(context, expnom=None, code_nip=None, REQUEST
"""
result = []
accessible_depts = []
deptList = context.list_depts() # definis dans Zope
deptList = context._list_depts() # definis dans Zope
for dept in deptList:
# log('%s searching %s' % (str(REQUEST.AUTHENTICATED_USER),dept))
if can_view_dept(dept, REQUEST):
@ -383,9 +347,9 @@ def search_inscr_etud_by_nip(context, code_nip, REQUEST=None, format="json"):
Seuls les départements accessibles par l'utilisateur sont cherchés.
Renvoie une liste des inscriptions de l'étudiants dans tout ScoDoc:
code_nip, nom, prenom, sexe, dept, formsemestre_id, date_debut_sem, date_fin_sem
code_nip, nom, prenom, civilite_str, dept, formsemestre_id, date_debut_sem, date_fin_sem
"""
result, depts = search_etud_in_accessible_depts(
result, _ = search_etud_in_accessible_depts(
context, code_nip=code_nip, REQUEST=REQUEST
)
@ -400,7 +364,7 @@ def search_inscr_etud_by_nip(context, code_nip, REQUEST=None, format="json"):
"dept": DeptId,
"etudid": e["etudid"],
"code_nip": e["code_nip"],
"sexe": e["sexe"],
"civilite_str": e["civilite_str"],
"nom": e["nom"],
"prenom": e["prenom"],
"formsemestre_id": sem["formsemestre_id"],
@ -413,7 +377,7 @@ def search_inscr_etud_by_nip(context, code_nip, REQUEST=None, format="json"):
"dept",
"etudid",
"code_nip",
"sexe",
"civilite_str",
"nom",
"prenom",
"formsemestre_id",

View File

@ -28,16 +28,19 @@
"""Import / Export de formations
"""
from operator import itemgetter
from sco_utils import *
import xml.dom.minidom
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
import sco_codes_parcours
import sco_formsemestre
import sco_tag_module
from gen_tables import GenTable
from sco_exceptions import ScoValueError
from sco_permissions import ScoChangeFormation
import VERSION
def formation_export(
@ -81,7 +84,7 @@ def formation_export(
if mod["ects"] is None:
del mod["ects"]
return sendResult(
return scu.sendResult(
REQUEST, F, name="formation", format=format, force_outer_xml_tag=False
)
@ -113,7 +116,7 @@ def XMLToDicts(element, encoding):
def formation_import_xml(
context, REQUEST, doc, import_tags=True, encoding=SCO_ENCODING
context, REQUEST, doc, import_tags=True, encoding=scu.SCO_ENCODING
):
"""Create a formation from XML representation
(format dumped by formation_export( format='xml' ))
@ -131,11 +134,11 @@ def formation_import_xml(
F = D[1]
F_quoted = F.copy()
log("F=%s" % F)
quote_dict(F_quoted)
ndb.quote_dict(F_quoted)
log("F_quoted=%s" % F_quoted)
# find new version number
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
log(
"select max(version) from notes_formations where acronyme=%(acronyme)s and titre=%(titre)s"
% F_quoted
@ -204,13 +207,13 @@ def formation_list_table(context, formation_id=None, args={}, REQUEST=None):
"""
formations = context.formation_list(formation_id=formation_id, args=args)
title = "Programmes pédagogiques"
lockicon = icontag(
lockicon = scu.icontag(
"lock32_img", title="Comporte des semestres verrouillés", border="0"
)
suppricon = icontag(
suppricon = scu.icontag(
"delete_small_img", border="0", alt="supprimer", title="Supprimer"
)
editicon = icontag(
editicon = scu.icontag(
"edit_img", border="0", alt="modifier", title="Modifier titres et code"
)
@ -226,6 +229,7 @@ def formation_list_table(context, formation_id=None, args={}, REQUEST=None):
f["parcours_name"] = ""
f["_titre_target"] = "ue_list?formation_id=%(formation_id)s" % f
f["_titre_link_class"] = "stdlink"
f["_titre_id"] = "titre_%s" % f["acronyme"]
# Ajoute les semestres associés à chaque formation:
f["sems"] = sco_formsemestre.do_formsemestre_list(
context, args={"formation_id": f["formation_id"]}
@ -238,8 +242,8 @@ def formation_list_table(context, formation_id=None, args={}, REQUEST=None):
for s in f["sems"]
]
+ [
'<a class="stdlink" href="formsemestre_createwithmodules?formation_id=%(formation_id)s&amp;semestre_id=1">ajouter</a>'
% f
'<a class="stdlink" id="addSemestre_%s" href="formsemestre_createwithmodules?formation_id=%s&semestre_id=1">ajouter</a>'
% (f["acronyme"],f["formation_id"])
]
)
if f["sems"]:
@ -256,15 +260,15 @@ def formation_list_table(context, formation_id=None, args={}, REQUEST=None):
but_locked = '<span class="but_placeholder"></span>'
if editable and not locked:
but_suppr = (
'<a class="stdlink" href="formation_delete?formation_id=%s">%s</a>'
% (f["formation_id"], suppricon)
'<a class="stdlink" id="delete_Formation_%s" href="formation_delete?formation_id=%s">%s</a>'
% (f["acronyme"],f["formation_id"], suppricon)
)
else:
but_suppr = '<span class="but_placeholder"></span>'
if editable:
but_edit = (
'<a class="stdlink" href="formation_edit?formation_id=%s">%s</a>'
% (f["formation_id"], editicon)
'<a class="stdlink" id="edit_Formation_%s" href="formation_edit?formation_id=%s">%s</a>'
% (f["acronyme"],f["formation_id"], editicon)
)
else:
but_edit = '<span class="but_placeholder"></span>'
@ -302,7 +306,7 @@ def formation_list_table(context, formation_id=None, args={}, REQUEST=None):
columns_ids=columns_ids,
rows=formations,
titles=titles,
origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "",
origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "",
caption=title,
html_caption=title,
table_id="formation_list_table",

View File

@ -27,16 +27,18 @@
"""Operations de base sur les formsemestres
"""
import time
from sco_utils import *
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
from gen_tables import GenTable
import sco_codes_parcours
from sco_codes_parcours import NO_SEMESTRE_ID
from sco_exceptions import ScoValueError
_formsemestreEditor = EditableTable(
_formsemestreEditor = ndb.EditableTable(
"notes_formsemestre",
"formsemestre_id",
(
@ -60,16 +62,16 @@ _formsemestreEditor = EditableTable(
),
sortkey="date_debut",
output_formators={
"date_debut": DateISOtoDMY,
"date_fin": DateISOtoDMY,
"date_debut": ndb.DateISOtoDMY,
"date_fin": ndb.DateISOtoDMY,
"gestion_compensation": str,
"gestion_semestrielle": str,
"etat": str,
"bul_hide_xml": str,
},
input_formators={
"date_debut": DateDMYtoISO,
"date_fin": DateDMYtoISO,
"date_debut": ndb.DateDMYtoISO,
"date_fin": ndb.DateDMYtoISO,
"gestion_compensation": int,
"gestion_semestrielle": int,
"etat": int,
@ -144,9 +146,9 @@ def formsemestre_enrich(context, sem):
sem["semestre_id"],
) # eg "DUT Informatique semestre 2"
sem["dateord"] = DateDMYtoISO(sem["date_debut"])
sem["date_debut_iso"] = DateDMYtoISO(sem["date_debut"])
sem["date_fin_iso"] = DateDMYtoISO(sem["date_fin"])
sem["dateord"] = ndb.DateDMYtoISO(sem["date_debut"])
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
sem["date_fin_iso"] = ndb.DateDMYtoISO(sem["date_fin"])
try:
mois_debut, annee_debut = sem["date_debut"].split("/")[1:]
except:
@ -162,7 +164,9 @@ def formsemestre_enrich(context, sem):
sem["annee"] = annee_debut
# 2007 ou 2007-2008:
sem["anneescolaire"] = annee_scolaire_repr(int(annee_debut), sem["mois_debut_ord"])
sem["anneescolaire"] = scu.annee_scolaire_repr(
int(annee_debut), sem["mois_debut_ord"]
)
# La période: considère comme "S1" (ou S3) les débuts en aout-sept-octobre
# devrait sans doute pouvoir etre changé...
if sem["mois_debut_ord"] >= 8 and sem["mois_debut_ord"] <= 10:
@ -213,6 +217,8 @@ def etapes_apo_str(etapes):
def do_formsemestre_edit(context, sem, cnx=None, **kw):
"""Apply modifications to formsemestre.
Update etapes and resps. Invalidate cache."""
if not cnx:
cnx = context.GetDBConnexion()
@ -227,7 +233,7 @@ def read_formsemestre_responsables(context, formsemestre_id):
"""recupere liste des responsables de ce semestre
:returns: liste de chaines
"""
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT responsable_id FROM notes_formsemestre_responsables WHERE formsemestre_id = %(formsemestre_id)s",
{"formsemestre_id": formsemestre_id},
@ -239,11 +245,58 @@ def write_formsemestre_responsables(context, sem):
return _write_formsemestre_aux(context, sem, "responsables", "responsable_id")
# ---------------------- Coefs des UE
_formsemestre_uecoef_editor = ndb.EditableTable(
"notes_formsemestre_uecoef",
"formsemestre_uecoef_id",
("formsemestre_uecoef_id", "formsemestre_id", "ue_id", "coefficient"),
)
formsemestre_uecoef_create = _formsemestre_uecoef_editor.create
formsemestre_uecoef_edit = _formsemestre_uecoef_editor.edit
formsemestre_uecoef_list = _formsemestre_uecoef_editor.list
formsemestre_uecoef_delete = _formsemestre_uecoef_editor.delete
def do_formsemestre_uecoef_edit_or_create(context, cnx, formsemestre_id, ue_id, coef):
"modify or create the coef"
coefs = formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id}
)
if coefs:
formsemestre_uecoef_edit(
cnx,
args={
"formsemestre_uecoef_id": coefs[0]["formsemestre_uecoef_id"],
"coefficient": coef,
},
)
else:
formsemestre_uecoef_create(
cnx,
args={
"formsemestre_id": formsemestre_id,
"ue_id": ue_id,
"coefficient": coef,
},
)
def do_formsemestre_uecoef_delete(context, cnx, formsemestre_id, ue_id):
"delete coef for this (ue,sem)"
coefs = formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id}
)
if coefs:
formsemestre_uecoef_delete(cnx, coefs[0]["formsemestre_uecoef_id"])
def read_formsemestre_etapes(context, formsemestre_id):
"""recupere liste des codes etapes associés à ce semestre
:returns: liste d'instance de ApoEtapeVDI
"""
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT etape_apo FROM notes_formsemestre_etapes WHERE formsemestre_id = %(formsemestre_id)s",
{"formsemestre_id": formsemestre_id},
@ -259,17 +312,20 @@ def _write_formsemestre_aux(context, sem, fieldname, valuename):
"""fieldname: 'etapes' ou 'responsables'
valuename: 'etape_apo' ou 'responsable_id'
"""
if not "etapes" in sem:
if not fieldname in sem:
return
# uniquify
values = set([str(x) for x in sem[fieldname]])
cnx = context.GetDBConnexion(autocommit=False)
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
tablename = "notes_formsemestre_" + fieldname
try:
cursor.execute(
"DELETE from " + tablename + " where formsemestre_id = %(formsemestre_id)s",
{"formsemestre_id": sem["formsemestre_id"]},
)
for item in sem[fieldname]:
for item in values:
if item:
cursor.execute(
"INSERT INTO "
@ -279,7 +335,7 @@ def _write_formsemestre_aux(context, sem, fieldname, valuename):
+ ") VALUES (%(formsemestre_id)s, %("
+ valuename
+ ")s)",
{"formsemestre_id": sem["formsemestre_id"], valuename: str(item)},
{"formsemestre_id": sem["formsemestre_id"], valuename: item},
)
except:
log("Warning: exception in write_formsemestre_aux !")
@ -374,12 +430,12 @@ def sem_in_semestre_scolaire(context, sem, year=False, saison=0, REQUEST=None):
si saison non spécifiée: année complète
pivot de saison au 1er décembre
XXX TODO: la période (ici appelée "saison" devrait être éditable
manuellement dans le formsemestre_edit afin de couvrir els cas particulier
comme un semestre S2 qui commecerait en décembre... voire novembre.
manuellement dans le formsemestre_edit afin de couvrir les cas particulier
comme un semestre S2 qui commencerait en décembre... voire novembre.
)
"""
if not year:
year = AnneeScolaire(REQUEST)
year = scu.AnneeScolaire(REQUEST)
# est-on dans la même année universitaire ?
if sem["mois_debut_ord"] > 7:
if sem["annee_debut"] != str(year):
@ -403,7 +459,7 @@ def sem_in_annee_scolaire(context, sem, year=False, REQUEST=None):
Si annee non specifiée, année scolaire courante
"""
if not year:
year = AnneeScolaire(REQUEST)
year = scu.AnneeScolaire(REQUEST)
return ((sem["annee_debut"] == str(year)) and (sem["mois_debut_ord"] > 7)) or (
(sem["annee_debut"] == str(year + 1)) and (sem["mois_debut_ord"] <= 7)
)
@ -429,9 +485,17 @@ def sem_une_annee(context, sem):
return debut == fin
def sem_est_courant(context, sem):
"""Vrai si la date actuelle (now) est dans le semestre (les dates de début et fin sont incluses)"""
now = time.strftime("%Y-%m-%d")
debut = ndb.DateDMYtoISO(sem["date_debut"])
fin = ndb.DateDMYtoISO(sem["date_fin"])
return (debut <= now) and (now <= fin)
def scodoc_get_all_unlocked_sems(context):
"""Liste de tous les semestres non verrouillés de tous les départements"""
depts = context.list_depts()
depts = context._list_depts()
semdepts = []
for dept in depts:
semdepts += [
@ -537,7 +601,7 @@ def view_formsemestre_by_etape(context, etape_apo=None, format="html", REQUEST=N
tab = table_formsemestres(
context,
list_formsemestre_by_etape(
context, etape_apo=etape_apo, annee_scolaire=AnneeScolaire(REQUEST)
context, etape_apo=etape_apo, annee_scolaire=scu.AnneeScolaire(REQUEST)
),
html_title=html_title,
html_next_section="""<form action="view_formsemestre_by_etape">

View File

@ -29,15 +29,15 @@
"""
from sco_utils import *
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
from TrivialFormulator import TrivialFormulator, TF
import sco_formsemestre
import sco_formsemestre_status
import sco_edt_cal
_custommenuEditor = EditableTable(
_custommenuEditor = ndb.EditableTable(
"notes_formsemestre_custommenu",
"custommenu_id",
("custommenu_id", "formsemestre_id", "title", "url", "idx"),
@ -74,12 +74,15 @@ def formsemestre_custommenu_html(context, formsemestre_id, base_url=""):
+ formsemestre_id,
}
)
return sco_formsemestre_status.makeMenu("Liens", menu)
return sco_formsemestre_status.htmlutils.make_menu("Liens", menu)
def formsemestre_custommenu_edit(context, formsemestre_id, REQUEST=None):
"""Dialog to edit the custom menu"""
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
dest_url = (
context.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
H = [
context.html_sem_header(REQUEST, "Modification du menu du semestre ", sem),
"""<p class="help">Ce menu, spécifique à chaque semestre, peut être utilisé pour placer des liens vers vos applications préférées.</p>
@ -126,7 +129,7 @@ def formsemestre_custommenu_edit(context, formsemestre_id, REQUEST=None):
if tf[0] == 0:
return "\n".join(H) + "\n" + tf[1] + context.sco_footer(REQUEST)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(dest_url)
else:
# form submission
cnx = context.GetDBConnexion()
@ -153,6 +156,4 @@ def formsemestre_custommenu_edit(context, formsemestre_id, REQUEST=None):
"url": tf[2]["url_" + custommenu_id],
},
)
REQUEST.RESPONSE.redirect(
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
return REQUEST.RESPONSE.redirect(dest_url)

View File

@ -28,8 +28,8 @@
"""Form choix modules / responsables et creation formsemestre
"""
from notesdb import *
from sco_utils import *
import notesdb as ndb
import sco_utils as scu
import sco_groups
from notes_log import log
from TrivialFormulator import TrivialFormulator, TF
@ -41,7 +41,10 @@ import sco_codes_parcours
import sco_compute_moy
import sco_modalites
import sco_formsemestre
import sco_moduleimpl
from sco_formsemestre import ApoEtapeVDI
from sco_permissions import ScoImplement
from sco_exceptions import AccessDenied, ScoValueError
def _default_sem_title(F):
@ -69,9 +72,7 @@ def formsemestre_createwithmodules(context, REQUEST=None):
def formsemestre_editwithmodules(context, REQUEST, formsemestre_id):
"""Page modification semestre"""
# portage from dtml
authuser = REQUEST.AUTHENTICATED_USER
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
F = context.formation_list(args={"formation_id": sem["formation_id"]})[0]
H = [
context.html_sem_header(
REQUEST,
@ -85,7 +86,7 @@ def formsemestre_editwithmodules(context, REQUEST, formsemestre_id):
if sem["etat"] != "1":
H.append(
"""<p>%s<b>Ce semestre est verrouillé.</b></p>"""
% icontag("lock_img", border="0", title="Semestre verrouillé")
% scu.icontag("lock_img", border="0", title="Semestre verrouillé")
)
else:
H.append(do_formsemestre_createwithmodules(context, REQUEST=REQUEST, edit=1))
@ -155,7 +156,9 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
# if REQUEST.form.get('tf-submitted',False) and not REQUEST.form.has_key('inscrire_etudslist'):
# REQUEST.form['inscrire_etudslist'] = []
# add associated modules to tf-checked
ams = context.do_moduleimpl_list(formsemestre_id=formsemestre_id)
ams = sco_moduleimpl.do_moduleimpl_list(
context, formsemestre_id=formsemestre_id
)
sem_module_ids = set([x["module_id"] for x in ams])
initvalues["tf-checked"] = [x["module_id"] for x in ams]
for x in ams:
@ -173,7 +176,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
# Liste des ID de semestres
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute("select semestre_id from notes_semestres")
semestre_id_list = [str(x[0]) for x in cursor.fetchall()]
semestre_id_labels = []
@ -341,7 +344,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
# fallback: code etape libre
mf = mf_manual
for n in range(1, EDIT_NB_ETAPES + 1):
for n in range(1, scu.EDIT_NB_ETAPES + 1):
mf["title"] = "Etape Apogée (%d)" % n
modform.append(("etape_apo" + str(n), mf.copy()))
modform.append(
@ -636,10 +639,10 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
msg = ""
if tf[0] == 1:
# check dates
if DateDMYtoISO(tf[2]["date_debut"]) > DateDMYtoISO(tf[2]["date_fin"]):
if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
if context.get_preference("always_require_apo_sem_codes") and not any(
[tf[2]["etape_apo" + str(n)] for n in range(0, EDIT_NB_ETAPES + 1)]
[tf[2]["etape_apo" + str(n)] for n in range(0, scu.EDIT_NB_ETAPES + 1)]
):
msg = '<ul class="tf-msg"><li class="tf-msg">Code étape Apogée manquant</li></ul>'
@ -690,7 +693,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
start_i = 0
else:
start_i = 1
for n in range(start_i, EDIT_NB_ETAPES + 1):
for n in range(start_i, scu.EDIT_NB_ETAPES + 1):
tf[2]["etapes"].append(
ApoEtapeVDI(
etape=tf[2]["etape_apo" + str(n)], vdi=tf[2]["vdi_apo" + str(n)]
@ -706,9 +709,9 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
"formsemestre_id": formsemestre_id,
"responsable_id": tf[2][module_id],
}
mid = context.do_moduleimpl_create(modargs)
_ = sco_moduleimpl.do_moduleimpl_create(context, modargs)
return REQUEST.RESPONSE.redirect(
"formsemestre_status?formsemestre_id=%s&amp;head_message=Nouveau%%20semestre%%20créé"
"formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé"
% formsemestre_id
)
else:
@ -720,7 +723,9 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
# nouveaux modules
checkedmods = tf[2]["tf-checked"]
sco_formsemestre.do_formsemestre_edit(context, tf[2])
ams = context.do_moduleimpl_list(formsemestre_id=formsemestre_id)
ams = sco_moduleimpl.do_moduleimpl_list(
context, formsemestre_id=formsemestre_id
)
existingmods = [x["module_id"] for x in ams]
mods_tocreate = [x for x in checkedmods if not x in existingmods]
# modules a existants a modifier
@ -735,7 +740,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
"formsemestre_id": formsemestre_id,
"responsable_id": tf[2][module_id],
}
moduleimpl_id = context.do_moduleimpl_create(modargs)
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(context, modargs)
mod = context.do_module_list({"module_id": module_id})[0]
msg += ["création de %s (%s)" % (mod["code"], mod["titre"])]
# INSCRIPTIONS DES ETUDIANTS
@ -753,8 +758,12 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
"inscription module:module_id=%s,moduleimpl_id=%s: %s"
% (module_id, moduleimpl_id, etudids)
)
context.do_moduleimpl_inscrit_etuds(
moduleimpl_id, formsemestre_id, etudids, REQUEST=REQUEST
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
context,
moduleimpl_id,
formsemestre_id,
etudids,
REQUEST=REQUEST,
)
msg += [
"inscription de %d étudiants au module %s"
@ -771,8 +780,8 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
)
msg += diag
for module_id in mods_toedit:
moduleimpl_id = context.do_moduleimpl_list(
formsemestre_id=formsemestre_id, module_id=module_id
moduleimpl_id = sco_moduleimpl.do_moduleimpl_list(
context, formsemestre_id=formsemestre_id, module_id=module_id
)[0]["moduleimpl_id"]
modargs = {
"moduleimpl_id": moduleimpl_id,
@ -780,7 +789,9 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
"formsemestre_id": formsemestre_id,
"responsable_id": tf[2][module_id],
}
context.do_moduleimpl_edit(modargs, formsemestre_id=formsemestre_id)
sco_moduleimpl.do_moduleimpl_edit(
context, modargs, formsemestre_id=formsemestre_id
)
mod = context.do_module_list({"module_id": module_id})[0]
if msg:
@ -800,7 +811,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
return msg_html
else:
return REQUEST.RESPONSE.redirect(
"formsemestre_status?formsemestre_id=%s&amp;head_message=Semestre modifié"
"formsemestre_status?formsemestre_id=%s&head_message=Semestre modifié"
% formsemestre_id
)
@ -814,8 +825,8 @@ def formsemestre_delete_moduleimpls(context, formsemestre_id, module_ids_to_del)
msg = []
for module_id in module_ids_to_del:
# get id
moduleimpl_id = context.do_moduleimpl_list(
formsemestre_id=formsemestre_id, module_id=module_id
moduleimpl_id = sco_moduleimpl.do_moduleimpl_list(
context, formsemestre_id=formsemestre_id, module_id=module_id
)[0]["moduleimpl_id"]
mod = context.do_module_list({"module_id": module_id})[0]
# Evaluations dans ce module ?
@ -828,7 +839,9 @@ def formsemestre_delete_moduleimpls(context, formsemestre_id, module_ids_to_del)
ok = False
else:
msg += ["suppression de %s (%s)" % (mod["code"], mod["titre"])]
context.do_moduleimpl_delete(moduleimpl_id, formsemestre_id=formsemestre_id)
sco_moduleimpl.do_moduleimpl_delete(
context, moduleimpl_id, formsemestre_id=formsemestre_id
)
return ok, msg
@ -837,7 +850,6 @@ def formsemestre_clone(context, formsemestre_id, REQUEST=None):
"""
Formulaire clonage d'un semestre
"""
authuser = REQUEST.AUTHENTICATED_USER
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
# Liste des enseignants avec forme pour affichage / saisie avec suggestion
userlist = context.Users.get_userlist()
@ -933,7 +945,7 @@ def formsemestre_clone(context, formsemestre_id, REQUEST=None):
msg = ""
if tf[0] == 1:
# check dates
if DateDMYtoISO(tf[2]["date_debut"]) > DateDMYtoISO(tf[2]["date_fin"]):
if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
if tf[0] == 0 or msg:
return "".join(H) + msg + tf[1] + context.sco_footer(REQUEST)
@ -953,7 +965,7 @@ def formsemestre_clone(context, formsemestre_id, REQUEST=None):
REQUEST=REQUEST,
)
return REQUEST.RESPONSE.redirect(
"formsemestre_status?formsemestre_id=%s&amp;head_message=Nouveau%%20semestre%%20créé"
"formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé"
% new_formsemestre_id
)
@ -984,17 +996,21 @@ def do_formsemestre_clone(
formsemestre_id = context.do_formsemestre_create(args, REQUEST)
log("created formsemestre %s" % formsemestre_id)
# 2- create moduleimpls
mods_orig = context.do_moduleimpl_list(formsemestre_id=orig_formsemestre_id)
mods_orig = sco_moduleimpl.do_moduleimpl_list(
context, formsemestre_id=orig_formsemestre_id
)
for mod_orig in mods_orig:
args = mod_orig.copy()
args["formsemestre_id"] = formsemestre_id
mid = context.do_moduleimpl_create(args)
mid = sco_moduleimpl.do_moduleimpl_create(context, args)
# copy notes_modules_enseignants
ens = context.do_ens_list(args={"moduleimpl_id": mod_orig["moduleimpl_id"]})
ens = sco_moduleimpl.do_ens_list(
context, args={"moduleimpl_id": mod_orig["moduleimpl_id"]}
)
for e in ens:
args = e.copy()
args["moduleimpl_id"] = mid
context.do_ens_create(args)
sco_moduleimpl.do_ens_create(context, args)
# optionally, copy evaluations
if clone_evaluations:
evals = context.do_evaluation_list(
@ -1004,14 +1020,16 @@ def do_formsemestre_clone(
args = e.copy()
del args["jour"] # erase date
args["moduleimpl_id"] = mid
evaluation_id = context.do_evaluation_create(REQUEST=REQUEST, **args)
_ = context.do_evaluation_create(REQUEST=REQUEST, **args)
# 3- copy uecoefs
objs = formsemestre_uecoef_list(cnx, args={"formsemestre_id": orig_formsemestre_id})
objs = sco_formsemestre.formsemestre_uecoef_list(
cnx, args={"formsemestre_id": orig_formsemestre_id}
)
for obj in objs:
args = obj.copy()
args["formsemestre_id"] = formsemestre_id
c = formsemestre_uecoef_create(cnx, args)
_ = sco_formsemestre.formsemestre_uecoef_create(cnx, args)
# NB: don't copy notes_formsemestre_custommenu (usually specific)
@ -1034,7 +1052,7 @@ def do_formsemestre_clone(
for obj in objs:
args = obj.copy()
args["formsemestre_id"] = formsemestre_id
c = sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, args)
_ = sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, args)
# 5- Copy partitions
if clone_partitions:
@ -1044,7 +1062,6 @@ def do_formsemestre_clone(
for part in sco_groups.get_partitions_list(context, orig_formsemestre_id):
if part["partition_name"] != None:
partname = part["partition_name"]
orig_partition_id = part["partition_id"]
new_partition_id = sco_groups.partition_create(
context,
formsemestre_id,
@ -1064,7 +1081,7 @@ def do_formsemestre_clone(
if newpart["partition_id"] == g[0]:
part_id = g[0]
for group_name in g[1]:
group_id = sco_groups.createGroup(
_ = sco_groups.createGroup(
context, part_id, group_name=group_name, REQUEST=REQUEST
)
@ -1131,7 +1148,7 @@ def formsemestre_associate_new_version(
context, [formsemestre_id] + other_formsemestre_ids, REQUEST=REQUEST
)
return REQUEST.RESPONSE.redirect(
"formsemestre_status?formsemestre_id=%s&amp;head_message=Formation%%20dupliquée"
"formsemestre_status?formsemestre_id=%s&head_message=Formation%%20dupliquée"
% formsemestre_id
)
@ -1177,10 +1194,12 @@ def _reassociate_moduleimpls(
et met à jour les décisions de jury (validations d'UE).
"""
# re-associate moduleimpls to new modules:
modimpls = context.do_moduleimpl_list(formsemestre_id=formsemestre_id)
modimpls = sco_moduleimpl.do_moduleimpl_list(
context, formsemestre_id=formsemestre_id
)
for mod in modimpls:
mod["module_id"] = modules_old2new[mod["module_id"]]
context.do_moduleimpl_edit(mod, formsemestre_id=formsemestre_id, cnx=cnx)
sco_moduleimpl.do_moduleimpl_edit(context, mod, formsemestre_id=formsemestre_id)
# update decisions:
events = scolars.scolar_events_list(cnx, args={"formsemestre_id": formsemestre_id})
for e in events:
@ -1244,7 +1263,11 @@ def formsemestre_delete(context, formsemestre_id, REQUEST=None):
H.append(tf[1])
return "\n".join(H) + context.sco_footer(REQUEST)
elif tf[0] == -1: # cancel
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(
context.NotesURL()
+ "/formsemestre_status?formsemestre_id="
+ formsemestre_id
)
else:
return REQUEST.RESPONSE.redirect(
"formsemestre_delete2?formsemestre_id=" + formsemestre_id
@ -1255,9 +1278,6 @@ def formsemestre_delete2(
context, formsemestre_id, dialog_confirmed=False, REQUEST=None
):
"""Delete a formsemestre (confirmation)"""
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
F = context.formation_list(args={"formation_id": sem["formation_id"]})[0]
H = [context.html_sem_header(REQUEST, "Suppression du semestre", sem)]
# Confirmation dialog
if not dialog_confirmed:
return context.confirmDialog(
@ -1269,14 +1289,16 @@ def formsemestre_delete2(
)
# Bon, s'il le faut...
do_formsemestre_delete(context, formsemestre_id, REQUEST)
return REQUEST.RESPONSE.redirect(REQUEST.URL2 + "?head_message=Semestre%20supprimé")
return REQUEST.RESPONSE.redirect(
context.ScoURL() + "?head_message=Semestre%20supprimé"
)
def formsemestre_has_decisions_or_compensations(context, formsemestre_id):
"""True if decision de jury dans ce semestre
ou bien compensation de ce semestre par d'autre ssemestres.
"""
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT v.* FROM scolar_formsemestre_validation v WHERE v.formsemestre_id = %(formsemestre_id)s OR v.compense_formsemestre_id = %(formsemestre_id)s",
{"formsemestre_id": formsemestre_id},
@ -1297,28 +1319,28 @@ def do_formsemestre_delete(context, formsemestre_id, REQUEST):
# evaluations
evals = context.do_evaluation_list(args={"moduleimpl_id": mod["moduleimpl_id"]})
for e in evals:
SimpleQuery(
ndb.SimpleQuery(
context,
"DELETE FROM notes_notes WHERE evaluation_id=%(evaluation_id)s",
e,
)
SimpleQuery(
ndb.SimpleQuery(
context,
"DELETE FROM notes_notes_log WHERE evaluation_id=%(evaluation_id)s",
e,
)
SimpleQuery(
ndb.SimpleQuery(
context,
"DELETE FROM notes_evaluation WHERE evaluation_id=%(evaluation_id)s",
e,
)
context.get_evaluations_cache().inval_cache(key=e["evaluation_id"])
context.do_moduleimpl_delete(
mod["moduleimpl_id"], formsemestre_id=formsemestre_id
sco_moduleimpl.do_moduleimpl_delete(
context, mod["moduleimpl_id"], formsemestre_id=formsemestre_id
)
# --- Desinscription des etudiants
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
req = "DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=%(formsemestre_id)s"
cursor.execute(req, {"formsemestre_id": formsemestre_id})
# --- Suppression des evenements
@ -1420,7 +1442,7 @@ def formsemestre_change_lock(
args = {"formsemestre_id": formsemestre_id, "etat": etat}
context.do_formsemestre_edit(args)
if REQUEST:
REQUEST.RESPONSE.redirect(
return REQUEST.RESPONSE.redirect(
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
@ -1460,23 +1482,10 @@ def formsemestre_change_publication_bul(
args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat}
context.do_formsemestre_edit(args)
if REQUEST:
REQUEST.RESPONSE.redirect(
return REQUEST.RESPONSE.redirect(
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
# ---------------------- Coefs des UE
_formsemestre_uecoef_editor = EditableTable(
"notes_formsemestre_uecoef",
"formsemestre_uecoef_id",
("formsemestre_uecoef_id", "formsemestre_id", "ue_id", "coefficient"),
)
formsemestre_uecoef_create = _formsemestre_uecoef_editor.create
formsemestre_uecoef_edit = _formsemestre_uecoef_editor.edit
formsemestre_uecoef_list = _formsemestre_uecoef_editor.list
formsemestre_uecoef_delete = _formsemestre_uecoef_editor.delete
return None
def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST=None):
@ -1486,7 +1495,6 @@ def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST=
if not ok:
return err
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
F = context.formation_list(args={"formation_id": sem["formation_id"]})[0]
footer = context.sco_footer(REQUEST)
help = """<p class="help">
@ -1527,7 +1535,7 @@ def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST=
initvalues = {"formsemestre_id": formsemestre_id}
form = [("formsemestre_id", {"input_type": "hidden"})]
for ue in ues:
coefs = formsemestre_uecoef_list(
coefs = sco_formsemestre.formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
)
if coefs:
@ -1564,7 +1572,7 @@ def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST=
msg = []
for ue in ues:
val = tf[2]["ue_" + ue["ue_id"]]
coefs = formsemestre_uecoef_list(
coefs = sco_formsemestre.formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
)
if val == "" or val == "auto":
@ -1594,11 +1602,13 @@ def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST=
# apply modifications
for ue in ue_modified:
do_formsemestre_uecoef_edit_or_create(
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
context, cnx, formsemestre_id, ue["ue_id"], ue["coef"]
)
for ue in ue_deleted:
do_formsemestre_uecoef_delete(context, cnx, formsemestre_id, ue["ue_id"])
sco_formsemestre.do_formsemestre_uecoef_delete(
context, cnx, formsemestre_id, ue["ue_id"]
)
if ue_modified or ue_deleted:
z = ["""<h3>Modification effectuées</h3>"""]
@ -1630,39 +1640,6 @@ def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST=
)
def do_formsemestre_uecoef_edit_or_create(context, cnx, formsemestre_id, ue_id, coef):
"modify or create the coef"
coefs = formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id}
)
if coefs:
formsemestre_uecoef_edit(
cnx,
args={
"formsemestre_uecoef_id": coefs[0]["formsemestre_uecoef_id"],
"coefficient": coef,
},
)
else:
formsemestre_uecoef_create(
cnx,
args={
"formsemestre_id": formsemestre_id,
"ue_id": ue_id,
"coefficient": coef,
},
)
def do_formsemestre_uecoef_delete(context, cnx, formsemestre_id, ue_id):
"delete coef for this (ue,sem)"
coefs = formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id}
)
if coefs:
formsemestre_uecoef_delete(cnx, coefs[0]["formsemestre_uecoef_id"])
# ----- identification externe des sessions (pour SOJA et autres logiciels)
def get_formsemestre_session_id(context, sem, F, parcours):
"""Identifiant de session pour ce semestre
@ -1692,12 +1669,12 @@ def get_formsemestre_session_id(context, sem, F, parcours):
(modalite or "").replace("FAP", "FA").replace("APP", "FA")
) # exception pour code Apprentissage
if sem["semestre_id"] > 0:
decale = sem_decale_str(sem)
decale = scu.sem_decale_str(sem)
semestre_id = "S%d" % sem["semestre_id"] + decale
else:
semestre_id = F["code_specialite"]
annee_sco = str(annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"]))
annee_sco = str(scu.annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"]))
return sanitize_string(
return scu.sanitize_string(
"-".join((ImputationDept, parcours_type, modalite, semestre_id, annee_sco))
)

View File

@ -86,7 +86,7 @@ def formsemestre_ext_create_form(context, etudid, formsemestre_id, REQUEST=None)
<p class="help">
Notez que si un semestre extérieur similaire a déjà été créé pour un autre étudiant,
il est préférable d'utiliser la fonction
"<a href="formsemestre_inscription_with_modules_form?etudid=%s&amp;only_ext=1">
"<a href="formsemestre_inscription_with_modules_form?etudid=%s&only_ext=1">
inscrire à un autre semestre</a>"
</p>
"""
@ -191,7 +191,7 @@ def formsemestre_ext_create_form(context, etudid, formsemestre_id, REQUEST=None)
return "\n".join(H) + "\n" + tf[1] + F
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(
"%s/formsemestre_bulletinetud?formsemestre_id==%s&amp;etudid=%s"
"%s/formsemestre_bulletinetud?formsemestre_id==%s&etudid=%s"
% (context.ScoURL(), formsemestre_id, etudid)
)
else:
@ -436,7 +436,7 @@ def _list_ue_with_coef_and_validations(context, sem, etudid):
ue_list = context.do_ue_list({"formation_id": sem["formation_id"]})
for ue in ue_list:
# add coefficient
uecoef = sco_formsemestre_edit.formsemestre_uecoef_list(
uecoef = sco_formsemestre.formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
)
if uecoef:

View File

@ -28,8 +28,11 @@
"""Opérations d'inscriptions aux semestres et modules
"""
from sco_utils import ScoEtudInscrit, log, ScoValueError, DictDefault
from sco_utils import UE_STANDARD, UE_SPORT, UE_TYPE_NAME
import sco_utils as scu
from notes_log import log
from sco_exceptions import ScoValueError
from sco_permissions import ScoEtudInscrit
from sco_codes_parcours import UE_STANDARD, UE_SPORT, UE_TYPE_NAME
from notesdb import ScoDocCursor, DateISOtoDMY, DateDMYtoISO
from TrivialFormulator import TrivialFormulator, TF
@ -37,6 +40,7 @@ from TrivialFormulator import TrivialFormulator, TF
# from notes_table import *
import sco_find_etud
import sco_formsemestre
import sco_moduleimpl
import sco_groups
@ -75,10 +79,13 @@ def do_formsemestre_inscription_with_modules(
gdone[group_id] = 1
# inscription a tous les modules de ce semestre
modimpls = context.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
modimpls = sco_moduleimpl.do_moduleimpl_withmodule_list(
context, formsemestre_id=formsemestre_id
)
for mod in modimpls:
if mod["ue"]["type"] != UE_SPORT:
context.do_moduleimpl_inscription_create(
sco_moduleimpl.do_moduleimpl_inscription_create(
context,
{"moduleimpl_id": mod["moduleimpl_id"], "etudid": etudid},
REQUEST=REQUEST,
formsemestre_id=formsemestre_id,
@ -140,9 +147,9 @@ def formsemestre_inscription_with_modules_form(
if (not only_ext) or (sem["modalite"] == "EXT"):
H.append(
"""
<li><a class="stdlink" href="formsemestre_inscription_with_modules?etudid=%s&amp;formsemestre_id=%s">%s</a>
<li><a class="stdlink" id="inscription_Semestre_%s" href="formsemestre_inscription_with_modules?etudid=%s&formsemestre_id=%s">%s</a>
"""
% (etudid, sem["formsemestre_id"], sem["titremois"])
% (sem["formsemestre_id"],etudid, sem["formsemestre_id"], sem["titremois"])
)
H.append("</ul>")
else:
@ -210,12 +217,12 @@ def formsemestre_inscription_with_modules(
H.append("<ul>")
for s in others:
H.append(
'<li><a href="formsemestre_desinscription?formsemestre_id=%s&amp;etudid=%s">déinscrire de %s</li>'
'<li><a href="formsemestre_desinscription?formsemestre_id=%s&etudid=%s">déinscrire de %s</li>'
% (s["formsemestre_id"], etudid, s["titreannee"])
)
H.append("</ul>")
H.append(
"""<p><a href="formsemestre_inscription_with_modules?etudid=%s&amp;formsemestre_id=%s&amp;multiple_ok=1&amp;%s">Continuer quand même l'inscription</a></p>"""
"""<p><a href="formsemestre_inscription_with_modules?etudid=%s&formsemestre_id=%s&multiple_ok=1&%s">Continuer quand même l'inscription</a></p>"""
% (etudid, formsemestre_id, sco_groups.make_query_groups(group_ids))
)
return "\n".join(H) + F
@ -278,11 +285,15 @@ def formsemestre_inscription_option(context, etudid, formsemestre_id, REQUEST=No
]
# Cherche les moduleimpls et les inscriptions
mods = context.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
inscr = context.do_moduleimpl_inscription_list(etudid=etudid)
mods = sco_moduleimpl.do_moduleimpl_withmodule_list(
context, formsemestre_id=formsemestre_id
)
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(context, etudid=etudid)
# Formulaire
modimpls_by_ue_ids = DictDefault(defaultvalue=[]) # ue_id : [ moduleimpl_id ]
modimpls_by_ue_names = DictDefault(defaultvalue=[]) # ue_id : [ moduleimpl_name ]
modimpls_by_ue_ids = scu.DictDefault(defaultvalue=[]) # ue_id : [ moduleimpl_id ]
modimpls_by_ue_names = scu.DictDefault(
defaultvalue=[]
) # ue_id : [ moduleimpl_name ]
ues = []
ue_ids = set()
initvalues = {}
@ -321,7 +332,7 @@ def formsemestre_inscription_option(context, etudid, formsemestre_id, REQUEST=No
sem_origin = sco_formsemestre.get_formsemestre(
context, ue_status["formsemestre_id"]
)
ue_descr += ' <a class="discretelink" href="formsemestre_bulletinetud?formsemestre_id=%s&amp;etudid=%s" title="%s">(capitalisée le %s)' % (
ue_descr += ' <a class="discretelink" href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s">(capitalisée le %s)' % (
sem_origin["formsemestre_id"],
etudid,
sem_origin["titreannee"],
@ -502,13 +513,14 @@ def do_moduleimpl_incription_options(
# inscriptions
for moduleimpl_id in a_inscrire:
# verifie que ce module existe bien
mods = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)
mods = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)
if len(mods) != 1:
raise ScoValueError(
"inscription: invalid moduleimpl_id: %s" % moduleimpl_id
)
mod = mods[0]
context.do_moduleimpl_inscription_create(
sco_moduleimpl.do_moduleimpl_inscription_create(
context,
{"moduleimpl_id": moduleimpl_id, "etudid": etudid},
REQUEST=REQUEST,
formsemestre_id=mod["formsemestre_id"],
@ -516,14 +528,14 @@ def do_moduleimpl_incription_options(
# desinscriptions
for moduleimpl_id in a_desinscrire:
# verifie que ce module existe bien
mods = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)
mods = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)
if len(mods) != 1:
raise ScoValueError(
"desinscription: invalid moduleimpl_id: %s" % moduleimpl_id
)
mod = mods[0]
inscr = context.do_moduleimpl_inscription_list(
moduleimpl_id=moduleimpl_id, etudid=etudid
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(
context, moduleimpl_id=moduleimpl_id, etudid=etudid
)
if not inscr:
raise ScoValueError(
@ -531,8 +543,8 @@ def do_moduleimpl_incription_options(
% (etudid, moduleimpl_id)
)
oid = inscr[0]["moduleimpl_inscription_id"]
context.do_moduleimpl_inscription_delete(
oid, formsemestre_id=mod["formsemestre_id"]
sco_moduleimpl.do_moduleimpl_inscription_delete(
context, oid, formsemestre_id=mod["formsemestre_id"]
)
if REQUEST:

View File

@ -29,11 +29,19 @@
"""
# Rewritten from ancient DTML code
from mx.DateTime import DateTime as mxDateTime
from notesdb import *
from notes_log import log
from sco_utils import *
import sco_utils as scu
from sco_permissions import (
ScoImplement,
ScoChangeFormation,
ScoEtudInscrit,
ScoView,
ScoEtudChangeAdr,
)
from sco_exceptions import ScoValueError
import VERSION
import htmlutils
from sco_formsemestre_custommenu import formsemestre_custommenu_html
from gen_tables import GenTable
import sco_archives
@ -41,64 +49,12 @@ import sco_groups
import sco_evaluations
import sco_formsemestre
import sco_formsemestre_edit
import sco_moduleimpl
import sco_compute_moy
import sco_codes_parcours
import sco_bulletins
def makeMenu(title, items, css_class="", base_url="", alone=False):
"""HTML snippet to render a simple drop down menu.
items is a list of dicts:
{ 'title' :
'url' :
'id' :
'attr' : "" # optionnal html <a> attributes
'enabled' : # True by default
'helpmsg' :
'submenu' : [ list of sub-items ]
}
"""
def gen_menu_items(items):
H.append("<ul>")
for item in items:
if not item.get("enabled", True):
cls = ' class="ui-state-disabled"'
else:
cls = ""
the_id = item.get("id", "")
if the_id:
li_id = 'id="%s" ' % the_id
else:
li_id = ""
if base_url and "url" in item:
item["urlq"] = base_url + item["url"]
else:
item["urlq"] = item.get("url", "#")
item["attr"] = item.get("attr", "")
submenu = item.get("submenu", None)
H.append(
"<li "
+ li_id
+ cls
+ '><a href="%(urlq)s" %(attr)s>%(title)s</a>' % item
)
if submenu:
gen_menu_items(submenu)
H.append("</li>")
H.append("</ul>")
H = []
if alone:
H.append('<ul class="sco_dropdown_menu %s">' % css_class)
H.append("""<li><a href="#">%s</a>""" % title)
gen_menu_items(items)
H.append("</li>")
if alone:
H.append("</ul>")
return "".join(H)
# H = [ """<span class="barrenav"><ul class="nav">
# <li onmouseover="MenuDisplay(this)" onmouseout="MenuHide(this)"><a href="#" class="menu %s">%s</a><ul>""" % (cssclass, title)
# ]
@ -130,7 +86,7 @@ def defMenuStats(context, formsemestre_id):
{
"title": "Graphe des parcours",
"url": "formsemestre_graph_parcours?formsemestre_id=" + formsemestre_id,
"enabled": WITH_PYDOT,
"enabled": scu.WITH_PYDOT,
},
{
"title": "Codes des parcours",
@ -198,7 +154,7 @@ def formsemestre_status_menubar(context, sem, REQUEST):
},
{
"title": "Modifier le semestre",
"url": "formsemestre_editwithmodules?formation_id=%(formation_id)s&amp;formsemestre_id=%(formsemestre_id)s"
"url": "formsemestre_editwithmodules?formation_id=%(formation_id)s&formsemestre_id=%(formsemestre_id)s"
% sem,
"enabled": (
authuser.has_permission(ScoImplement, context)
@ -336,7 +292,7 @@ def formsemestre_status_menubar(context, sem, REQUEST):
},
{
"title": "Exporter table des étudiants",
"url": "groups_view?format=allxls&amp;group_ids="
"url": "groups_view?format=allxls&group_ids="
+ sco_groups.get_default_group(
context, formsemestre_id, fix_if_missing=True, REQUEST=REQUEST
),
@ -364,7 +320,7 @@ def formsemestre_status_menubar(context, sem, REQUEST):
{
"title": "Créer/modifier les partitions...",
"url": "editPartitionForm?formsemestre_id=" + formsemestre_id,
"enabled": context.can_change_groups(REQUEST, formsemestre_id),
"enabled": sco_groups.can_change_groups(context, REQUEST, formsemestre_id),
},
]
# 1 item / partition:
@ -372,7 +328,9 @@ def formsemestre_status_menubar(context, sem, REQUEST):
context, formsemestre_id, with_default=False
)
submenu = []
enabled = context.can_change_groups(REQUEST, formsemestre_id) and partitions
enabled = (
sco_groups.can_change_groups(context, REQUEST, formsemestre_id) and partitions
)
for partition in partitions:
submenu.append(
{
@ -430,7 +388,7 @@ def formsemestre_status_menubar(context, sem, REQUEST):
},
{
"title": "Saisie des décisions du jury",
"url": "formsemestre_recapcomplet?modejury=1&amp;hidemodules=1&amp;hidebac=1&amp;pref_override=0&amp;formsemestre_id="
"url": "formsemestre_recapcomplet?modejury=1&hidemodules=1&hidebac=1&pref_override=0&formsemestre_id="
+ formsemestre_id,
"enabled": context._can_validate_sem(REQUEST, formsemestre_id),
},
@ -453,12 +411,12 @@ def formsemestre_status_menubar(context, sem, REQUEST):
H = [
# <table><tr><td>',
'<ul id="sco_menu">',
makeMenu("Semestre", menuSemestre, base_url=base_url),
makeMenu("Inscriptions", menuInscriptions, base_url=base_url),
makeMenu("Groupes", menuGroupes, base_url=base_url),
makeMenu("Notes", menuNotes, base_url=base_url),
makeMenu("Jury", menuJury, base_url=base_url),
makeMenu("Statistiques", menuStats, base_url=base_url),
htmlutils.make_menu("Semestre", menuSemestre, base_url=base_url),
htmlutils.make_menu("Inscriptions", menuInscriptions, base_url=base_url),
htmlutils.make_menu("Groupes", menuGroupes, base_url=base_url),
htmlutils.make_menu("Notes", menuNotes, base_url=base_url),
htmlutils.make_menu("Jury", menuJury, base_url=base_url),
htmlutils.make_menu("Statistiques", menuStats, base_url=base_url),
formsemestre_custommenu_html(context, formsemestre_id, base_url=base_url),
"</ul>",
#'</td></tr></table>'
@ -476,8 +434,8 @@ def retreive_formsemestre_from_request(context, REQUEST):
if REQUEST.form.has_key("formsemestre_id"):
formsemestre_id = REQUEST.form["formsemestre_id"]
elif REQUEST.form.has_key("moduleimpl_id"):
modimpl = context.do_moduleimpl_list(
moduleimpl_id=REQUEST.form["moduleimpl_id"]
modimpl = sco_moduleimpl.do_moduleimpl_list(
context, moduleimpl_id=REQUEST.form["moduleimpl_id"]
)
if not modimpl:
return None # suppressed ?
@ -488,7 +446,9 @@ def retreive_formsemestre_from_request(context, REQUEST):
if not E:
return None # evaluation suppressed ?
E = E[0]
modimpl = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
modimpl = sco_moduleimpl.do_moduleimpl_list(
context, moduleimpl_id=E["moduleimpl_id"]
)[0]
formsemestre_id = modimpl["formsemestre_id"]
elif REQUEST.form.has_key("group_id"):
group = sco_groups.get_group(context, REQUEST.form["group_id"])
@ -545,10 +505,7 @@ def formsemestre_page_title(context, REQUEST):
def fill_formsemestre(context, sem, REQUEST=None):
"""Add some useful fields to help display formsemestres"""
# Notes URL
notes_url = context.absolute_url()
if "/Notes" not in notes_url:
notes_url += "/Notes"
notes_url = context.NotesURL()
sem["notes_url"] = notes_url
formsemestre_id = sem["formsemestre_id"]
if sem["etat"] != "1":
@ -557,15 +514,15 @@ def fill_formsemestre(context, sem, REQUEST=None):
] = """<a href="%s/formsemestre_change_lock?formsemestre_id=%s">%s</a>""" % (
notes_url,
sem["formsemestre_id"],
icontag("lock_img", border="0", title="Semestre verrouillé"),
scu.icontag("lock_img", border="0", title="Semestre verrouillé"),
)
else:
sem["locklink"] = ""
if context.get_preference("bul_display_publication", formsemestre_id):
if sem["bul_hide_xml"] != "0":
eyeicon = icontag("hide_img", border="0", title="Bulletins NON publiés")
eyeicon = scu.icontag("hide_img", border="0", title="Bulletins NON publiés")
else:
eyeicon = icontag("eye_img", border="0", title="Bulletins publiés")
eyeicon = scu.icontag("eye_img", border="0", title="Bulletins publiés")
sem["eyelink"] = (
"""<a href="%s/formsemestre_change_publication_bul?formsemestre_id=%s">%s</a>"""
% (notes_url, sem["formsemestre_id"], eyeicon)
@ -614,7 +571,9 @@ def formsemestre_description_table(
use_ue_coefs = context.get_preference("use_ue_coefs", formsemestre_id)
F = context.formation_list(args={"formation_id": sem["formation_id"]})[0]
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
Mlist = context.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
context, formsemestre_id=formsemestre_id
)
R = []
sum_coef = 0
@ -641,8 +600,8 @@ def formsemestre_description_table(
ue_info["Coef._class"] = "ue_coef"
R.append(ue_info)
ModInscrits = context.do_moduleimpl_inscription_list(
moduleimpl_id=M["moduleimpl_id"]
ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
context, moduleimpl_id=M["moduleimpl_id"]
)
enseignants = ", ".join(
[
@ -715,17 +674,17 @@ def formsemestre_description_table(
titles["coefficient"] = "Coef. éval."
titles["evalcomplete_str"] = "Complète"
titles["publish_incomplete_str"] = "Toujours Utilisée"
title = "%s %s" % (strcapitalize(parcours.SESSION_NAME), sem["titremois"])
title = "%s %s" % (scu.strcapitalize(parcours.SESSION_NAME), sem["titremois"])
return GenTable(
columns_ids=columns_ids,
rows=R,
titles=titles,
origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "",
origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "",
caption=title,
html_caption=title,
html_class="table_leftalign formsemestre_description",
base_url="%s?formsemestre_id=%s&amp;with_evals=%s"
base_url="%s?formsemestre_id=%s&with_evals=%s"
% (REQUEST.URL0, formsemestre_id, with_evals),
page_title=title,
html_title=context.html_sem_header(
@ -779,8 +738,8 @@ def html_expr_diagnostic(context, diagnostics):
last_id, last_msg = None, None
for diag in diagnostics:
if "moduleimpl_id" in diag:
mod = context.do_moduleimpl_withmodule_list(
moduleimpl_id=diag["moduleimpl_id"]
mod = sco_moduleimpl.do_moduleimpl_withmodule_list(
context, moduleimpl_id=diag["moduleimpl_id"]
)[0]
H.append(
'<li>module <a href="moduleimpl_status?moduleimpl_id=%s">%s</a>: %s</li>'
@ -875,10 +834,12 @@ def formsemestre_status(context, formsemestre_id=None, REQUEST=None):
# porté du DTML
cnx = context.GetDBConnexion()
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
Mlist = context.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
inscrits = context.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id}
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
context, formsemestre_id=formsemestre_id
)
# inscrits = context.do_formsemestre_inscription_list(
# args={"formsemestre_id": formsemestre_id}
# )
prev_ue_id = None
can_edit = sco_formsemestre_edit.can_edit_sem(
@ -926,8 +887,8 @@ def formsemestre_status(context, formsemestre_id=None, REQUEST=None):
ModEns += " (resp.), " + ", ".join(
[context.Users.user_info(e["ens_id"])["nomcomplet"] for e in M["ens"]]
)
ModInscrits = context.do_moduleimpl_inscription_list(
moduleimpl_id=M["moduleimpl_id"]
ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
context, moduleimpl_id=M["moduleimpl_id"]
)
mails_enseignants.add(
context.Users.user_info(M["responsable_id"], REQUEST)["email"]
@ -956,11 +917,11 @@ def formsemestre_status(context, formsemestre_id=None, REQUEST=None):
if can_edit:
H.append(
' <a href="edit_ue_expr?formsemestre_id=%s&amp;ue_id=%s">'
' <a href="edit_ue_expr?formsemestre_id=%s&ue_id=%s">'
% (formsemestre_id, ue["ue_id"])
)
H.append(
icontag(
scu.icontag(
"formula",
title="Mode calcul moyenne d'UE",
style="vertical-align:middle",
@ -976,7 +937,7 @@ def formsemestre_status(context, formsemestre_id=None, REQUEST=None):
H.append("</td></tr>")
if M["ue"]["type"] != UE_STANDARD:
if M["ue"]["type"] != sco_codes_parcours.UE_STANDARD:
fontorange = " fontorange" # style css additionnel
else:
fontorange = ""
@ -1011,7 +972,7 @@ def formsemestre_status(context, formsemestre_id=None, REQUEST=None):
)
)
if Mod["module_type"] == MODULE_STANDARD:
if Mod["module_type"] == scu.MODULE_STANDARD:
H.append('<td class="evals">')
nb_evals = (
etat["nb_evals_completes"]
@ -1033,7 +994,7 @@ def formsemestre_status(context, formsemestre_id=None, REQUEST=None):
' <span><a class="redlink" href="moduleimpl_status?moduleimpl_id=%s" title="Il y a des notes en attente">[en attente]</a></span>'
% M["moduleimpl_id"]
)
elif Mod["module_type"] == MODULE_MALUS:
elif Mod["module_type"] == scu.MODULE_MALUS:
nb_malus_notes = sum(
[
e["etat"]["nb_notes"]

View File

@ -29,14 +29,14 @@
"""
import urllib, time, datetime
from notesdb import *
from sco_utils import *
import notesdb as ndb
import sco_utils as scu
from notes_log import log
from scolog import logdb
from notes_table import *
import notes_table
from ZAbsences import getAbsSemEtud
from TrivialFormulator import TrivialFormulator, tf_error_message
from sco_exceptions import ScoValueError
from sco_abs import getAbsSemEtud
import sco_formsemestre
import sco_formsemestre_edit
import sco_formsemestre_status
@ -109,13 +109,13 @@ def formsemestre_validation_etud_form(
if etud_index_prev != None:
etud_p = context.getEtudInfo(etudid=T[etud_index_prev][-1], filled=True)[0]
Footer.append(
'<span><a href="formsemestre_validation_etud_form?formsemestre_id=%s&amp;etud_index=%s">Etud. précédent (%s)</a></span>'
'<span><a href="formsemestre_validation_etud_form?formsemestre_id=%s&etud_index=%s">Etud. précédent (%s)</a></span>'
% (formsemestre_id, etud_index_prev, etud_p["nomprenom"])
)
if etud_index_next != None:
etud_n = context.getEtudInfo(etudid=T[etud_index_next][-1], filled=True)[0]
Footer.append(
'<span style="padding-left: 50px;"><a href="formsemestre_validation_etud_form?formsemestre_id=%s&amp;etud_index=%s">Etud. suivant (%s)</a></span>'
'<span style="padding-left: 50px;"><a href="formsemestre_validation_etud_form?formsemestre_id=%s&etud_index=%s">Etud. suivant (%s)</a></span>'
% (formsemestre_id, etud_index_next, etud_n["nomprenom"])
)
Footer.append("</p>")
@ -171,12 +171,12 @@ def formsemestre_validation_etud_form(
if check:
if not desturl:
desturl = (
"formsemestre_recapcomplet?modejury=1&amp;hidemodules=1&amp;hidebac=1&amp;pref_override=0&amp;formsemestre_id="
"formsemestre_recapcomplet?modejury=1&hidemodules=1&hidebac=1&pref_override=0&formsemestre_id="
+ formsemestre_id
)
if sortcol:
desturl += (
"&amp;sortcol=" + sortcol
"&sortcol=" + sortcol
) # pour refaire tri sorttable du tableau de notes
desturl += "#etudid%s" % etudid # va a la bonne ligne
H.append('<ul><li><a href="%s">Continuer</a></li></ul>' % desturl)
@ -205,13 +205,13 @@ def formsemestre_validation_etud_form(
if not Se.prev_decision:
H.append(
tf_error_message(
"""Le jury n\'a pas statué sur le semestre précédent ! (<a href="formsemestre_validation_etud_form?formsemestre_id=%s&amp;etudid=%s">le faire maintenant</a>)"""
"""Le jury n\'a pas statué sur le semestre précédent ! (<a href="formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s">le faire maintenant</a>)"""
% (Se.prev["formsemestre_id"], etudid)
)
)
if decision_jury:
H.append(
'<a href="formsemestre_validation_suppress_etud?etudid=%s&amp;formsemestre_id=%s" class="stdlink">Supprimer décision existante</a>'
'<a href="formsemestre_validation_suppress_etud?etudid=%s&formsemestre_id=%s" class="stdlink">Supprimer décision existante</a>'
% (etudid, formsemestre_id)
)
H.append(context.sco_footer(REQUEST))
@ -249,7 +249,7 @@ def formsemestre_validation_etud_form(
<input type="submit" value="Statuer sur le semestre précédent"/>
<input type="hidden" name="formsemestre_id" value="%s"/>
<input type="hidden" name="etudid" value="%s"/>
<input type="hidden" name="desturl" value="formsemestre_validation_etud_form?etudid=%s&amp;formsemestre_id=%s"/>
<input type="hidden" name="desturl" value="formsemestre_validation_etud_form?etudid=%s&formsemestre_id=%s"/>
"""
% (Se.prev["formsemestre_id"], etudid, etudid, formsemestre_id)
)
@ -309,7 +309,7 @@ def formsemestre_validation_etud_form(
H.append(form_decision_manuelle(context, Se, formsemestre_id, etudid))
H.append(
"""<div class="link_defaillance">Ou <a class="stdlink" href="formDef?etudid=%s&amp;formsemestre_id=%s">déclarer l'étudiant comme défaillant dans ce semestre</a></div>"""
"""<div class="link_defaillance">Ou <a class="stdlink" href="formDef?etudid=%s&formsemestre_id=%s">déclarer l'étudiant comme défaillant dans ce semestre</a></div>"""
% (etudid, formsemestre_id)
)
@ -346,7 +346,7 @@ def formsemestre_validation_etud(
raise ValueError("code choix invalide ! (%s)" % codechoice)
#
Se.valide_decision(choice, REQUEST) # enregistre
_redirect_valid_choice(
return _redirect_valid_choice(
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
)
@ -393,7 +393,7 @@ def formsemestre_validation_etud_manu(
#
Se.valide_decision(choice, REQUEST) # enregistre
if redirect:
_redirect_valid_choice(
return _redirect_valid_choice(
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
)
@ -402,23 +402,23 @@ def _redirect_valid_choice(
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
):
adr = (
"formsemestre_validation_etud_form?formsemestre_id=%s&amp;etudid=%s&amp;check=1"
"formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1"
% (formsemestre_id, etudid)
)
if sortcol:
adr += "&amp;sortcol=" + sortcol
if desturl:
desturl += "&amp;desturl=" + desturl
REQUEST.RESPONSE.redirect(adr)
adr += "&sortcol=" + sortcol
# if desturl:
# desturl += "&desturl=" + desturl
return REQUEST.RESPONSE.redirect(adr)
# Si le precedent a été modifié, demande relecture du parcours.
# sinon renvoie au listing general,
# if choice.new_code_prev:
# REQUEST.RESPONSE.redirect( 'formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&amp;check=1&amp;desturl=%s' % (formsemestre_id, etudid, desturl) )
# REQUEST.RESPONSE.redirect( 'formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1&desturl=%s' % (formsemestre_id, etudid, desturl) )
# else:
# if not desturl:
# desturl = 'formsemestre_recapcomplet?modejury=1&amp;hidemodules=1&amp;formsemestre_id=' + formsemestre_id
# desturl = 'formsemestre_recapcomplet?modejury=1&hidemodules=1&formsemestre_id=' + formsemestre_id
# REQUEST.RESPONSE.redirect(desturl)
@ -454,8 +454,8 @@ def decisions_possible_rows(Se, assiduite, subtitle="", trclass=""):
H.append("<th>Code %s</th><th>Devenir</th></tr>" % TitleCur)
for ch in choices:
H.append(
"""<tr class="%s"><td title="règle %s"><input type="radio" name="codechoice" value="%s" onClick="document.getElementById('subut').disabled=false;">"""
% (trclass, ch.rule_id, ch.codechoice)
"""<tr class="%s"><td title="règle %s"><input type="radio" name="codechoice" value="%s" id="choice_input_%s" onClick="document.getElementById('subut').disabled=false;">"""
% (trclass, ch.rule_id, ch.codechoice,ch.rule_id)
)
H.append("%s </input></td>" % ch.explication)
if Se.prev:
@ -486,8 +486,8 @@ def formsemestre_recap_parcours_table(
"""
H = []
linktmpl = '<span onclick="toggle_vis(this);" class="toggle_sem sem_%%s">%s</span>'
minuslink = linktmpl % icontag("minus_img", border="0", alt="-")
pluslink = linktmpl % icontag("plus_img", border="0", alt="+")
minuslink = linktmpl % scu.icontag("minus_img", border="0", alt="-")
pluslink = linktmpl % scu.icontag("plus_img", border="0", alt="+")
if show_details:
sd = " recap_show_details"
plusminus = minuslink
@ -497,7 +497,7 @@ def formsemestre_recap_parcours_table(
H.append('<table class="recap_parcours%s"><tr>' % sd)
H.append(
'<th><span onclick="toggle_all_sems(this);" title="Ouvrir/fermer tous les semestres">%s</span></th><th></th><th>Semestre</th>'
% icontag("plus18_img", width=18, height=18, border=0, title="", alt="+")
% scu.icontag("plus18_img", width=18, height=18, border=0, title="", alt="+")
)
H.append("<th>Etat</th><th>Abs</th>")
# titres des UE
@ -555,7 +555,7 @@ def formsemestre_recap_parcours_table(
)
H.append('<td class="datedebut">%(mois_debut)s</td>' % sem)
H.append(
'<td class="rcp_titre_sem"><a class="formsemestre_status_link" href="%sformsemestre_bulletinetud?formsemestre_id=%s&amp;etudid=%s" title="Bulletin de notes">%s</a></td>'
'<td class="rcp_titre_sem"><a class="formsemestre_status_link" href="%sformsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="Bulletin de notes">%s</a></td>'
% (a_url, sem["formsemestre_id"], etudid, sem["titreannee"])
)
if decision_sem:
@ -591,7 +591,7 @@ def formsemestre_recap_parcours_table(
else:
default_sem_info = ""
if sem["etat"] != "1": # locked
lockicon = icontag("lock32_img", title="verrouillé", border="0")
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
default_sem_info += lockicon
if sem["formation_code"] != Se.formation["formation_code"]:
default_sem_info += "Autre formation: %s" % sem["formation_code"]
@ -601,8 +601,7 @@ def formsemestre_recap_parcours_table(
)
# Moy Gen (sous le code decision)
H.append(
'<td class="rcp_moy">%s</td>'
% notes_table.fmt_note(nt.get_etud_moy_gen(etudid))
'<td class="rcp_moy">%s</td>' % scu.fmt_note(nt.get_etud_moy_gen(etudid))
)
# Absences (nb d'abs non just. dans ce semestre)
AbsEtudSem = getAbsSemEtud(context, sem, etudid)
@ -641,8 +640,8 @@ def formsemestre_recap_parcours_table(
# log('')
H.append(
'<td class="%s" title="%s">%s</td>'
% (class_ue, " ".join(explanation_ue), notes_table.fmt_note(moy_ue))
'<td class="%s" id="ue_%s" title="%s">%s</td>'
% (class_ue,ue["acronyme"], " ".join(explanation_ue), scu.fmt_note(moy_ue))
)
if len(ues) < Se.nb_max_ue:
H.append('<td colspan="%d"></td>' % (Se.nb_max_ue - len(ues)))
@ -650,7 +649,7 @@ def formsemestre_recap_parcours_table(
H.append("<td></td>")
if with_links:
H.append(
'<td><a href="%sformsemestre_validation_etud_form?formsemestre_id=%s&amp;etudid=%s">modifier</a></td>'
'<td><a href="%sformsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s">modifier</a></td>'
% (a_url, sem["formsemestre_id"], etudid)
)
@ -810,7 +809,7 @@ def form_decision_manuelle(
H.append(
"""</table>
<input type="submit" name="formvalidmanu_submit" value="Valider décision manuelle"/>
<span style="padding-left: 5em;"><a href="formsemestre_validation_suppress_etud?etudid=%s&amp;formsemestre_id=%s" class="stdlink">Supprimer décision existante</a></span>
<span style="padding-left: 5em;"><a href="formsemestre_validation_suppress_etud?etudid=%s&formsemestre_id=%s" class="stdlink">Supprimer décision existante</a></span>
</form>
"""
% (etudid, formsemestre_id)
@ -929,12 +928,12 @@ def do_formsemestre_validation_auto(context, formsemestre_id, REQUEST):
)
for etud in conflicts:
H.append(
'<li><a href="formsemestre_validation_etud_form?formsemestre_id=%s&amp;etudid=%s&amp;check=1">%s</li>'
'<li><a href="formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1">%s</li>'
% (formsemestre_id, etud["etudid"], etud["nomprenom"])
)
H.append("</ul>")
H.append(
'<a href="formsemestre_recapcomplet?formsemestre_id=%s&amp;modejury=1&amp;hidemodules=1&amp;hidebac=1&amp;pref_override=0">continuer</a>'
'<a href="formsemestre_recapcomplet?formsemestre_id=%s&modejury=1&hidemodules=1&hidebac=1&pref_override=0">continuer</a>'
% formsemestre_id
)
H.append(context.sco_footer(REQUEST))
@ -1035,7 +1034,7 @@ def formsemestre_validation_suppress_etud(context, formsemestre_id, etudid):
"""Suppression des decisions de jury pour un etudiant."""
log("formsemestre_validation_suppress_etud( %s, %s)" % (formsemestre_id, etudid))
cnx = context.GetDBConnexion(autocommit=False)
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
try:
# -- Validation du semestre et des UEs
@ -1164,8 +1163,8 @@ def formsemestre_validate_previous_ue(context, formsemestre_id, etudid, REQUEST=
return "\n".join(H) + tf[1] + X + warn + context.sco_footer(REQUEST)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(
context.ScoURL()
+ "/Notes/formsemestre_status?formsemestre_id="
context.NotesURL()
+ "/formsemestre_status?formsemestre_id="
+ formsemestre_id
)
else:
@ -1185,7 +1184,7 @@ def formsemestre_validate_previous_ue(context, formsemestre_id, etudid, REQUEST=
)
return REQUEST.RESPONSE.redirect(
context.ScoURL()
+ "/Notes/formsemestre_bulletinetud?formsemestre_id=%s&amp;etudid=%s&amp;head_message=Validation%%20d'UE%%20enregistree"
+ "/Notes/formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s&head_message=Validation%%20d'UE%%20enregistree"
% (formsemestre_id, etudid)
)
@ -1212,11 +1211,11 @@ def do_formsemestre_validate_previous_ue(
context, formsemestre_id
) # > get_etud_ue_status
if ue_coefficient != None:
sco_formsemestre_edit.do_formsemestre_uecoef_edit_or_create(
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
context, cnx, formsemestre_id, ue_id, ue_coefficient
)
else:
sco_formsemestre_edit.do_formsemestre_uecoef_delete(
sco_formsemestre.do_formsemestre_uecoef_delete(
context, cnx, formsemestre_id, ue_id
)
sco_parcours_dut.do_formsemestre_validate_ue(
@ -1246,7 +1245,7 @@ def do_formsemestre_validate_previous_ue(
def _invalidate_etud_formation_caches(context, etudid, formation_id):
"Invalide tous les semestres de cette formation où l'etudiant est inscrit..."
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"""SELECT sem.*
FROM notes_formsemestre sem, notes_formsemestre_inscription i
@ -1264,7 +1263,7 @@ def _invalidate_etud_formation_caches(context, etudid, formation_id):
def get_etud_ue_cap_html(context, etudid, formsemestre_id, ue_id, REQUEST=None):
"""Ramene bout de HTML pour pouvoir supprimer une validation de cette UE"""
valids = SimpleDictFetch(
valids = ndb.SimpleDictFetch(
context,
"""SELECT SFV.* FROM scolar_formsemestre_validation SFV
WHERE ue_id=%(ue_id)s AND etudid=%(etudid)s""",
@ -1276,7 +1275,7 @@ def get_etud_ue_cap_html(context, etudid, formsemestre_id, ue_id, REQUEST=None):
'<div class="existing_valids"><span>Validations existantes pour cette UE:</span><ul>'
]
for valid in valids:
valid["event_date"] = DateISOtoDMY(valid["event_date"])
valid["event_date"] = ndb.DateISOtoDMY(valid["event_date"])
if valid["moy_ue"] != None:
valid["m"] = ", moyenne %(moy_ue)g/20" % valid
else:
@ -1290,7 +1289,7 @@ def get_etud_ue_cap_html(context, etudid, formsemestre_id, ue_id, REQUEST=None):
valid["s"] += " (<b>S%d</b>)" % valid["semestre_id"]
valid["ds"] = formsemestre_id
H.append(
'<li>%(code)s%(m)s%(s)s, le %(event_date)s <a class="stdlink" href="etud_ue_suppress_validation?etudid=%(etudid)s&amp;ue_id=%(ue_id)s&amp;formsemestre_id=%(ds)s" title="supprime cette validation">effacer</a></li>'
'<li>%(code)s%(m)s%(s)s, le %(event_date)s <a class="stdlink" href="etud_ue_suppress_validation?etudid=%(etudid)s&ue_id=%(ue_id)s&formsemestre_id=%(ds)s" title="supprime cette validation">effacer</a></li>'
% valid
)
H.append("</ul></div>")
@ -1301,7 +1300,7 @@ def etud_ue_suppress_validation(context, etudid, formsemestre_id, ue_id, REQUEST
"""Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
log("etud_ue_suppress_validation( %s, %s, %s)" % (etudid, formsemestre_id, ue_id))
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"DELETE FROM scolar_formsemestre_validation WHERE etudid=%(etudid)s and ue_id=%(ue_id)s",
{"etudid": etudid, "ue_id": ue_id},
@ -1311,8 +1310,8 @@ def etud_ue_suppress_validation(context, etudid, formsemestre_id, ue_id, REQUEST
_invalidate_etud_formation_caches(context, etudid, sem["formation_id"])
return REQUEST.RESPONSE.redirect(
context.ScoURL()
+ "/Notes/formsemestre_validate_previous_ue?etudid=%s&amp;formsemestre_id=%s"
context.NotesURL()
+ "/formsemestre_validate_previous_ue?etudid=%s&formsemestre_id=%s"
% (etudid, formsemestre_id)
)
@ -1327,7 +1326,7 @@ def check_formation_ues(context, formation_id):
ue_multiples = {} # { ue_id : [ liste des formsemestre ] }
for ue in ues:
# formsemestres utilisant cette ue ?
sems = SimpleDictFetch(
sems = ndb.SimpleDictFetch(
context,
"""SELECT DISTINCT sem.*
FROM notes_formsemestre sem, notes_modules mod, notes_moduleimpl mi

View File

@ -32,7 +32,7 @@ import operator
import traceback
from types import FloatType, IntType, LongType, StringType
from sco_utils import *
import sco_utils as scu
from notes_log import log

View File

@ -28,34 +28,46 @@
"""Gestion des groupes, nouvelle mouture (juin/nov 2009)
TODO:
* Groupes:
- changement de groupe d'un seul etudiant:
formChangeGroupe: pour l'instant supprimé, pas vraiment utile ?
doChangeGroupe
Optimisation possible:
revoir do_evaluation_listeetuds_groups() pour extraire aussi les groupes (de chaque etudiant)
et eviter ainsi l'appel ulterieur à get_etud_groups() dans _make_table_notes
et éviter ainsi l'appel ulterieur à get_etud_groups() dans _make_table_notes
"""
import re, sets
import time
import collections
import re
import sets
import operator
# XML generation package (apt-get install jaxml)
import jaxml
import xml.dom.minidom
from sco_utils import *
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
from scolog import logdb
from TrivialFormulator import TrivialFormulator, TF
import sco_formsemestre
import scolars
import sco_parcours_dut
import sco_codes_parcours
from sco_permissions import ScoEtudChangeGroups
from sco_exceptions import ScoException, AccessDenied, ScoValueError
def can_change_groups(context, REQUEST, formsemestre_id):
"Vrai si l'utilisateur peut changer les groupes dans ce semestre"
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
if sem["etat"] != "1":
return False # semestre verrouillé
authuser = REQUEST.AUTHENTICATED_USER
if authuser.has_permission(ScoEtudChangeGroups, context):
return True # admin, chef dept
uid = str(authuser)
if uid in sem["responsables"]:
return True
return False
def checkGroupName(
@ -63,13 +75,14 @@ def checkGroupName(
): # XXX unused: now allow any string as a group or partition name
"Raises exception if not a valid group name"
if groupName and (
not re.match(r"^\w+$", groupName) or (simplesqlquote(groupName) != groupName)
not re.match(r"^\w+$", groupName)
or (scu.simplesqlquote(groupName) != groupName)
):
log("!!! invalid group name: " + groupName)
raise ValueError("invalid group name: " + groupName)
partitionEditor = EditableTable(
partitionEditor = ndb.EditableTable(
"partition",
"partition_id",
(
@ -83,7 +96,7 @@ partitionEditor = EditableTable(
),
)
groupEditor = EditableTable(
groupEditor = ndb.EditableTable(
"group_descr", "group_id", ("group_id", "partition_id", "group_name")
)
@ -92,7 +105,7 @@ group_list = groupEditor.list
def get_group(context, group_id):
"""Returns group object, with partition"""
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT gd.*, p.* FROM group_descr gd, partition p WHERE gd.group_id=%(group_id)s AND p.partition_id = gd.partition_id",
{"group_id": group_id},
@ -107,15 +120,17 @@ def group_delete(context, group, force=False):
# if not group['group_name'] and not force:
# raise ValueError('cannot suppress this group')
# remove memberships:
SimpleQuery(
ndb.SimpleQuery(
context, "DELETE FROM group_membership WHERE group_id=%(group_id)s", group
)
# delete group:
SimpleQuery(context, "DELETE FROM group_descr WHERE group_id=%(group_id)s", group)
ndb.SimpleQuery(
context, "DELETE FROM group_descr WHERE group_id=%(group_id)s", group
)
def get_partition(context, partition_id):
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT p.* FROM partition p WHERE p.partition_id = %(partition_id)s",
{"partition_id": partition_id},
@ -127,7 +142,7 @@ def get_partition(context, partition_id):
def get_partitions_list(context, formsemestre_id, with_default=True):
"""Liste des partitions pour ce semestre (list of dicts)"""
partitions = SimpleDictFetch(
partitions = ndb.SimpleDictFetch(
context,
"SELECT * FROM partition WHERE formsemestre_id=%(formsemestre_id)s order by numero",
{"formsemestre_id": formsemestre_id},
@ -141,7 +156,7 @@ def get_partitions_list(context, formsemestre_id, with_default=True):
def get_default_partition(context, formsemestre_id):
"""Get partition for 'all' students (this one always exists, with NULL name)"""
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT * FROM partition WHERE formsemestre_id=%(formsemestre_id)s AND partition_name is NULL",
{"formsemestre_id": formsemestre_id},
@ -169,7 +184,7 @@ def get_formsemestre_groups(context, formsemestre_id, with_default=False):
def get_partition_groups(context, partition):
"""List of groups in this partition (list of dicts).
Some groups may be empty."""
return SimpleDictFetch(
return ndb.SimpleDictFetch(
context,
"SELECT gd.*, p.* FROM group_descr gd, partition p WHERE gd.partition_id=%(partition_id)s AND gd.partition_id=p.partition_id ORDER BY group_name",
partition,
@ -178,7 +193,7 @@ def get_partition_groups(context, partition):
def get_default_group(context, formsemestre_id, fix_if_missing=False, REQUEST=None):
"""Returns group_id for default ('tous') group"""
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT gd.group_id FROM group_descr gd, partition p WHERE p.formsemestre_id=%(formsemestre_id)s AND p.partition_name is NULL AND p.partition_id = gd.partition_id",
{"formsemestre_id": formsemestre_id},
@ -210,7 +225,7 @@ def get_default_group(context, formsemestre_id, fix_if_missing=False, REQUEST=No
def get_sem_groups(context, formsemestre_id):
"""Returns groups for this sem (in all partitions)."""
return SimpleDictFetch(
return ndb.SimpleDictFetch(
context,
"SELECT gd.*, p.* FROM group_descr gd, partition p WHERE p.formsemestre_id=%(formsemestre_id)s AND p.partition_id = gd.partition_id",
{"formsemestre_id": formsemestre_id},
@ -226,14 +241,14 @@ def get_group_members(context, group_id, etat=None):
if etat is not None:
req += " and ins.etat = %(etat)s"
r = SimpleDictFetch(context, req, {"group_id": group_id, "etat": etat})
r = ndb.SimpleDictFetch(context, req, {"group_id": group_id, "etat": etat})
for etud in r:
scolars.format_etud_ident(etud)
r.sort(key=operator.itemgetter("nom_disp", "prenom")) # tri selon nom_usuel ou nom
if CONFIG.ALLOW_NULL_PRENOM:
if scu.CONFIG.ALLOW_NULL_PRENOM:
for x in r:
x["prenom"] = x["prenom"] or ""
@ -287,7 +302,7 @@ def get_group_infos(context, group_id, etat=None): # was _getlisteetud
else:
t["etath"] = "(dem.)"
nbdem += 1
elif t["etat"] == DEF:
elif t["etat"] == sco_codes_parcours.DEF:
t["etath"] = "Défaillant"
else:
t["etath"] = t["etat"]
@ -322,7 +337,7 @@ def get_etud_groups(context, etudid, sem, exclude_default=False):
req = "SELECT p.*, g.* from group_descr g, partition p, group_membership gm WHERE gm.etudid=%(etudid)s and gm.group_id = g.group_id and g.partition_id = p.partition_id and p.formsemestre_id = %(formsemestre_id)s"
if exclude_default:
req += " and p.partition_name is not NULL"
groups = SimpleDictFetch(
groups = ndb.SimpleDictFetch(
context,
req + " ORDER BY p.numero",
{"etudid": etudid, "formsemestre_id": sem["formsemestre_id"]},
@ -350,7 +365,7 @@ def formsemestre_get_etud_groupnames(context, formsemestre_id, attr="group_name"
"""Recupere les groupes de tous les etudiants d'un semestre
{ etudid : { partition_id : group_name }} (attr=group_name or group_id)
"""
infos = SimpleDictFetch(
infos = ndb.SimpleDictFetch(
context,
"select i.etudid, p.partition_id, gd.group_name, gd.group_id from notes_formsemestre_inscription i, partition p, group_descr gd, group_membership gm where i.formsemestre_id=%(formsemestre_id)s and i.formsemestre_id=p.formsemestre_id and p.partition_id=gd.partition_id and gm.etudid=i.etudid and gm.group_id = gd.group_id and p.partition_name is not NULL",
{"formsemestre_id": formsemestre_id},
@ -373,7 +388,7 @@ def etud_add_group_infos(context, etud, sem, sep=" "):
etud["groupes"] = ""
return etud
infos = SimpleDictFetch(
infos = ndb.SimpleDictFetch(
context,
"SELECT p.partition_name, g.* from group_descr g, partition p, group_membership gm WHERE gm.etudid=%(etudid)s and gm.group_id = g.group_id and g.partition_id = p.partition_id and p.formsemestre_id = %(formsemestre_id)s ORDER BY p.numero",
{"etudid": etud["etudid"], "formsemestre_id": sem["formsemestre_id"]},
@ -400,7 +415,7 @@ def etud_add_group_infos(context, etud, sem, sep=" "):
def get_etud_groups_in_partition(context, partition_id):
"""Returns { etudid : group }, with all students in this partition"""
infos = SimpleDictFetch(
infos = ndb.SimpleDictFetch(
context,
"SELECT gd.*, etudid from group_descr gd, group_membership gm where gd.partition_id = %(partition_id)s and gm.group_id = gd.group_id",
{"partition_id": partition_id},
@ -419,7 +434,7 @@ def formsemestre_partition_list(context, formsemestre_id, format="xml", REQUEST=
# Ajoute les groupes
for p in partitions:
p["group"] = get_partition_groups(context, p)
return sendResult(REQUEST, partitions, name="partition", format=format)
return scu.sendResult(REQUEST, partitions, name="partition", format=format)
def XMLgetGroupsInPartition(context, partition_id, REQUEST=None): # was XMLgetGroupesTD
@ -440,8 +455,8 @@ def XMLgetGroupsInPartition(context, partition_id, REQUEST=None): # was XMLgetG
) # > inscrdict
etuds_set = set(nt.inscrdict)
# XML response:
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
doc = jaxml.XML_document(encoding=SCO_ENCODING)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
doc._text('<ajax-response><response type="object" id="MyUpdater">')
doc._push()
@ -458,7 +473,8 @@ def XMLgetGroupsInPartition(context, partition_id, REQUEST=None): # was XMLgetG
doc._push()
doc.etud(
etudid=e["etudid"],
sexe=scolars.format_sexe(etud["sexe"]),
civilite=etud["civilite_str"],
sexe=etud["civilite_str"], # compat
nom=scolars.format_nom(etud["nom"]),
prenom=scolars.format_prenom(etud["prenom"]),
origin=comp_origin(etud, sem),
@ -482,7 +498,7 @@ def XMLgetGroupsInPartition(context, partition_id, REQUEST=None): # was XMLgetG
doc._push()
doc.etud(
etudid=etud["etudid"],
sexe=scolars.format_sexe(etud["sexe"]),
sexe=etud["civilite_str"],
nom=scolars.format_nom(etud["nom"]),
prenom=scolars.format_prenom(etud["prenom"]),
origin=comp_origin(etud, sem),
@ -523,10 +539,10 @@ def set_group(context, etudid, group_id):
Warning: don't check if group_id exists (the caller should check).
"""
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
args = {"etudid": etudid, "group_id": group_id}
# déjà inscrit ?
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT * FROM group_membership gm WHERE etudid=%(etudid)s and group_id=%(group_id)s",
args,
@ -535,7 +551,7 @@ def set_group(context, etudid, group_id):
if len(r):
return False
# inscrit
SimpleQuery(
ndb.SimpleQuery(
context,
"INSERT INTO group_membership (etudid, group_id) VALUES (%(etudid)s, %(group_id)s)",
args,
@ -562,7 +578,7 @@ def change_etud_group_in_partition(
else:
partition = get_partition(context, group["partition_id"])
# 1- Supprime membership dans cette partition
SimpleQuery(
ndb.SimpleQuery(
context,
"""DELETE FROM group_membership WHERE group_membership_id IN
(SELECT gm.group_membership_id
@ -606,7 +622,7 @@ def setGroups(
"""
partition = get_partition(context, partition_id)
formsemestre_id = partition["formsemestre_id"]
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
log("***setGroups: partition_id=%s" % partition_id)
log("groupsLists=%s" % groupsLists)
@ -644,10 +660,10 @@ def setGroups(
)
# Retire les anciens membres:
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
for etudid in old_members_set:
log("removing %s from group %s" % (etudid, group_id))
SimpleQuery(
ndb.SimpleQuery(
context,
"DELETE FROM group_membership WHERE etudid=%(etudid)s and group_id=%(group_id)s",
{"etudid": etudid, "group_id": group_id},
@ -673,7 +689,7 @@ def setGroups(
if not group_name:
continue
# ajax arguments are encoded in utf-8:
group_name = unicode(group_name, "utf-8").encode(SCO_ENCODING)
group_name = unicode(group_name, "utf-8").encode(scu.SCO_ENCODING)
group_id = createGroup(context, partition_id, group_name, REQUEST=REQUEST)
# Place dans ce groupe les etudiants indiqués:
for etudid in fs[1:-1]:
@ -681,7 +697,7 @@ def setGroups(
context, etudid, group_id, partition, REQUEST=REQUEST
)
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
return (
'<?xml version="1.0" encoding="utf-8"?><response>Groupes enregistrés</response>'
)
@ -693,7 +709,7 @@ def createGroup(context, partition_id, group_name="", default=False, REQUEST=Non
"""
partition = get_partition(context, partition_id)
formsemestre_id = partition["formsemestre_id"]
if REQUEST and not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if REQUEST and not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
#
if group_name:
@ -730,7 +746,7 @@ def suppressGroup(context, group_id, partition_id=None, REQUEST=None):
else:
partition_id = group["partition_id"]
partition = get_partition(context, partition_id)
if not context.Notes.can_change_groups(REQUEST, partition["formsemestre_id"]):
if not can_change_groups(context, REQUEST, partition["formsemestre_id"]):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
log(
"suppressGroup: group_id=%s group_name=%s partition_name=%s"
@ -749,7 +765,7 @@ def partition_create(
redirect=1,
):
"""Create a new partition"""
if REQUEST and not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if REQUEST and not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
if partition_name:
partition_name = partition_name.strip()
@ -783,9 +799,9 @@ def partition_create(
def getArrowIconsTags(context, REQUEST):
"""returns html tags for arrows"""
#
arrow_up = icontag("arrow_up", title="remonter")
arrow_down = icontag("arrow_down", title="descendre")
arrow_none = icontag("arrow_none", title="")
arrow_up = scu.icontag("arrow_up", title="remonter")
arrow_down = scu.icontag("arrow_down", title="descendre")
arrow_none = scu.icontag("arrow_none", title="")
return arrow_up, arrow_down, arrow_none
@ -793,11 +809,11 @@ def getArrowIconsTags(context, REQUEST):
def editPartitionForm(context, formsemestre_id=None, REQUEST=None):
"""Form to create/suppress partitions"""
# ad-hoc form
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
partitions = get_partitions_list(context, formsemestre_id)
arrow_up, arrow_down, arrow_none = getArrowIconsTags(context, REQUEST)
suppricon = icontag(
suppricon = scu.icontag(
"delete_small_img", border="0", alt="supprimer", title="Supprimer"
)
#
@ -805,7 +821,7 @@ def editPartitionForm(context, formsemestre_id=None, REQUEST=None):
context.sco_header(
REQUEST, page_title="Partitions...", javascripts=["js/editPartitionForm.js"]
),
"""<script type="text/javascript">
r"""<script type="text/javascript">
function checkname() {
var val = document.editpart.partition_name.value.replace(/^\s+/, "").replace(/\s+$/, "");
if (val.length > 0) {
@ -816,7 +832,7 @@ def editPartitionForm(context, formsemestre_id=None, REQUEST=None):
}
</script>
""",
"""<h2>Partitions du semestre</h2>
r"""<h2>Partitions du semestre</h2>
<form name="editpart" id="editpart" method="POST" action="partition_create">
<div id="epmsg"></div>
<table><tr class="eptit"><th></th><th></th><th></th><th>Partition</th><th>Groupes</th><th></th><th></th><th></th></tr>
@ -831,13 +847,13 @@ def editPartitionForm(context, formsemestre_id=None, REQUEST=None):
)
if i != 0:
H.append(
'<a href="partition_move?partition_id=%s&amp;after=0">%s</a>'
'<a href="partition_move?partition_id=%s&after=0">%s</a>'
% (p["partition_id"], arrow_up)
)
H.append('</td><td class="epnav">')
if i < len(partitions) - 2:
H.append(
'<a href="partition_move?partition_id=%s&amp;after=1">%s</a>'
'<a href="partition_move?partition_id=%s&after=1">%s</a>'
% (p["partition_id"], arrow_down)
)
i += 1
@ -920,7 +936,7 @@ def partition_set_attr(context, partition_id, attr, value, REQUEST=None):
partition = get_partition(context, partition_id)
formsemestre_id = partition["formsemestre_id"]
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
log("partition_set_attr(%s, %s, %s)" % (partition_id, attr, value))
@ -943,7 +959,7 @@ def partition_delete(
default partition cannot be suppressed (unless force)"""
partition = get_partition(context, partition_id)
formsemestre_id = partition["formsemestre_id"]
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
if not partition["partition_name"] and not force:
@ -986,7 +1002,7 @@ def partition_move(context, partition_id, after=0, REQUEST=None, redirect=1):
"""Move before/after previous one (decrement/increment numero)"""
partition = get_partition(context, partition_id)
formsemestre_id = partition["formsemestre_id"]
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
#
redirect = int(redirect)
@ -1021,7 +1037,7 @@ def partition_rename(context, partition_id, REQUEST=None):
"""Form to rename a partition"""
partition = get_partition(context, partition_id)
formsemestre_id = partition["formsemestre_id"]
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
H = ["<h2>Renommer une partition</h2>"]
tf = TrivialFormulator(
@ -1072,7 +1088,7 @@ def partition_set_name(context, partition_id, partition_name, REQUEST=None, redi
formsemestre_id = partition["formsemestre_id"]
# check unicity
r = SimpleDictFetch(
r = ndb.SimpleDictFetch(
context,
"SELECT p.* FROM partition p WHERE p.partition_name = %(partition_name)s AND formsemestre_id = %(formsemestre_id)s",
{"partition_name": partition_name, "formsemestre_id": formsemestre_id},
@ -1082,7 +1098,7 @@ def partition_set_name(context, partition_id, partition_name, REQUEST=None, redi
"Partition %s déjà existante dans ce semestre !" % partition_name
)
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
redirect = int(redirect)
cnx = context.GetDBConnexion()
@ -1107,7 +1123,7 @@ def group_set_name(context, group_id, group_name, REQUEST=None, redirect=1):
if group["group_name"] is None:
raise ValueError("can't set a name to default group")
formsemestre_id = group["formsemestre_id"]
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
redirect = int(redirect)
cnx = context.GetDBConnexion()
@ -1124,7 +1140,7 @@ def group_rename(context, group_id, REQUEST=None):
"""Form to rename a group"""
group = get_group(context, group_id)
formsemestre_id = group["formsemestre_id"]
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
H = ["<h2>Renommer un groupe de %s</h2>" % group["partition_name"]]
tf = TrivialFormulator(
@ -1170,7 +1186,9 @@ def groups_auto_repartition(context, partition_id=None, REQUEST=None):
"""
partition = get_partition(context, partition_id)
formsemestre_id = partition["formsemestre_id"]
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
# renvoie sur page édition groupes
dest_url = "affectGroups?partition_id=%s" % partition_id
if not can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
@ -1210,7 +1228,7 @@ def groups_auto_repartition(context, partition_id=None, REQUEST=None):
if tf[0] == 0:
return "\n".join(H) + "\n" + tf[1] + context.sco_footer(REQUEST)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(REQUEST.URL1)
return REQUEST.RESPONSE.redirect(dest_url)
else:
# form submission
log(
@ -1238,34 +1256,33 @@ def groups_auto_repartition(context, partition_id=None, REQUEST=None):
context.Notes, formsemestre_id
) # > identdict
identdict = nt.identdict
# build: { sexe : liste etudids trie par niveau croissant }
sexes = sets.Set([x["sexe"] for x in identdict.values()])
# build: { civilite : liste etudids trie par niveau croissant }
civilites = sets.Set([x["civilite"] for x in identdict.values()])
listes = {}
for sexe in sexes:
listes[sexe] = [
for civilite in civilites:
listes[civilite] = [
(get_prev_moy(context.Notes, x["etudid"], formsemestre_id), x["etudid"])
for x in identdict.values()
if x["sexe"] == sexe
if x["civilite"] == civilite
]
listes[sexe].sort()
log("listes[%s] = %s" % (sexe, listes[sexe]))
listes[civilite].sort()
log("listes[%s] = %s" % (civilite, listes[civilite]))
# affect aux groupes:
n = len(identdict)
igroup = 0
nbgroups = len(group_ids)
while n > 0:
for sexe in sexes:
if len(listes[sexe]):
for civilite in civilites:
if len(listes[civilite]):
n -= 1
etudid = listes[sexe].pop()[1]
etudid = listes[civilite].pop()[1]
group_id = group_ids[igroup]
igroup = (igroup + 1) % nbgroups
change_etud_group_in_partition(
context, etudid, group_id, partition, REQUEST=REQUEST
)
log("%s in group %s" % (etudid, group_id))
# envoie sur page edition groupes
return REQUEST.RESPONSE.redirect("affectGroups?partition_id=%s" % partition_id)
return REQUEST.RESPONSE.redirect(dest_url)
def get_prev_moy(context, etudid, formsemestre_id):
@ -1367,7 +1384,7 @@ def do_evaluation_listeetuds_groups(
req += " and Isem.etat='I'"
req += r
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(req, {"evaluation_id": evaluation_id})
# log('listeetuds_groups: getallstudents=%s groups=%s' % (getallstudents,groups))
# log('req=%s' % (req % { 'evaluation_id' : "'"+evaluation_id+"'" }))
@ -1386,7 +1403,7 @@ def do_evaluation_listegroupes(context, evaluation_id, include_default=False):
else:
c = " AND p.partition_name is not NULL"
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"SELECT DISTINCT gd.group_id FROM group_descr gd, group_membership gm, partition p, notes_moduleimpl m, notes_evaluation e WHERE gm.group_id = gd.group_id and gd.partition_id = p.partition_id and p.formsemestre_id = m.formsemestre_id and m.moduleimpl_id = e.moduleimpl_id and e.evaluation_id = %(evaluation_id)s"
+ c,
@ -1399,7 +1416,7 @@ def do_evaluation_listegroupes(context, evaluation_id, include_default=False):
def listgroups(context, group_ids):
cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
groups = []
for group_id in group_ids:
cursor.execute(
@ -1477,7 +1494,7 @@ def form_group_choice(
def make_query_groups(group_ids):
if group_ids:
return "&amp;".join(["group_ids%3Alist=" + group_id for group_id in group_ids])
return "&".join(["group_ids%3Alist=" + group_id for group_id in group_ids])
else:
return ""

View File

@ -30,11 +30,12 @@
import re
from sco_utils import *
from notesdb import *
import sco_utils as scu
import notesdb as ndb
from notes_log import log
import sco_formsemestre
import sco_groups
from sco_exceptions import AccessDenied
def affectGroups(context, partition_id, REQUEST=None):
@ -44,7 +45,7 @@ def affectGroups(context, partition_id, REQUEST=None):
# Ported from DTML and adapted to new group management (nov 2009)
partition = sco_groups.get_partition(context, partition_id)
formsemestre_id = partition["formsemestre_id"]
if not context.Notes.can_change_groups(REQUEST, formsemestre_id):
if not sco_groups.can_change_groups(context, REQUEST, formsemestre_id):
raise AccessDenied("vous n'avez pas la permission d'effectuer cette opération")
H = [

Some files were not shown because too many files have changed in this diff Show More