Assiduite: tableau etat_abs_date

This commit is contained in:
Emmanuel Viennet 2023-12-09 15:53:45 +01:00
parent 0cf2030656
commit 4db178ea52
12 changed files with 146 additions and 109 deletions

View File

@ -171,7 +171,7 @@ class Identite(db.Model, models.ScoDocModel):
def html_link_fiche(self) -> str: def html_link_fiche(self) -> str:
"lien vers la fiche" "lien vers la fiche"
return f"""<a class="stdlink" href="{self.url_fiche()}">{self.nomprenom}</a>""" return f"""<a class="etudlink" href="{self.url_fiche()}">{self.nomprenom}</a>"""
def url_fiche(self) -> str: def url_fiche(self) -> str:
"url de la fiche étudiant" "url de la fiche étudiant"
@ -319,6 +319,8 @@ class Identite(db.Model, models.ScoDocModel):
@cached_property @cached_property
def sort_key(self) -> tuple: def sort_key(self) -> tuple:
"clé pour tris par ordre alphabétique" "clé pour tris par ordre alphabétique"
# Note: scodoc7 utilisait sco_etud.etud_sort_key, à mettre à jour
# si on modifie cette méthode.
return ( return (
scu.sanitize_string( scu.sanitize_string(
self.nom_usuel or self.nom or "", remove_spaces=False self.nom_usuel or self.nom or "", remove_spaces=False

View File

@ -334,9 +334,9 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
""" """
req = """SELECT E.id AS evaluation_id, E.* req = """SELECT E.id AS evaluation_id, E.*
FROM notes_evaluation E, notes_moduleimpl MI FROM notes_evaluation E, notes_moduleimpl MI
WHERE MI.formsemestre_id = %(formsemestre_id)s WHERE MI.formsemestre_id = %(formsemestre_id)s
and MI.id = E.moduleimpl_id and MI.id = E.moduleimpl_id
ORDER BY MI.id, numero desc, date_debut desc ORDER BY MI.id, numero desc, date_debut desc
""" """
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
@ -494,7 +494,7 @@ def formsemestre_evaluations_cal(formsemestre_id):
</p> </p>
<ul> <ul>
<li>en <span style= <li>en <span style=
"background-color: {color_incomplete}">rouge</span> "background-color: {color_incomplete}">rouge</span>
les évaluations passées auxquelles il manque des notes les évaluations passées auxquelles il manque des notes
</li> </li>
<li>en <span style= <li>en <span style=
@ -654,7 +654,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True):
can_edit = modimpl.can_edit_notes(current_user, allow_ens=False) can_edit = modimpl.can_edit_notes(current_user, allow_ens=False)
mod_descr = f"""<a class="stdlink" href="{url_for("notes.moduleimpl_status", mod_descr = f"""<a class="stdlink" href="{url_for("notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id, moduleimpl_id=modimpl.id,
)}">{modimpl.module.code or ""} {modimpl.module.abbrev or modimpl.module.titre or "?"}</a> )}">{modimpl.module.code or ""} {modimpl.module.abbrev or modimpl.module.titre or "?"}</a>
<span class="resp">(resp. <a title="{resp_nomcomplet}">{resp_nomprenom}</a>)</span> <span class="resp">(resp. <a title="{resp_nomcomplet}">{resp_nomprenom}</a>)</span>
@ -690,9 +690,9 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True):
H.append( H.append(
f"""<span class="evallink"><a class="stdlink" href="{url_for( f"""<span class="evallink"><a class="stdlink" href="{url_for(
'assiduites.etat_abs_date', 'assiduites.etat_abs_date',
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
group_ids=group_id, group_ids=group_id,
desc=evaluation.description or "", evaluation_id=evaluation.id,
date_debut=evaluation.date_debut.isoformat(), date_debut=evaluation.date_debut.isoformat(),
date_fin=evaluation.date_fin.isoformat(), date_fin=evaluation.date_fin.isoformat(),
) )

View File

@ -257,10 +257,11 @@ def get_sem_groups(formsemestre_id):
) )
def get_group_members(group_id, etat=None): def get_group_members(group_id: int, etat=None) -> list[dict]:
"""Liste des etudiants d'un groupe. """Liste des etudiants d'un groupe.
Si etat, filtre selon l'état de l'inscription Si etat, filtre selon l'état de l'inscription
Trié par nom_usuel (ou nom) puis prénom Trié par nom_usuel (ou nom) puis prénom
Résultat: list de dict avec champs étudiant, adresse, group_membership
""" """
req = """SELECT i.id as etudid, i.*, a.*, gm.*, ins.etat req = """SELECT i.id as etudid, i.*, a.*, gm.*, ins.etat
FROM identite i, adresse a, group_membership gm, FROM identite i, adresse a, group_membership gm,

