Compare commits
4 Commits
9393f0d17b
...
1c718dffde
Author | SHA1 | Date | |
---|---|---|---|
1c718dffde | |||
72d126c04d | |||
612b62227c | |||
912c1dbd0b |
@ -249,6 +249,9 @@ class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Impl
|
||||
security.declareProtected(ScoView, "formsemestre_recapcomplet")
|
||||
formsemestre_recapcomplet = sco_recapcomplet.formsemestre_recapcomplet
|
||||
|
||||
security.declareProtected(ScoObservateur, "formsemestres_bulletins")
|
||||
formsemestres_bulletins = sco_recapcomplet.formsemestres_bulletins
|
||||
|
||||
security.declareProtected(ScoView, "moduleimpl_status")
|
||||
moduleimpl_status = sco_moduleimpl_status.moduleimpl_status
|
||||
|
||||
|
@ -10,8 +10,8 @@ export PATH="${PATH}":/usr/sbin:/sbin
|
||||
# ScoDoc: environment variables
|
||||
umask 0022
|
||||
|
||||
export SCODOC_DIR=${PWD%/*}
|
||||
# normalement: /opt/scodoc/Products/ScoDoc
|
||||
export SCODOC_DIR=/opt/scodoc/Products/ScoDoc
|
||||
|
||||
export SCODOC_VAR_DIR=$(realpath "$SCODOC_DIR/../../var/scodoc")
|
||||
# = /opt/scodoc/var/scodoc
|
||||
export SCODOC_VERSION_DIR="${SCODOC_VAR_DIR}/config/version"
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Modify ScoDoc install tu use systemd
|
||||
# Modify ScoDoc install to use systemd
|
||||
# E. Viennet, 2020-12-04
|
||||
source config.sh
|
||||
source utils.sh
|
||||
|
@ -122,7 +122,9 @@ def formsemestre_bulletinetud_published_dict(
|
||||
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)),
|
||||
photo_url=quote_xml_attr(
|
||||
sco_photos.etud_photo_url(context, etudinfo, fast=True)
|
||||
),
|
||||
email=quote_xml_attr(etudinfo["email"]),
|
||||
emailperso=quote_xml_attr(etudinfo["emailperso"]),
|
||||
)
|
||||
|
@ -124,8 +124,7 @@ def do_formsemestre_list(context, *a, **kw):
|
||||
|
||||
|
||||
def formsemestre_enrich(context, sem):
|
||||
"""Ajoute champs souvent utiles: titre + annee et dateord (pour tris)
|
||||
"""
|
||||
"""Ajoute champs souvent utiles: titre + annee et dateord (pour tris)"""
|
||||
# imports ici pour eviter refs circulaires
|
||||
import sco_formsemestre_edit
|
||||
import scolars
|
||||
@ -294,8 +293,7 @@ class ApoEtapeVDI:
|
||||
_ETAPE_VDI_SEP = "!"
|
||||
|
||||
def __init__(self, etape_vdi=None, etape="", vdi=""):
|
||||
"""Build from string representation, e.g. 'V1RT!111'
|
||||
"""
|
||||
"""Build from string representation, e.g. 'V1RT!111'"""
|
||||
if etape_vdi:
|
||||
self.etape_vdi = etape_vdi
|
||||
self.etape, self.vdi = self.split_etape_vdi(etape_vdi)
|
||||
@ -371,11 +369,11 @@ def sem_set_responsable_name(context, sem):
|
||||
def sem_in_semestre_scolaire(context, sem, year=False, saison=0, REQUEST=None):
|
||||
"""n'utilise que la date de debut, pivot au 1er aout
|
||||
si annee non specifiée, année scolaire courante
|
||||
Patch Jmp: ajout du parametre optionnel saison
|
||||
Patch Jmp: ajout du parametre optionnel saison
|
||||
1 = sept, 0 = janvier, None = année complète
|
||||
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
|
||||
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.
|
||||
)
|
||||
@ -417,17 +415,20 @@ def sem_une_annee(context, sem):
|
||||
pivot au 1er août.
|
||||
"""
|
||||
if sem["date_debut_iso"] > sem["date_fin_iso"]:
|
||||
log('Warning: semestre %(formsemestre_id)s begins after ending !' % sem)
|
||||
log("Warning: semestre %(formsemestre_id)s begins after ending !" % sem)
|
||||
return False
|
||||
|
||||
|
||||
debut = int(sem["annee_debut"])
|
||||
if sem["mois_debut_ord"] < 8: # considere que debut sur l'anne scolaire precedente
|
||||
if sem["mois_debut_ord"] < 8: # considere que debut sur l'anne scolaire precedente
|
||||
debut -= 1
|
||||
fin = int(sem["annee_fin"])
|
||||
if sem["mois_fin_ord"] < 9: # 9 (sept) pour autoriser un début en sept et une fin en aout
|
||||
if (
|
||||
sem["mois_fin_ord"] < 9
|
||||
): # 9 (sept) pour autoriser un début en sept et une fin en aout
|
||||
fin -= 1
|
||||
return debut == 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()
|
||||
@ -450,8 +451,7 @@ def table_formsemestres(
|
||||
html_next_section="",
|
||||
REQUEST=None,
|
||||
):
|
||||
"""Une table presentant des semestres
|
||||
"""
|
||||
"""Une table presentant des semestres"""
|
||||
for sem in sems:
|
||||
sem_set_responsable_name(context, sem)
|
||||
sem["_titre_num_target"] = (
|
||||
@ -498,17 +498,16 @@ def table_formsemestres(
|
||||
|
||||
|
||||
def list_formsemestre_by_etape(
|
||||
context, etape_apo=None, annee_scolaire=False, REQUEST=None
|
||||
context, etape_apo=False, annee_scolaire=False, REQUEST=None
|
||||
):
|
||||
"""Liste des semestres de cette etape, pour l'annee scolaire indiquée (sinon, pour toutes)
|
||||
"""
|
||||
"""Liste des semestres de cette etape, pour l'annee scolaire indiquée (sinon, pour toutes)"""
|
||||
ds = {} # formsemestre_id : sem
|
||||
if etape_apo:
|
||||
sems = do_formsemestre_list(context, args={"etape_apo": etape_apo})
|
||||
for sem in sems:
|
||||
if annee_scolaire: # restriction annee scolaire
|
||||
if sem_in_annee_scolaire(
|
||||
context, sem, year=annee_scolaire, REQUEST=REQUEST
|
||||
context, sem, year=int(annee_scolaire), REQUEST=REQUEST
|
||||
):
|
||||
ds[sem["formsemestre_id"]] = sem
|
||||
sems = ds.values()
|
||||
@ -519,7 +518,7 @@ def list_formsemestre_by_etape(
|
||||
sem
|
||||
for sem in sems
|
||||
if sem_in_annee_scolaire(
|
||||
context, sem, year=annee_scolaire, REQUEST=REQUEST
|
||||
context, sem, year=int(annee_scolaire), REQUEST=REQUEST
|
||||
)
|
||||
]
|
||||
|
||||
@ -528,8 +527,7 @@ def list_formsemestre_by_etape(
|
||||
|
||||
|
||||
def view_formsemestre_by_etape(context, etape_apo=None, format="html", REQUEST=None):
|
||||
"""Affiche table des semestres correspondants à l'étape
|
||||
"""
|
||||
"""Affiche table des semestres correspondants à l'étape"""
|
||||
if etape_apo:
|
||||
html_title = (
|
||||
"""<h2>Semestres courants de l'étape <tt>%s</tt></h2>""" % etape_apo
|
||||
|
@ -35,7 +35,7 @@ ScoEditFormationTags = (
|
||||
|
||||
ScoView = "Sco View"
|
||||
ScoEnsView = "Sco View Ens" # parties visibles par enseignants slt
|
||||
|
||||
ScoObservateur = "Sco Observateur" # accès lecture restreint aux bulletins
|
||||
ScoUsersAdmin = "Sco Users Manage"
|
||||
ScoUsersView = "Sco Users View"
|
||||
|
||||
@ -51,6 +51,7 @@ ScoSuperAdmin = "Sco Super Admin"
|
||||
# Default permissions for default roles
|
||||
# (set once on instance creation):
|
||||
Sco_Default_Permissions = {
|
||||
ScoObservateur: ("Ens", "Secr", "Admin", "RespPe"),
|
||||
ScoView: ("Ens", "Secr", "Admin", "RespPe"),
|
||||
ScoEnsView: ("Ens", "Admin", "RespPe"),
|
||||
ScoUsersView: ("Ens", "Secr", "Admin", "RespPe"),
|
||||
|
@ -53,9 +53,9 @@ from PIL import Image as PILImage
|
||||
from cStringIO import StringIO
|
||||
import glob
|
||||
|
||||
from sco_utils import *
|
||||
from sco_utils import CONFIG, SCO_SRCDIR
|
||||
from notes_log import log
|
||||
from notesdb import *
|
||||
|
||||
import scolars
|
||||
import sco_portal_apogee
|
||||
from scolog import logdb
|
||||
@ -64,7 +64,7 @@ from scolog import logdb
|
||||
PHOTO_DIR = os.path.join(os.environ["INSTANCE_HOME"], "var", "scodoc", "photos")
|
||||
ICONS_DIR = os.path.join(SCO_SRCDIR, "static", "icons")
|
||||
UNKNOWN_IMAGE_PATH = os.path.join(ICONS_DIR, "unknown.jpg")
|
||||
UNKNOWN_IMAGE_URL = "get_photo_image?etudid=" # with empty etudid => unknown face image
|
||||
UNKNOWN_IMAGE_URL = "get_photo_image?etudid=" # with empty etudid => unknown face image
|
||||
IMAGE_EXT = ".jpg"
|
||||
JPG_QUALITY = 0.92
|
||||
REDUCED_HEIGHT = 90 # pixels
|
||||
@ -81,11 +81,14 @@ def photo_portal_url(context, etud):
|
||||
else:
|
||||
return None
|
||||
|
||||
def etud_photo_url(context, etud, size="small", REQUEST=None):
|
||||
|
||||
def etud_photo_url(context, etud, size="small", fast=False, REQUEST=None):
|
||||
"""url to the image of the student, in "small" size or "orig" size.
|
||||
If ScoDoc doesn't have an image and a portal is configured, link to it.
|
||||
"""
|
||||
photo_url = "get_photo_image?etudid=%s&size=%s" % (etud["etudid"], size)
|
||||
if fast:
|
||||
return photo_url
|
||||
path = photo_pathname(context, etud, size=size)
|
||||
if not path:
|
||||
# Portail ?
|
||||
@ -95,7 +98,7 @@ def etud_photo_url(context, etud, size="small", REQUEST=None):
|
||||
photo_url = UNKNOWN_IMAGE_URL
|
||||
else:
|
||||
# essaie de copier la photo du portail
|
||||
new_path, diag = copy_portal_photo_to_fs(context, etud, REQUEST=REQUEST)
|
||||
new_path, _ = copy_portal_photo_to_fs(context, etud, REQUEST=REQUEST)
|
||||
if not new_path:
|
||||
# copy failed, can we use external url ?
|
||||
# nb: rarement utile, car le portail est rarement accessible sans authentification
|
||||
@ -114,7 +117,7 @@ def get_photo_image(context, etudid=None, size="small", REQUEST=None):
|
||||
filename = UNKNOWN_IMAGE_PATH
|
||||
else:
|
||||
etud = context.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0]
|
||||
filename = photo_pathname(context, etud, size=size) # os.path.join( PHOTO_DIR, etud["photo_filename"] )
|
||||
filename = photo_pathname(context, etud, size=size)
|
||||
if not filename:
|
||||
filename = UNKNOWN_IMAGE_PATH
|
||||
return _http_jpeg_file(context, filename, REQUEST=REQUEST)
|
||||
@ -160,23 +163,24 @@ def etud_photo_is_local(context, etud, size="small"):
|
||||
return photo_pathname(context, etud, size=size)
|
||||
|
||||
|
||||
def etud_photo_html(context, etud=None, etudid=None, title=None, size="small", REQUEST=None):
|
||||
"""HTML img tag for the photo, either in small size (h90)
|
||||
def etud_photo_html(
|
||||
context, etud=None, etudid=None, title=None, size="small", REQUEST=None
|
||||
):
|
||||
"""HTML img tag for the photo, either in small size (h90)
|
||||
or original size (size=="orig")
|
||||
"""
|
||||
if not etud:
|
||||
if etudid:
|
||||
etud = context.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0]
|
||||
else:
|
||||
raise ValueError('etud_photo_html: either etud or etudid must be specified')
|
||||
raise ValueError("etud_photo_html: either etud or etudid must be specified")
|
||||
photo_url = etud_photo_url(context, etud, size=size, REQUEST=REQUEST)
|
||||
nom = etud.get("nomprenom", etud["nom_disp"])
|
||||
if title is None:
|
||||
title = nom
|
||||
if not etud_photo_is_local(context, etud):
|
||||
fallback = (
|
||||
"""onerror='this.onerror = null; this.src="%s"'"""
|
||||
% UNKNOWN_IMAGE_URL
|
||||
"""onerror='this.onerror = null; this.src="%s"'""" % UNKNOWN_IMAGE_URL
|
||||
)
|
||||
else:
|
||||
fallback = ""
|
||||
@ -192,12 +196,16 @@ def etud_photo_html(context, etud=None, etudid=None, title=None, size="small", R
|
||||
fallback,
|
||||
)
|
||||
|
||||
|
||||
def etud_photo_orig_html(context, etud=None, etudid=None, title=None, REQUEST=None):
|
||||
"""HTML img tag for the photo, in full size.
|
||||
Full-size images are always stored locally in the filesystem.
|
||||
They are the original uploaded images, converted in jpeg.
|
||||
"""
|
||||
return etud_photo_html(context, etud=etud, etudid=etudid, title=title, size="orig", REQUEST=REQUEST)
|
||||
return etud_photo_html(
|
||||
context, etud=etud, etudid=etudid, title=title, size="orig", REQUEST=REQUEST
|
||||
)
|
||||
|
||||
|
||||
def photo_pathname(context, etud, size="orig"):
|
||||
"""Returns full path of image file if etud has a photo (in the filesystem), or False.
|
||||
@ -256,7 +264,6 @@ def suppress_photo(context, etud, REQUEST=None):
|
||||
scolars.identite_edit_nocheck(cnx, etud)
|
||||
cnx.commit()
|
||||
# 2- erase images files
|
||||
#log("rel_path=%s" % rel_path)
|
||||
if rel_path:
|
||||
# remove extension and glob
|
||||
rel_path = rel_path[: -len(IMAGE_EXT)]
|
||||
@ -274,6 +281,7 @@ def suppress_photo(context, etud, REQUEST=None):
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal functions
|
||||
|
||||
|
||||
def save_image(context, etudid, data):
|
||||
"""img_file is a file-like object.
|
||||
Save image in JPEG in 2 sizes (original and h90).
|
||||
@ -284,7 +292,7 @@ def save_image(context, etudid, data):
|
||||
data_file.seek(0)
|
||||
img = PILImage.open(data_file)
|
||||
filename = get_new_filename(context, etudid)
|
||||
path = os.path.join( PHOTO_DIR, filename )
|
||||
path = os.path.join(PHOTO_DIR, filename)
|
||||
log("saving %dx%d jpeg to %s" % (img.size[0], img.size[1], path))
|
||||
img.save(path + IMAGE_EXT, format="JPEG", quality=92)
|
||||
# resize:
|
||||
@ -342,6 +350,7 @@ def copy_portal_photo_to_fs(context, etud, REQUEST=None):
|
||||
f = urllib2.urlopen(url, timeout=portal_timeout) # python >= 2.7
|
||||
except:
|
||||
log("download failed: exception:\n%s" % traceback.format_exc())
|
||||
log("called from:\n" + "".join(traceback.format_stack()))
|
||||
return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
|
||||
if not f:
|
||||
log("download failed")
|
||||
|
@ -51,6 +51,7 @@ def formsemestre_recapcomplet(
|
||||
xml_with_decisions=False, # XML avec decisions
|
||||
rank_partition_id=None, # si None, calcul rang global
|
||||
pref_override=True, # si vrai, les prefs ont la priorite sur le param hidebac
|
||||
force_publishing=True, # publie les XML/JSON meme si bulletins non publiés
|
||||
REQUEST=None,
|
||||
):
|
||||
"""Page récapitulant les notes d'un semestre.
|
||||
@ -72,6 +73,7 @@ def formsemestre_recapcomplet(
|
||||
else:
|
||||
hidebac = int(hidebac)
|
||||
xml_with_decisions = int(xml_with_decisions)
|
||||
force_publishing = int(force_publishing)
|
||||
isFile = tabformat in ("csv", "xls", "xml", "xlsall", "json")
|
||||
H = []
|
||||
if not isFile:
|
||||
@ -144,6 +146,7 @@ def formsemestre_recapcomplet(
|
||||
sortcol=sortcol,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
rank_partition_id=rank_partition_id,
|
||||
force_publishing=force_publishing,
|
||||
)
|
||||
)
|
||||
|
||||
@ -191,6 +194,7 @@ def do_formsemestre_recapcomplet(
|
||||
xml_with_decisions=False,
|
||||
disable_etudlink=False,
|
||||
rank_partition_id=None, # si None, calcul rang global
|
||||
force_publishing=True,
|
||||
):
|
||||
"""Calcule et renvoie le tableau récapitulatif."""
|
||||
data, filename, format = make_formsemestre_recapcomplet(**vars())
|
||||
@ -219,6 +223,7 @@ def make_formsemestre_recapcomplet(
|
||||
xml_with_decisions=False,
|
||||
disable_etudlink=False,
|
||||
rank_partition_id=None, # si None, calcul rang global
|
||||
force_publishing=True, # donne bulletins JSON/XML meme si non publiés
|
||||
):
|
||||
"""Grand tableau récapitulatif avec toutes les notes de modules
|
||||
pour tous les étudiants, les moyennes par UE et générale,
|
||||
@ -226,11 +231,19 @@ def make_formsemestre_recapcomplet(
|
||||
"""
|
||||
if format == "xml":
|
||||
return _formsemestre_recapcomplet_xml(
|
||||
context, formsemestre_id, xml_nodate, xml_with_decisions=xml_with_decisions
|
||||
context,
|
||||
formsemestre_id,
|
||||
xml_nodate,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
)
|
||||
elif format == "json":
|
||||
return _formsemestre_recapcomplet_json(
|
||||
context, formsemestre_id, xml_nodate, xml_with_decisions=xml_with_decisions
|
||||
context,
|
||||
formsemestre_id,
|
||||
xml_nodate=xml_nodate,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
)
|
||||
if format[:3] == "xls":
|
||||
keep_numeric = True # pas de conversion des notes en strings
|
||||
@ -791,7 +804,11 @@ def _list_notes_evals_stats(context, evals, key):
|
||||
|
||||
|
||||
def _formsemestre_recapcomplet_xml(
|
||||
context, formsemestre_id, xml_nodate, xml_with_decisions=False
|
||||
context,
|
||||
formsemestre_id,
|
||||
xml_nodate,
|
||||
xml_with_decisions=False,
|
||||
force_publishing=True,
|
||||
):
|
||||
"XML export: liste tous les bulletins XML."
|
||||
|
||||
@ -825,7 +842,7 @@ def _formsemestre_recapcomplet_xml(
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
doc=doc,
|
||||
force_publishing=True,
|
||||
force_publishing=force_publishing,
|
||||
xml_nodate=xml_nodate,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
)
|
||||
@ -834,9 +851,17 @@ def _formsemestre_recapcomplet_xml(
|
||||
|
||||
|
||||
def _formsemestre_recapcomplet_json(
|
||||
context, formsemestre_id, xml_nodate, xml_with_decisions=False
|
||||
context,
|
||||
formsemestre_id,
|
||||
xml_nodate=False,
|
||||
xml_with_decisions=False,
|
||||
force_publishing=True,
|
||||
):
|
||||
"JSON export: liste tous les bulletins JSON"
|
||||
"""JSON export: liste tous les bulletins JSON
|
||||
:param xml_nodate(bool): indique la date courante (attribut docdate)
|
||||
:param force_publishing: donne les bulletins même si non "publiés sur portail"
|
||||
:returns: dict, "", "json"
|
||||
"""
|
||||
if xml_nodate:
|
||||
docdate = ""
|
||||
else:
|
||||
@ -865,8 +890,27 @@ def _formsemestre_recapcomplet_json(
|
||||
context,
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
force_publishing=True,
|
||||
force_publishing=force_publishing,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
)
|
||||
)
|
||||
return J, "", "json"
|
||||
|
||||
|
||||
def formsemestres_bulletins(context, annee_scolaire, REQUEST=None):
|
||||
"""Tous les bulletins des semestres publiés des semestres de l'année indiquée.
|
||||
:param annee_scolaire(int): année de début de l'année scoalaire
|
||||
:returns: JSON
|
||||
"""
|
||||
jslist = []
|
||||
sems = sco_formsemestre.list_formsemestre_by_etape(
|
||||
context, annee_scolaire=annee_scolaire
|
||||
)
|
||||
log("formsemestres_bulletins(%s): %d sems" % (annee_scolaire, len(sems)))
|
||||
for sem in sems:
|
||||
J, _, _ = _formsemestre_recapcomplet_json(
|
||||
context, sem["formsemestre_id"], force_publishing=False
|
||||
)
|
||||
jslist.append(J)
|
||||
|
||||
return sendJSON(REQUEST, jslist)
|
||||
|
Loading…
Reference in New Issue
Block a user