Diverses modernisation du code

This commit is contained in:
Emmanuel Viennet 2024-02-03 23:25:05 +01:00
parent 5bbdc567f3
commit 4b2e88c678
12 changed files with 144 additions and 1307 deletions

View File

@ -52,7 +52,7 @@ class ScoDocModel(db.Model):
def create_from_dict(cls, data: dict) -> "ScoDocModel":
"""Create a new instance of the model with attributes given in dict.
The instance is added to the session (but not flushed nor committed).
Use only relevant arributes for the given model and ignore others.
Use only relevant attributes for the given model and ignore others.
"""
if data:
args = cls.convert_dict_fields(cls.filter_model_attributes(data))

View File

@ -742,6 +742,7 @@ def check_etud_duplicate_code(args, code_name, edit=True):
Raises ScoGenError si problème.
"""
etudid = args.get("etudid", None)
assert (not edit) or (etudid is not None) # si edit, etudid doit être spécifié
if not args.get(code_name, None):
return
etuds = Identite.query.filter_by(
@ -749,9 +750,7 @@ def check_etud_duplicate_code(args, code_name, edit=True):
).all()
duplicate = False
if edit:
duplicate = (len(etuds) > 1) or (
(len(etuds) == 1) and etuds[0].id != args["etudid"]
)
duplicate = (len(etuds) > 1) or ((len(etuds) == 1) and etuds[0].id != etudid)
else:
duplicate = len(etuds) > 0
if duplicate:

View File

@ -3,12 +3,13 @@
"""
import pandas as pd
from flask import abort, g
from flask_login import current_user
from flask_sqlalchemy.query import Query
from app import db
from app.auth.models import User
from app.comp import df_cache
from app.models import APO_CODE_STR_LEN
from app.models import APO_CODE_STR_LEN, ScoDocModel
from app.models.etudiants import Identite
from app.models.evaluations import Evaluation
from app.models.modules import Module
@ -17,7 +18,7 @@ from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_utils as scu
class ModuleImpl(db.Model):
class ModuleImpl(ScoDocModel):
"""Mise en oeuvre d'un module pour une annee/semestre"""
__tablename__ = "notes_moduleimpl"
@ -52,7 +53,6 @@ class ModuleImpl(db.Model):
secondary="notes_modules_enseignants",
lazy="dynamic",
backref="moduleimpl",
viewonly=True,
)
def __repr__(self):
@ -85,7 +85,7 @@ class ModuleImpl(db.Model):
@classmethod
def get_modimpl(cls, moduleimpl_id: int | str, dept_id: int = None) -> "ModuleImpl":
"""FormSemestre ou 404, cherche uniquement dans le département spécifié ou le courant."""
"""ModuleImpl ou 404, cherche uniquement dans le département spécifié ou le courant."""
from app.models.formsemestre import FormSemestre
if not isinstance(moduleimpl_id, int):
@ -187,7 +187,7 @@ class ModuleImpl(db.Model):
return allow_ens and user.id in (ens.id for ens in self.enseignants)
return True
def can_change_ens_by(self, user: User, raise_exc=False) -> bool:
def can_change_responsable(self, user: User, raise_exc=False) -> bool:
"""Check if user can modify module resp.
If raise_exc, raises exception (AccessDenied or ScoLockedSemError) if not.
= Admin, et dir des etud. (si option l'y autorise)
@ -208,6 +208,27 @@ class ModuleImpl(db.Model):
raise AccessDenied(f"Modification impossible pour {user}")
return False
def can_change_ens(self, user: User | None = None, raise_exc=True) -> bool:
"""check if user can modify ens list (raise exception if not)"
if user is None, current user.
"""
user = current_user if user is None else user
if not self.formsemestre.etat:
if raise_exc:
raise ScoLockedSemError("Modification impossible: semestre verrouille")
return False
# -- check access
# admin, resp. module ou resp. semestre
if (
user.id != self.responsable_id
and not user.has_permission(Permission.EditFormSemestre)
and user.id not in (u.id for u in self.formsemestre.responsables)
):
if raise_exc:
raise AccessDenied(f"Modification impossible pour {user}")
return False
return True
def est_inscrit(self, etud: Identite) -> bool:
"""
Vérifie si l'étudiant est bien inscrit au moduleimpl (même si DEM ou DEF au semestre).

