diff --git a/app/models/etudiants.py b/app/models/etudiants.py index c4e2ab4254..6485be2aec 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -9,7 +9,6 @@ from app import models from app.models import APO_CODE_STR_LEN from app.models import SHORT_STR_LEN from app.models import CODE_STR_LEN -from app.scodoc import sco_photos class Identite(db.Model): @@ -71,9 +70,11 @@ class Identite(db.Model): "le mail associé à la première adrese de l'étudiant, ou None" return self.adresses[0].email or None if self.adresses.count() > 0 else None - def to_dict_bul(self): + def to_dict_bul(self, include_photo=True): """Infos exportées dans les bulletins""" - return { + from app.scodoc import sco_photos + + d = { "civilite": self.civilite, "code_ine": self.code_nip, "code_nip": self.code_ine, @@ -84,9 +85,11 @@ class Identite(db.Model): "emailperso": self.get_first_email("emailperso"), "etudid": self.id, "nom": self.nom_disp(), - "photo_url": sco_photos.get_etud_photo_url(self.id), "prenom": self.prenom, } + if include_photo: + d["photo_url"] = (sco_photos.get_etud_photo_url(self.id),) + return d def inscription_courante(self): """La première inscription à un formsemestre _actuellement_ en cours. diff --git a/app/scodoc/sco_photos.py b/app/scodoc/sco_photos.py index b76e001d71..5d70168b60 100644 --- a/app/scodoc/sco_photos.py +++ b/app/scodoc/sco_photos.py @@ -42,9 +42,6 @@ Les images sont servies par ScoDoc, via la méthode getphotofile?etudid=xxx - support for legacy ZODB removed in v1909. """ - -from flask.helpers import make_response, url_for -from app.scodoc.sco_exceptions import ScoGenError import datetime import glob import io @@ -52,24 +49,26 @@ import os import random import requests import time -import traceback import PIL from PIL import Image as PILImage from flask import request, g +from flask.helpers import make_response, url_for -from config import Config - +from app import log +from app import db +from app.models import Identite from app.scodoc import sco_etud from app.scodoc import sco_portal_apogee from app.scodoc import sco_preferences -from app import log +from app.scodoc.sco_exceptions import ScoGenError from app.scodoc.scolog import logdb import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu +from config import Config -# Full paths on server's filesystem. Something like "/opt/scodoc/var/scodoc/photos" +# Full paths on server's filesystem. Something like "/opt/scodoc-data/photos" PHOTO_DIR = os.path.join(Config.SCODOC_VAR_DIR, "photos") ICONS_DIR = os.path.join(Config.SCODOC_DIR, "app", "static", "icons") UNKNOWN_IMAGE_PATH = os.path.join(ICONS_DIR, "unknown.jpg") @@ -97,14 +96,15 @@ def get_etud_photo_url(etudid, size="small"): ) -def etud_photo_url(etud, size="small", fast=False): +def etud_photo_url(etud: dict, size="small", fast=False) -> str: """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_etud_photo_url(etud["etudid"], size=size) if fast: return photo_url - path = photo_pathname(etud, size=size) + path = photo_pathname(etud["photo_filename"], size=size) if not path: # Portail ? ext_url = photo_portal_url(etud) @@ -131,8 +131,8 @@ def get_photo_image(etudid=None, size="small"): if not etudid: filename = UNKNOWN_IMAGE_PATH else: - etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0] - filename = photo_pathname(etud, size=size) + etud = Identite.query.get_or_404(etudid) + filename = photo_pathname(etud.photo_filename, size=size) if not filename: filename = UNKNOWN_IMAGE_PATH return _http_jpeg_file(filename) @@ -171,8 +171,8 @@ def _http_jpeg_file(filename): return response -def etud_photo_is_local(etud, size="small"): - return photo_pathname(etud, size=size) +def etud_photo_is_local(etud: dict, size="small"): + return photo_pathname(etud["photo_filename"], size=size) def etud_photo_html(etud=None, etudid=None, title=None, size="small"): @@ -215,9 +215,12 @@ def etud_photo_orig_html(etud=None, etudid=None, title=None): return etud_photo_html(etud=etud, etudid=etudid, title=title, size="orig") -def photo_pathname(etud, size="orig"): - """Returns full path of image file if etud has a photo (in the filesystem), or False. +def photo_pathname(photo_filename: str, size="orig"): + """Returns full path of image file if etud has a photo (in the filesystem), + or False. Do not distinguish the cases: no photo, or file missing. + Argument: photo_filename (Identite attribute) + Resultat: False or str """ if size == "small": version = H90 @@ -225,9 +228,9 @@ def photo_pathname(etud, size="orig"): version = "" else: raise ValueError("invalid size parameter for photo") - if not etud["photo_filename"]: + if not photo_filename: return False - path = os.path.join(PHOTO_DIR, etud["photo_filename"]) + version + IMAGE_EXT + path = os.path.join(PHOTO_DIR, photo_filename) + version + IMAGE_EXT if os.path.exists(path): return path else: @@ -264,15 +267,14 @@ def store_photo(etud, data): return 1, "ok" -def suppress_photo(etud): +def suppress_photo(etud: Identite) -> None: """Suppress a photo""" - log("suppress_photo etudid=%s" % etud["etudid"]) - rel_path = photo_pathname(etud) + log("suppress_photo etudid=%s" % etud.id) + rel_path = photo_pathname(etud.photo_filename) # 1- remove ref. from database - etud["photo_filename"] = None - cnx = ndb.GetDBConnexion() - sco_etud.identite_edit_nocheck(cnx, etud) - cnx.commit() + etud.photo_filename = None + db.session.add(etud) + # 2- erase images files if rel_path: # remove extension and glob @@ -281,8 +283,10 @@ def suppress_photo(etud): for filename in filenames: log("removing file %s" % filename) os.remove(filename) + db.session.commit() # 3- log - logdb(cnx, method="changePhoto", msg="suppression", etudid=etud["etudid"]) + cnx = ndb.GetDBConnexion() + logdb(cnx, method="changePhoto", msg="suppression", etudid=etud.id) # --------------------------------------------------------------------------- @@ -373,6 +377,9 @@ def copy_portal_photo_to_fs(etud): log("copy_portal_photo_to_fs: failure (exception in store_photo)!") if status == 1: log("copy_portal_photo_to_fs: copied %s" % url) - return photo_pathname(etud), "%s: photo chargée" % etud["nomprenom"] + return ( + photo_pathname(etud["photo_filename"]), + f"{etud['nomprenom']}: photo chargée", + ) else: return None, "%s: %s" % (etud["nomprenom"], diag) diff --git a/app/scodoc/sco_trombino.py b/app/scodoc/sco_trombino.py index b5bfb6b527..0849b03740 100644 --- a/app/scodoc/sco_trombino.py +++ b/app/scodoc/sco_trombino.py @@ -183,10 +183,11 @@ def trombino_html(groups_infos): def check_local_photos_availability(groups_infos, format=""): - """Verifie que toutes les photos (des gropupes indiqués) sont copiées localement - dans ScoDoc (seules les photos dont nous disposons localement peuvent être exportées - en pdf ou en zip). - Si toutes ne sont pas dispo, retourne un dialogue d'avertissement pour l'utilisateur. + """Vérifie que toutes les photos (des groupes indiqués) sont copiées + localement dans ScoDoc (seules les photos dont nous disposons localement + peuvent être exportées en pdf ou en zip). + Si toutes ne sont pas dispo, retourne un dialogue d'avertissement + pour l'utilisateur. """ nb_missing = 0 for t in groups_infos.members: @@ -221,7 +222,7 @@ def _trombino_zip(groups_infos): # assume we have the photos (or the user acknowledged the fact) # Archive originals (not reduced) images, in JPEG for t in groups_infos.members: - im_path = sco_photos.photo_pathname(t, size="orig") + im_path = sco_photos.photo_pathname(t["photo_filename"], size="orig") if not im_path: continue img = open(im_path, "rb").read() @@ -292,9 +293,9 @@ def trombino_copy_photos(group_ids=[], dialog_confirmed=False): def _get_etud_platypus_image(t, image_width=2 * cm): - """Returns aplatypus object for the photo of student t""" + """Returns a platypus object for the photo of student t""" try: - path = sco_photos.photo_pathname(t, size="small") + path = sco_photos.photo_pathname(t["photo_filename"], size="small") if not path: # log('> unknown') path = sco_photos.UNKNOWN_IMAGE_PATH diff --git a/app/views/scodoc.py b/app/views/scodoc.py index dfd8bcfe96..c098f0347d 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -134,7 +134,7 @@ def get_etud_dept(): last_etud = None last_date = None for etud in etuds: - inscriptions = FormSemestreInscription.query.filter_by(etudid=etud.id).all() + inscriptions = FormsemestreInscription.query.filter_by(etudid=etud.id).all() for ins in inscriptions: date_fin = FormSemestre.query.get(ins.formsemestre_id).date_fin if (last_date is None) or date_fin > last_date: diff --git a/app/views/scolar.py b/app/views/scolar.py index ebfd4d1a23..b83dbcace0 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -49,6 +49,7 @@ from app.decorators import ( admin_required, login_required, ) +from app.models.etudiants import Identite from app.views import scolar_bp as bp @@ -944,21 +945,21 @@ def formChangePhoto(etudid=None): @scodoc7func def formSuppressPhoto(etudid=None, dialog_confirmed=False): """Formulaire suppression photo étudiant""" - etud = sco_etud.get_etud_info(filled=True)[0] + etud = Identite.query.get_or_404(etudid) if not dialog_confirmed: return scu.confirm_dialog( - "

Confirmer la suppression de la photo de %(nomprenom)s ?

" % etud, + f"

Confirmer la suppression de la photo de {etud.nom_disp()} ?

", dest_url="", cancel_url=url_for( - "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid + "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id ), - parameters={"etudid": etudid}, + parameters={"etudid": etud.id}, ) sco_photos.suppress_photo(etud) return flask.redirect( - url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) + url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) )