diff --git a/app/models/assiduites.py b/app/models/assiduites.py index 44cf1fc1e7e83dc0e354ddb1fb24c28d1cf5a0d0..c920bbe4490a17a8329165b008f5da00550d533f 100644 --- a/app/models/assiduites.py +++ b/app/models/assiduites.py @@ -141,7 +141,7 @@ class Assiduite(ScoDocModel): }""" def __repr__(self) -> str: - return f"<Assiduite {self.id}: {self.__str__()}>" + return f"<Assiduite {self.id}: {self.etudiant.nom}: {self.__str__()}>" @classmethod def create_assiduite( diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 669a4b031b9e840dbad71869419b5c53dfb42f48..8d6e02664312af0771a6c2e7ee72d4f113984902 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -127,6 +127,12 @@ class Identite(models.ScoDocModel): cascade="all, delete-orphan", lazy="dynamic", ) + notes_log = db.relationship( + "NotesNotesLog", + backref="etudiant", + cascade="all, delete-orphan", + lazy="dynamic", + ) # Relations avec les assiduites et les justificatifs assiduites = db.relationship( "Assiduite", back_populates="etudiant", lazy="dynamic", cascade="all, delete" diff --git a/app/models/evaluations.py b/app/models/evaluations.py index cf998a56a429633b4a54808cf34976bfefda326b..a27f43090aa7af052471dfdc9ddec26e3713b8e2 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -64,6 +64,13 @@ class Evaluation(models.ScoDocModel): cascade="all, delete-orphan", lazy="dynamic", ) + notes_log = db.relationship( + "NotesNotesLog", + backref="evaluation", + cascade="all, delete-orphan", + lazy="dynamic", + primaryjoin="Evaluation.id == foreign(NotesNotesLog.evaluation_id)", + ) ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True) _sco_dept_relations = ("ModuleImpl", "FormSemestre") # accès au dept_id diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py index 5820d20a3155eb81a42f572c012162206f4e4604..3161c9cbc9ce77a7807e1b84522289f06233cd2a 100644 --- a/app/scodoc/gen_tables.py +++ b/app/scodoc/gen_tables.py @@ -499,11 +499,7 @@ class GenTable: H.append(caption) if self.base_url: H.append('<span class="gt_export_icons">') - if self.xls_link: - H.append( - f""" <a href="{add_query_param(self.base_url, "fmt", "xls") - }">{scu.ICON_XLS}</a>""" - ) + H.append(self.xls_export_button()) if self.xls_link and self.pdf_link: H.append(" ") if self.pdf_link: @@ -517,6 +513,15 @@ class GenTable: H.append(self.html_next_section) return "\n".join(H) + def xls_export_button(self) -> str: + "markup pour export excel" + return ( + f""" <a href="{add_query_param(self.base_url, "fmt", "xls") + }">{scu.ICON_XLS}</a>""" + if self.xls_link + else "" + ) + def excel(self, wb=None): """Simple Excel representation of the table""" if wb is None: diff --git a/app/scodoc/sco_undo_notes.py b/app/scodoc/sco_undo_notes.py index 9d9102b2a21cb547b5de6347ff7c3653f67ff67e..d34b84385901644e5584ba5a7df0cbdcc48260cb 100644 --- a/app/scodoc/sco_undo_notes.py +++ b/app/scodoc/sco_undo_notes.py @@ -46,9 +46,11 @@ Opérations: """ import datetime -from flask import g, request, url_for +from flask import g, render_template, request, url_for -from app.models import Evaluation, FormSemestre +from app import db +from app.auth.models import User +from app.models import Evaluation, FormSemestre, ModuleImpl, NotesNotes, NotesNotesLog from app.scodoc.intervals import intervalmap import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb @@ -178,36 +180,67 @@ def evaluation_list_operations(evaluation_id: int): return tab.make_page() -def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"): +def formsemestre_list_saisies_notes(formsemestre_id, only_modifs=False, fmt="html"): """Table listant toutes les opérations de saisies de notes, dans toutes les évaluations du semestre. """ - formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id) - rows = ndb.SimpleDictFetch( - """SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.date_debut, - u.user_name, e.id as evaluation_id - FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi, - notes_modules mod, identite i, "user" u - WHERE mi.id = e.moduleimpl_id - and mi.module_id = mod.id - and e.id = n.evaluation_id - and i.id = n.etudid - and u.id = n.uid - and mi.formsemestre_id = %(formsemestre_id)s - ORDER BY date desc - """, - {"formsemestre_id": formsemestre_id}, + formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id) + only_modifs = scu.to_bool(only_modifs) + model = NotesNotesLog if only_modifs else NotesNotes + notes_query = ( + db.session.query(model) + .join(Evaluation, Evaluation.id == model.evaluation_id) + .join(ModuleImpl) + .filter_by(formsemestre_id=formsemestre.id) + .order_by(model.date.desc()) ) + # Formate les notes keep_numeric = fmt in scu.FORMATS_NUMERIQUES - for row in rows: - row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric) - row["date_evaluation"] = ( - row["date_debut"].strftime("%d/%m/%Y %H:%M") if row["date_debut"] else "" - ) - row["_date_evaluation_order"] = ( - row["date_debut"].isoformat() if row["date_debut"] else "" + rows = [] + for note in notes_query: + ens = User.get_user(note.uid) + evaluation = note.evaluation + rows.append( + { + "date": note.date.strftime(scu.DATEATIME_FMT), + "_date_order": note.date.isoformat(), + "code_nip": note.etudiant.code_nip, + "nom": note.etudiant.nom_disp(), + "prenom": note.etudiant.prenom_str, + "date_evaluation": ( + evaluation.date_debut.strftime(scu.DATEATIME_FMT) + if evaluation and note.evaluation.date_debut + else "" + ), + "_date_evaluation_order": ( + note.evaluation.date_debut.isoformat() + if evaluation and note.evaluation.date_debut + else "" + ), + "value": scu.fmt_note(note.value, keep_numeric=keep_numeric), + "module": ( + ( + note.evaluation.moduleimpl.module.code + or note.evaluation.moduleimpl.module.titre + ) + if evaluation + else "" + ), + "evaluation": note.evaluation.description if evaluation else "", + "_evaluation_target": ( + url_for( + "notes.evaluation_listenotes", + scodoc_dept=g.scodoc_dept, + evaluation_id=note.evaluation_id, + ) + if evaluation + else "" + ), + "user_name": ens.user_name if ens else "", + } ) + columns_ids = ( "date", "code_nip", @@ -215,9 +248,8 @@ def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"): "prenom", "value", "user_name", - "titre", - "evaluation_id", - "description", + "module", + "evaluation", "date_evaluation", "comment", ) @@ -230,11 +262,11 @@ def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"): "comment": "Remarque", "user_name": "Enseignant", "evaluation_id": "evaluation_id", - "titre": "Module", - "description": "Evaluation", + "module": "Module", + "evaluation": "Evaluation", "date_evaluation": "Date éval.", } - tab = GenTable( + table = GenTable( titles=titles, columns_ids=columns_ids, rows=rows, @@ -244,11 +276,25 @@ def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"): html_sortable=True, caption=f"Saisies de notes dans {formsemestre.titre_annee()}", preferences=sco_preferences.SemPreferences(formsemestre_id), - base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id), + base_url=f"""{request.base_url}?formsemestre_id={ + formsemestre_id}&only_modifs={int(only_modifs)}""", origin=f"Généré par {sco_version.SCONAME} le " + scu.timedate_human_repr() + "", table_id="formsemestre_list_saisies_notes", + filename=( + f"modifs_notes_S{formsemestre.semestre_id}" + if only_modifs + else f"saisies_notes_S{formsemestre.semestre_id}" + ), ) - return tab.make_page(fmt=fmt) + if fmt == "html": + return render_template( + "formsemestre/list_saisies_notes.j2", + table=table, + title="Opérations de saisies de notes", + only_modifs=only_modifs, + formsemestre_id=formsemestre.id, + ) + return table.make_page(fmt=fmt, page_title="Opérations de saisies de notes") def get_note_history(evaluation_id, etudid, fmt=""): diff --git a/app/templates/formsemestre/list_saisies_notes.j2 b/app/templates/formsemestre/list_saisies_notes.j2 new file mode 100644 index 0000000000000000000000000000000000000000..517d2142e51c6991efd7baf0c886b35c30b4e9db --- /dev/null +++ b/app/templates/formsemestre/list_saisies_notes.j2 @@ -0,0 +1,48 @@ +{% extends "sco_page.j2" %} + +{% block styles %} +{{super()}} +<style> +.export_xls_but { + margin-left: 32px; +} +</style> +{% endblock %} + +{% block app_content %} +<h2 class="formsemestre">{{title_h2}}</h2> + +<div>{{table.get_nb_rows()}} + {% if only_modifs %}modifications{% else %}saisies{% endif %} + de notes dans ce semestre. +</div> +<form id="filter-form"> + <label> + <input type="checkbox" id="only-modifs-checkbox" name="only_modifs" value="1" + {% if only_modifs %}checked{% endif %}> + Lister uniquement les modifications + </label> + <span class="export_xls_but">{{table.xls_export_button()|safe}} excel</span> +</form> + +{{table.html()|safe}} + + +{% endblock %} + +{% block scripts %} +{{super()}} + +<script> + document.getElementById('only-modifs-checkbox').addEventListener('change', function() { + var form = document.getElementById('filter-form'); + var onlyModifs = this.checked ? '1' : '0'; + + var url = new URL(window.location.href); + url.searchParams.set('formsemestre_id', {{formsemestre_id}}); + url.searchParams.set('only_modifs', onlyModifs); + + window.location.href = url.toString(); + }); +</script> +{% endblock %} \ No newline at end of file diff --git a/sco_version.py b/sco_version.py index 8110b6d018b207f579da34c38a96f06a8940edcb..9e646dd9cdec5fcc3f22beb911c6a2ed85c192d2 100644 --- a/sco_version.py +++ b/sco_version.py @@ -3,7 +3,7 @@ "Infos sur version ScoDoc" -SCOVERSION = "9.7.39" +SCOVERSION = "9.7.40" SCONAME = "ScoDoc" diff --git a/tests/api/test_api_permissions.py b/tests/api/test_api_permissions.py index 53393654aa73496378da51f1d43403144c22e54a..c459aeed878a6c02a1f1f7d669dd74123a64538e 100755 --- a/tests/api/test_api_permissions.py +++ b/tests/api/test_api_permissions.py @@ -94,6 +94,7 @@ def test_permissions(api_headers): "/ScoDoc/api/justificatif/1/list", # demande AbsJustifView "/ScoDoc/api/justificatif/1/justifies", # demande ScoJustifChange "/ScoDoc/api/justificatif/1/export", # demande AbsChange + "/ScoDoc/api/operations/user/", # demande superamin ou user lui même ] ): # On passe ces routes spéciales