############################################################################## # ScoDoc # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # See LICENSE ############################################################################## """Jury BUT: affichage/formulaire """ import re import numpy as np import flask from flask import flash, render_template, url_for from flask import g, request from app import db from app.but import jury_but from app.but.jury_but import ( DecisionsProposeesAnnee, DecisionsProposeesRCUE, DecisionsProposeesUE, ) from app.models import ( ApcNiveau, FormSemestre, FormSemestreInscription, Identite, UniteEns, ScolarAutorisationInscription, ScolarFormSemestreValidation, ScolarNews, ) from app.scodoc import codes_cursus as sco_codes from app.scodoc.sco_exceptions import ScoValueError from app.scodoc import sco_utils as scu def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: """Affichage des décisions annuelles BUT Si pas read_only, menus sélection codes jury. """ H = [] if deca.jury_annuel: H.append( f""" <div class="but_section_annee"> <div> <b>Décision de jury pour l'année :</b> { _gen_but_select("code_annee", deca.codes, deca.code_valide, disabled=True, klass="manual") } <span>({deca.code_valide or 'non'} enregistrée)</span> </div> </div> """ ) formsemestre_1 = deca.formsemestre_impair formsemestre_2 = deca.formsemestre_pair # Ordonne selon les dates des 2 semestres considérés (pour les redoublants à cheval): reverse_semestre = ( deca.formsemestre_pair and deca.formsemestre_impair and deca.formsemestre_pair.date_debut < deca.formsemestre_impair.date_debut ) if reverse_semestre: formsemestre_1, formsemestre_2 = formsemestre_2, formsemestre_1 H.append( f""" <div class="titre_niveaux"> <b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b> <a style="margin-left: 32px;" class="stdlink" target="_blank" rel="noopener noreferrer" href={ url_for("notes.validation_rcues", scodoc_dept=g.scodoc_dept, etudid=deca.etud.id, formsemestre_id=formsemestre_2.id if formsemestre_2 else formsemestre_1.id ) }>visualiser son cursus</a> </div> <div class="but_explanation">{deca.explanation}</div> <div class="but_annee"> <div class="titre"></div> <div class="titre">{"S" +str(formsemestre_1.semestre_id) if formsemestre_1 else "-"} <span class="avertissement_redoublement">{formsemestre_1.annee_scolaire_str() if formsemestre_1 else ""}</span> </div> <div class="titre">{"S"+str(formsemestre_2.semestre_id) if formsemestre_2 else "-"} <span class="avertissement_redoublement">{formsemestre_2.annee_scolaire_str() if formsemestre_2 else ""}</span> </div> <div class="titre" title="Décisions sur RCUEs enregistrées sur l'ensemble du cursus">RCUE</div> """ ) for dec_rcue in deca.get_decisions_rcues_annee(): rcue = dec_rcue.rcue niveau = rcue.niveau H.append( f"""<div class="but_niveau_titre"> <div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div> </div>""" ) ue_impair, ue_pair = rcue.ue_1, rcue.ue_2 # Les UEs à afficher : on regarde si read only et si dispense (non ré-inscription à l'UE) # tuples (UniteEns, read_only, dispense) ues_ro_dispense = [ ( ue_impair, rcue.ue_cur_impair is None, deca.res_impair and ue_impair and (deca.etud.id, ue_impair.id) in deca.res_impair.dispense_ues, ), ( ue_pair, rcue.ue_cur_pair is None, deca.res_pair and ue_pair and (deca.etud.id, ue_pair.id) in deca.res_pair.dispense_ues, ), ] # Ordonne selon les dates des 2 semestres considérés: if reverse_semestre: ues_ro_dispense[0], ues_ro_dispense[1] = ( ues_ro_dispense[1], ues_ro_dispense[0], ) # Colonnes d'UE: for ue, ue_read_only, ue_dispense in ues_ro_dispense: if ue: H.append( _gen_but_niveau_ue( ue, deca.decisions_ues[ue.id], disabled=read_only or ue_read_only, annee_prec=ue_read_only, niveau_id=ue.niveau_competence.id, ue_dispense=ue_dispense, ) ) else: H.append("""<div class="niveau_vide"></div>""") # Colonne RCUE H.append(_gen_but_rcue(dec_rcue, niveau)) H.append("</div>") # but_annee return "\n".join(H) def _gen_but_select( name: str, codes: list[str], code_valide: str, disabled: bool = False, klass: str = "", data: dict = None, code_valide_label: str = "", ) -> str: "Le menu html select avec les codes" # if disabled: # mauvaise idée car le disabled est traité en JS # return f"""<div class="but_code {klass}">{code_valide}</div>""" data = data or {} options_htm = "\n".join( [ f"""<option value="{code}" {'selected' if code == code_valide else ''} class="{'recorded' if code == code_valide else ''}" >{code if ((code != code_valide) or not code_valide_label) else code_valide_label }</option>""" for code in codes ] ) return f"""<select required name="{name}" class="but_code {klass}" data-orig_code="{code_valide or (codes[0] if codes else '')}" data-orig_recorded="{code_valide or ''}" onchange="change_menu_code(this);" {"disabled" if disabled else ""} {" ".join( f'data-{k}="{v}"' for (k,v) in data.items() )} >{options_htm}</select> """ def _gen_but_niveau_ue( ue: UniteEns, dec_ue: DecisionsProposeesUE, disabled: bool = False, annee_prec: bool = False, niveau_id: int = None, ue_dispense: bool = False, ) -> str: if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]: moy_ue_str = f"""<span class="ue_cap">{ scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>""" if ue_dispense: etat_en_cours = """Non (ré)inscrit à cette UE""" else: etat_en_cours = f"""UE en cours { "sans notes" if np.isnan(dec_ue.moy_ue) else ("avec moyenne <b>" + scu.fmt_note(dec_ue.moy_ue) + "</b>") } """ scoplement = f"""<div class="scoplement"> <div> <b>UE {ue.acronyme} capitalisée </b> <span>le {dec_ue.ue_status["event_date"].strftime(scu.DATE_FMT)} </span> </div> <div> { etat_en_cours } </div> </div> """ elif dec_ue.formsemestre is None: # Validation d'UE antérieure (semestre hors année scolaire courante) if dec_ue.validation: moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.validation.moy_ue)}</span>""" scoplement = f"""<div class="scoplement"> <div> <b>UE {ue.acronyme} antérieure </b> <span>validée {dec_ue.validation.code} le {dec_ue.validation.event_date.strftime(scu.DATE_FMT)} </span> </div> <div>Non reprise dans l'année en cours</div> </div> """ else: moy_ue_str = """<span>-</span>""" scoplement = """<div class="scoplement"> <div> <b>Pas d'UE en cours ou validée dans cette compétence de ce côté.</b> </div> </div> """ else: moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>""" if dec_ue.code_valide: date_str = ( f"""enregistré le {dec_ue.validation.event_date.strftime(scu.DATEATIME_FMT)}""" if dec_ue.validation and dec_ue.validation.event_date else "" ) scoplement = f"""<div class="scoplement"> <div>Code {dec_ue.code_valide} {date_str} </div> </div> """ else: if dec_ue.ue_status and dec_ue.ue_status["was_capitalized"]: scoplement = """<div class="scoplement"> UE déjà capitalisée avec résultat moins favorable. </div> """ else: scoplement = "" ue_class = "" # 'recorded' if dec_ue.code_valide is not None else '' if dec_ue.code_valide is not None and dec_ue.codes: if dec_ue.code_valide == dec_ue.codes[0]: ue_class = "recorded" else: ue_class = "recorded_different" return f"""<div class="but_niveau_ue {ue_class} {'annee_prec' if annee_prec else ''} "> <div title="{ue.titre or ''}">{ue.acronyme}</div> <div class="but_note with_scoplement"> <div>{moy_ue_str}</div> {scoplement} </div> <div class="but_code">{ _gen_but_select("code_ue_"+str(ue.id), dec_ue.codes, dec_ue.code_valide, disabled=disabled, klass=f"code_ue ue_rcue_{niveau_id}" if not disabled else "" ) }</div> </div>""" def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str: if dec_rcue is None or not dec_rcue.rcue.complete: return """ <div class="but_niveau_rcue niveau_vide with_scoplement"> <div></div> <div class="scoplement">Pas de RCUE (UE non capitalisée ?)</div> </div> """ code_propose_menu = dec_rcue.code_valide # le code enregistré code_valide_label = code_propose_menu if dec_rcue.validation: if dec_rcue.code_valide == dec_rcue.codes[0]: descr_validation = dec_rcue.validation.html() else: # on une validation enregistrée différence de celle proposée descr_validation = f"""Décision recommandée: <b>{dec_rcue.codes[0]}.</b> Il y avait {dec_rcue.validation.html()}""" if ( sco_codes.BUT_CODES_ORDER[dec_rcue.codes[0]] > sco_codes.BUT_CODES_ORDER[dec_rcue.code_valide] ): code_propose_menu = dec_rcue.codes[0] code_valide_label = ( f"{dec_rcue.codes[0]} (actuel {dec_rcue.code_valide})" ) scoplement = f"""<div class="scoplement">{descr_validation}</div>""" else: scoplement = "" # "pas de validation" # Déjà enregistré ? niveau_rcue_class = "" if dec_rcue.code_valide is not None and dec_rcue.codes: if dec_rcue.code_valide == dec_rcue.codes[0]: niveau_rcue_class = "recorded" else: niveau_rcue_class = "recorded_different" return f""" <div class="but_niveau_rcue {niveau_rcue_class} "> <div class="but_note with_scoplement"> <div>{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div> {scoplement} </div> <div class="but_code"> {_gen_but_select("code_rcue_"+str(niveau.id), dec_rcue.codes, code_propose_menu, disabled=True, klass="manual code_rcue", data = { "niveau_id" : str(niveau.id)}, code_valide_label = code_valide_label, )} </div> </div> """ # ------------- def infos_fiche_etud_html(etudid: int) -> str: """Section html pour fiche etudiant provisoire pour BUT 2022 """ etud = Identite.get_etud(etudid) inscriptions = ( FormSemestreInscription.query.join(FormSemestreInscription.formsemestre) .filter( FormSemestreInscription.etudid == etud.id, ) .order_by(FormSemestre.date_debut) ) formsemestres_but = [ i.formsemestre for i in inscriptions if i.formsemestre.formation.is_apc() ] if len(formsemestres_but) == 0: return "" # temporaire quick & dirty: affiche le dernier try: deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1]) return f"""<div class="infos_but"> {show_etud(deca, read_only=True)} </div> """ except ScoValueError: pass return ""