diff --git a/app/__init__.py b/app/__init__.py index 738ad0970..2ecd0d33a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -204,7 +204,7 @@ class ReverseProxied(object): def create_app(config_class=DevConfig): app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static") app.wsgi_app = ReverseProxied(app.wsgi_app) - app.logger.setLevel(logging.DEBUG) + app.logger.setLevel(logging.INFO) # Evite de logguer toutes les requetes dans notre log logging.getLogger("werkzeug").disabled = True diff --git a/app/but/apc_edit_ue.py b/app/but/apc_edit_ue.py index dd8e60a86..3e8be20e8 100644 --- a/app/but/apc_edit_ue.py +++ b/app/but/apc_edit_ue.py @@ -70,7 +70,9 @@ def form_ue_choix_niveau(formation: Formation, ue: UniteEns) -> str:
Niveau de compétence associé: - +

Le calcul prend quelques minutes, soyez patients !

- """ - % formsemestre_id, + """, html_sco_header.sco_footer(), ] return "\n".join(H) @@ -908,55 +904,56 @@ def do_formsemestre_validation_auto(formsemestre_id): etudids = nt.get_etudids() nb_valid = 0 conflicts = [] # liste des etudiants avec decision differente déjà saisie - for etudid in etudids: - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) - ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( - {"etudid": etudid, "formsemestre_id": formsemestre_id} - )[0] + with sco_cache.DeferredSemCacheManager(): + for etudid in etudids: + etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] + Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) + ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( + {"etudid": etudid, "formsemestre_id": formsemestre_id} + )[0] - # Conditions pour validation automatique: - if ins["etat"] == "I" and ( - ( - (not Se.prev) - or (Se.prev_decision and Se.prev_decision["code"] in (ADM, ADC, ADJ)) - ) - and Se.barre_moy_ok - and Se.barres_ue_ok - and not etud_has_notes_attente(etudid, formsemestre_id) - ): - # check: s'il existe une decision ou autorisation et qu'elles sont differentes, - # warning (et ne fait rien) - decision_sem = nt.get_etud_decision_sem(etudid) - ok = True - if decision_sem and decision_sem["code"] != ADM: - ok = False - conflicts.append(etud) - autorisations = sco_parcours_dut.formsemestre_get_autorisation_inscription( - etudid, formsemestre_id - ) - if ( - len(autorisations) != 0 - ): # accepte le cas ou il n'y a pas d'autorisation : BUG 23/6/7, A RETIRER ENSUITE - if ( - len(autorisations) != 1 - or autorisations[0]["semestre_id"] != next_semestre_id - ): - if ok: - conflicts.append(etud) - ok = False - - # ok, valide ! - if ok: - formsemestre_validation_etud_manu( - formsemestre_id, - etudid, - code_etat=ADM, - devenir="NEXT", - assidu=True, - redirect=False, + # Conditions pour validation automatique: + if ins["etat"] == "I" and ( + ( + (not Se.prev) + or ( + Se.prev_decision and Se.prev_decision["code"] in (ADM, ADC, ADJ) + ) ) - nb_valid += 1 + and Se.barre_moy_ok + and Se.barres_ue_ok + and not etud_has_notes_attente(etudid, formsemestre_id) + ): + # check: s'il existe une decision ou autorisation et qu'elles sont differentes, + # warning (et ne fait rien) + decision_sem = nt.get_etud_decision_sem(etudid) + ok = True + if decision_sem and decision_sem["code"] != ADM: + ok = False + conflicts.append(etud) + autorisations = ScolarAutorisationInscription.query.filter_by( + etudid=etudid, origin_formsemestre_id=formsemestre_id + ).all() + if len(autorisations) != 0: + if ( + len(autorisations) > 1 + or autorisations[0].semestre_id != next_semestre_id + ): + if ok: + conflicts.append(etud) + ok = False + + # ok, valide ! + if ok: + formsemestre_validation_etud_manu( + formsemestre_id, + etudid, + code_etat=ADM, + devenir="NEXT", + assidu=True, + redirect=False, + ) + nb_valid += 1 log( "do_formsemestre_validation_auto: %d validations, %d conflicts" % (nb_valid, len(conflicts)) @@ -1176,7 +1173,7 @@ def do_formsemestre_validate_previous_ue( ) else: sco_formsemestre.do_formsemestre_uecoef_delete(cnx, formsemestre_id, ue_id) - sco_parcours_dut.do_formsemestre_validate_ue( + sco_cursus_dut.do_formsemestre_validate_ue( cnx, nt, formsemestre_id, # "importe" cette UE dans le semestre (new 3/2015) diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index f17e017e6..d2cd636c3 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -56,8 +56,9 @@ import app.scodoc.notesdb as ndb from app import log, cache from app.scodoc.scolog import logdb from app.scodoc import html_sco_header -from app.scodoc import sco_codes_parcours from app.scodoc import sco_cache +from app.scodoc import sco_codes_parcours +from app.scodoc import sco_cursus from app.scodoc import sco_etud from app.scodoc import sco_permissions_check from app.scodoc import sco_xml @@ -1489,13 +1490,13 @@ def _get_prev_moy(etudid, formsemestre_id): """Donne la derniere moyenne generale calculee pour cette étudiant, ou 0 si on n'en trouve pas (nouvel inscrit,...). """ - from app.scodoc import sco_parcours_dut + from app.scodoc import sco_cursus_dut info = sco_etud.get_etud_info(etudid=etudid, filled=True) if not info: raise ScoValueError("etudiant invalide: etudid=%s" % etudid) etud = info[0] - Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) + Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) if Se.prev: prev_sem = FormSemestre.query.get(Se.prev["formsemestre_id"]) nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem) diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index 259256bd4..9588abd4b 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -49,7 +49,7 @@ from app.scodoc import sco_excel from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -from app.scodoc import sco_parcours_dut +from app.scodoc import sco_cursus from app.scodoc import sco_portal_apogee from app.scodoc import sco_preferences from app.scodoc import sco_etud @@ -776,7 +776,7 @@ def groups_table( m.update(etud) sco_etud.etud_add_lycee_infos(etud) # et ajoute le parcours - Se = sco_parcours_dut.SituationEtudParcours( + Se = sco_cursus.get_situation_etud_cursus( etud, groups_infos.formsemestre_id ) m["parcours"] = Se.get_parcours_descr() diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index c49ef080a..66387efa5 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -39,7 +39,7 @@ from app.models import ModuleImpl from app.models.evaluations import Evaluation import app.scodoc.sco_utils as scu from app.scodoc.sco_exceptions import ScoInvalidIdType -from app.scodoc.sco_parcours_dut import formsemestre_has_decisions +from app.scodoc.sco_cursus_dut import formsemestre_has_decisions from app.scodoc.sco_permissions import Permission from app.scodoc import html_sco_header diff --git a/app/scodoc/sco_page_etud.py b/app/scodoc/sco_page_etud.py index b0690ea43..298181bf8 100644 --- a/app/scodoc/sco_page_etud.py +++ b/app/scodoc/sco_page_etud.py @@ -46,7 +46,7 @@ from app.scodoc import sco_codes_parcours from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_status from app.scodoc import sco_groups -from app.scodoc import sco_parcours_dut +from app.scodoc import sco_cursus from app.scodoc import sco_permissions_check from app.scodoc import sco_photos from app.scodoc import sco_users @@ -269,7 +269,7 @@ def ficheEtud(etudid=None): sem_info[sem["formsemestre_id"]] = grlink if info["sems"]: - Se = sco_parcours_dut.SituationEtudParcours(etud, info["last_formsemestre_id"]) + Se = sco_cursus.get_situation_etud_cursus(etud, info["last_formsemestre_id"]) info["liste_inscriptions"] = formsemestre_recap_parcours_table( Se, etudid, diff --git a/app/scodoc/sco_pdf.py b/app/scodoc/sco_pdf.py index 2f468ccfc..3a7d7a553 100755 --- a/app/scodoc/sco_pdf.py +++ b/app/scodoc/sco_pdf.py @@ -55,6 +55,7 @@ from reportlab.lib import styles from flask import g +from app.scodoc import sco_utils as scu from app.scodoc.sco_utils import CONFIG from app import log from app.scodoc.sco_exceptions import ScoGenError, ScoValueError @@ -67,7 +68,7 @@ PAGE_WIDTH = defaultPageSize[0] DEFAULT_PDF_FOOTER_TEMPLATE = CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE -def SU(s): +def SU(s: str) -> str: "convert s from string to string suitable for ReportLab" if not s: return "" @@ -145,9 +146,9 @@ def makeParas(txt, style, suppress_empty=False): ) from e else: raise e - except Exception as e: + except Exception as exc: log(traceback.format_exc()) - log("Invalid pdf para format: %s" % txt) + log(f"Invalid pdf para format: {txt}") try: result = [ Paragraph( @@ -155,13 +156,14 @@ def makeParas(txt, style, suppress_empty=False): style, ) ] - except ValueError as e: # probleme font ? essaye sans style + except ValueError as exc2: # probleme font ? essaye sans style # recupere font en cause ? m = re.match(r".*family/bold/italic for (.*)", e.args[0], re.DOTALL) if m: message = f"police non disponible: {m[1]}" else: message = "format invalide" + scu.flash_once(f"problème génération PDF: {message}") return [ Paragraph( SU(f'Erreur: {message}'), diff --git a/app/scodoc/sco_permissions_check.py b/app/scodoc/sco_permissions_check.py index fe21b1672..9ad457d9f 100644 --- a/app/scodoc/sco_permissions_check.py +++ b/app/scodoc/sco_permissions_check.py @@ -24,14 +24,14 @@ def can_edit_notes(authuser, moduleimpl_id, allow_ens=True): seul le directeur des études peut saisir des notes (et il ne devrait pas). """ from app.scodoc import sco_formsemestre - from app.scodoc import sco_parcours_dut + from app.scodoc import sco_cursus_dut M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"]) if not sem["etat"]: return False # semestre verrouillé - if sco_parcours_dut.formsemestre_has_decisions(sem["formsemestre_id"]): + if sco_cursus_dut.formsemestre_has_decisions(sem["formsemestre_id"]): # il y a des décisions de jury dans ce semestre ! return ( authuser.has_permission(Permission.ScoEditAllNotes) diff --git a/app/scodoc/sco_prepajury.py b/app/scodoc/sco_prepajury.py index 678b81ab2..0afb1c415 100644 --- a/app/scodoc/sco_prepajury.py +++ b/app/scodoc/sco_prepajury.py @@ -37,14 +37,14 @@ from flask_login import current_user from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre, Identite +from app.models import FormSemestre, Identite, ScolarAutorisationInscription from app.scodoc import sco_abs from app.scodoc import sco_codes_parcours from app.scodoc import sco_groups from app.scodoc import sco_etud from app.scodoc import sco_excel from app.scodoc import sco_formsemestre -from app.scodoc import sco_parcours_dut +from app.scodoc import sco_cursus from app.scodoc import sco_preferences import app.scodoc.sco_utils as scu import sco_version @@ -78,7 +78,7 @@ def feuille_preparation_jury(formsemestre_id): nbabs = {} nbabsjust = {} for etud in etuds: - Se = sco_parcours_dut.SituationEtudParcours( + Se = sco_cursus.get_situation_etud_cursus( etud.to_dict_scodoc7(), formsemestre_id ) if Se.prev: @@ -103,14 +103,14 @@ def feuille_preparation_jury(formsemestre_id): moy[etud.id] = nt.get_etud_moy_gen(etud.id) for ue in nt.get_ues_stat_dict(filter_sport=True): ue_status = nt.get_etud_ue_status(etud.id, ue["ue_id"]) - ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"] + ue_code_s = f'{ue["ue_code"]}_{nt.sem["semestre_id"]}' moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else "" ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"]) if Se.prev: try: moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0 - except: + except (KeyError, TypeError): pass decision = nt.get_etud_decision_sem(etud.id) @@ -119,10 +119,13 @@ def feuille_preparation_jury(formsemestre_id): if decision["compense_formsemestre_id"]: code[etud.id] += "+" # indique qu'il a servi a compenser assidu[etud.id] = {False: "Non", True: "Oui"}.get(decision["assidu"], "") - aut_list = sco_parcours_dut.formsemestre_get_autorisation_inscription( - etud.id, formsemestre_id + + autorisations_etud = ScolarAutorisationInscription.query.filter_by( + etudid=etud.id, origin_formsemestre_id=formsemestre_id + ).all() + autorisations[etud.id] = ", ".join( + [f"S{x.semestre_id}" for x in autorisations_etud] ) - autorisations[etud.id] = ", ".join(["S%s" % x["semestre_id"] for x in aut_list]) # parcours: parcours[etud.id] = Se.get_parcours_descr() # groupe principal (td) @@ -153,11 +156,11 @@ def feuille_preparation_jury(formsemestre_id): sid = sem["semestre_id"] sn = sp = "" if sid >= 0: - sn = "S%s" % sid + sn = f"S{sid}" if prev_moy: # si qq chose dans precedent - sp = "S%s" % (sid - 1) + sp = f"S{sid - 1}" - ws = sco_excel.ScoExcelSheet(sheet_name="Prepa Jury %s" % sn) + sheet = sco_excel.ScoExcelSheet(sheet_name=f"Prepa Jury {sn}") # génération des styles style_bold = sco_excel.excel_make_style(size=10, bold=True) style_center = sco_excel.excel_make_style(halign="center") @@ -173,10 +176,10 @@ def feuille_preparation_jury(formsemestre_id): ) # Première ligne - ws.append_single_cell_row( + sheet.append_single_cell_row( "Feuille préparation Jury %s" % scu.unescape_html(sem["titreannee"]), style_bold ) - ws.append_blank_row() + sheet.append_blank_row() # Ligne de titre titles = ["Rang"] @@ -198,25 +201,25 @@ def feuille_preparation_jury(formsemestre_id): ] if prev_moy: # si qq chose dans precedent titles += [prev_ue_acro[x][1] for x in ue_prev_codes] + [ - "Moy %s" % sp, - "Décision %s" % sp, + f"Moy {sp}", + f"Décision {sp}", ] - titles += [ue_acro[x][1] for x in ue_codes] + ["Moy %s" % sn] + titles += [ue_acro[x][1] for x in ue_codes] + [f"Moy {sn}"] if moy_inter: - titles += ["Moy %s-%s" % (sp, sn)] + titles += [f"Moy {sp}-{sn}"] titles += ["Abs", "Abs Injust."] if code: - titles.append("Proposit. %s" % sn) + titles.append("Proposit. {sn}") if autorisations: titles.append("Autorisations") # titles.append('Assidu') - ws.append_row(ws.make_row(titles, style_boldcenter)) - if prev_moy: - tit_prev_moy = "Moy " + sp - col_prev_moy = titles.index(tit_prev_moy) - tit_moy = "Moy " + sn - col_moy = titles.index(tit_moy) - col_abs = titles.index("Abs") + sheet.append_row(sheet.make_row(titles, style_boldcenter)) + # if prev_moy: + # tit_prev_moy = "Moy " + sp + # # col_prev_moy = titles.index(tit_prev_moy) + # tit_moy = "Moy " + sn + # col_moy = titles.index(tit_moy) + # col_abs = titles.index("Abs") def fmt(x): "reduit les notes a deux chiffres" @@ -229,13 +232,13 @@ def feuille_preparation_jury(formsemestre_id): i = 1 # numero etudiant for etud in etuds: cells = [] - cells.append(ws.make_cell(str(i))) + cells.append(sheet.make_cell(str(i))) if sco_preferences.get_preference("prepa_jury_nip"): - cells.append(ws.make_cell(etud.code_nip)) + cells.append(sheet.make_cell(etud.code_nip)) if sco_preferences.get_preference("prepa_jury_ine"): - cells.append(ws.make_cell(etud.code_ine)) + cells.append(sheet.make_cell(etud.code_ine)) admission = etud.admission.first() - cells += ws.make_row( + cells += sheet.make_row( [ etud.id, etud.civilite_str, @@ -253,50 +256,52 @@ def feuille_preparation_jury(formsemestre_id): if prev_moy: for ue_acro in ue_prev_codes: cells.append( - ws.make_cell( + sheet.make_cell( fmt(prev_moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note ) ) co += 1 cells.append( - ws.make_cell(fmt(prev_moy.get(etud.id, "")), style_bold) + sheet.make_cell(fmt(prev_moy.get(etud.id, "")), style_bold) ) # moy gen prev cells.append( - ws.make_cell(fmt(prev_code.get(etud.id, "")), style_moy) + sheet.make_cell(fmt(prev_code.get(etud.id, "")), style_moy) ) # decision prev co += 2 for ue_acro in ue_codes: cells.append( - ws.make_cell(fmt(moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note) + sheet.make_cell( + fmt(moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note + ) ) co += 1 cells.append( - ws.make_cell(fmt(moy.get(etud.id, "")), style_note_bold) + sheet.make_cell(fmt(moy.get(etud.id, "")), style_note_bold) ) # moy gen co += 1 if moy_inter: - cells.append(ws.make_cell(fmt(moy_inter.get(etud.id, "")), style_note)) - cells.append(ws.make_cell(str(nbabs.get(etud.id, "")), style_center)) - cells.append(ws.make_cell(str(nbabsjust.get(etud.id, "")), style_center)) + cells.append(sheet.make_cell(fmt(moy_inter.get(etud.id, "")), style_note)) + cells.append(sheet.make_cell(str(nbabs.get(etud.id, "")), style_center)) + cells.append(sheet.make_cell(str(nbabsjust.get(etud.id, "")), style_center)) if code: - cells.append(ws.make_cell(code.get(etud.id, ""), style_moy)) - cells.append(ws.make_cell(autorisations.get(etud.id, ""), style_moy)) + cells.append(sheet.make_cell(code.get(etud.id, ""), style_moy)) + cells.append(sheet.make_cell(autorisations.get(etud.id, ""), style_moy)) # l.append(assidu.get(etud.id, '')) - ws.append_row(cells) + sheet.append_row(cells) i += 1 # - ws.append_blank_row() + sheet.append_blank_row() # Explications des codes codes = list(sco_codes_parcours.CODES_EXPL.keys()) codes.sort() - ws.append_single_cell_row("Explication des codes") + sheet.append_single_cell_row("Explication des codes") for code in codes: - ws.append_row( - ws.make_row(["", "", "", code, sco_codes_parcours.CODES_EXPL[code]]) + sheet.append_row( + sheet.make_row(["", "", "", code, sco_codes_parcours.CODES_EXPL[code]]) ) - ws.append_row( - ws.make_row( + sheet.append_row( + sheet.make_row( [ "", "", @@ -307,16 +312,16 @@ def feuille_preparation_jury(formsemestre_id): ) ) # UE : Correspondances acronyme et titre complet - ws.append_blank_row() - ws.append_single_cell_row("Titre des UE") + sheet.append_blank_row() + sheet.append_single_cell_row("Titre des UE") if prev_moy: for ue in ntp.get_ues_stat_dict(filter_sport=True): - ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]])) + sheet.append_row(sheet.make_row(["", "", "", ue["acronyme"], ue["titre"]])) for ue in nt.get_ues_stat_dict(filter_sport=True): - ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]])) + sheet.append_row(sheet.make_row(["", "", "", ue["acronyme"], ue["titre"]])) # - ws.append_blank_row() - ws.append_single_cell_row( + sheet.append_blank_row() + sheet.append_single_cell_row( "Préparé par %s le %s sur %s pour %s" % ( sco_version.SCONAME, @@ -325,7 +330,7 @@ def feuille_preparation_jury(formsemestre_id): current_user, ) ) - xls = ws.generate() + xls = sheet.generate() flash("Feuille préparation jury générée") return scu.send_file( xls, diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py index 9b374b8e3..395f6107d 100644 --- a/app/scodoc/sco_pvjury.py +++ b/app/scodoc/sco_pvjury.py @@ -57,32 +57,38 @@ from flask import g, request from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre, UniteEns +from app.models import ( + FormSemestre, + UniteEns, + ScolarAutorisationInscription, + but_validations, +) +from app.models.etudiants import Identite import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log from app.scodoc import html_sco_header from app.scodoc import sco_codes_parcours -from app.scodoc import sco_cache +from app.scodoc import sco_cursus +from app.scodoc import sco_cursus_dut from app.scodoc import sco_edit_ue +from app.scodoc import sco_etud from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_groups_view -from app.scodoc import sco_parcours_dut from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc import sco_pvpdf -from app.scodoc import sco_etud from app.scodoc.gen_tables import GenTable from app.scodoc.sco_codes_parcours import NO_SEMESTRE_ID from app.scodoc.sco_pdf import PDFLOCK from app.scodoc.TrivialFormulator import TrivialFormulator -def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem): - """Liste des UE validées dans ce semestre""" +def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]: + """Liste des UE validées dans ce semestre (incluant les UE capitalisées)""" if not decisions_ue: return [] uelist = [] @@ -90,17 +96,20 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem): for ue_id in decisions_ue.keys(): try: if decisions_ue[ue_id] and ( - decisions_ue[ue_id]["code"] == sco_codes_parcours.ADM + sco_codes_parcours.code_ue_validant(decisions_ue[ue_id]["code"]) or ( # XXX ceci devrait dépendre du parcours et non pas être une option ! #sco8 - scu.CONFIG.CAPITALIZE_ALL_UES + decision_sem + and scu.CONFIG.CAPITALIZE_ALL_UES and sco_codes_parcours.code_semestre_validant(decision_sem["code"]) ) ): ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0] uelist.append(ue) except: - log("descr_decisions_ues: ue_id=%s decisions_ue=%s" % (ue_id, decisions_ue)) + log( + f"Exception in descr_decisions_ues: ue_id={ue_id} decisions_ue={decisions_ue}" + ) # Les UE capitalisées dans d'autres semestres: if etudid in nt.validations.ue_capitalisees.index: for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]: @@ -138,12 +147,9 @@ def _descr_decision_sem_abbrev(etat, decision_sem): return decision -def descr_autorisations(autorisations): +def descr_autorisations(autorisations: list[ScolarAutorisationInscription]) -> str: "résumé textuel des autorisations d'inscription (-> 'S1, S3' )" - alist = [] - for aut in autorisations: - alist.append("S" + str(aut["semestre_id"])) - return ", ".join(alist) + return ", ".join([f"S{a.semestre_id}" for a in autorisations]) def _comp_ects_by_ue_code(nt, decision_ues): @@ -233,8 +239,11 @@ def dict_pvjury( L = [] D = {} # même chose que L, mais { etudid : dec } for etudid in etudids: - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) + # etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] + etud: Identite = Identite.query.get(etudid) + Se = sco_cursus.get_situation_etud_cursus( + etud.to_dict_scodoc7(), formsemestre_id + ) semestre_non_terminal = semestre_non_terminal or Se.semestre_non_terminal d = {} d["identite"] = nt.identdict[etudid] @@ -243,6 +252,8 @@ def dict_pvjury( ) # I|D|DEF (inscription ou démission ou défaillant) d["decision_sem"] = nt.get_etud_decision_sem(etudid) d["decisions_ue"] = nt.get_etud_decision_ues(etudid) + if formsemestre.formation.is_apc(): + d.update(but_validations.dict_decision_jury(etud, formsemestre)) d["last_formsemestre_id"] = Se.get_semestres()[ -1 ] # id du dernier semestre (chronologiquement) dans lequel il a été inscrit @@ -280,17 +291,18 @@ def dict_pvjury( else: d["decision_sem_descr"] = _descr_decision_sem(d["etat"], d["decision_sem"]) - d["autorisations"] = sco_parcours_dut.formsemestre_get_autorisation_inscription( - etudid, formsemestre_id - ) - d["autorisations_descr"] = descr_autorisations(d["autorisations"]) + autorisations = ScolarAutorisationInscription.query.filter_by( + etudid=etudid, origin_formsemestre_id=formsemestre_id + ).all() + d["autorisations"] = [a.to_dict() for a in autorisations] + d["autorisations_descr"] = descr_autorisations(autorisations) d["validation_parcours"] = Se.parcours_validated() d["parcours"] = Se.get_parcours_descr(filter_futur=True) if with_parcours_decisions: d["parcours_decisions"] = Se.get_parcours_decisions() # Observations sur les compensations: - compensators = sco_parcours_dut.scolar_formsemestre_validation_list( + compensators = sco_cursus_dut.scolar_formsemestre_validation_list( cnx, args={"compense_formsemestre_id": formsemestre_id, "etudid": etudid} ) obs = [] @@ -307,12 +319,7 @@ def dict_pvjury( d["decision_sem"]["compense_formsemestre_id"] ) obs.append( - "%s compense %s (%s)" - % ( - sem["sem_id_txt"], - compensed["sem_id_txt"], - compensed["anneescolaire"], - ) + f"""{sem["sem_id_txt"]} compense {compensed["sem_id_txt"]} ({compensed["anneescolaire"]})""" ) d["observation"] = ", ".join(obs) diff --git a/app/scodoc/sco_pvpdf.py b/app/scodoc/sco_pvpdf.py index 8ef1c12cc..ac067339a 100644 --- a/app/scodoc/sco_pvpdf.py +++ b/app/scodoc/sco_pvpdf.py @@ -30,9 +30,11 @@ import io import re +from PIL import Image as PILImage + import reportlab from reportlab.lib.units import cm, mm -from reportlab.lib.enums import TA_RIGHT, TA_JUSTIFY +from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_JUSTIFY from reportlab.platypus import Paragraph, Spacer, Frame, PageBreak from reportlab.platypus import Table, TableStyle, Image from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate @@ -41,16 +43,16 @@ from reportlab.lib import styles from reportlab.lib.colors import Color from flask import g +from app.models import FormSemestre, Identite import app.scodoc.sco_utils as scu from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_codes_parcours from app.scodoc import sco_etud -from app.scodoc import sco_formsemestre from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc.sco_logos import find_logo -from app.scodoc.sco_parcours_dut import SituationEtudParcours +from app.scodoc.sco_cursus_dut import SituationEtudCursus from app.scodoc.sco_pdf import SU import sco_version @@ -125,6 +127,7 @@ def page_footer(canvas, doc, logo, preferences, with_page_numbers=True): def page_header(canvas, doc, logo, preferences, only_on_first_page=False): + "Ajoute au canvas le frame avec le logo" if only_on_first_page and int(doc.page) > 1: return height = doc.pagesize[1] @@ -147,12 +150,12 @@ def page_header(canvas, doc, logo, preferences, only_on_first_page=False): class CourrierIndividuelTemplate(PageTemplate): - """Template pour courrier avisant des decisions de jury (1 page /etudiant)""" + """Template pour courrier avisant des decisions de jury (1 page par étudiant)""" def __init__( self, document, - pagesbookmarks={}, + pagesbookmarks=None, author=None, title=None, subject=None, @@ -163,7 +166,7 @@ class CourrierIndividuelTemplate(PageTemplate): template_name="CourrierJuryTemplate", ): """Initialise our page template.""" - self.pagesbookmarks = pagesbookmarks + self.pagesbookmarks = pagesbookmarks or {} self.pdfmeta_author = author self.pdfmeta_title = title self.pdfmeta_subject = subject @@ -237,32 +240,32 @@ class CourrierIndividuelTemplate(PageTemplate): width=LOGO_HEADER_WIDTH, ) - def beforeDrawPage(self, canvas, doc): + def beforeDrawPage(self, canv, doc): """Draws a logo and an contribution message on each page.""" # ---- Add some meta data and bookmarks if self.pdfmeta_author: - canvas.setAuthor(SU(self.pdfmeta_author)) + canv.setAuthor(SU(self.pdfmeta_author)) if self.pdfmeta_title: - canvas.setTitle(SU(self.pdfmeta_title)) + canv.setTitle(SU(self.pdfmeta_title)) if self.pdfmeta_subject: - canvas.setSubject(SU(self.pdfmeta_subject)) + canv.setSubject(SU(self.pdfmeta_subject)) bm = self.pagesbookmarks.get(doc.page, None) if bm != None: key = bm txt = SU(bm) - canvas.bookmarkPage(key) - canvas.addOutlineEntry(txt, bm) + canv.bookmarkPage(key) + canv.addOutlineEntry(txt, bm) # ---- Background image if self.background_image_filename and self.with_page_background: - canvas.drawImage( + canv.drawImage( self.background_image_filename, 0, 0, doc.pagesize[0], doc.pagesize[1] ) # ---- Header/Footer if self.with_header: page_header( - canvas, + canv, doc, self.logo_header, self.preferences, @@ -270,7 +273,7 @@ class CourrierIndividuelTemplate(PageTemplate): ) if self.with_footer: page_footer( - canvas, + canv, doc, self.logo_footer, self.preferences, @@ -332,6 +335,42 @@ class PVTemplate(CourrierIndividuelTemplate): # self.__pageNum += 1 +def _simulate_br(paragraph_txt: str, para="") -> str: + """Reportlab bug turnaround (could be removed in a future version). + p is a string with Reportlab intra-paragraph XML tags. + Replaces
(currently ignored by Reportlab) by
+ Also replaces
by
+ """ + return ("
" + para).join( + re.split(r"<.*?br.*?/>", paragraph_txt.replace("
", "
")) + ) + + +def _make_signature_image(signature, leftindent, formsemestre_id) -> Table: + "crée un paragraphe avec l'image signature" + # cree une image PIL pour avoir la taille (W,H) + + f = io.BytesIO(signature) + img = PILImage.open(f) + width, height = img.size + pdfheight = ( + 1.0 + * sco_preferences.get_preference("pv_sig_image_height", formsemestre_id) + * mm + ) + f.seek(0, 0) + + style = styles.ParagraphStyle({}) + style.leading = 1.0 * sco_preferences.get_preference( + "SCOLAR_FONT_SIZE", formsemestre_id + ) # vertical space + style.leftIndent = leftindent + return Table( + [("", Image(f, width=width * pdfheight / float(height), height=pdfheight))], + colWidths=(9 * cm, 7 * cm), + ) + + def pdf_lettres_individuelles( formsemestre_id, etudids=None, @@ -352,7 +391,7 @@ def pdf_lettres_individuelles( etuds = [x["identite"] for x in dpv["decisions"]] sco_etud.fill_etuds_info(etuds) # - sem = sco_formsemestre.get_formsemestre(formsemestre_id) + formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) prefs = sco_preferences.SemPreferences(formsemestre_id) params = { "date_jury": date_jury, @@ -363,18 +402,22 @@ def pdf_lettres_individuelles( } # copie preferences for name in sco_preferences.get_base_preferences().prefs_name: - params[name] = sco_preferences.get_preference(name, sem["formsemestre_id"]) + params[name] = sco_preferences.get_preference(name, formsemestre_id) bookmarks = {} objects = [] # list of PLATYPUS objects npages = 0 - for e in dpv["decisions"]: - if e["decision_sem"]: # decision prise - etud = sco_etud.get_etud_info(e["identite"]["etudid"], filled=True)[0] - params["nomEtud"] = etud["nomprenom"] - bookmarks[npages + 1] = scu.suppress_accents(etud["nomprenom"]) + for decision in dpv["decisions"]: + if ( + decision["decision_sem"] + or decision.get("decision_annee") + or decision.get("decision_rcue") + ): # decision prise + etud: Identite = Identite.query.get(decision["identite"]["etudid"]) + params["nomEtud"] = etud.nomprenom + bookmarks[npages + 1] = scu.suppress_accents(etud.nomprenom) objects += pdf_lettre_individuelle( - dpv["formsemestre"], e, etud, params, signature + dpv["formsemestre"], decision, etud, params, signature ) objects.append(PageBreak()) npages += 1 @@ -394,8 +437,8 @@ def pdf_lettres_individuelles( document.addPageTemplates( CourrierIndividuelTemplate( document, - author="%s %s (E. Viennet)" % (sco_version.SCONAME, sco_version.SCOVERSION), - title="Lettres décision %s" % sem["titreannee"], + author=f"{sco_version.SCONAME} {sco_version.SCOVERSION} (E. Viennet)", + title=f"Lettres décision {formsemestre.titre_annee()}", subject="Décision jury", margins=margins, pagesbookmarks=bookmarks, @@ -408,36 +451,41 @@ def pdf_lettres_individuelles( return data -def _descr_jury(sem, diplome): +def _descr_jury(formsemestre: FormSemestre, diplome): + if not diplome: - t = "passage de Semestre %d en Semestre %d" % ( - sem["semestre_id"], - sem["semestre_id"] + 1, - ) - s = "passage de semestre" + if formsemestre.formation.is_apc(): + t = f"""BUT{(formsemestre.semestre_id+1)//2}""" + s = t + else: + t = f"""passage de Semestre {formsemestre.semestre_id} en Semestre {formsemestre.semestre_id + 1}""" + s = "passage de semestre" else: t = "délivrance du diplôme" s = t return t, s # titre long, titre court -def pdf_lettre_individuelle(sem, decision, etud, params, signature=None): +def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=None): """ Renvoie une liste d'objets PLATYPUS pour intégration dans un autre document. """ # formsemestre_id = sem["formsemestre_id"] - Se: SituationEtudParcours = decision["Se"] - t, s = _descr_jury(sem, Se.parcours_validated() or not Se.semestre_non_terminal) + formsemestre = FormSemestre.query.get(formsemestre_id) + Se: SituationEtudCursus = decision["Se"] + t, s = _descr_jury( + formsemestre, Se.parcours_validated() or not Se.semestre_non_terminal + ) objects = [] style = reportlab.lib.styles.ParagraphStyle({}) style.fontSize = 14 style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre_id) style.leading = 18 - style.alignment = TA_JUSTIFY + style.alignment = TA_LEFT - params["semestre_id"] = sem["semestre_id"] + params["semestre_id"] = formsemestre.semestre_id params["decision_sem_descr"] = decision["decision_sem_descr"] params["type_jury"] = t # type de jury (passage ou delivrance) params["type_jury_abbrv"] = s # idem, abbrégé @@ -450,28 +498,18 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None): params["INSTITUTION_CITY"] = ( sco_preferences.get_preference("INSTITUTION_CITY", formsemestre_id) or "" ) + if decision["prev_decision_sem"]: params["prev_semestre_id"] = decision["prev"]["semestre_id"] - params["prev_code_descr"] = decision["prev_code_descr"] + + params["prev_decision_sem_txt"] = "" + params["decision_orig"] = "" params.update(decision["identite"]) # fix domicile if params["domicile"]: params["domicile"] = params["domicile"].replace("\\n", "
") - # Décision semestre courant: - if sem["semestre_id"] >= 0: - params["decision_orig"] = "du semestre S%s" % sem["semestre_id"] - else: - params["decision_orig"] = "" - - if decision["prev_decision_sem"]: - params["prev_decision_sem_txt"] = ( - """Décision du semestre antérieur S%(prev_semestre_id)s : %(prev_code_descr)s""" - % params - ) - else: - params["prev_decision_sem_txt"] = "" # UE capitalisées: if decision["decisions_ue"] and decision["decisions_ue_descr"]: params["decision_ue_txt"] = ( @@ -498,7 +536,7 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None): params[ "autorisations_txt" ] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : %s""" % ( - etud["ne"], + etud.e, s, s, decision["autorisations_descr"], @@ -513,6 +551,14 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None): else: params["diplome_txt"] = "" + # Les fonctions ci-dessous ajoutent ou modifient des champs: + if formsemestre.formation.is_apc(): + # ajout champs spécifiques PV BUT + add_apc_infos(formsemestre, params, decision) + else: + # ajout champs spécifiques PV DUT + add_classic_infos(formsemestre, params, decision) + # Corps de la lettre: objects += sco_bulletins_pdf.process_field( sco_preferences.get_preference("PV_LETTER_TEMPLATE", sem["formsemestre_id"]), @@ -567,39 +613,30 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None): return objects -def _simulate_br(p, para=""): - """Reportlab bug turnaround (could be removed in a future version). - p is a string with Reportlab intra-paragraph XML tags. - Replaces
(currently ignored by Reportlab) by
+def add_classic_infos(formsemestre: FormSemestre, params: dict, decision: dict): + """Ajoute les champs pour les formations classiques, donc avec codes semestres""" + if decision["prev_decision_sem"]: + params["prev_code_descr"] = decision["prev_code_descr"] + params[ + "prev_decision_sem_txt" + ] = f"""Décision du semestre antérieur S{params['prev_semestre_id']} : {params['prev_code_descr']}""" + # Décision semestre courant: + if formsemestre.semestre_id >= 0: + params["decision_orig"] = f"du semestre S{formsemestre.semestre_id}" + else: + params["decision_orig"] = "" + + +def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict): + """Ajoute les champs pour les formations APC (BUT), donc avec codes RCUE et année""" + annee_but = (formsemestre.semestre_id + 1) // 2 + params["decision_orig"] = f"année BUT{annee_but}" + params["decision_sem_descr"] = decision.get("decision_annee", {}).get("code", "") + params[ + "decision_ue_txt" + ] = f"""{params["decision_ue_txt"]}
+ Niveaux de compétences:
{decision.get("descr_decisions_rcue", "")} """ - l = re.split(r"<.*?br.*?/>", p) - return ("
" + para).join(l) - - -def _make_signature_image(signature, leftindent, formsemestre_id): - "cree un paragraphe avec l'image signature" - # cree une image PIL pour avoir la taille (W,H) - from PIL import Image as PILImage - - f = io.BytesIO(signature) - im = PILImage.open(f) - width, height = im.size - pdfheight = ( - 1.0 - * sco_preferences.get_preference("pv_sig_image_height", formsemestre_id) - * mm - ) - f.seek(0, 0) - - style = styles.ParagraphStyle({}) - style.leading = 1.0 * sco_preferences.get_preference( - "SCOLAR_FONT_SIZE", formsemestre_id - ) # vertical space - style.leftIndent = leftindent - return Table( - [("", Image(f, width=width * pdfheight / float(height), height=pdfheight))], - colWidths=(9 * cm, 7 * cm), - ) # ---------------------------------------------- @@ -699,7 +736,8 @@ def _pvjury_pdf_type( sem = dpv["formsemestre"] formsemestre_id = sem["formsemestre_id"] - titre_jury, _ = _descr_jury(sem, diplome) + formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) + titre_jury, _ = _descr_jury(formsemestre, diplome) titre_diplome = pv_title or dpv["formation"]["titre_officiel"] objects = [] diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py index aad0b96b0..96378587b 100644 --- a/app/scodoc/sco_recapcomplet.py +++ b/app/scodoc/sco_recapcomplet.py @@ -386,6 +386,7 @@ def gen_formsemestre_recapcomplet_html( table_html = sco_cache.TableRecapWithEvalsCache.get(formsemestre.id) else: table_html = sco_cache.TableRecapCache.get(formsemestre.id) + # en mode jury ne cache pas la table html if mode_jury or (table_html is None): table_html = _gen_formsemestre_recapcomplet_html( formsemestre, diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py index 214532a55..97f01f756 100644 --- a/app/scodoc/sco_report.py +++ b/app/scodoc/sco_report.py @@ -41,7 +41,7 @@ import pydot from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre +from app.models import FormSemestre, ScolarAutorisationInscription import app.scodoc.sco_utils as scu from app.models import FormationModalite @@ -51,7 +51,6 @@ from app.scodoc import sco_codes_parcours from app.scodoc import sco_etud from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions -from app.scodoc import sco_parcours_dut from app.scodoc import sco_preferences import sco_version from app.scodoc.gen_tables import GenTable @@ -81,10 +80,10 @@ def formsemestre_etuds_stats(sem, only_primo=False): if "codedecision" not in etud: etud["codedecision"] = "(nd)" # pas de decision jury # Ajout devenir (autorisations inscriptions), utile pour stats passage - aut_list = sco_parcours_dut.formsemestre_get_autorisation_inscription( - etudid, sem["formsemestre_id"] - ) - autorisations = ["S%s" % x["semestre_id"] for x in aut_list] + aut_list = ScolarAutorisationInscription.query.filter_by( + etudid=etudid, origin_formsemestre_id=sem["formsemestre_id"] + ).all() + autorisations = [f"S{a.semestre_id}" for a in aut_list] autorisations.sort() autorisations_str = ", ".join(autorisations) etud["devenir"] = autorisations_str diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 31de4bb22..2087a141e 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -664,6 +664,15 @@ def flash_errors(form): # see https://getbootstrap.com/docs/4.0/components/alerts/ +def flash_once(message: str): + """Flash the message, but only once per request""" + if not hasattr(g, "sco_flashed_once"): + g.sco_flashed_once = set() + if not message in g.sco_flashed_once: + flash(message) + g.sco_flashed_once.add(message) + + def sendCSVFile(data, filename): # DEPRECATED utiliser send_file """publication fichier CSV.""" return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True) diff --git a/app/static/css/releve-but.css b/app/static/css/releve-but.css index a38e11122..745b6a970 100644 --- a/app/static/css/releve-but.css +++ b/app/static/css/releve-but.css @@ -169,7 +169,7 @@ section>div:nth-child(1){ border: none; margin-left: auto; } -.rang{ +.rang, .competence{ font-weight: bold; } .ue .rang{ diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index c4db0a261..6a2dcd99b 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -2210,6 +2210,7 @@ ul.notes_module_list { list-style-type: none; } +/*Choix niveau dans form edit UE */ div.ue_choix_niveau { background-color: rgb(191, 242, 255); border: 1px solid blue; @@ -2219,6 +2220,19 @@ div.ue_choix_niveau { margin-right: 15px; } +/* Choix niveau dans edition programme (ue_table) */ +div.formation_list_ues div.ue_choix_niveau { + margin-left: 64px; + margin-right: 64px; + margin-top: 2px; + padding: 4px; + font-size: 14px; +} + +div.formation_list_ues div.ue_choix_niveau b { + font-weight: normal; +} + div#ue_list_modules { background-color: rgb(251, 225, 165); border: 1px solid blue; diff --git a/app/static/js/edit_ue.js b/app/static/js/edit_ue.js index 18bae83fb..056d4cbae 100644 --- a/app/static/js/edit_ue.js +++ b/app/static/js/edit_ue.js @@ -1,13 +1,16 @@ // Affiche et met a jour la liste des UE partageant le meme code $().ready(function () { - update_ue_list(); - $("#tf_ue_code").bind("keyup", update_ue_list); + if (document.querySelector("#tf_ue_id")) { + /* fonctions spécifiques pour edition UE */ + update_ue_list(); + $("#tf_ue_code").bind("keyup", update_ue_list); - $("select#tf_type").change(function () { + $("select#tf_type").change(function () { + update_bonus_description(); + }); update_bonus_description(); - }); - update_bonus_description(); + } }); function update_bonus_description() { @@ -33,11 +36,10 @@ function update_ue_list() { }); } -function set_ue_niveau_competence() { - let ue_id = document.querySelector("#tf_ue_id").value; - let select = document.querySelector("#form_ue_choix_niveau select"); - let niveau_id = select.value; - let set_ue_niveau_competence_url = select.dataset.setter; +function set_ue_niveau_competence(elem) { + let ue_id = elem.dataset.ue_id; + let niveau_id = elem.value; + let set_ue_niveau_competence_url = elem.dataset.setter; $.post(set_ue_niveau_competence_url, { ue_id: ue_id, diff --git a/app/static/js/releve-but.js b/app/static/js/releve-but.js index 7d3bc985e..085b8638e 100644 --- a/app/static/js/releve-but.js +++ b/app/static/js/releve-but.js @@ -232,7 +232,21 @@ class releveBUT extends HTMLElement { ${(()=>{ let output = ""; data.semestre.decision_rcue.forEach(competence=>{ - output += `
${competence.niveau.competence.titre}
${competence.code}
`; + output += `
${competence.niveau.competence.titre}
${competence.code}
`; + }) + return output; + })()} +
+ ` + } + if(data.semestre.decision_ue?.length){ + output += ` +
+
UE
+ ${(()=>{ + let output = ""; + data.semestre.decision_ue.forEach(ue=>{ + output += `
${ue.acronyme}
${ue.code}
`; }) return output; })()} diff --git a/app/templates/pn/form_ues.html b/app/templates/pn/form_ues.html index fed118333..aa6b32359 100644 --- a/app/templates/pn/form_ues.html +++ b/app/templates/pn/form_ues.html @@ -30,7 +30,7 @@ - {{ue.acronyme}} {{ue.titre}} + {% set virg = joiner(", ") %} ( {%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%} @@ -53,16 +54,16 @@ ) - {% if (ue.niveau_competence is none) and ue.type == 0 %} - pas de compétence associée - {% endif %} - + {% if editable and not ue.is_locked() %} modifier {% endif %} + {{ form_ue_choix_niveau(formation, ue)|safe }} + + {% if ue.type == 1 and ue.modules.count() == 0 %} aucun module rattaché ! {% endif %} diff --git a/app/views/notes.py b/app/views/notes.py index c1715c927..fe1774a73 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -296,7 +296,9 @@ def formsemestre_bulletinetud( format = format or "html" if not isinstance(formsemestre_id, int): - raise ScoInvalidIdType("formsemestre_id must be an integer !") + raise ScoInvalidIdType( + "formsemestre_bulletinetud: formsemestre_id must be an integer !" + ) formsemestre = FormSemestre.query.get_or_404(formsemestre_id) if etudid: etud = models.Identite.query.get_or_404(etudid) @@ -826,7 +828,9 @@ def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None): if not formsemestre_id: return flask.abort(404, "argument manquant: formsemestre_id") if not isinstance(formsemestre_id, int): - return flask.abort(404, "formsemestre_id must be an integer !") + return flask.abort( + 404, "XMLgetFormsemestres: formsemestre_id must be an integer !" + ) args = {} if etape_apo: args["etape_apo"] = etape_apo @@ -2548,9 +2552,8 @@ def formsemestre_validation_suppress_etud( ) if not dialog_confirmed: d = sco_bulletins_json.dict_decision_jury( - etudid, formsemestre_id, with_decisions=True + etud, formsemestre, with_decisions=True ) - d.update(but_validations.dict_decision_jury(etud, formsemestre)) descr_ues = [f"{u['acronyme']}: {u['code']}" for u in d.get("decision_ue", [])] dec_annee = d.get("decision_annee") @@ -2661,6 +2664,7 @@ def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None): deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) deca.erase() db.session.commit() + log(f"formsemestre_jury_but_erase({formsemestre_id}, {etudid})") flash("décisions de jury effacées") return redirect(dest_url) diff --git a/sco_version.py b/sco_version.py index 256794b73..83068bb61 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.3.16" +SCOVERSION = "9.3.19" SCONAME = "ScoDoc" diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py index a00ab66e5..52ed79f0d 100644 --- a/tests/unit/test_sco_basic.py +++ b/tests/unit/test_sco_basic.py @@ -20,6 +20,7 @@ from config import TestConfig from tests.unit import sco_fake_gen import app +from app import db from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre @@ -31,8 +32,7 @@ from app.scodoc import sco_codes_parcours from app.scodoc import sco_evaluations from app.scodoc import sco_evaluation_db from app.scodoc import sco_formsemestre_validation -from app.scodoc import sco_parcours_dut -from app.scodoc import sco_cache +from app.scodoc import sco_cursus_dut from app.scodoc import sco_saisie_notes from app.scodoc import sco_utils as scu @@ -194,20 +194,20 @@ def run_sco_basic(verbose=False): # --- Permission saisie notes et décisions de jury, avec ou sans démission ou défaillance # on n'a pas encore saisi de décisions - assert not sco_parcours_dut.formsemestre_has_decisions(formsemestre_id) + assert not sco_cursus_dut.formsemestre_has_decisions(formsemestre_id) # Saisie d'un décision AJ, non assidu etudid = etuds[-1]["etudid"] - sco_parcours_dut.formsemestre_validate_ues( + sco_cursus_dut.formsemestre_validate_ues( formsemestre_id, etudid, sco_codes_parcours.AJ, False ) - assert sco_parcours_dut.formsemestre_has_decisions( + assert sco_cursus_dut.formsemestre_has_decisions( formsemestre_id ), "décisions manquantes" # Suppression de la décision sco_formsemestre_validation.formsemestre_validation_suppress_etud( formsemestre_id, etudid ) - assert not sco_parcours_dut.formsemestre_has_decisions( + assert not sco_cursus_dut.formsemestre_has_decisions( formsemestre_id ), "décisions non effacées"