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:
"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:
"url de la fiche étudiant"
@ -319,6 +319,8 @@ class Identite(db.Model, models.ScoDocModel):
@cached_property
def sort_key(self) -> tuple:
"clé pour tris par ordre alphabétique"
# Note: scodoc7 utilisait sco_etud.etud_sort_key, à mettre à jour
# si on modifie cette méthode.
return (
scu.sanitize_string(
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.*
FROM notes_evaluation E, notes_moduleimpl MI
WHERE MI.formsemestre_id = %(formsemestre_id)s
and MI.id = E.moduleimpl_id
FROM notes_evaluation E, notes_moduleimpl MI
WHERE MI.formsemestre_id = %(formsemestre_id)s
and MI.id = E.moduleimpl_id
ORDER BY MI.id, numero desc, date_debut desc
"""
cnx = ndb.GetDBConnexion()
@ -494,7 +494,7 @@ def formsemestre_evaluations_cal(formsemestre_id):
</p>
<ul>
<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
</li>
<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)
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,
)}">{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>
@ -690,9 +690,9 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True):
H.append(
f"""<span class="evallink"><a class="stdlink" href="{url_for(
'assiduites.etat_abs_date',
scodoc_dept=g.scodoc_dept,
scodoc_dept=g.scodoc_dept,
group_ids=group_id,
desc=evaluation.description or "",
evaluation_id=evaluation.id,
date_debut=evaluation.date_debut.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.
Si etat, filtre selon l'état de l'inscription
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
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'
.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)
.members
.members :
.groups_titles
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",
"args": {
"group_ids": group_id,
"desc": evaluation.description or "",
"evaluation_id": evaluation.id,
"date_debut": evaluation.date_debut.isoformat()
if evaluation.date_debut
else "",

View File

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

View File

@ -54,6 +54,7 @@ class RowEtud(tb.Row):
super().__init__(table, etud.id, *args, **kwargs)
self.etud = etud
self.target_url = etud.url_fiche()
self.target_title = "" # pour bulle aide sur lien
def add_etud_cols(self):
"""Ajoute colonnes étudiant: codes, noms"""
@ -81,7 +82,14 @@ class RowEtud(tb.Row):
# formsemestre_id=res.formsemestre.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(
"nom_disp",
"Nom",
@ -89,9 +97,20 @@ class RowEtud(tb.Row):
"identite_detail",
data={"order": etud.sort_key},
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]:

View File

@ -524,7 +524,7 @@ class AssiDisplayOptions:
self.show_module = to_bool(show_module)
def remplacer(self, **kwargs):
"Positionnne options booléennes selon arguments"
"Positionne options booléennes selon arguments"
for k, v in kwargs.items():
if k.startswith("show_"):
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")}}
de {{date_debut.strftime("%H:%M")}} à {{date_fin.strftime("%H:%M")}}
{% extends "sco_page.j2" %}
{% 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>
<table>
<thead>
<tr>
<th>
Nom
</th>
<th>
Présence
</th>
</tr>
</thead>
<tbody>
{% for etud in etudiants %}
<tr>
<td>
{{etud.nom | safe}}
</td>
<td style="text-align: center;">
{{etud.etat}}
</td>
</tr>
{% endfor %}
</tbody>
{% if evaluation %}
<div>Assiduité lors de l'évaluation
<a class="stdlink" href="{{
url_for('notes.evaluation_listenotes',
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
}}"><em>{{evaluation.description or ''}}</em></a>
{% endif %}
<div>{{description}}</div>
</table>
{{table.html()|safe}}
<style>
tr,
td {
background-color: #FFFFFF;
}
</style>
</div>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% block app_content %}
<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 }}
</div>
{% endblock app_content %}
{% endblock app_content %}

View File

@ -74,7 +74,7 @@
}
try {
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 { }
});
@ -320,4 +320,4 @@
.mini-timeline-block.invalid_justified {
background-image: var(--motif-justi-invalide);
}
</style>
</style>

View File

@ -45,6 +45,7 @@ from app.models import (
Departement,
Evaluation,
FormSemestre,
GroupDescr,
Identite,
Justificatif,
ModuleImpl,
@ -53,6 +54,7 @@ from app.models import (
from app.scodoc.codes_cursus import UE_STANDARD
from app.auth.models import User
from app.models.assiduites import get_assiduites_justif
from app.tables.list_etuds import RowEtud, TableEtud
import app.tables.liste_assiduites as liste_assi
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
@permission_required(Permission.ScoView)
def liste_assiduites_etud():
@ -1011,18 +1013,59 @@ def visu_assiduites_group():
).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")
@scodoc
@permission_required(Permission.ScoView)
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_fin_str = request.args.get("date_fin")
title = request.args.get("desc")
group_ids: list[int] = request.args.get("group_ids", None)
group_ids = request.args.getlist("group_ids", int)
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
try:
date_debut = datetime.datetime.fromisoformat(date_debut_str)
@ -1033,71 +1076,45 @@ def etat_abs_date():
except ValueError as exc:
raise ScoValueError("date_fin invalide") from exc
# Vérification des groupes
if group_ids is None:
group_ids = []
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
# Les groupes:
groups = [GroupDescr.query.get_or_404(group_id) for group_id in group_ids]
# Les étudiants de tous les groupes sélectionnés, flat list
etuds = [
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
for m in groups_infos.members
etud for gr_etuds in [group.etuds for group in groups] for etud in gr_etuds
]
# Récupération des assiduites des étudiants
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
assiduites = scass.filter_by_date(
assiduites, Assiduite, date_debut, date_fin, False
)
# Génération d'objet étudiant simplifié (nom+lien cal, etat_assiduite)
etudiants: list[dict] = []
for etud in etuds:
# Génération table
table = TableEtud(row_class=RowEtudWithAssi)
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
assi = assiduites.filter_by(etudid=etud["etudid"]).first()
assi = assiduites.filter_by(etudid=etud.id).first()
etat = ""
if assi is not None and assi.etat != scu.EtatAssiduite.PRESENT:
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)
etudiant = {
"nom": f"""<a href="{url_for(
"assiduites.calendrier_etud",
scodoc_dept=g.scodoc_dept,
etudid=etud["etudid"])
}"><font color="#A00000">{etud["nomprenom"]}</font></a>""",
"etat": etat,
}
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 render_template(
"assiduites/pages/etat_abs_date.j2",
date_debut=date_debut,
date_fin=date_fin,
evaluation=evaluation,
etuds=etuds,
group_title=", ".join(gr.get_nom_with_part("tous") for gr in groups),
sco=ScoData(),
table=table,
)
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")
@scodoc