View File

@ -304,7 +304,7 @@ class DisplayedGroupsInfos:
.groups_query_args : 'group_ids=xxx&group_ids=yyy' .groups_query_args : 'group_ids=xxx&group_ids=yyy'
.base_url : url de la requete, avec les groupes, sans les autres paramètres .base_url : url de la requete, avec les groupes, sans les autres paramètres
.formsemestre_id : semestre "principal" (en fait celui du 1er groupe de la liste) .formsemestre_id : semestre "principal" (en fait celui du 1er groupe de la liste)
.members .members :
.groups_titles .groups_titles
etat: filtrage selon l'état de l'inscription etat: filtrage selon l'état de l'inscription

View File

@ -126,7 +126,7 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
"endpoint": "assiduites.etat_abs_date", "endpoint": "assiduites.etat_abs_date",
"args": { "args": {
"group_ids": group_id, "group_ids": group_id,
"desc": evaluation.description or "", "evaluation_id": evaluation.id,
"date_debut": evaluation.date_debut.isoformat() "date_debut": evaluation.date_debut.isoformat()
if evaluation.date_debut if evaluation.date_debut
else "", else "",

View File

@ -1012,10 +1012,7 @@ span.linktitresem {
font-weight: normal; font-weight: normal;
} }
span.linktitresem a:link { span.linktitresem a:link,
color: red;
}
span.linktitresem a:visited { span.linktitresem a:visited {
color: red; color: red;
} }
@ -1031,6 +1028,14 @@ a.stdlink:hover {
text-decoration: underline; text-decoration: underline;
} }
a.etudlink,
a.etud:visited {
color: red;
}
a.etudlink:hover {
text-decoration: underline;
}
/* a.link_accessible {} */ /* a.link_accessible {} */
a.link_unauthorized, a.link_unauthorized,
a.link_unauthorized:visited { a.link_unauthorized:visited {

View File

@ -54,6 +54,7 @@ class RowEtud(tb.Row):
super().__init__(table, etud.id, *args, **kwargs) super().__init__(table, etud.id, *args, **kwargs)
self.etud = etud self.etud = etud
self.target_url = etud.url_fiche() self.target_url = etud.url_fiche()
self.target_title = "" # pour bulle aide sur lien
def add_etud_cols(self): def add_etud_cols(self):
"""Ajoute colonnes étudiant: codes, noms""" """Ajoute colonnes étudiant: codes, noms"""
@ -81,7 +82,14 @@ class RowEtud(tb.Row):
# formsemestre_id=res.formsemestre.id, # formsemestre_id=res.formsemestre.id,
# etudid=etud.id, # etudid=etud.id,
# ) # )
self.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail") self.add_cell(
"civilite_str",
"Civ.",
etud.civilite_str,
"identite_detail",
target=self.target_url,
target_attrs={"class": "discretelink", "title": self.target_title},
)
self.add_cell( self.add_cell(
"nom_disp", "nom_disp",
"Nom", "Nom",
@ -89,9 +97,20 @@ class RowEtud(tb.Row):
"identite_detail", "identite_detail",
data={"order": etud.sort_key}, data={"order": etud.sort_key},
target=self.target_url, target=self.target_url,
target_attrs={"class": "etudinfo discretelink", "id": str(etud.id)}, target_attrs={
"class": "etudinfo discretelink",
"id": str(etud.id),
"title": self.target_title,
},
)
self.add_cell(
"prenom",
"Prénom",
etud.prenom,
"identite_detail",
target=self.target_url,
target_attrs={"class": "discretelink", "title": self.target_title},
) )
self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail")
def etuds_sorted_from_ids(etudids) -> list[Identite]: def etuds_sorted_from_ids(etudids) -> list[Identite]:

View File

@ -524,7 +524,7 @@ class AssiDisplayOptions:
self.show_module = to_bool(show_module) self.show_module = to_bool(show_module)
def remplacer(self, **kwargs): def remplacer(self, **kwargs):
"Positionnne options booléennes selon arguments" "Positionne options booléennes selon arguments"
for k, v in kwargs.items(): for k, v in kwargs.items():
if k.startswith("show_"): if k.startswith("show_"):
setattr(self, k, to_bool(v)) setattr(self, k, to_bool(v))

View File

@ -1,37 +1,30 @@
<h2>Présence du groupe {{group_title}} le {{date_debut.strftime("%d/%m/%Y")}} {% extends "sco_page.j2" %}
de {{date_debut.strftime("%H:%M")}} à {{date_fin.strftime("%H:%M")}}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
{% endblock %}
{% block app_content %}
<div class="tab-content">
<h2>Présence du groupe {{group_title}} le {{date_debut.strftime("%d/%m/%Y")}}
de {{date_debut.strftime("%H:%M")}} à {{date_fin.strftime("%H:%M")}}
</h2> </h2>
<table>
<thead>
<tr>
<th>
Nom
</th>
<th>
Présence
</th>
</tr>
</thead>
<tbody> {% if evaluation %}
{% for etud in etudiants %} <div>Assiduité lors de l'évaluation
<tr> <a class="stdlink" href="{{
<td> url_for('notes.evaluation_listenotes',
{{etud.nom | safe}} scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
</td> }}"><em>{{evaluation.description or ''}}</em></a>
<td style="text-align: center;"> {% endif %}
{{etud.etat}} <div>{{description}}</div>
</td>
</tr>
{% endfor %}
</tbody>
</table> {{table.html()|safe}}
<style>
tr,
td {
background-color: #FFFFFF;
} </div>
</style> {% endblock %}

