1
0
forked from ScoDoc/ScoDoc

Remplissage des notes des étudiants inscrits en cours de route

This commit is contained in:
Emmanuel Viennet 2022-10-05 23:48:54 +02:00
parent 91e77dd2dc
commit 1ce4ffecad
6 changed files with 203 additions and 6 deletions

View File

@ -14,7 +14,6 @@ from app import log
from app.comp import moy_sem from app.comp import moy_sem
from app.comp.aux_stats import StatsMoyenne from app.comp.aux_stats import StatsMoyenne
from app.comp.res_common import ResultatsSemestre from app.comp.res_common import ResultatsSemestre
from app.comp import res_sem
from app.models import FormSemestre from app.models import FormSemestre
from app.models import Identite from app.models import Identite
from app.models import ModuleImpl from app.models import ModuleImpl

View File

@ -5,7 +5,9 @@
import datetime import datetime
from app import db from app import db
from app.models.etudiants import Identite
from app.models.moduleimpls import ModuleImpl from app.models.moduleimpls import ModuleImpl
from app.models.notes import NotesNotes
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
@ -176,6 +178,12 @@ class Evaluation(db.Model):
] ]
) )
def get_etud_note(self, etud: Identite) -> NotesNotes:
"""La note de l'étudiant, ou None si pas noté.
(nb: pas de cache, lent, ne pas utiliser pour des calculs)
"""
return NotesNotes.query.filter_by(etudid=etud.id, evaluation_id=self.id).first()
class EvaluationUEPoids(db.Model): class EvaluationUEPoids(db.Model):
"""Poids des évaluations (BUT) """Poids des évaluations (BUT)

View File

