forked from ScoDoc/ScoDoc
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.aux_stats import StatsMoyenne
|
||||
from app.comp.res_common import ResultatsSemestre
|
||||
from app.comp import res_sem
|
||||
from app.models import FormSemestre
|
||||
from app.models import Identite
|
||||
from app.models import ModuleImpl
|
||||
|
@ -5,7 +5,9 @@
|
||||
import datetime
|
||||
|
||||
from app import db
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.moduleimpls import ModuleImpl
|
||||
from app.models.notes import NotesNotes
|
||||
from app.models.ues import UniteEns
|
||||
|
||||
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):
|
||||
"""Poids des évaluations (BUT)
|
||||
|
@ -27,13 +27,14 @@ from app.models.but_refcomp import (
|
||||
ApcReferentielCompetences,
|
||||
)
|
||||
from app.models.groups import GroupDescr, Partition
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.models.but_refcomp import parcours_formsemestre
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formations import Formation
|
||||
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.validations import ScolarFormSemestreValidation
|
||||
|
||||
@ -690,6 +691,31 @@ class FormSemestre(db.Model):
|
||||
)
|
||||
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
|
||||
notes_formsemestre_responsables = db.Table(
|
||||
|
@ -29,16 +29,19 @@
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
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
|
||||
|
||||
import pandas as pd
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import ResultatsSemestre
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import Evaluation, Module
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
@ -605,7 +608,9 @@ def formsemestre_description_table(
|
||||
"""Description du semestre sous forme de table exportable
|
||||
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)
|
||||
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_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_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>
|
||||
<span class="help">cliquez sur un module pour saisir des notes</span>
|
||||
</p>""",
|
||||
@ -1326,3 +1332,135 @@ def formsemestre_tableau_modules(
|
||||
|
||||
H.append("</td></tr>")
|
||||
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.models import Evaluation, FormSemestre
|
||||
from app.models import ScolarNews
|
||||
from app.models.etudiants import Identite
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
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>"
|
||||
|
||||
|
||||
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):
|
||||
"""Initialisation des notes manquantes"""
|
||||
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
|
||||
if etudid not in notes_db: # pas de note
|
||||
notes.append((etudid, value))
|
||||
# Check value
|
||||
# Convert and check values
|
||||
L, invalids, _, _, _ = _check_notes(
|
||||
notes, evaluation.to_dict(), modimpl.module.to_dict()
|
||||
)
|
||||
|
@ -203,6 +203,12 @@ sco_publish(
|
||||
Permission.ScoImplement,
|
||||
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(
|
||||
"/formsemestre_recapcomplet",
|
||||
sco_recapcomplet.formsemestre_recapcomplet,
|
||||
|
Loading…
Reference in New Issue
Block a user