View File

@ -33,7 +33,7 @@ from flask import g, request
from flask_login import current_user
from app import db
from app.models import Evaluation, GroupDescr, ModuleImpl, Partition
from app.models import Evaluation, GroupDescr, Identite, ModuleImpl, Partition
import app.scodoc.sco_utils as scu
from app.scodoc import sco_preferences
from app.scodoc.sco_permissions import Permission
@ -160,27 +160,32 @@ def sidebar(etudid: int = None):
etudid = request.form.get("etudid", None)
if etudid is not None:
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
params.update(etud)
params["fiche_url"] = url_for(
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
)
etud = Identite.get_etud(etudid)
# compte les absences du semestre en cours
H.append(
"""<h2 id="insidebar-etud"><a href="%(fiche_url)s" class="sidebar">
<font color="#FF0000">%(civilite_str)s %(nom_disp)s</font></a>
</h2>
<b>Absences</b>"""
% params
f"""<h2 id="insidebar-etud"><a href="{
url_for(
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
)
}" class="sidebar">
<font color="#FF0000">{etud.civilite_str} {etud.nom_disp()}</font></a>
</h2>
<b>Absences</b>"""
)
if etud["cursem"]:
cur_sem = etud["cursem"]
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, cur_sem)
inscription = etud.inscription_courante()
if inscription:
formsemestre = inscription.formsemestre
nbabs, nbabsjust = sco_assiduites.formsemestre_get_assiduites_count(
etudid, formsemestre
)
nbabsnj = nbabs - nbabsjust
H.append(
f"""<span title="absences du { cur_sem["date_debut"] } au {
cur_sem["date_fin"] }">({
sco_preferences.get_preference("assi_metrique", None)})
f"""<span title="absences du {
formsemestre.date_debut.strftime("%d/%m/%Y")
} au {
formsemestre.date_fin.strftime("%d/%m/%Y")
}">({
sco_preferences.get_preference("assi_metrique", None)})
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
)
H.append("<ul>")
@ -189,21 +194,24 @@ def sidebar(etudid: int = None):
cur_formsemestre_id = retreive_formsemestre_from_request()
H.append(
f"""
<li><a href="{ url_for('assiduites.ajout_assiduite_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid)
}">Ajouter</a></li>
<li><a href="{ url_for('assiduites.ajout_justificatif_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid,
formsemestre_id=cur_formsemestre_id,
)
}">Justifier</a></li>
<li><a href="{
url_for('assiduites.ajout_assiduite_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid)
}">Ajouter</a></li>
<li><a href="{
url_for('assiduites.ajout_justificatif_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid,
formsemestre_id=cur_formsemestre_id,
)
}">Justifier</a></li>
"""
)
if sco_preferences.get_preference("handle_billets_abs"):
H.append(
f"""<li><a href="{ url_for('absences.billets_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid)
}">Billets</a></li>"""
f"""<li><a href="{
url_for('absences.billets_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid)
}">Billets</a></li>"""
)
H.append(
f"""

File diff suppressed because it is too large Load Diff

View File

@ -268,7 +268,7 @@ def abs_notification_message(
"""
from app.scodoc import sco_bulletins
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
etud = Identite.get_etud(etudid)
# Variables accessibles dans les balises du template: %(nom_variable)s :
values = sco_bulletins.make_context_dict(formsemestre, etud)
@ -287,7 +287,7 @@ def abs_notification_message(
log("abs_notification_message: empty template, not sending message")
return None
subject = f"""[ScoDoc] Trop d'absences pour {etud["nomprenom"]}"""
subject = f"""[ScoDoc] Trop d'absences pour {etud.nomprenom}"""
msg = Message(subject, sender=email.get_from_addr(formsemestre.departement.acronym))
msg.body = txt
return msg

View File

@ -138,21 +138,18 @@ def etud_upload_file_form(etudid):
"""Page with a form to choose and upload a file, with a description."""
# check permission
if not can_edit_etud_archive(current_user):
raise AccessDenied("opération non autorisée pour %s" % current_user)
etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
raise AccessDenied(f"opération non autorisée pour {current_user}")
etud = Identite.get_etud(etudid)
H = [
html_sco_header.sco_header(
page_title="Chargement d'un document associé à %(nomprenom)s" % etud,
page_title=f"Chargement d'un document associé à {etud.nomprenom}",
),
"""<h2>Chargement d'un document associé à %(nomprenom)s</h2>
"""
% etud,
"""<p>Le fichier ne doit pas dépasser %sMo.</p>
"""
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
f"""<h2>Chargement d'un document associé à {etud.nomprenom}</h2>
<p>Le fichier ne doit pas dépasser {
scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)}Mo.</p>
""",
]
tf = TrivialFormulator(
request.base_url,
@ -176,20 +173,13 @@ def etud_upload_file_form(etudid):
if tf[0] == 0:
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
elif tf[0] == -1:
return flask.redirect(
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)
else:
data = tf[2]["datafile"].read()
descr = tf[2]["description"]
filename = tf[2]["datafile"].filename
etud_archive_id = etud["etudid"]
_store_etud_file_to_new_archive(
etud_archive_id, data, filename, description=descr
)
return flask.redirect(
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)
return flask.redirect(etud.url_fiche())
data = tf[2]["datafile"].read()
descr = tf[2]["description"]
filename = tf[2]["datafile"].filename
etud_archive_id = (etudid,)
_store_etud_file_to_new_archive(etud_archive_id, data, filename, description=descr)
return flask.redirect(etud.url_fiche())
def _store_etud_file_to_new_archive(
@ -209,23 +199,20 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
# check permission
if not can_edit_etud_archive(current_user):
raise AccessDenied(f"opération non autorisée pour {current_user}")
etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["etudid"]
etud = Identite.get_etud(etudid)
etud_archive_id = etudid
archive_id = ETUDS_ARCHIVER.get_id_from_name(
etud_archive_id, archive_name, dept_id=etud["dept_id"]
etud_archive_id, archive_name, dept_id=etud.dept_id
)
if not dialog_confirmed:
return scu.confirm_dialog(
"""<h2>Confirmer la suppression des fichiers ?</h2>
<p>Fichier associé le %s à l'étudiant %s</p>
<p>La suppression sera définitive.</p>"""
% (
ETUDS_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
etud["nomprenom"],
),
f"""<h2>Confirmer la suppression des fichiers ?</h2>
<p>Fichier associé le {
ETUDS_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M")
} à l'étudiant {etud.nomprenom}
</p>
<p>La suppression sera définitive.</p>
""",
dest_url="",
cancel_url=url_for(
"scolar.fiche_etud",
@ -236,22 +223,17 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
parameters={"etudid": etudid, "archive_name": archive_name},
)
ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud["dept_id"])
ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud.dept_id)
flash("Archive supprimée")
return flask.redirect(
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)
return flask.redirect(etud.url_fiche())
def etud_get_archived_file(etudid, archive_name, filename):
"""Send file to client."""
etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["etudid"]
etud = Identite.get_etud(etudid)
etud_archive_id = etud.id
return ETUDS_ARCHIVER.get_archived_file(
etud_archive_id, archive_name, filename, dept_id=etud["dept_id"]
etud_archive_id, archive_name, filename, dept_id=etud.dept_id
)

View File

@ -1273,29 +1273,24 @@ def do_formsemestre_clone(
log(f"created formsemestre {formsemestre_id}")
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
# 2- create moduleimpls
mods_orig = sco_moduleimpl.moduleimpl_list(formsemestre_id=orig_formsemestre_id)
for mod_orig in mods_orig:
args = mod_orig.copy()
modimpl_orig: ModuleImpl
for modimpl_orig in formsemestre_orig.modimpls:
args = modimpl_orig.to_dict(with_module=False)
args["formsemestre_id"] = formsemestre_id
mid = sco_moduleimpl.do_moduleimpl_create(args)
# copy notes_modules_enseignants
ens = sco_moduleimpl.do_ens_list(
args={"moduleimpl_id": mod_orig["moduleimpl_id"]}
)
for e in ens:
args = e.copy()
args["moduleimpl_id"] = mid
sco_moduleimpl.do_ens_create(args)
modimpl_new = ModuleImpl.create_from_dict(args)
db.session.flush()
# copy enseignants
for ens in modimpl_orig.enseignants:
modimpl_new.enseignants.append(ens)
db.session.add(modimpl_new)
# optionally, copy evaluations
if clone_evaluations:
for e in Evaluation.query.filter_by(
moduleimpl_id=mod_orig["moduleimpl_id"]
):
for e in Evaluation.query.filter_by(moduleimpl_id=modimpl_orig.id):
# copie en enlevant la date
new_eval = e.clone(
not_copying=("date_debut", "date_fin", "moduleimpl_id")
)
new_eval.moduleimpl_id = mid
new_eval.moduleimpl_id = modimpl_new.id
# Copie les poids APC de l'évaluation
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
db.session.commit()

View File

@ -37,7 +37,6 @@ from app.models import Formation
from app.scodoc import scolog
from app.scodoc import sco_formsemestre
from app.scodoc import sco_cache
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ScoValueError, AccessDenied
@ -362,45 +361,3 @@ def do_ens_create(args):
cnx = ndb.GetDBConnexion()
r = _modules_enseignantsEditor.create(cnx, args)
return r
def can_change_module_resp(moduleimpl_id):
"""Check if current user can modify module resp. (raise exception if not).
= Admin, et dir des etud. (si option l'y autorise)
"""
M = moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
# -- check lock
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
if not sem["etat"]:
raise ScoValueError("Modification impossible: semestre verrouille")
# -- check access
# admin ou resp. semestre avec flag resp_can_change_resp
if not current_user.has_permission(Permission.EditFormSemestre) and (
(current_user.id not in sem["responsables"]) or (not sem["resp_can_change_ens"])
):
raise AccessDenied(f"Modification impossible pour {current_user}")
return M, sem
def can_change_ens(moduleimpl_id, raise_exc=True):
"check if current user can modify ens list (raise exception if not)"
M = moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
# -- check lock
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
if not sem["etat"]:
if raise_exc:
raise ScoValueError("Modification impossible: semestre verrouille")
else:
return False
# -- check access
# admin, resp. module ou resp. semestre
if (
current_user.id != M["responsable_id"]
and not current_user.has_permission(Permission.EditFormSemestre)
and (current_user.id not in sem["responsables"])
):
if raise_exc:
raise AccessDenied("Modification impossible pour %s" % current_user)
else:
return False
return M, sem

View File

@ -244,7 +244,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
<span class="blacktt">({module_resp.user_name})</span>
""",
]
if modimpl.can_change_ens_by(current_user):
if modimpl.can_change_responsable(current_user):
H.append(
f"""<a class="stdlink" href="{url_for("notes.edit_moduleimpl_resp",
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
@ -253,14 +253,15 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
H.append("""</td><td>""")
H.append(", ".join([u.get_nomprenom() for u in modimpl.enseignants]))
H.append("""</td><td>""")
try:
sco_moduleimpl.can_change_ens(moduleimpl_id)
if modimpl.can_change_ens(raise_exc=False):
H.append(
"""<a class="stdlink" href="edit_enseignants_form?moduleimpl_id=%s">modifier les enseignants</a>"""
% moduleimpl_id
f"""<a class="stdlink" href="{
url_for("notes.edit_enseignants_form",
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id
)
}">modifier les enseignants</a>"""
)
except:
pass
H.append("""</td></tr>""")
# 2ieme ligne: Semestre, Coef

View File

@ -961,12 +961,15 @@ def formsemestre_custommenu_edit(formsemestre_id):
@scodoc7func
def edit_enseignants_form(moduleimpl_id):
"modif liste enseignants/moduleimpl"
M, sem = sco_moduleimpl.can_change_ens(moduleimpl_id)
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
modimpl.can_change_ens(raise_exc=True)
# --
header = html_sco_header.html_sem_header(
'Enseignants du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
% (moduleimpl_id, M["module"]["titre"]),
page_title="Enseignants du module %s" % M["module"]["titre"],
f"""Enseignants du <a href="{
url_for("notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
}">module {modimpl.module.titre or modimpl.module.code}</a>""",
page_title=f"Enseignants du module {modimpl.module.titre or modimpl.module.code}",
javascripts=["libjs/AutoSuggest.js"],
cssstyles=["css/autosuggest_inquisitor.css"],
bodyOnLoad="init_tf_form('')",
@ -981,21 +984,18 @@ def edit_enseignants_form(moduleimpl_id):
allowed_user_names = list(uid2display.values())
H = [
"<ul><li><b>%s</b> (responsable)</li>"
% uid2display.get(M["responsable_id"], M["responsable_id"])
f"""<ul><li><b>{
uid2display.get(modimpl.responsable_id, modimpl.responsable_id)
}</b> (responsable)</li>"""
]
for ens in M["ens"]:
u = db.session.get(User, ens["ens_id"])
if u:
nom = u.get_nomcomplet()
else:
nom = "? (compte inconnu)"
u: User
for u in modimpl.enseignants:
H.append(
f"""
<li>{nom} (<a class="stdlink" href="{
<li>{u.get_nomcomplet()} (<a class="stdlink" href="{
url_for('notes.edit_enseignants_form_delete',
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id,
ens_id=ens["ens_id"])
ens_id=u.id)
}">supprimer</a>)
</li>"""
)
@ -1006,7 +1006,7 @@ def edit_enseignants_form(moduleimpl_id):
<p class="help">Pour changer le responsable du module, passez par la
page "<a class="stdlink" href="{
url_for("notes.formsemestre_editwithmodules", scodoc_dept=g.scodoc_dept,
formsemestre_id=M["formsemestre_id"])
formsemestre_id=modimpl.formsemestre_id)
}">Modification du semestre</a>",
accessible uniquement au responsable de la formation (chef de département)
</p>
@ -1061,8 +1061,8 @@ def edit_enseignants_form(moduleimpl_id):
else:
# et qu'il n'est pas deja:
if (
ens_id in [x["ens_id"] for x in M["ens"]]
or ens_id == M["responsable_id"]
ens_id in (x.id for x in modimpl.enseignants)
or ens_id == modimpl.responsable_id
):
H.append(
f"""<p class="help">Enseignant {ens_id} déjà dans la liste !</p>"""
@ -1090,7 +1090,7 @@ def edit_moduleimpl_resp(moduleimpl_id: int):
Accessible par Admin et dir des etud si flag resp_can_change_ens
"""
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
modimpl.can_change_ens_by(current_user, raise_exc=True) # access control
modimpl.can_change_responsable(current_user, raise_exc=True) # access control
H = [
html_sco_header.html_sem_header(
f"""Modification du responsable du <a href="{
@ -1372,22 +1372,17 @@ def edit_enseignants_form_delete(moduleimpl_id, ens_id: int):
ens_id: user.id
"""
M, _ = sco_moduleimpl.can_change_ens(moduleimpl_id)
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
modimpl.can_change_ens(raise_exc=True)
# search ens_id
ok = False
for ens in M["ens"]:
if ens["ens_id"] == ens_id:
ok = True
ens: User | None = None
for ens in modimpl.enseignants:
if ens.id == ens_id:
break
if not ok:
if ens is None:
raise ScoValueError(f"invalid ens_id ({ens_id})")
ndb.SimpleQuery(
"""DELETE FROM notes_modules_enseignants
WHERE moduleimpl_id = %(moduleimpl_id)s
AND ens_id = %(ens_id)s
""",
{"moduleimpl_id": moduleimpl_id, "ens_id": ens_id},
)
modimpl.enseignants.remove(ens)
db.session.commit()
return flask.redirect(
url_for(
"notes.edit_enseignants_form",
@ -1399,18 +1394,6 @@ def edit_enseignants_form_delete(moduleimpl_id, ens_id: int):
# --- Gestion des inscriptions aux semestres
# Ancienne API, pas certain de la publier en ScoDoc8
# sco_publish(
# "/do_formsemestre_inscription_create",
# sco_formsemestre_inscriptions.do_formsemestre_inscription_create,
# Permission.EtudInscrit,
# )
# sco_publish(
# "/do_formsemestre_inscription_edit",
# sco_formsemestre_inscriptions.do_formsemestre_inscription_edit,
# Permission.EtudInscrit,
# )
sco_publish(
"/do_formsemestre_inscription_list",
sco_formsemestre_inscriptions.do_formsemestre_inscription_list,

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.6.92"
SCOVERSION = "9.6.93"
SCONAME = "ScoDoc"