@ -27,13 +27,14 @@ from app.models.but_refcomp import (
ApcReferentielCompetences, ApcReferentielCompetences,
) )
from app.models.groups import GroupDescr, Partition from app.models.groups import GroupDescr, Partition
from app.scodoc.sco_exceptions import ScoValueError
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.models.but_refcomp import parcours_formsemestre from app.models.but_refcomp import parcours_formsemestre
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.formations import Formation from app.models.formations import Formation
from app.models.modules import Module from app.models.modules import Module
from app.models.moduleimpls import ModuleImpl from app.models.moduleimpls import ModuleImpl, ModuleImplInscription
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.models.validations import ScolarFormSemestreValidation from app.models.validations import ScolarFormSemestreValidation
@ -690,6 +691,31 @@ class FormSemestre(db.Model):
) )
return "\n".join(H) return "\n".join(H)
def etud_set_all_missing_notes(self, etud: Identite, value=None) -> int:
"""Met toutes les notes manquantes de cet étudiant dans ce semestre
(ie dans toutes les évaluations des modules auxquels il est inscrit et n'a pas de note)
à la valeur donnée par value, qui est en général "ATT", "ABS", "EXC".
"""
from app.scodoc import sco_saisie_notes
inscriptions = (
ModuleImplInscription.query.filter_by(etudid=etud.id)
.join(ModuleImpl)
.filter_by(formsemestre_id=self.id)
)
nb_recorded = 0
for inscription in inscriptions:
for evaluation in inscription.modimpl.evaluations:
if evaluation.get_etud_note(etud) is None:
if not sco_saisie_notes.do_evaluation_set_etud_note(
evaluation, etud, value
):
raise ScoValueError(
"erreur lors de l'enregistrement de la note"
)
nb_recorded += 1
return nb_recorded
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre # Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
notes_formsemestre_responsables = db.Table( notes_formsemestre_responsables = db.Table(

View File

@ -29,16 +29,19 @@
""" """
import datetime import datetime
from flask import current_app from flask import current_app
from flask import g from flask import g
from flask import request from flask import request
from flask import render_template, url_for from flask import flash, redirect, render_template, url_for
from flask_login import current_user from flask_login import current_user
import pandas as pd
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import ResultatsSemestre
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import Evaluation, Module from app.models import Evaluation, Module
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
@ -605,7 +608,9 @@ def formsemestre_description_table(
"""Description du semestre sous forme de table exportable """Description du semestre sous forme de table exportable
Liste des modules et de leurs coefficients Liste des modules et de leurs coefficients
""" """
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre: FormSemestre = FormSemestre.query.filter_by(
id=formsemestre_id, dept_id=g.scodoc_dept_id
).first_or_404()
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id) use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
F = sco_formations.formation_list(args={"formation_id": formsemestre.formation_id})[ F = sco_formations.formation_list(args={"formation_id": formsemestre.formation_id})[
@ -1063,6 +1068,7 @@ def formsemestre_status(formsemestre_id=None):
formsemestre_status_head( formsemestre_status_head(
formsemestre_id=formsemestre_id, page_title="Tableau de bord" formsemestre_id=formsemestre_id, page_title="Tableau de bord"
), ),
formsemestre_warning_etuds_sans_note(formsemestre, nt),
"""<p><b style="font-size: 130%">Tableau de bord: </b> """<p><b style="font-size: 130%">Tableau de bord: </b>
<span class="help">cliquez sur un module pour saisir des notes</span> <span class="help">cliquez sur un module pour saisir des notes</span>
</p>""", </p>""",
@ -1326,3 +1332,135 @@ def formsemestre_tableau_modules(
H.append("</td></tr>") H.append("</td></tr>")
return "\n".join(H) return "\n".join(H)
# Expérimental
def get_formsemestre_etudids_sans_notes(
formsemestre: FormSemestre, res: ResultatsSemestre
) -> set[int]:
"""Les étudis d'étudiants de ce semestre n'ayant aucune note
alors que d'autres en ont.
"""
# Il y a-t-il des notes prises en compte ?
# On regarde la moy. gen., qui pour les étudiants sans notes est NaN en classique
# ou nulle en APC.
if all(res.etud_moy_gen.eq(0.0, fill_value=0.0)):
return set() # tout est 0 ou NaN, empty set
etudids_sans_notes = set.intersection(
*[
set.intersection(*m_res.evals_etudids_sans_note.values())
for m_res in res.modimpls_results.values()
if m_res.evals_etudids_sans_note
]
)
nb_sans_notes = len(etudids_sans_notes)
if nb_sans_notes > 0 and nb_sans_notes < len(
formsemestre.get_inscrits(include_demdef=False)
):
return etudids_sans_notes
return set()
def formsemestre_warning_etuds_sans_note(
formsemestre: FormSemestre, res: ResultatsSemestre
) -> str:
"""Vérifie si on est dans la situation où certains (mais pas tous) étudiants
n'ont aucune note alors que d'autres en ont.
Ce cas se produit typiquement quand on inscrit un étudiant en cours de semestre.
Il est alors utile de proposer de mettre toutes ses notes à ABS, ATT ou EXC
pour éviter de laisser toutes les évaluations "incomplètes".
"""
etudids_sans_notes = get_formsemestre_etudids_sans_notes(formsemestre, res)
if not etudids_sans_notes:
return ""
nb_sans_notes = len(etudids_sans_notes)
if nb_sans_notes < 5:
# peu d'étudiants, affiche leurs noms
etuds: list[Identite] = sorted(
[Identite.query.get(etudid) for etudid in etudids_sans_notes],
key=lambda e: e.sort_key,
)
noms = ", ".join(
[
f"""<a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
}" class="discretelink">{etud.nomprenom}</a>"""
for etud in etuds
]
)
msg_etuds = (
f"""{noms} n'{"a" if nb_sans_notes == 1 else "ont"} aucune note&nbsp;:"""
)
else:
msg_etuds = f"""{nb_sans_notes} étudiants n'ont aucune note&nbsp;:"""
return f"""<div class="warning_inscriptions_notes">Attention: {msg_etuds}
<a class="stdlink" href="{url_for(
"notes.formsemestre_note_etuds_sans_notes",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
)}">{"lui" if nb_sans_notes == 1 else "leur"}
<span title="pour ne pas bloquer les autres étudiants, il est souvent préférable
que les nouveaux aient des notes provisoires">affecter des notes</a>.
</div>
"""
def formsemestre_note_etuds_sans_notes(formsemestre_id: int, code: str = None):
"""Vue affichant les étudiants sans notes"""
formsemestre: FormSemestre = FormSemestre.query.filter_by(
id=formsemestre_id, dept_id=g.scodoc_dept_id
).first_or_404()
res: ResultatsSemestre = res_sem.load_formsemestre_results(formsemestre)
etudids_sans_notes = get_formsemestre_etudids_sans_notes(formsemestre, res)
etuds: list[Identite] = sorted(
[Identite.query.get(etudid) for etudid in etudids_sans_notes],
key=lambda e: e.sort_key,
)
if request.method == "POST":
if not code in ("ATT", "EXC", "ABS"):
raise ScoValueError("code invalide: doit être ATT, ABS ou EXC")
for etud in etuds:
formsemestre.etud_set_all_missing_notes(etud, code)
flash(f"Notes de {len(etuds)} étudiants affectées à {code}")
return redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
)
)
noms = "</li><li>".join(
[
f"""<a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
}" class="discretelink">{etud.nomprenom}</a>"""
for etud in etuds
]
)
return f"""
{html_sco_header.sco_header(
page_title=f"{formsemestre.sem_modalite()} {formsemestre.titre_annee()}"
)}
<div class="formsemestre_status">
{formsemestre_status_head(
formsemestre_id=formsemestre_id, page_title="Étudiants sans notes"
)}
</div>
<h3>Étudiants sans notes:</h3>
<ul>
<li>{noms}</li>
</ul>
<form method="post">
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}">
Mettre toutes les notes de ces étudiants à&nbsp;:
<select name="code">
<option value="ABS">ABS (absent, compte zéro)</option>
<option value="ATT" selected>ATT (en attente)</option>
<option value="EXC">EXC (neutralisée)</option>
</select>
<input type="submit" name="enregistrer">
</form>
{html_sco_header.sco_footer()}
"""

View File

@ -40,6 +40,7 @@ from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import Evaluation, FormSemestre from app.models import Evaluation, FormSemestre
from app.models import ScolarNews from app.models import ScolarNews
from app.models.etudiants import Identite
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -300,6 +301,25 @@ def do_evaluation_upload_xls():
return 0, msg + "<p>(pas de notes modifiées)</p>" return 0, msg + "<p>(pas de notes modifiées)</p>"
def do_evaluation_set_etud_note(evaluation: Evaluation, etud: Identite, value) -> bool:
"""Enregistre la note d'un seul étudiant
value: valeur externe (float ou str)
"""
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl.id):
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
# Convert and check value
L, invalids, _, _, _ = _check_notes(
[(etud.id, value)], evaluation.to_dict(), evaluation.moduleimpl.module.to_dict()
)
if len(invalids) == 0:
nb_changed, _, _ = notes_add(
current_user, evaluation.id, L, "Initialisation notes"
)
if nb_changed == 1:
return True
return False # error
def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False): def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
"""Initialisation des notes manquantes""" """Initialisation des notes manquantes"""
evaluation = Evaluation.query.get_or_404(evaluation_id) evaluation = Evaluation.query.get_or_404(evaluation_id)
@ -318,7 +338,7 @@ def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
for etudid, _ in etudid_etats: # pour tous les inscrits for etudid, _ in etudid_etats: # pour tous les inscrits
if etudid not in notes_db: # pas de note if etudid not in notes_db: # pas de note
notes.append((etudid, value)) notes.append((etudid, value))
# Check value # Convert and check values
L, invalids, _, _, _ = _check_notes( L, invalids, _, _, _ = _check_notes(
notes, evaluation.to_dict(), modimpl.module.to_dict() notes, evaluation.to_dict(), modimpl.module.to_dict()
) )

View File

@ -203,6 +203,12 @@ sco_publish(
Permission.ScoImplement, Permission.ScoImplement,
methods=["GET", "POST"], methods=["GET", "POST"],
) )
sco_publish(
"/formsemestre_view_etuds_sans_note",
sco_formsemestre_status.formsemestre_note_etuds_sans_notes,
Permission.ScoView,
methods=["GET", "POST"],
)
sco_publish( sco_publish(
"/formsemestre_recapcomplet", "/formsemestre_recapcomplet",
sco_recapcomplet.formsemestre_recapcomplet, sco_recapcomplet.formsemestre_recapcomplet,