diff --git a/app/api/__init__.py b/app/api/__init__.py index 6c2e493ff..5acb09096 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -122,6 +122,7 @@ from app.api import ( justificatifs, logos, moduleimpl, + operations, partitions, semset, users, diff --git a/app/api/operations.py b/app/api/operations.py new file mode 100644 index 000000000..51ba5c618 --- /dev/null +++ b/app/api/operations.py @@ -0,0 +1,100 @@ +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## + +""" + ScoDoc 9 API : liste opérations effectuées par un utilisateur + + CATEGORY + -------- + Operations +""" + +from flask import url_for +from flask_json import as_json +from flask_login import login_required + +import app +from app import db +from app.api import api_bp as bp, api_web_bp +from app.api import api_permission_required as permission_required +from app.decorators import scodoc +from app.models import NotesNotes +from app.scodoc.sco_permissions import Permission +from app.scodoc import sco_utils as scu + +MAX_QUERY_LENGTH = 10000 + + +@bp.route("/operations/user//notes") +@api_web_bp.route("/operations/user//notes") +@login_required +@scodoc +@permission_required(Permission.ScoView) +@as_json +def operations_user_notes(uid: int): + """Liste les opérations de saisie de notes effectuées par utilisateur. + + QUERY + ----- + start: indice de début de la liste + length: nombre d'éléments à retourner + draw: numéro de la requête (pour pagination, renvoyé tel quel) + order[dir]: desc ou asc + search[value]: chaîne à chercher (dans évaluation et étudiant) + PARAMS + ----- + uid: l'id de l'utilisateur + """ + start = int(app.request.args.get("start", 0)) + length = min(int(app.request.args.get("length", 10)), MAX_QUERY_LENGTH) + order = app.request.args.get("order[dir]", "desc") + draw = int(app.request.args.get("draw", 1)) + search = app.request.args.get("search[value]", "") + query = db.session.query(NotesNotes).filter(NotesNotes.uid == uid) + if order == "asc": + query = query.order_by(NotesNotes.date.asc()) + else: + query = query.order_by(NotesNotes.date.desc()) + + # Pour l'efficacité, limite si pas de recherche en python + limited_query = query.offset(start).limit(length) if not search else query + + data = [] + for note in limited_query: + obj = { + "date": note.date.isoformat(), + "date_dmy": note.date.strftime(scu.DATEATIME_FMT), + "operation": "Saisie de note", + "value": scu.fmt_note(note.value), + "id": note.id, + "uid": note.uid, + "etudiant": note.etudiant.to_dict_short(), + "etudiant_link": note.etudiant.html_link_fiche(), + "evaluation": note.evaluation.to_dict_api(), + "evaluation_link": f"""{note.evaluation.descr()}""", + } + if search: + search = search.lower() + if ( + search not in note.etudiant.nomprenom.lower() + and search not in note.evaluation.descr().lower() + and search not in obj["date_dmy"] + ): + continue # skip + + data.append(obj) + + result = data[start : start + length] if search else data + return { + "draw": draw, + "recordsTotal": query.count(), # unfiltered + "recordsFiltered": len(data) if search else query.count(), + "data": result, + } diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 6ef71ff2f..6574047ee 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -121,6 +121,12 @@ class Identite(models.ScoDocModel): cascade="all, delete-orphan", lazy="dynamic", ) + notes = db.relationship( + "NotesNotes", + 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 06db8075d..cf998a56a 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -58,6 +58,12 @@ class Evaluation(models.ScoDocModel): # ordre de presentation (par défaut, le plus petit numero # est la plus ancienne eval): numero = db.Column(db.Integer, nullable=False, default=0) + notes = db.relationship( + "NotesNotes", + backref="evaluation", + cascade="all, delete-orphan", + lazy="dynamic", + ) ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True) _sco_dept_relations = ("ModuleImpl", "FormSemestre") # accès au dept_id @@ -380,6 +386,13 @@ class Evaluation(models.ScoDocModel): return f"""du {self.date_debut.strftime('%d/%m/%Y')} à { _h(self.date_debut)} au {self.date_fin.strftime('%d/%m/%Y')} à {_h(self.date_fin)}""" + def descr(self) -> str: + "Description de l'évaluation pour affichage (avec module et semestre)" + return f"""{self.description} {self.descr_date()} en { + self.moduleimpl.module.titre_str()} du { + self.moduleimpl.formsemestre.titre_formation(with_sem_idx=True) + }""" + def heure_debut(self) -> str: """L'heure de début (sans la date), en ISO. Chaine vide si non renseignée.""" diff --git a/app/templates/user_board/user_board.j2 b/app/templates/user_board/user_board.j2 index 5f5dac7ab..2832f9518 100644 --- a/app/templates/user_board/user_board.j2 +++ b/app/templates/user_board/user_board.j2 @@ -1,8 +1,10 @@ {# Tableau de bord utilisateur #} {% extends "base.j2" %} - -{% block app_content %} +{% block styles %} +{{super()}} + + +{% endblock %} + +{% block app_content %}

{{user.get_nomcomplet()}}

@@ -105,6 +114,61 @@ {% endfor %}
{% endfor %} + + +
+
+ Dernières saisies de notes par {{user.get_prenomnom()}} +
+ + + + + + + + + + + + +
DateÉvaluationÉtudiantNote
+ + + + + {% endblock app_content %} + + +{% block scripts %} + {{ super() }} + + + +{% endblock %} diff --git a/app/views/user_board.py b/app/views/user_board.py index fe3489e41..30d51c3ea 100644 --- a/app/views/user_board.py +++ b/app/views/user_board.py @@ -14,6 +14,7 @@ from app.decorators import ( permission_required, ) from app.models import Departement, FormSemestre +from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission from app.scodoc import sco_preferences from app.views import scodoc_bp as bp @@ -25,6 +26,9 @@ from app.views import ScoData @login_required def user_board(user_name: str): """Tableau de bord utilisateur: liens vers ses objets""" + fallback_dept = db.session.query(Departement).first() + if not fallback_dept: + raise ScoValueError("Aucun département existant") user = User.query.filter_by(user_name=user_name).first_or_404() ( formsemestres_by_dept, @@ -47,6 +51,7 @@ def user_board(user_name: str): "user_board/user_board.j2", depts=depts, dept_names=dept_names_sorted, + fallback_dept=fallback_dept, formsemestres_by_dept=formsemestres_by_dept, modimpls_by_formsemestre=modimpls_by_formsemestre, sco=ScoData(), diff --git a/sco_version.py b/sco_version.py index 83b81513e..2e58c9f14 100644 --- a/sco_version.py +++ b/sco_version.py @@ -3,7 +3,7 @@ "Infos sur version ScoDoc" -SCOVERSION = "9.7.37" +SCOVERSION = "9.7.38" SCONAME = "ScoDoc"