diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py
index 918d14fd2..b73b871a8 100644
--- a/app/but/bulletin_but.py
+++ b/app/but/bulletin_but.py
@@ -104,9 +104,11 @@ class BulletinBUT:
"competence": None, # XXX TODO lien avec référentiel
"moyenne": None,
# Le bonus sport appliqué sur cette UE
- "bonus": fmt_note(res.bonus_ues[ue.id][etud.id])
- if res.bonus_ues is not None and ue.id in res.bonus_ues
- else fmt_note(0.0),
+ "bonus": (
+ fmt_note(res.bonus_ues[ue.id][etud.id])
+ if res.bonus_ues is not None and ue.id in res.bonus_ues
+ else fmt_note(0.0)
+ ),
"malus": fmt_note(res.malus[ue.id][etud.id]),
"capitalise": None, # "AAAA-MM-JJ" TODO #sco93
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
@@ -181,14 +183,16 @@ class BulletinBUT:
"is_external": ue_capitalisee.is_external,
"date_capitalisation": ue_capitalisee.event_date,
"formsemestre_id": ue_capitalisee.formsemestre_id,
- "bul_orig_url": url_for(
- "notes.formsemestre_bulletinetud",
- scodoc_dept=g.scodoc_dept,
- etudid=etud.id,
- formsemestre_id=ue_capitalisee.formsemestre_id,
- )
- if ue_capitalisee.formsemestre_id
- else None,
+ "bul_orig_url": (
+ url_for(
+ "notes.formsemestre_bulletinetud",
+ scodoc_dept=g.scodoc_dept,
+ etudid=etud.id,
+ formsemestre_id=ue_capitalisee.formsemestre_id,
+ )
+ if ue_capitalisee.formsemestre_id
+ else None
+ ),
"ressources": {}, # sans détail en BUT
"saes": {},
}
@@ -227,13 +231,15 @@ class BulletinBUT:
"id": modimpl.id,
"titre": modimpl.module.titre,
"code_apogee": modimpl.module.code_apogee,
- "url": url_for(
- "notes.moduleimpl_status",
- scodoc_dept=g.scodoc_dept,
- moduleimpl_id=modimpl.id,
- )
- if has_request_context()
- else "na",
+ "url": (
+ url_for(
+ "notes.moduleimpl_status",
+ scodoc_dept=g.scodoc_dept,
+ moduleimpl_id=modimpl.id,
+ )
+ if has_request_context()
+ else "na"
+ ),
"moyenne": {
# # moyenne indicative de module: moyenne des UE,
# # ignorant celles sans notes (nan)
@@ -242,18 +248,20 @@ class BulletinBUT:
# "max": fmt_note(moyennes_etuds.max()),
# "moy": fmt_note(moyennes_etuds.mean()),
},
- "evaluations": [
- self.etud_eval_results(etud, e)
- for e in modimpl.evaluations
- if (e.visibulletin or version == "long")
- and (e.id in modimpl_results.evaluations_etat)
- and (
- modimpl_results.evaluations_etat[e.id].is_complete
- or self.prefs["bul_show_all_evals"]
- )
- ]
- if version != "short"
- else [],
+ "evaluations": (
+ [
+ self.etud_eval_results(etud, e)
+ for e in modimpl.evaluations
+ if (e.visibulletin or version == "long")
+ and (e.id in modimpl_results.evaluations_etat)
+ and (
+ modimpl_results.evaluations_etat[e.id].is_complete
+ or self.prefs["bul_show_all_evals"]
+ )
+ ]
+ if version != "short"
+ else []
+ ),
}
return d
@@ -274,35 +282,43 @@ class BulletinBUT:
poids = collections.defaultdict(lambda: 0.0)
d = {
"id": e.id,
- "coef": fmt_note(e.coefficient)
- if e.evaluation_type == scu.EVALUATION_NORMALE
- else None,
+ "coef": (
+ fmt_note(e.coefficient)
+ if e.evaluation_type == Evaluation.EVALUATION_NORMALE
+ else None
+ ),
"date_debut": e.date_debut.isoformat() if e.date_debut else None,
"date_fin": e.date_fin.isoformat() if e.date_fin else None,
"description": e.description,
"evaluation_type": e.evaluation_type,
- "note": {
- "value": fmt_note(
- eval_notes[etud.id],
- note_max=e.note_max,
- ),
- "min": fmt_note(notes_ok.min(), note_max=e.note_max),
- "max": fmt_note(notes_ok.max(), note_max=e.note_max),
- "moy": fmt_note(notes_ok.mean(), note_max=e.note_max),
- },
+ "note": (
+ {
+ "value": fmt_note(
+ eval_notes[etud.id],
+ note_max=e.note_max,
+ ),
+ "min": fmt_note(notes_ok.min(), note_max=e.note_max),
+ "max": fmt_note(notes_ok.max(), note_max=e.note_max),
+ "moy": fmt_note(notes_ok.mean(), note_max=e.note_max),
+ }
+ if not e.is_blocked()
+ else {}
+ ),
"poids": poids,
- "url": url_for(
- "notes.evaluation_listenotes",
- scodoc_dept=g.scodoc_dept,
- evaluation_id=e.id,
- )
- if has_request_context()
- else "na",
+ "url": (
+ url_for(
+ "notes.evaluation_listenotes",
+ scodoc_dept=g.scodoc_dept,
+ evaluation_id=e.id,
+ )
+ if has_request_context()
+ else "na"
+ ),
# deprecated (supprimer avant #sco9.7)
"date": e.date_debut.isoformat() if e.date_debut else None,
- "heure_debut": e.date_debut.time().isoformat("minutes")
- if e.date_debut
- else None,
+ "heure_debut": (
+ e.date_debut.time().isoformat("minutes") if e.date_debut else None
+ ),
"heure_fin": e.date_fin.time().isoformat("minutes") if e.date_fin else None,
}
return d
@@ -524,9 +540,9 @@ class BulletinBUT:
d.update(infos)
# --- Rangs
- d[
- "rang_nt"
- ] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
+ d["rang_nt"] = (
+ f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
+ )
d["rang_txt"] = "Rang " + d["rang_nt"]
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
diff --git a/app/but/bulletin_but_court.py b/app/but/bulletin_but_court.py
index 7fb389a5c..a4fc6ec13 100644
--- a/app/but/bulletin_but_court.py
+++ b/app/but/bulletin_but_court.py
@@ -119,6 +119,12 @@ def _build_bulletin_but_infos(
refcomp = formsemestre.formation.referentiel_competence
if refcomp is None:
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
+
+ warn_html = cursus_but.formsemestre_warning_apc_setup(
+ formsemestre, bulletins_sem.res
+ )
+ if warn_html:
+ raise ScoValueError("Formation mal configurée pour le BUT" + warn_html)
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
refcomp, etud
)
diff --git a/app/but/bulletin_but_pdf.py b/app/but/bulletin_but_pdf.py
index f56b86c0f..999846f77 100644
--- a/app/but/bulletin_but_pdf.py
+++ b/app/but/bulletin_but_pdf.py
@@ -24,7 +24,7 @@ from reportlab.lib.colors import blue
from reportlab.lib.units import cm, mm
from reportlab.platypus import Paragraph, Spacer
-from app.models import ScoDocSiteConfig
+from app.models import Evaluation, ScoDocSiteConfig
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
from app.scodoc import gen_tables
from app.scodoc.codes_cursus import UE_SPORT
@@ -422,7 +422,11 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
def evaluations_rows(self, rows, evaluations: list[dict], ue_acros=()):
"lignes des évaluations"
for e in evaluations:
- coef = e["coef"] if e["evaluation_type"] == scu.EVALUATION_NORMALE else "*"
+ coef = (
+ e["coef"]
+ if e["evaluation_type"] == Evaluation.EVALUATION_NORMALE
+ else "*"
+ )
t = {
"titre": f"{e['description'] or ''}",
"moyenne": e["note"]["value"],
@@ -431,7 +435,10 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
),
"coef": coef,
"_coef_pdf": Paragraph(
- f"
UE : {ue.acronyme}
") + if ( + modimpl.module.module_type == ModuleType.MALUS + or evaluation.evaluation_type == Evaluation.EVALUATION_BONUS + ): # store min/max values used by JS client-side checks: H.append( """-20. 20.""" ) else: - # date et absences (pas pour evals de malus) + # date et absences (pas pour evals bonus ni des modules de malus) if evaluation.date_debut is not None: H.append(f"Réalisée le {evaluation.descr_date()} ") group_id = sco_groups.get_default_group(modimpl.formsemestre_id) diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py index 0d4095b6f..ea72d2b58 100644 --- a/app/scodoc/sco_excel.py +++ b/app/scodoc/sco_excel.py @@ -534,7 +534,7 @@ def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, line # description evaluation ws.append_single_cell_row(scu.unescape_html(description), style_titres) ws.append_single_cell_row( - f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient or 0.0):g})", + f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient):g})", style, ) # ligne blanche diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py index 82339db95..23552c5c7 100644 --- a/app/scodoc/sco_exceptions.py +++ b/app/scodoc/sco_exceptions.py @@ -28,6 +28,7 @@ """Exception handling """ from flask_login import current_user +import app # --- Exceptions @@ -237,8 +238,11 @@ class ScoTemporaryError(ScoValueError): def __init__(self, msg: str = ""): msg = """ -
"Erreur temporaire
-Veuillez ré-essayer. Si le problème persiste, merci de contacter l'assistance ScoDoc +
Erreur temporaire
+Veuillez ré-essayer. Si le problème persiste (ou s'il venait + à se produire fréquemment), merci de contacter l'assistance ScoDoc + (voir les informations de contact).
""" + app.clear_scodoc_cache() super().__init__(msg) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index a9f46380c..921c0070b 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -31,6 +31,7 @@ import flask from flask import url_for, flash, redirect from flask import g, request from flask_login import current_user +import sqlalchemy as sa from app import db from app.auth.models import User @@ -63,8 +64,6 @@ from app.scodoc import html_sco_header from app.scodoc import codes_cursus from app.scodoc import sco_compute_moy from app.scodoc import sco_edit_module -from app.scodoc import sco_edit_ue -from app.scodoc import sco_evaluation_db from app.scodoc import sco_formsemestre from app.scodoc import sco_groups_copy from app.scodoc import sco_modalites @@ -1113,7 +1112,8 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del): f"""impossible de supprimer {module.code} ({module.titre or ""}) car il y a {nb_evals} évaluations définies (supprimez-les d\'abord)""" ] ok = False @@ -1233,7 +1233,11 @@ def formsemestre_clone(formsemestre_id): return "".join(H) + msg + tf[1] + html_sco_header.sco_footer() elif tf[0] == -1: # cancel return flask.redirect( - "formsemestre_status?formsemestre_id=%s" % formsemestre_id + url_for( + "notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre_id, + ) ) else: resp = User.get_user_from_nomplogin(tf[2]["responsable_id"]) @@ -1356,9 +1360,9 @@ def do_formsemestre_clone( return formsemestre_id -def formsemestre_delete(formsemestre_id): +def formsemestre_delete(formsemestre_id: int) -> str | flask.Response: """Delete a formsemestre (affiche avertissements)""" - formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) + formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id) H = [ html_sco_header.html_sem_header("Suppression du semestre"), """Attention: il y a {len(evals)} évaluations + f"""
Attention: il y a {len(evaluations)} évaluations dans ce semestre (sa suppression entrainera l'effacement définif des notes) !
""" ) - submit_label = ( - f"Confirmer la suppression (du semestre et des {len(evals)} évaluations !)" - ) + submit_label = f"Confirmer la suppression (du semestre et des {len(evaluations)} évaluations !)" else: submit_label = "Confirmer la suppression du semestre" tf = TrivialFormulator( @@ -1413,8 +1418,10 @@ Ceci n'est possible que si : ) else: H.append(tf[1]) + return "\n".join(H) + html_sco_header.sco_footer() - elif tf[0] == -1: # cancel + + if tf[0] == -1: # cancel return flask.redirect( url_for( "notes.formsemestre_status", @@ -1422,10 +1429,9 @@ Ceci n'est possible que si : formsemestre_id=formsemestre_id, ) ) - else: - return flask.redirect( - "formsemestre_delete2?formsemestre_id=" + str(formsemestre_id) - ) + return flask.redirect( + "formsemestre_delete2?formsemestre_id=" + str(formsemestre_id) + ) def formsemestre_delete2(formsemestre_id, dialog_confirmed=False): @@ -1486,106 +1492,165 @@ def formsemestre_has_decisions_or_compensations( return False, "" -def do_formsemestre_delete(formsemestre_id): +def do_formsemestre_delete(formsemestre_id: int): """delete formsemestre, and all its moduleimpls. No checks, no warnings: erase all ! """ - cnx = ndb.GetDBConnexion() - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - - sco_cache.EvaluationCache.invalidate_sem(formsemestre_id) - + formsemestre = FormSemestre.get_formsemestre(formsemestre_id) + sco_cache.EvaluationCache.invalidate_sem(formsemestre.id) + titre_sem = formsemestre.titre_annee() # --- Destruction des modules de ce semestre - mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) - for mod in mods: + for modimpl in formsemestre.modimpls: # evaluations - evals = sco_evaluation_db.get_evaluations_dict( - args={"moduleimpl_id": mod["moduleimpl_id"]} - ) - for e in evals: - ndb.SimpleQuery( - "DELETE FROM notes_notes WHERE evaluation_id=%(evaluation_id)s", - e, + for e in modimpl.evaluations: + db.session.execute( + sa.text( + """DELETE FROM notes_notes WHERE evaluation_id=:evaluation_id""" + ), + {"evaluation_id": e.id}, ) - ndb.SimpleQuery( - "DELETE FROM notes_notes_log WHERE evaluation_id=%(evaluation_id)s", - e, - ) - ndb.SimpleQuery( - "DELETE FROM notes_evaluation WHERE id=%(evaluation_id)s", - e, + db.session.execute( + sa.text( + """DELETE FROM notes_notes_log WHERE evaluation_id=:evaluation_id""" + ), + {"evaluation_id": e.id}, ) - sco_moduleimpl.do_moduleimpl_delete( - mod["moduleimpl_id"], formsemestre_id=formsemestre_id - ) + db.session.delete(e) + db.session.delete(modimpl) # --- Desinscription des etudiants - 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}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) + # --- Suppression des evenements - req = "DELETE FROM scolar_events WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text("DELETE FROM scolar_events WHERE formsemestre_id=:formsemestre_id"), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des appreciations - req = "DELETE FROM notes_appreciations WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_appreciations WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Supression des validations (!!!) - req = "DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Supression des references a ce semestre dans les compensations: - req = "UPDATE scolar_formsemestre_validation SET compense_formsemestre_id=NULL WHERE compense_formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + """UPDATE scolar_formsemestre_validation + SET compense_formsemestre_id=NULL + WHERE compense_formsemestre_id=:formsemestre_id""" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des autorisations - req = "DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des coefs d'UE capitalisées - req = "DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des item du menu custom - req = "DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des formules - req = "DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des preferences - req = "DELETE FROM sco_prefs WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text("DELETE FROM sco_prefs WHERE formsemestre_id=:formsemestre_id"), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des groupes et partitions - req = """DELETE FROM group_membership + db.session.execute( + sa.text( + """ + DELETE FROM group_membership WHERE group_id IN (SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd WHERE gm.group_id = gd.id AND gd.partition_id = p.id - AND p.formsemestre_id=%(formsemestre_id)s) + AND p.formsemestre_id=:formsemestre_id) """ - cursor.execute(req, {"formsemestre_id": formsemestre_id}) - req = """DELETE FROM group_descr + ), + {"formsemestre_id": formsemestre_id}, + ) + + db.session.execute( + sa.text( + """ + DELETE FROM group_descr WHERE id IN (SELECT gd.id FROM group_descr gd, partition p WHERE gd.partition_id = p.id - AND p.formsemestre_id=%(formsemestre_id)s) + AND p.formsemestre_id=:formsemestre_id) """ - cursor.execute(req, {"formsemestre_id": formsemestre_id}) - req = "DELETE FROM partition WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + ), + {"formsemestre_id": formsemestre_id}, + ) + db.session.execute( + sa.text("DELETE FROM partition WHERE formsemestre_id=:formsemestre_id"), + {"formsemestre_id": formsemestre_id}, + ) # --- Responsables - req = """DELETE FROM notes_formsemestre_responsables - WHERE formsemestre_id=%(formsemestre_id)s""" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_responsables WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Etapes - req = """DELETE FROM notes_formsemestre_etapes - WHERE formsemestre_id=%(formsemestre_id)s""" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_etapes WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) + # --- SemSets + db.session.execute( + sa.text( + "DELETE FROM notes_semset_formsemestre WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Dispenses d'UE - req = """DELETE FROM "dispenseUE" WHERE formsemestre_id=%(formsemestre_id)s""" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text("""DELETE FROM "dispenseUE" WHERE formsemestre_id=:formsemestre_id"""), + {"formsemestre_id": formsemestre_id}, + ) # --- Destruction du semestre - sco_formsemestre._formsemestreEditor.delete(cnx, formsemestre_id) + db.session.delete(formsemestre) # news ScolarNews.add( typ=ScolarNews.NEWS_SEM, obj=formsemestre_id, - text="Suppression du semestre %(titre)s" % sem, + text=f"Suppression du semestre {titre_sem}", max_frequency=0, ) @@ -1678,7 +1743,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): sum_coefs_by_ue_id = {} for ue in ues: sum_coefs_by_ue_id[ue.id] = sum( - modimpl.module.coefficient + modimpl.module.coefficient or 0.0 for modimpl in modimpls if modimpl.module.ue_id == ue.id ) diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py index e3474ebf2..f8ddc30d5 100644 --- a/app/scodoc/sco_formsemestre_inscriptions.py +++ b/app/scodoc/sco_formsemestre_inscriptions.py @@ -191,12 +191,26 @@ def do_formsemestre_inscription_edit(args=None, formsemestre_id=None): ) # > modif inscription semestre -def do_formsemestre_desinscription(etudid, formsemestre_id): +def check_if_has_decision_jury( + formsemestre: FormSemestre, etudids: list[int] | set[int] +): + "raise exception if one of the etuds has a decision in formsemestre" + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + for etudid in etudids: + if nt.etud_has_decision(etudid): + etud = Identite.query.get(etudid) + raise ScoValueError( + f"""désinscription impossible: l'étudiant {etud.nomprenom} a + une décision de jury (la supprimer avant si nécessaire)""" + ) + + +def do_formsemestre_desinscription( + etudid, formsemestre_id: int, check_has_dec_jury=True +): """Désinscription d'un étudiant. Si semestre extérieur et dernier inscrit, suppression de ce semestre. """ - from app.scodoc import sco_formsemestre_edit - formsemestre = FormSemestre.get_formsemestre(formsemestre_id) etud = Identite.get_etud(etudid) # -- check lock @@ -204,13 +218,8 @@ def do_formsemestre_desinscription(etudid, formsemestre_id): raise ScoValueError("désinscription impossible: semestre verrouille") # -- Si decisions de jury, désinscription interdite - nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - - if nt.etud_has_decision(etudid): - raise ScoValueError( - f"""désinscription impossible: l'étudiant {etud.nomprenom} a - une décision de jury (la supprimer avant si nécessaire)""" - ) + if check_has_dec_jury: + check_if_has_decision_jury(formsemestre, [etudid]) insem = do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id, "etudid": etudid} @@ -247,17 +256,14 @@ def do_formsemestre_desinscription(etudid, formsemestre_id): sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) # --- Semestre extérieur if formsemestre.modalite == "EXT": - inscrits = do_formsemestre_inscription_list( - args={"formsemestre_id": formsemestre_id} - ) - nbinscrits = len(inscrits) - if nbinscrits == 0: + if 0 == len(formsemestre.inscriptions): log( f"""do_formsemestre_desinscription: suppression du semestre extérieur {formsemestre}""" ) - flash("Semestre exterieur supprimé") - sco_formsemestre_edit.do_formsemestre_delete(formsemestre_id) + db.session.delete(formsemestre) + db.session.commit() + flash(f"Semestre extérieur supprimé: {formsemestre.titre_annee()}") logdb( cnx, @@ -576,26 +582,29 @@ def formsemestre_inscription_option(etudid, formsemestre_id): ue_id = ue.id ue_descr = ue.acronyme if ue.type != UE_STANDARD: - ue_descr += " %s" % UE_TYPE_NAME[ue.type] + ue_descr += f" {UE_TYPE_NAME[ue.type]}" ue_status = nt.get_etud_ue_status(etudid, ue_id) if ue_status and ue_status["is_capitalized"]: sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"]) - ue_descr += ( - ' (capitalisée le %s)' - % ( - sem_origin["formsemestre_id"], - etudid, - sem_origin["titreannee"], - ndb.DateISOtoDMY(ue_status["event_date"]), - ) - ) + ue_descr += f""" + (capitalisée le { + ndb.DateISOtoDMY(ue_status["event_date"]) + }) + """ descr.append( ( - "sec_%s" % ue_id, + f"sec_{ue_id}", { "input_type": "separator", - "title": """%s : inscrire | désinscrire à tous les modules""" - % (ue_descr, ue_id, ue_id), + "title": f"""{ue_descr} : + inscrire | désinscrire + à tous les modules + """, }, ) ) @@ -765,9 +774,7 @@ def do_moduleimpl_incription_options( # verifie que ce module existe bien mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id) if len(mods) != 1: - raise ScoValueError( - "inscription: invalid moduleimpl_id: %s" % moduleimpl_id - ) + raise ScoValueError(f"inscription: invalid moduleimpl_id: {moduleimpl_id}") mod = mods[0] sco_moduleimpl.do_moduleimpl_inscription_create( {"moduleimpl_id": moduleimpl_id, "etudid": etudid}, @@ -779,7 +786,7 @@ def do_moduleimpl_incription_options( mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id) if len(mods) != 1: raise ScoValueError( - "desinscription: invalid moduleimpl_id: %s" % moduleimpl_id + f"desinscription: invalid moduleimpl_id: {moduleimpl_id}" ) mod = mods[0] inscr = sco_moduleimpl.do_moduleimpl_inscription_list( @@ -787,8 +794,7 @@ def do_moduleimpl_incription_options( ) if not inscr: raise ScoValueError( - "pas inscrit a ce module ! (etudid=%s, moduleimpl_id=%s)" - % (etudid, moduleimpl_id) + f"pas inscrit a ce module ! (etudid={etudid}, moduleimpl_id={moduleimpl_id})" ) oid = inscr[0]["moduleimpl_inscription_id"] sco_moduleimpl.do_moduleimpl_inscription_delete( @@ -797,11 +803,13 @@ def do_moduleimpl_incription_options( H = [ html_sco_header.sco_header(), - """+ Retour à la fiche étudiant +
+ """, html_sco_header.sco_footer(), ] return "\n".join(H) @@ -845,49 +853,59 @@ def formsemestre_inscrits_ailleurs(formsemestre_id): """Page listant les étudiants inscrits dans un autre semestre dont les dates recouvrent le semestre indiqué. """ - sem = sco_formsemestre.get_formsemestre(formsemestre_id) H = [ html_sco_header.html_sem_header( "Inscriptions multiples parmi les étudiants du semestre ", + init_qtip=True, + javascripts=["js/etud_info.js"], ) ] insd = list_inscrits_ailleurs(formsemestre_id) # liste ordonnée par nom - etudlist = [ - sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - for etudid in insd.keys() - if insd[etudid] - ] - etudlist.sort(key=lambda x: x["nom"]) + etudlist = [Identite.get_etud(etudid) for etudid, sems in insd.items() if sems] + etudlist.sort(key=lambda x: x.sort_key) if etudlist: H.append("Total: %d étudiants concernés.
" % len(etudlist)) H.append( - """Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps !
Sauf exception, cette situation est anormale:
Total: {len(etudlist)} étudiants concernés.
+ +Ces étudiants sont inscrits dans le semestre sélectionné et aussi + dans d'autres semestres qui se déroulent en même temps ! +
++ Sauf exception, cette situation est anormale: +
Confirmer ?
" if todo else "", add_headers=False, cancel_url="formsemestre_inscr_passage?formsemestre_id=" @@ -395,16 +407,26 @@ def formsemestre_inscr_passage( ) ) else: + # check decisions jury ici pour éviter de recontruire le cache + # après chaque desinscription + sco_formsemestre_inscriptions.check_if_has_decision_jury( + formsemestre, a_desinscrire + ) + # check decisions jury ici pour éviter de recontruire le cache + # après chaque desinscription + sco_formsemestre_inscriptions.check_if_has_decision_jury( + formsemestre, a_desinscrire + ) with sco_cache.DeferredSemCacheManager(): # Inscription des étudiants au nouveau semestre: do_inscrit( - sem, + formsemestre, a_inscrire, inscrit_groupes=inscrit_groupes, inscrit_parcours=inscrit_parcours, ) # Désinscriptions: - do_desinscrit(sem, a_desinscrire) + do_desinscrit(formsemestre, a_desinscrire, check_has_dec_jury=False) H.append( f"""Cette page permet d'inscrire des étudiants dans le semestre destination {sem['titreannee']}, + url_for("notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id ) + }">{formsemestre.titre_annee()}, et d'en désincrire si besoin.
Les étudiants sont groupés par semestre d'origine. Ceux qui sont en caractères - gras sont déjà inscrits dans le semestre destination. - Ceux qui sont en gras et en rouge sont inscrits + gras sont déjà inscrits dans le semestre destination. + Ceux qui sont en gras et en rouge sont inscrits dans un autre semestre.
Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter @@ -555,7 +577,7 @@ def formsemestre_inscr_passage_help(sem: dict): conserve les groupes, on conserve les parcours (là aussi, pensez à les cocher dans modifier le semestre avant de faire passer les étudiants). @@ -656,25 +678,24 @@ def etuds_select_boxes( H.append("
L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules mais permet de "dispenser" un étudiant de suivre certaines UEs de son parcours.
-Il peut s'agit d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE +
Il peut s'agir d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE présente dans le semestre mais pas dans le parcours de l'étudiant, ou bien d'autres cas particuliers.
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index 9ee69aec3..336d49ff2 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -519,16 +519,22 @@ def _ligne_evaluation( partition_id=partition_id, select_first_partition=True, ) - if evaluation.evaluation_type in ( - scu.EVALUATION_RATTRAPAGE, - scu.EVALUATION_SESSION2, - ): + if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE: tr_class = "mievr mievr_rattr" + elif evaluation.evaluation_type == Evaluation.EVALUATION_SESSION2: + tr_class = "mievr mievr_session2" + elif evaluation.evaluation_type == Evaluation.EVALUATION_BONUS: + tr_class = "mievr mievr_bonus" else: tr_class = "mievr" + if not evaluation.visibulletin: tr_class += " non_visible_inter" tr_class_1 = "mievr" + if evaluation.is_blocked(): + tr_class += " evaluation_blocked" + tr_class_1 += " evaluation_blocked" + if not first_eval: H.append("""