diff --git a/app/scodoc/sco_page_etud.py b/app/scodoc/sco_page_etud.py index 9e45d0ba770377a92b5cf2693b25c04fc69432fa..7839f7f75a46eb29912d82506b15a0f7ae5d918d 100644 --- a/app/scodoc/sco_page_etud.py +++ b/app/scodoc/sco_page_etud.py @@ -108,13 +108,15 @@ def _menu_scolarite( and authuser.has_permission(Permission.EtudInscrit) and not locked ) - note_enabled = etat_inscription == scu.INSCRIT and ( - etat_inscription != scu.DEMISSION + note_enabled = ( + (etat_inscription == scu.INSCRIT) + and (not locked) and ( authuser.has_permission(Permission.EditAllNotes) or (authuser.id in [resp.id for resp in formsemestre.responsables]) ) ) + items = [ { "title": dem_title, @@ -171,7 +173,7 @@ def _menu_scolarite( "enabled": authuser.has_permission(Permission.EtudInscrit), }, { - "title": "Editer toutes les notes", + "title": "Éditer toutes les notes", "endpoint": "notes.form_saisie_notes_par_etu", "args": {"etu_id": etudid, "semestre_id": formsemestre.id}, "enabled": note_enabled, diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index 0e65b1dca4c1bf90e65f5e68b3d069f52dbc3fd7..54933d7a526a4fc3299f4940713800509b9991fc 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -42,7 +42,6 @@ from app.comp.res_compat import NotesTableCompat from app.models import ( Evaluation, FormSemestre, - Module, ModuleImpl, ScolarNews, Assiduite, @@ -793,13 +792,8 @@ class DataModimpl: moduleimpl_id=modimpl.id, ) self.link = ( - f'<a class="stdlink" href="{href}">{self.module_code} {self.titre}</a>.' + f'<a class="stdlink" href="{href}">{self.module_code} {self.titre}</a>' ) - # self.cursus = {} - formation_id = modimpl.formsemestre.formation_id - # for ins in etudiant.inscriptions(): - # if ins.formsemestre.formation_id == formation_id: - # self.cursus[ins.formsemestre.id] = ins.formsemestre def add_eval(self, data_eval: DataSection): self.data_evals.append(data_eval) @@ -814,11 +808,8 @@ class DataEval: self.evaluation_id = evaluation.id self.description = evaluation.description self.note_min, self.note_max = evaluation.get_bounds() - self.evaluation_type: EvaluationType = evaluation.evaluation_type + self.evaluation_type: int = evaluation.evaluation_type self.note = get_evaluation_etud_note(evaluation, etudid) - # self.data_debut = evaluation.date_debut - # self.date_fin = evaluation.date_fin - # self.ue_poids = evaluation.get_ue_poids_dict() self.note_value = ( "" if self.note is None @@ -840,8 +831,9 @@ class DataEval: """ def forminput(self) -> str: - return f"""<input type="text" size="5" id="formnotes_note_{self.evaluation_id}" - class="note" onkeypress="return enter_focus_next(this, event);" + "Le champ html input" + return f"""<input type="text" size="5" id="formnotes_note_{self.evaluation_id}" + class="note" onkeypress="return enter_focus_next(this, event);" note_min="{self.note_min}" note_max="{self.note_max}" evaluation_id="{self.evaluation_id}" original-value="{self.note_value}" value="{self.note_value}" />""" @@ -854,6 +846,7 @@ class DataEval: ) def history(self): + "le span html avec le menu historique des saisies" return f"""<span id="formnotes_hist_{self.evaluation_id}"> {get_note_history_menu(self.evaluation.id, self.etudid)}</span>""" @@ -862,8 +855,9 @@ class DataForm: """ Class qui décrit les données d'une page de saisie par étudiant: decision_jury: bool = indique si une décision de jury a été prise pour cet étudiant - data_sections: dict[ModuleTYpe, DataSection] = pour chaque section (ressource, SAE, Malus, Standard), - la liste de ses modimpl concernés + data_sections: dict[ModuleTYpe, DataSection] = + pour chaque section (ressource, SAE, Malus, Standard), + la liste de ses modimpl concernés """ def __init__(self, etudiant: Identite, semestre: FormSemestre, decision_jury: bool): @@ -872,10 +866,8 @@ class DataForm: self.decision_jury = decision_jury self.data_sections = {} - def add_section(self, data_section: DataSection): - self.data_sections.append(data_section) - def link_to_etudiant(self) -> str: + "Lien html vers le bulletin de l'étudiant" link: str = url_for( "notes.formsemestre_bulletinetud", scodoc_dept=g.scodoc_dept, @@ -897,7 +889,7 @@ def get_data(semestre: FormSemestre, etudiant: Identite) -> DataForm: for module_type in ModuleType: data.data_sections[module_type] = DataSection(module_type) else: - for module_type in [ ModuleType.STANDARD, ModuleType.MALUS]: + for module_type in [ModuleType.STANDARD, ModuleType.MALUS]: data.data_sections[module_type] = DataSection(module_type) for modimpl in semestre.modimpls_sorted: module_type = modimpl.module.module_type @@ -909,30 +901,33 @@ def get_data(semestre: FormSemestre, etudiant: Identite) -> DataForm: return data -def saisie_notes_par_etu(semestre: FormSemestre, etudiant: Identite): +def saisie_notes_par_etu(formsemestre: FormSemestre, etud: Identite): + "Formulaire de saisie de toutes les notes d'un semestre pour un étudiant" # Check access # (admin, respformation, and responsable_id) - locked = not semestre.etat - # readonly peut être uinutile si l accès à cette page n est pas autorisée au public - readonly = locked or not ( + if not formsemestre.etat or not ( current_user.has_permission(Permission.EditAllNotes) - or (current_user in semestre.responsables) - ) - inscriptions = etudiant.formsemestre_inscriptions - inscription = None - for ins in inscriptions: - if ins.formsemestre_id == semestre.id: - inscription = ins - # assert ins is not None - if ins is None: - raise ScoValueError("Tentative d accès à une inscription inexistante") # bug - if ins.etat != scu.INSCRIT: - raise ValueError( - "Etudiant démissionnaire ou défaillant" - ) # ne devrait pas être accessible. bug - "Formulaire de saisie de note centré sur l'étudiant" - data = get_data(semestre, etudiant) - if semestre.formation.is_apc(): + or (current_user.id in [resp.id for resp in formsemestre.responsables]) + ): + return render_template( + "sco_page.j2", + content=f""" + <h2>Modification des notes impossible pour {current_user.user_name}</h2> + + <p>(vérifiez que le semestre n'est pas verrouillé et que vous + avez l'autorisation d'effectuer cette opération)</p> + <p>Retour à la fiche de {etud.html_link_fiche()} + </p> + """, + sco=ScoData(formsemestre=formsemestre, etud=etud), + ) + inscription = formsemestre.etuds_inscriptions.get(etud.id) + if inscription is None: + raise ScoValueError("Étudiant non inscrit à ce semestre") + if inscription.etat != scu.INSCRIT: + raise ScoValueError("Étudiant démissionnaire ou défaillant") + data = get_data(formsemestre, etud) + if formsemestre.formation.is_apc(): module_type_order = [ ModuleType.RESSOURCE, ModuleType.SAE, @@ -948,18 +943,15 @@ def saisie_notes_par_etu(semestre: FormSemestre, etudiant: Identite): "etud/saisie_notes_par_etu.j2", title="Saisie notes par étudiant", javascripts=["js/saisie_notes.js", "js/saisie_notes_par_etu.js"], - sco=ScoData(formsemestre=semestre), + sco=ScoData(formsemestre=formsemestre, etud=etud), data=data, - readonly=readonly, get_note_history_menu=get_note_history_menu, module_type_order=module_type_order, ) -# US 1030 - FIN - - -def get_evaluation_etud_note(evaluation: Evaluation, etudid: int) -> float | str | None: +def get_evaluation_etud_note(evaluation: Evaluation, etudid: int) -> NotesNotes | None: + "La note d'un étudiant à l'évaluation, None si pas de note" return NotesNotes.query.filter_by( evaluation_id=evaluation.id, etudid=etudid ).first() diff --git a/app/templates/etud/saisie_notes_par_etu.j2 b/app/templates/etud/saisie_notes_par_etu.j2 index b0c6d6948cfb6bc228862096c462a73f1cfd2b04..df358c3b7d5df7c5fa573c5c8725de1e6225b868 100644 --- a/app/templates/etud/saisie_notes_par_etu.j2 +++ b/app/templates/etud/saisie_notes_par_etu.j2 @@ -8,6 +8,8 @@ display:inline-grid; grid-template-columns: 40px 40px minmax(350px, 1fr) 70px minmax(200px, 1fr); align-items: baseline; + border: 1px solid black; + padding: 5px; } #formnotes .section { grid-column: 1 / span 5; @@ -23,6 +25,37 @@ } #formnotes .label { grid-column: 3; + display: flex; + align-items: center; + white-space: nowrap; + } + + #formnotes .label:hover { + background-color: lightgray; + } + #formnotes .label a::before { + content: "•"; + } + #formnotes .label a { + color: black; + flex-shrink: 0; + margin-right: 0.5em; + } + .dots { + flex-grow: 1; + border-bottom: 1px dotted #000; /* fallback look */ + background-image: repeating-linear-gradient(to right, black 0 1px, transparent 1px 4px); + background-position: bottom; + background-repeat: repeat-x; + background-size: 4px 1px; + height: 1em; + margin-right: 0.5em; + } + #formnotes .commentaire { + grid-column: 3; + text-align: right; + white-space: nowrap; + margin-right: 0.5em; } #formnotes .input { grid-column: 4 } #formnotes .history { grid-column: 5;} @@ -33,7 +66,6 @@ {% block app_content %} <div id="saisie_notes_par_etu" class="table-container"> - <span class="titre">{{title}}</span> <h3> Saisie des notes de l'étudiant : {{data.link_to_etudiant() | safe }} </h3> {% if data.decision_jury %} <div class="warning"> @@ -48,7 +80,7 @@ <input type="hidden" id="formnotes_formsemestre_id" value="{{data.semestre.id}}" /> {% if data.decision_jury %} {% endif %} - <div class="label">commentaire</div> + <div class="commentaire">commentaire :</div> <div class="long_input"> <input type="text" id="formnotes_comment" name="comment" class="input" size="44" onkeypress="return enter_focus_next(this, event);"> </div> @@ -60,6 +92,7 @@ {% for data_eval in data_modimpl.data_evals %} <div class="label"> {{ data_eval.titre() | safe }} + <span class="dots"></span> {# <input type="hidden" id="date_debut_{{ data_eval.evaluation_id }}" value="{{ data_eval.date_debut }}">#} {# <input type="hidden" id="date_fin_{{ data_eval.evaluation_id }}" value="{{ data_eval.date_fin }}">#} {# {% for ue in data_eval.ue_poids %}#} @@ -95,7 +128,7 @@ </form> </div> <div class="sco_help"> - <p> + <p> Les modifications sont enregistrées au fur et à mesure. </p> <h4> Codes spéciaux: </h4> diff --git a/app/views/notes.py b/app/views/notes.py index 7dfa03bd23ff942b820e08bf4a21d4231460cf6d..8f5929a270c991e4dc5a292e244886b80831d465 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -1816,20 +1816,14 @@ def form_saisie_notes(evaluation_id: int): return sco_saisie_notes.saisie_notes(evaluation, group_ids) -# modification US 1030 - DEB - - -@bp.route("/form_saisie_notes_par_etu/<int:semestre_id>/<int:etu_id>") +@bp.route("/form_saisie_notes_par_etu/<int:formsemestre_id>/<int:etudid>") @scodoc @permission_required(Permission.EnsView) -def form_saisie_notes_par_etu(etu_id: int, semestre_id: int): +def form_saisie_notes_par_etu(etudid: int, formsemestre_id: int): "Formulaire de saisie des notes centré sur l'étudiant" - etudiant = Identite.get_etud(etu_id) - semestre = FormSemestre.get_formsemestre(semestre_id) - return sco_saisie_notes.saisie_notes_par_etu(semestre, etudiant) - - -# modification US 1030 - FIN + formsemestre = FormSemestre.get_formsemestre(formsemestre_id) + etud = Identite.get_etud(etudid) + return sco_saisie_notes.saisie_notes_par_etu(formsemestre, etud) @bp.route("/formsemestre_import_notes/<int:formsemestre_id>", methods=["GET", "POST"])