Remplissage des notes des étudiants inscrits en cours de route
This commit is contained in:
parent
91e77dd2dc
commit
1ce4ffecad
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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 :"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
msg_etuds = f"""{nb_sans_notes} étudiants n'ont aucune note :"""
|
||||||
|
|
||||||
|
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 à :
|
||||||
|
<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()}
|
||||||
|
"""
|
||||||
|
@ -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()
|
||||||
)
|
)
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user