Liste des saisies de notes sur le user_board

This commit is contained in:
Emmanuel Viennet 2024-11-04 21:26:18 +01:00
parent d7f4209a5a
commit 2642a25cf7
7 changed files with 192 additions and 3 deletions

View File

@ -122,6 +122,7 @@ from app.api import (
justificatifs, justificatifs,
logos, logos,
moduleimpl, moduleimpl,
operations,
partitions, partitions,
semset, semset,
users, users,

100
app/api/operations.py Normal file
View File

@ -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/<int:uid>/notes")
@api_web_bp.route("/operations/user/<int:uid>/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"""<a href="{
url_for('notes.evaluation_listenotes',
scodoc_dept=note.evaluation.moduleimpl.formsemestre.departement.acronym,
evaluation_id=note.evaluation_id)
}">{note.evaluation.descr()}</a>""",
}
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,
}

View File

@ -121,6 +121,12 @@ class Identite(models.ScoDocModel):
cascade="all, delete-orphan", cascade="all, delete-orphan",
lazy="dynamic", lazy="dynamic",
) )
notes = db.relationship(
"NotesNotes",
backref="etudiant",
cascade="all, delete-orphan",
lazy="dynamic",
)
# Relations avec les assiduites et les justificatifs # Relations avec les assiduites et les justificatifs
assiduites = db.relationship( assiduites = db.relationship(
"Assiduite", back_populates="etudiant", lazy="dynamic", cascade="all, delete" "Assiduite", back_populates="etudiant", lazy="dynamic", cascade="all, delete"

View File

@ -58,6 +58,12 @@ class Evaluation(models.ScoDocModel):
# ordre de presentation (par défaut, le plus petit numero # ordre de presentation (par défaut, le plus petit numero
# est la plus ancienne eval): # est la plus ancienne eval):
numero = db.Column(db.Integer, nullable=False, default=0) 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) ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
_sco_dept_relations = ("ModuleImpl", "FormSemestre") # accès au dept_id _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')} à { 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)}""" _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: def heure_debut(self) -> str:
"""L'heure de début (sans la date), en ISO. """L'heure de début (sans la date), en ISO.
Chaine vide si non renseignée.""" Chaine vide si non renseignée."""

View File

@ -1,8 +1,10 @@
{# Tableau de bord utilisateur #} {# Tableau de bord utilisateur #}
{% extends "base.j2" %} {% extends "base.j2" %}
{% block styles %}
{% block app_content %} {{super()}}
<link type="text/css" rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/qtip/jquery.qtip-3.0.3.min.css" />
<link rel="stylesheet" type="text/css" href="{{scu.STATIC_DIR}}/DataTables/datatables.min.css" />
<style> <style>
.ub-formsemestres { .ub-formsemestres {
display: flex; display: flex;
@ -60,7 +62,14 @@
color: black; color: black;
text-decoration: none; text-decoration: none;
} }
div.scobox.saisies-notes {
background-color: rgb(243, 255, 255);
}
</style> </style>
{% endblock %}
{% block app_content %}
<div class="tab-content"> <div class="tab-content">
<h2>{{user.get_nomcomplet()}}</h2> <h2>{{user.get_nomcomplet()}}</h2>
@ -105,6 +114,61 @@
{% endfor %} {% endfor %}
</div> </div>
{% endfor %} {% endfor %}
<div class="scobox saisies-notes">
<div class="scobox-title">
Dernières saisies de notes par {{user.get_prenomnom()}}
</div>
<table id="saisies-notes" class="display" style="width:100%">
<thead>
<tr>
<th>Date</th>
<th>Évaluation</th>
<th>Étudiant</th>
<th>Note</th>
</tr>
</thead>
<tbody>
<!-- Data will be loaded dynamically via JavaScript -->
</tbody>
</table>
</div> </div>
</div>
{% endblock app_content %} {% endblock app_content %}
{% block scripts %}
{{ super() }}
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
<script src="{{scu.STATIC_DIR}}/DataTables/datatables.min.js"></script>
<script>
$(document).ready(function() {
$('#saisies-notes').DataTable({
"processing": true,
"serverSide": true,
"ajax": {
"url": "{{ url_for('apiweb.operations_user_notes',
scodoc_dept=current_user.dept or g.scodoc_dept or fallback_dept.acronym,
uid=user.id) }}",
"type": "GET"
},
"columns": [
{ "data": "date_dmy", "orderable": false },
{ "data": "evaluation_link", "orderable": false },
{ "data": "etudiant_link", "orderable": false },
{ "data": "value", "orderable": false }
],
"language": {
search: "Chercher (date, titre, étudiant) :", // Change the "Search:" label
lengthMenu: "Show _MENU_ records per page"
}
});
});
</script>
{% endblock %}

View File

@ -14,6 +14,7 @@ from app.decorators import (
permission_required, permission_required,
) )
from app.models import Departement, FormSemestre from app.models import Departement, FormSemestre
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.views import scodoc_bp as bp from app.views import scodoc_bp as bp
@ -25,6 +26,9 @@ from app.views import ScoData
@login_required @login_required
def user_board(user_name: str): def user_board(user_name: str):
"""Tableau de bord utilisateur: liens vers ses objets""" """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() user = User.query.filter_by(user_name=user_name).first_or_404()
( (
formsemestres_by_dept, formsemestres_by_dept,
@ -47,6 +51,7 @@ def user_board(user_name: str):
"user_board/user_board.j2", "user_board/user_board.j2",
depts=depts, depts=depts,
dept_names=dept_names_sorted, dept_names=dept_names_sorted,
fallback_dept=fallback_dept,
formsemestres_by_dept=formsemestres_by_dept, formsemestres_by_dept=formsemestres_by_dept,
modimpls_by_formsemestre=modimpls_by_formsemestre, modimpls_by_formsemestre=modimpls_by_formsemestre,
sco=ScoData(), sco=ScoData(),

View File

@ -3,7 +3,7 @@
"Infos sur version ScoDoc" "Infos sur version ScoDoc"
SCOVERSION = "9.7.37" SCOVERSION = "9.7.38"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"