View File

@ -1,7 +1,7 @@
{% block app_content %} {% block app_content %}
<div class="pageContent"> <div class="pageContent">
<h2>Liste de l'assiduité et des justificatifs de <span class="rouge">{{sco.etud.nomprenom}}</span></h2> <h2>Liste de l'assiduité et des justificatifs de {{sco.etud.html_link_fiche()|safe}}</h2>
{{tableau | safe }} {{tableau | safe }}
</div> </div>
{% endblock app_content %} {% endblock app_content %}

View File

@ -74,7 +74,7 @@
} }
try { try {
if (isCalendrier()) { if (isCalendrier()) {
window.location = `ListeAssiduitesEtud?etudid=${etudid}&assiduite_id=${assiduité.assiduite_id}` window.location = `liste_assiduites_etud?etudid=${etudid}&assiduite_id=${assiduité.assiduite_id}`
} }
} catch { } } catch { }
}); });
@ -320,4 +320,4 @@
.mini-timeline-block.invalid_justified { .mini-timeline-block.invalid_justified {
background-image: var(--motif-justi-invalide); background-image: var(--motif-justi-invalide);
} }
</style> </style>

View File

@ -45,6 +45,7 @@ from app.models import (
Departement, Departement,
Evaluation, Evaluation,
FormSemestre, FormSemestre,
GroupDescr,
Identite, Identite,
Justificatif, Justificatif,
ModuleImpl, ModuleImpl,
@ -53,6 +54,7 @@ from app.models import (
from app.scodoc.codes_cursus import UE_STANDARD from app.scodoc.codes_cursus import UE_STANDARD
from app.auth.models import User from app.auth.models import User
from app.models.assiduites import get_assiduites_justif from app.models.assiduites import get_assiduites_justif
from app.tables.list_etuds import RowEtud, TableEtud
import app.tables.liste_assiduites as liste_assi import app.tables.liste_assiduites as liste_assi
from app.views import assiduites_bp as bp from app.views import assiduites_bp as bp
@ -466,7 +468,7 @@ def _record_assiduite_etud(
# ), # ),
@bp.route("/ListeAssiduitesEtud") @bp.route("/liste_assiduites_etud")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
def liste_assiduites_etud(): def liste_assiduites_etud():
@ -1011,18 +1013,59 @@ def visu_assiduites_group():
).build() ).build()
class RowEtudWithAssi(RowEtud):
"""Ligne de la table d'étudiants avec colonne Assiduité"""
def __init__(
self,
table: TableEtud,
etud: Identite,
etat_assiduite: str,
*args,
**kwargs,
):
super().__init__(table, etud, *args, **kwargs)
self.etat_assiduite = etat_assiduite
# remplace lien vers fiche par lien vers calendrier
self.target_url = url_for(
"assiduites.calendrier_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
)
self.target_title = f"Calendrier de {etud.nomprenom}"
def add_etud_cols(self):
"""Ajoute colonnes pour cet étudiant"""
super().add_etud_cols()
self.add_cell(
"assi-type",
"Présence",
self.etat_assiduite,
"assi-type",
)
self.classes += ["row-assiduite", self.etat_assiduite.lower()]
@bp.route("/etat_abs_date") @bp.route("/etat_abs_date")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
def etat_abs_date(): def etat_abs_date():
"""date_debut, date_fin en ISO""" """Tableau de l'état d'assiduité d'un ou plusieurs groupes
sur la plage de dates date_debut, date_fin.
group_ids=6573 : id de(s) groupe(s)
date_debut, date_fin: format ISO
evaluation_id: optionnel, évaluation concernée, pour titre et liens.
date_debut, date_fin en ISO
"""
# Récupération des paramètre de la requête # Récupération des paramètres de la requête
date_debut_str = request.args.get("date_debut") date_debut_str = request.args.get("date_debut")
date_fin_str = request.args.get("date_fin") date_fin_str = request.args.get("date_fin")
title = request.args.get("desc") group_ids = request.args.getlist("group_ids", int)
group_ids: list[int] = request.args.get("group_ids", None) evaluation_id = request.args.get("evaluation_id")
evaluation: Evaluation = (
Evaluation.query.get_or_404(evaluation_id)
if evaluation_id is not None
else None
)
# Vérification des dates # Vérification des dates
try: try:
date_debut = datetime.datetime.fromisoformat(date_debut_str) date_debut = datetime.datetime.fromisoformat(date_debut_str)
@ -1033,71 +1076,45 @@ def etat_abs_date():
except ValueError as exc: except ValueError as exc:
raise ScoValueError("date_fin invalide") from exc raise ScoValueError("date_fin invalide") from exc
# Vérification des groupes # Les groupes:
if group_ids is None: groups = [GroupDescr.query.get_or_404(group_id) for group_id in group_ids]
group_ids = [] # Les étudiants de tous les groupes sélectionnés, flat list
else:
group_ids = group_ids.split(",")
map(str, group_ids)
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
# Récupération des étudiants des groupes
etuds = [ etuds = [
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] etud for gr_etuds in [group.etuds for group in groups] for etud in gr_etuds
for m in groups_infos.members
] ]
# Récupération des assiduites des étudiants # Récupération des assiduites des étudiants
assiduites: Assiduite = Assiduite.query.filter( assiduites: Assiduite = Assiduite.query.filter(
Assiduite.etudid.in_([e["etudid"] for e in etuds]) Assiduite.etudid.in_([etud.id for etud in etuds])
) )
# Filtrage des assiduités en fonction des dates données # Filtrage des assiduités en fonction des dates données
assiduites = scass.filter_by_date( assiduites = scass.filter_by_date(
assiduites, Assiduite, date_debut, date_fin, False assiduites, Assiduite, date_debut, date_fin, False
) )
# Génération d'objet étudiant simplifié (nom+lien cal, etat_assiduite) # Génération table
etudiants: list[dict] = [] table = TableEtud(row_class=RowEtudWithAssi)
for etud in etuds: for etud in sorted(etuds, key=lambda e: e.sort_key):
# On récupère l'état de la première assiduité sur la période # On récupère l'état de la première assiduité sur la période
assi = assiduites.filter_by(etudid=etud["etudid"]).first() assi = assiduites.filter_by(etudid=etud.id).first()
etat = "" etat = ""
if assi is not None and assi.etat != scu.EtatAssiduite.PRESENT: if assi is not None and assi.etat != scu.EtatAssiduite.PRESENT:
etat = scu.EtatAssiduite.inverse().get(assi.etat).name etat = scu.EtatAssiduite.inverse().get(assi.etat).name
row = table.row_class(table, etud, etat)
row.add_etud_cols()
table.add_row(row)
# On génère l'objet simplifié (un dict) return render_template(
etudiant = { "assiduites/pages/etat_abs_date.j2",
"nom": f"""<a href="{url_for( date_debut=date_debut,
"assiduites.calendrier_etud", date_fin=date_fin,
scodoc_dept=g.scodoc_dept, evaluation=evaluation,
etudid=etud["etudid"]) etuds=etuds,
}"><font color="#A00000">{etud["nomprenom"]}</font></a>""", group_title=", ".join(gr.get_nom_with_part("tous") for gr in groups),
"etat": etat, sco=ScoData(),
} table=table,
etudiants.append(etudiant)
# On tri les étudiants
etudiants = list(sorted(etudiants, key=lambda x: x["nom"]))
# Génération de l'HTML
header: str = html_sco_header.sco_header(
page_title=safehtml.html_to_safe_html(title),
init_qtip=True,
) )
return HTMLBuilder(
header,
render_template(
"assiduites/pages/etat_abs_date.j2",
etudiants=etudiants,
group_title=groups_infos.groups_titles,
date_debut=date_debut,
date_fin=date_fin,
),
html_sco_header.sco_footer(),
).build()
@bp.route("/VisualisationAssiduitesGroupe") @bp.route("/VisualisationAssiduitesGroupe")
@scodoc @scodoc