forked from ScoDoc/ScoDoc
WIP: modernisation evaluations
This commit is contained in:
parent
0963462e31
commit
4e9ac3d3e2
@ -7,17 +7,19 @@
|
|||||||
"""
|
"""
|
||||||
ScoDoc 9 API : accès aux évaluations
|
ScoDoc 9 API : accès aux évaluations
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
from flask_login import login_required
|
from flask_login import current_user, login_required
|
||||||
|
|
||||||
import app
|
import app
|
||||||
|
from app import db
|
||||||
from app.api import api_bp as bp, api_web_bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import Evaluation, ModuleImpl, FormSemestre
|
from app.models import Evaluation, ModuleImpl, FormSemestre
|
||||||
from app.scodoc import sco_evaluation_db, sco_saisie_notes
|
from app.scodoc import sco_evaluation_db, sco_permissions_check, sco_saisie_notes
|
||||||
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
@ -181,3 +183,64 @@ def evaluation_set_notes(evaluation_id: int):
|
|||||||
return sco_saisie_notes.save_notes(
|
return sco_saisie_notes.save_notes(
|
||||||
evaluation, notes, comment=data.get("comment", "")
|
evaluation, notes, comment=data.get("comment", "")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/moduleimpl/<int:moduleimpl_id>/evaluation/create", methods=["POST"])
|
||||||
|
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>/evaluation/create", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEnsView) # permission gérée dans la fonction
|
||||||
|
@as_json
|
||||||
|
def evaluation_create(moduleimpl_id: int):
|
||||||
|
"""Création d'une évaluation.
|
||||||
|
The request content type should be "application/json",
|
||||||
|
and contains:
|
||||||
|
{
|
||||||
|
"description" : str,
|
||||||
|
"evaluation_type" : int, // {0,1,2} default 0 (normale)
|
||||||
|
"jour" : date_iso, // si non spécifié, vide
|
||||||
|
"date_debut" : date_iso, // optionnel
|
||||||
|
"date_fin" : date_iso, // si non spécifié, 08:00
|
||||||
|
"note_max" : float, // si non spécifié, 20.0
|
||||||
|
"numero" : int, // ordre de présentation, default tri sur date
|
||||||
|
"visibulletin" : boolean , //default true
|
||||||
|
"publish_incomplete" : boolean , //default false
|
||||||
|
"coefficient" : float, // si non spécifié, 1.0
|
||||||
|
"poids" : [ {
|
||||||
|
"ue_id": int,
|
||||||
|
"poids": float
|
||||||
|
},
|
||||||
|
...
|
||||||
|
] // si non spécifié, tous les poids à 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result: l'évaluation créée.
|
||||||
|
"""
|
||||||
|
moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
||||||
|
if not moduleimpl.can_edit_evaluation(current_user):
|
||||||
|
return scu.json_error(403, "opération non autorisée")
|
||||||
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
|
|
||||||
|
try:
|
||||||
|
evaluation = Evaluation.create(moduleimpl=moduleimpl, **data)
|
||||||
|
except AccessDenied:
|
||||||
|
return scu.json_error(403, "opération non autorisée (2)")
|
||||||
|
except ValueError:
|
||||||
|
return scu.json_error(400, "paramètre incorrect")
|
||||||
|
except ScoValueError as exc:
|
||||||
|
breakpoint() # XXX WIP
|
||||||
|
return scu.json_error(400, f"paramètre de type incorrect ({exc.msg})")
|
||||||
|
|
||||||
|
db.session.add(evaluation)
|
||||||
|
db.session.commit()
|
||||||
|
return evaluation.to_dict_api()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/evaluation/<int:evaluation_id>/delete", methods=["POST"])
|
||||||
|
@api_web_bp.route("/evaluation/<int:evaluation_id>/delete", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEnsView) # permission gérée dans la fonction
|
||||||
|
@as_json
|
||||||
|
def evaluation_delete(evaluation_id: int):
|
||||||
|
pass
|
||||||
|
@ -276,11 +276,10 @@ class BulletinBUT:
|
|||||||
"coef": fmt_note(e.coefficient)
|
"coef": fmt_note(e.coefficient)
|
||||||
if e.evaluation_type == scu.EVALUATION_NORMALE
|
if e.evaluation_type == scu.EVALUATION_NORMALE
|
||||||
else None,
|
else None,
|
||||||
"date": e.jour.isoformat() if e.jour else None,
|
"date_debut": e.date_debut.isoformat() if e.date_debut else None,
|
||||||
|
"date_fin": e.date_fin.isoformat() if e.date_fin else None,
|
||||||
"description": e.description,
|
"description": e.description,
|
||||||
"evaluation_type": e.evaluation_type,
|
"evaluation_type": e.evaluation_type,
|
||||||
"heure_debut": e.heure_debut.strftime("%H:%M") if e.heure_debut else None,
|
|
||||||
"heure_fin": e.heure_fin.strftime("%H:%M") if e.heure_debut else None,
|
|
||||||
"note": {
|
"note": {
|
||||||
"value": fmt_note(
|
"value": fmt_note(
|
||||||
eval_notes[etud.id],
|
eval_notes[etud.id],
|
||||||
@ -298,6 +297,12 @@ class BulletinBUT:
|
|||||||
)
|
)
|
||||||
if has_request_context()
|
if has_request_context()
|
||||||
else "na",
|
else "na",
|
||||||
|
# deprecated
|
||||||
|
"date": e.date_debut.isoformat() if e.date_debut else None,
|
||||||
|
"heure_debut": e.date_debut.time().isoformat("minutes")
|
||||||
|
if e.date_debut
|
||||||
|
else None,
|
||||||
|
"heure_fin": e.date_fin.time().isoformat("minutes") if e.date_fin else None,
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@ -202,12 +202,11 @@ def bulletin_but_xml_compat(
|
|||||||
if e.visibulletin or version == "long":
|
if e.visibulletin or version == "long":
|
||||||
x_eval = Element(
|
x_eval = Element(
|
||||||
"evaluation",
|
"evaluation",
|
||||||
jour=e.jour.isoformat() if e.jour else "",
|
date_debut=e.date_debut.isoformat()
|
||||||
heure_debut=e.heure_debut.isoformat()
|
if e.date_debut
|
||||||
if e.heure_debut
|
|
||||||
else "",
|
else "",
|
||||||
heure_fin=e.heure_fin.isoformat()
|
date_fin=e.date_fin.isoformat()
|
||||||
if e.heure_debut
|
if e.date_debut
|
||||||
else "",
|
else "",
|
||||||
coefficient=str(e.coefficient),
|
coefficient=str(e.coefficient),
|
||||||
# pas les poids en XML compat
|
# pas les poids en XML compat
|
||||||
@ -215,6 +214,16 @@ def bulletin_but_xml_compat(
|
|||||||
description=quote_xml_attr(e.description),
|
description=quote_xml_attr(e.description),
|
||||||
# notes envoyées sur 20, ceci juste pour garder trace:
|
# notes envoyées sur 20, ceci juste pour garder trace:
|
||||||
note_max_origin=str(e.note_max),
|
note_max_origin=str(e.note_max),
|
||||||
|
# --- deprecated
|
||||||
|
jour=e.date_debut.isoformat()
|
||||||
|
if e.date_debut
|
||||||
|
else "",
|
||||||
|
heure_debut=e.date_debut.time().isoformat("minutes")
|
||||||
|
if e.date_debut
|
||||||
|
else "",
|
||||||
|
heure_fin=e.date_fin.time().isoformat("minutes")
|
||||||
|
if e.date_fin
|
||||||
|
else "",
|
||||||
)
|
)
|
||||||
x_mod.append(x_eval)
|
x_mod.append(x_eval)
|
||||||
try:
|
try:
|
||||||
|
@ -5,17 +5,28 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
|
from flask import g, url_for
|
||||||
|
from flask_login import current_user
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
from app.models.events import ScolarNews
|
||||||
from app.models.moduleimpls import ModuleImpl
|
from app.models.moduleimpls import ModuleImpl
|
||||||
from app.models.notes import NotesNotes
|
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 import sco_cache
|
||||||
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
MAX_EVALUATION_DURATION = datetime.timedelta(days=365)
|
||||||
|
NOON = datetime.time(12, 00)
|
||||||
DEFAULT_EVALUATION_TIME = datetime.time(8, 0)
|
DEFAULT_EVALUATION_TIME = datetime.time(8, 0)
|
||||||
|
|
||||||
|
VALID_EVALUATION_TYPES = {0, 1, 2}
|
||||||
|
|
||||||
|
|
||||||
class Evaluation(db.Model):
|
class Evaluation(db.Model):
|
||||||
"""Evaluation (contrôle, examen, ...)"""
|
"""Evaluation (contrôle, examen, ...)"""
|
||||||
@ -27,9 +38,8 @@ class Evaluation(db.Model):
|
|||||||
moduleimpl_id = db.Column(
|
moduleimpl_id = db.Column(
|
||||||
db.Integer, db.ForeignKey("notes_moduleimpl.id"), index=True
|
db.Integer, db.ForeignKey("notes_moduleimpl.id"), index=True
|
||||||
)
|
)
|
||||||
jour = db.Column(db.Date)
|
date_debut = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||||
heure_debut = db.Column(db.Time)
|
date_fin = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||||
heure_fin = db.Column(db.Time)
|
|
||||||
description = db.Column(db.Text)
|
description = db.Column(db.Text)
|
||||||
note_max = db.Column(db.Float)
|
note_max = db.Column(db.Float)
|
||||||
coefficient = db.Column(db.Float)
|
coefficient = db.Column(db.Float)
|
||||||
@ -50,47 +60,106 @@ class Evaluation(db.Model):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"""<Evaluation {self.id} {
|
return f"""<Evaluation {self.id} {
|
||||||
self.jour.isoformat() if self.jour else ''} "{
|
self.date_debut.isoformat() if self.date_debut else ''} "{
|
||||||
self.description[:16] if self.description else ''}">"""
|
self.description[:16] if self.description else ''}">"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(
|
||||||
|
cls,
|
||||||
|
moduleimpl: ModuleImpl = None,
|
||||||
|
jour=None,
|
||||||
|
heure_debut=None,
|
||||||
|
heure_fin=None,
|
||||||
|
description=None,
|
||||||
|
note_max=None,
|
||||||
|
coefficient=None,
|
||||||
|
visibulletin=None,
|
||||||
|
publish_incomplete=None,
|
||||||
|
evaluation_type=None,
|
||||||
|
numero=None,
|
||||||
|
**kw, # ceci pour absorber les éventuel arguments excedentaires
|
||||||
|
):
|
||||||
|
"""Create an evaluation. Check permission and all arguments."""
|
||||||
|
if not moduleimpl.can_edit_evaluation(current_user):
|
||||||
|
raise AccessDenied(
|
||||||
|
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||||
|
)
|
||||||
|
args = locals()
|
||||||
|
del args["cls"]
|
||||||
|
del args["kw"]
|
||||||
|
check_convert_evaluation_args(moduleimpl, args)
|
||||||
|
# Check numeros
|
||||||
|
Evaluation.moduleimpl_evaluation_renumber(moduleimpl, only_if_unumbered=True)
|
||||||
|
if not "numero" in args or args["numero"] is None:
|
||||||
|
args["numero"] = cls.get_new_numero(moduleimpl, args["date_debut"])
|
||||||
|
#
|
||||||
|
evaluation = Evaluation(**args)
|
||||||
|
sco_cache.invalidate_formsemestre(formsemestre_id=moduleimpl.formsemestre_id)
|
||||||
|
url = url_for(
|
||||||
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=moduleimpl.id,
|
||||||
|
)
|
||||||
|
ScolarNews.add(
|
||||||
|
typ=ScolarNews.NEWS_NOTE,
|
||||||
|
obj=moduleimpl.id,
|
||||||
|
text=f"""Création d'une évaluation dans <a href="{url}">{
|
||||||
|
moduleimpl.module.titre or '(module sans titre)'}</a>""",
|
||||||
|
url=url,
|
||||||
|
)
|
||||||
|
return evaluation
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_new_numero(
|
||||||
|
cls, moduleimpl: ModuleImpl, date_debut: datetime.datetime
|
||||||
|
) -> int:
|
||||||
|
"""Get a new numero for an evaluation in this moduleimpl
|
||||||
|
If necessary, renumber existing evals to make room for a new one.
|
||||||
|
"""
|
||||||
|
n = None
|
||||||
|
# Détermine le numero grâce à la date
|
||||||
|
# Liste des eval existantes triées par date, la plus ancienne en tete
|
||||||
|
evaluations = moduleimpl.evaluations.order_by(Evaluation.date_debut).all()
|
||||||
|
if date_debut is not None:
|
||||||
|
next_eval = None
|
||||||
|
t = date_debut
|
||||||
|
for e in evaluations:
|
||||||
|
if e.date_debut > t:
|
||||||
|
next_eval = e
|
||||||
|
break
|
||||||
|
if next_eval:
|
||||||
|
n = _moduleimpl_evaluation_insert_before(evaluations, next_eval)
|
||||||
|
else:
|
||||||
|
n = None # à placer en fin
|
||||||
|
if n is None: # pas de date ou en fin:
|
||||||
|
if evaluations:
|
||||||
|
n = evaluations[-1].numero + 1
|
||||||
|
else:
|
||||||
|
n = 0 # the only one
|
||||||
|
return n
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
"Représentation dict (riche, compat ScoDoc 7)"
|
"Représentation dict (riche, compat ScoDoc 7)"
|
||||||
e = dict(self.__dict__)
|
e = dict(self.__dict__)
|
||||||
e.pop("_sa_instance_state", None)
|
e.pop("_sa_instance_state", None)
|
||||||
# ScoDoc7 output_formators
|
# ScoDoc7 output_formators
|
||||||
e["evaluation_id"] = self.id
|
e["evaluation_id"] = self.id
|
||||||
e["jour"] = e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
|
e["date_debut"] = e.date_debut.isoformat() if e.date_debut else None
|
||||||
if self.jour is None:
|
e["date_fin"] = e.date_debut.isoformat() if e.date_fin else None
|
||||||
e["date_debut"] = None
|
|
||||||
e["date_fin"] = None
|
|
||||||
else:
|
|
||||||
e["date_debut"] = datetime.datetime.combine(
|
|
||||||
self.jour, self.heure_debut or datetime.time(0, 0)
|
|
||||||
).isoformat()
|
|
||||||
e["date_fin"] = datetime.datetime.combine(
|
|
||||||
self.jour, self.heure_fin or datetime.time(0, 0)
|
|
||||||
).isoformat()
|
|
||||||
e["numero"] = ndb.int_null_is_zero(e["numero"])
|
e["numero"] = ndb.int_null_is_zero(e["numero"])
|
||||||
e["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
|
e["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
|
||||||
|
|
||||||
|
# Deprecated
|
||||||
|
e["jour"] = e.date_debut.strftime("%d/%m/%Y") if e.date_debut else ""
|
||||||
|
|
||||||
return evaluation_enrich_dict(e)
|
return evaluation_enrich_dict(e)
|
||||||
|
|
||||||
def to_dict_api(self) -> dict:
|
def to_dict_api(self) -> dict:
|
||||||
"Représentation dict pour API JSON"
|
"Représentation dict pour API JSON"
|
||||||
if self.jour is None:
|
|
||||||
date_debut = None
|
|
||||||
date_fin = None
|
|
||||||
else:
|
|
||||||
date_debut = datetime.datetime.combine(
|
|
||||||
self.jour, self.heure_debut or datetime.time(0, 0)
|
|
||||||
).isoformat()
|
|
||||||
date_fin = datetime.datetime.combine(
|
|
||||||
self.jour, self.heure_fin or datetime.time(0, 0)
|
|
||||||
).isoformat()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"coefficient": self.coefficient,
|
"coefficient": self.coefficient,
|
||||||
"date_debut": date_debut,
|
"date_debut": self.date_debut.isoformat(),
|
||||||
"date_fin": date_fin,
|
"date_fin": self.date_fin.isoformat(),
|
||||||
"description": self.description,
|
"description": self.description,
|
||||||
"evaluation_type": self.evaluation_type,
|
"evaluation_type": self.evaluation_type,
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
@ -104,11 +173,49 @@ class Evaluation(db.Model):
|
|||||||
|
|
||||||
def from_dict(self, data):
|
def from_dict(self, data):
|
||||||
"""Set evaluation attributes from given dict values."""
|
"""Set evaluation attributes from given dict values."""
|
||||||
check_evaluation_args(data)
|
check_convert_evaluation_args(self.moduleimpl, data)
|
||||||
|
if data.get("numero") is None:
|
||||||
|
data["numero"] = Evaluation.get_max_numero() + 1
|
||||||
for k in self.__dict__.keys():
|
for k in self.__dict__.keys():
|
||||||
if k != "_sa_instance_state" and k != "id" and k in data:
|
if k != "_sa_instance_state" and k != "id" and k in data:
|
||||||
setattr(self, k, data[k])
|
setattr(self, k, data[k])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_max_numero(cls, moduleimpl_id: int) -> int:
|
||||||
|
"""Return max numero among evaluations in this
|
||||||
|
moduleimpl (0 if None)
|
||||||
|
"""
|
||||||
|
max_num = (
|
||||||
|
db.session.query(sa.sql.functions.max(Evaluation.numero))
|
||||||
|
.filter_by(moduleimpl_id=moduleimpl_id)
|
||||||
|
.first()[0]
|
||||||
|
)
|
||||||
|
return max_num or 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def moduleimpl_evaluation_renumber(
|
||||||
|
cls, moduleimpl: ModuleImpl, only_if_unumbered=False
|
||||||
|
):
|
||||||
|
"""Renumber evaluations in this moduleimpl, according to their date. (numero=0: oldest one)
|
||||||
|
Needed because previous versions of ScoDoc did not have eval numeros
|
||||||
|
Note: existing numeros are ignored
|
||||||
|
"""
|
||||||
|
# Liste des eval existantes triées par date, la plus ancienne en tete
|
||||||
|
evaluations = moduleimpl.evaluations.order_by(
|
||||||
|
Evaluation.date_debut, Evaluation.numero
|
||||||
|
).all()
|
||||||
|
all_numbered = all(e.numero is not None for e in evaluations)
|
||||||
|
if all_numbered and only_if_unumbered:
|
||||||
|
return # all ok
|
||||||
|
|
||||||
|
# Reset all numeros:
|
||||||
|
i = 1
|
||||||
|
for e in evaluations:
|
||||||
|
e.numero = i
|
||||||
|
db.session.add(e)
|
||||||
|
i += 1
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
def descr_heure(self) -> str:
|
def descr_heure(self) -> str:
|
||||||
"Description de la plage horaire pour affichages"
|
"Description de la plage horaire pour affichages"
|
||||||
if self.heure_debut and (
|
if self.heure_debut and (
|
||||||
@ -146,19 +253,19 @@ class Evaluation(db.Model):
|
|||||||
return copy
|
return copy
|
||||||
|
|
||||||
def is_matin(self) -> bool:
|
def is_matin(self) -> bool:
|
||||||
"Evaluation ayant lieu le matin (faux si pas de date)"
|
"Evaluation commençant le matin (faux si pas de date)"
|
||||||
heure_debut_dt = self.heure_debut or datetime.time(8, 00)
|
if not self.date_debut:
|
||||||
# 8:00 au cas ou pas d'heure (note externe?)
|
return False
|
||||||
return bool(self.jour) and heure_debut_dt < datetime.time(12, 00)
|
return self.date_debut.time() < NOON
|
||||||
|
|
||||||
def is_apresmidi(self) -> bool:
|
def is_apresmidi(self) -> bool:
|
||||||
"Evaluation ayant lieu l'après midi (faux si pas de date)"
|
"Evaluation commençant l'après midi (faux si pas de date)"
|
||||||
heure_debut_dt = self.heure_debut or datetime.time(8, 00)
|
if not self.date_debut:
|
||||||
# 8:00 au cas ou pas d'heure (note externe?)
|
return False
|
||||||
return bool(self.jour) and heure_debut_dt >= datetime.time(12, 00)
|
return self.date_debut.time() >= NOON
|
||||||
|
|
||||||
def set_default_poids(self) -> bool:
|
def set_default_poids(self) -> bool:
|
||||||
"""Initialize les poids bvers les UE à leurs valeurs par défaut
|
"""Initialize les poids vers les UE à leurs valeurs par défaut
|
||||||
C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon.
|
C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon.
|
||||||
Les poids existants ne sont pas modifiés.
|
Les poids existants ne sont pas modifiés.
|
||||||
Return True if (uncommited) modification, False otherwise.
|
Return True if (uncommited) modification, False otherwise.
|
||||||
@ -278,15 +385,13 @@ class EvaluationUEPoids(db.Model):
|
|||||||
def evaluation_enrich_dict(e: dict):
|
def evaluation_enrich_dict(e: dict):
|
||||||
"""add or convert some fields in an evaluation dict"""
|
"""add or convert some fields in an evaluation dict"""
|
||||||
# For ScoDoc7 compat
|
# For ScoDoc7 compat
|
||||||
heure_debut_dt = e["heure_debut"] or datetime.time(
|
heure_debut_dt = e["date_debut"].time()
|
||||||
8, 00
|
heure_fin_dt = e["date_fin"].time()
|
||||||
) # au cas ou pas d'heure (note externe?)
|
e["heure_debut"] = heure_debut_dt.strftime("%Hh%M")
|
||||||
heure_fin_dt = e["heure_fin"] or datetime.time(8, 00)
|
e["heure_fin"] = heure_fin_dt.strftime("%Hh%M")
|
||||||
e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"])
|
e["jour_iso"] = e["date_debut"].isoformat() # XXX
|
||||||
e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"])
|
|
||||||
e["jour_iso"] = ndb.DateDMYtoISO(e["jour"])
|
|
||||||
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
|
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
|
||||||
d = ndb.TimeDuration(heure_debut, heure_fin)
|
d = _time_duration_HhM(heure_debut, heure_fin)
|
||||||
if d is not None:
|
if d is not None:
|
||||||
m = d % 60
|
m = d % 60
|
||||||
e["duree"] = "%dh" % (d / 60)
|
e["duree"] = "%dh" % (d / 60)
|
||||||
@ -313,49 +418,118 @@ def evaluation_enrich_dict(e: dict):
|
|||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
def check_evaluation_args(args):
|
def check_convert_evaluation_args(moduleimpl: ModuleImpl, data: dict):
|
||||||
"Check coefficient, dates and duration, raises exception if invalid"
|
"""Check coefficient, dates and duration, raises exception if invalid.
|
||||||
moduleimpl_id = args["moduleimpl_id"]
|
Convert date and time strings to date and time objects.
|
||||||
# check bareme
|
|
||||||
note_max = args.get("note_max", None)
|
Set required default value for unspecified fields.
|
||||||
if note_max is None:
|
May raise ScoValueError.
|
||||||
raise ScoValueError("missing note_max")
|
"""
|
||||||
|
# --- description
|
||||||
|
description = data.get("description", "")
|
||||||
|
if len(description) > scu.MAX_TEXT_LEN:
|
||||||
|
raise ScoValueError("description too large")
|
||||||
|
# --- evaluation_type
|
||||||
|
try:
|
||||||
|
data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0)
|
||||||
|
if not data["evaluation_type"] in VALID_EVALUATION_TYPES:
|
||||||
|
raise ScoValueError("Invalid evaluation_type value")
|
||||||
|
except ValueError:
|
||||||
|
raise ScoValueError("Invalid evaluation_type value")
|
||||||
|
|
||||||
|
# --- note_max (bareme)
|
||||||
|
note_max = data.get("note_max", 20.0) or 20.0
|
||||||
try:
|
try:
|
||||||
note_max = float(note_max)
|
note_max = float(note_max)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ScoValueError("Invalid note_max value")
|
raise ScoValueError("Invalid note_max value")
|
||||||
if note_max < 0:
|
if note_max < 0:
|
||||||
raise ScoValueError("Invalid note_max value (must be positive or null)")
|
raise ScoValueError("Invalid note_max value (must be positive or null)")
|
||||||
# check coefficient
|
data["note_max"] = note_max
|
||||||
coef = args.get("coefficient", None)
|
# --- coefficient
|
||||||
if coef is None:
|
coef = data.get("coefficient", 1.0) or 1.0
|
||||||
raise ScoValueError("missing coefficient")
|
|
||||||
try:
|
try:
|
||||||
coef = float(coef)
|
coef = float(coef)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ScoValueError("Invalid coefficient value")
|
raise ScoValueError("Invalid coefficient value")
|
||||||
if coef < 0:
|
if coef < 0:
|
||||||
raise ScoValueError("Invalid coefficient value (must be positive or null)")
|
raise ScoValueError("Invalid coefficient value (must be positive or null)")
|
||||||
# check date
|
data["coefficient"] = coef
|
||||||
jour = args.get("jour", None)
|
# --- jour (date de l'évaluation)
|
||||||
args["jour"] = jour
|
jour = data.get("jour", None)
|
||||||
if jour:
|
if jour and not isinstance(jour, datetime.date):
|
||||||
modimpl = db.session.get(ModuleImpl, moduleimpl_id)
|
if date_format == "dmy":
|
||||||
formsemestre = modimpl.formsemestre
|
y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
|
||||||
y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
|
jour = datetime.date(y, m, d)
|
||||||
jour = datetime.date(y, m, d)
|
else: # ISO
|
||||||
|
jour = datetime.date.fromisoformat(jour)
|
||||||
|
formsemestre = moduleimpl.formsemestre
|
||||||
if (jour > formsemestre.date_fin) or (jour < formsemestre.date_debut):
|
if (jour > formsemestre.date_fin) or (jour < formsemestre.date_debut):
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
|
f"""La date de l'évaluation ({jour.strftime("%d/%m/%Y")}) n'est pas dans le semestre !""",
|
||||||
% (d, m, y),
|
|
||||||
dest_url="javascript:history.back();",
|
dest_url="javascript:history.back();",
|
||||||
)
|
)
|
||||||
heure_debut = args.get("heure_debut", None)
|
data["jour"] = jour
|
||||||
args["heure_debut"] = heure_debut
|
# --- heures
|
||||||
heure_fin = args.get("heure_fin", None)
|
heure_debut = data.get("heure_debut", None)
|
||||||
args["heure_fin"] = heure_fin
|
if heure_debut and not isinstance(heure_debut, datetime.time):
|
||||||
|
if date_format == "dmy":
|
||||||
|
data["heure_debut"] = heure_to_time(heure_debut)
|
||||||
|
else: # ISO
|
||||||
|
data["heure_debut"] = datetime.time.fromisoformat(heure_debut)
|
||||||
|
heure_fin = data.get("heure_fin", None)
|
||||||
|
if heure_fin and not isinstance(heure_fin, datetime.time):
|
||||||
|
if date_format == "dmy":
|
||||||
|
data["heure_fin"] = heure_to_time(heure_fin)
|
||||||
|
else: # ISO
|
||||||
|
data["heure_fin"] = datetime.time.fromisoformat(heure_fin)
|
||||||
if jour and ((not heure_debut) or (not heure_fin)):
|
if jour and ((not heure_debut) or (not heure_fin)):
|
||||||
raise ScoValueError("Les heures doivent être précisées")
|
raise ScoValueError("Les heures doivent être précisées")
|
||||||
d = ndb.TimeDuration(heure_debut, heure_fin)
|
if heure_debut and heure_fin:
|
||||||
if d and ((d < 0) or (d > 60 * 12)):
|
duration = ((data["heure_fin"].hour * 60) + data["heure_fin"].minute) - (
|
||||||
raise ScoValueError("Heures de l'évaluation incohérentes !")
|
(data["heure_debut"].hour * 60) + data["heure_debut"].minute
|
||||||
|
)
|
||||||
|
if duration < 0 or duration > 60 * 12:
|
||||||
|
raise ScoValueError("Heures de l'évaluation incohérentes !")
|
||||||
|
|
||||||
|
|
||||||
|
def heure_to_time(heure: str) -> datetime.time:
|
||||||
|
"Convert external heure ('10h22' or '10:22') to a time"
|
||||||
|
t = heure.strip().upper().replace("H", ":")
|
||||||
|
h, m = t.split(":")[:2]
|
||||||
|
return datetime.time(int(h), int(m))
|
||||||
|
|
||||||
|
|
||||||
|
def _time_duration_HhM(heure_debut: str, heure_fin: str) -> int:
|
||||||
|
"""duree (nb entier de minutes) entre deux heures a notre format
|
||||||
|
ie 12h23
|
||||||
|
"""
|
||||||
|
if heure_debut and heure_fin:
|
||||||
|
h0, m0 = [int(x) for x in heure_debut.split("h")]
|
||||||
|
h1, m1 = [int(x) for x in heure_fin.split("h")]
|
||||||
|
d = (h1 - h0) * 60 + (m1 - m0)
|
||||||
|
return d
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _moduleimpl_evaluation_insert_before(
|
||||||
|
evaluations: list[Evaluation], next_eval: Evaluation
|
||||||
|
) -> int:
|
||||||
|
"""Renumber evaluations such that an evaluation with can be inserted before next_eval
|
||||||
|
Returns numero suitable for the inserted evaluation
|
||||||
|
"""
|
||||||
|
if next_eval:
|
||||||
|
n = next_eval.numero
|
||||||
|
if n is None:
|
||||||
|
Evaluation.moduleimpl_evaluation_renumber(next_eval.moduleimpl)
|
||||||
|
n = next_eval.numero
|
||||||
|
else:
|
||||||
|
n = 1
|
||||||
|
# all numeros >= n are incremented
|
||||||
|
for e in evaluations:
|
||||||
|
if e.numero >= n:
|
||||||
|
e.numero += 1
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.commit()
|
||||||
|
return n
|
||||||
|
@ -101,6 +101,23 @@ class ModuleImpl(db.Model):
|
|||||||
d.pop("module", None)
|
d.pop("module", None)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def can_edit_evaluation(self, user) -> bool:
|
||||||
|
"""True if this user can create, delete or edit and evaluation in this modimpl
|
||||||
|
(nb: n'implique pas le droit de saisir ou modifier des notes)
|
||||||
|
"""
|
||||||
|
# acces pour resp. moduleimpl et resp. form semestre (dir etud)
|
||||||
|
if (
|
||||||
|
user.has_permission(Permission.ScoEditAllEvals)
|
||||||
|
or user.id == self.responsable_id
|
||||||
|
or user.id in (r.id for r in self.formsemestre.responsables)
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
elif self.formsemestre.ens_can_edit_eval:
|
||||||
|
if user.id in (e.id for e in self.enseignants):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def can_change_ens_by(self, user: User, raise_exc=False) -> bool:
|
def can_change_ens_by(self, user: User, raise_exc=False) -> bool:
|
||||||
"""Check if user can modify module resp.
|
"""Check if user can modify module resp.
|
||||||
If raise_exc, raises exception (AccessDenied or ScoLockedSemError) if not.
|
If raise_exc, raises exception (AccessDenied or ScoLockedSemError) if not.
|
||||||
|
@ -459,8 +459,10 @@ def dictfilter(d, fields, filter_nulls=True):
|
|||||||
# --- Misc Tools
|
# --- Misc Tools
|
||||||
|
|
||||||
|
|
||||||
def DateDMYtoISO(dmy: str, null_is_empty=False) -> str:
|
def DateDMYtoISO(dmy: str, null_is_empty=False) -> str: # XXX deprecated
|
||||||
"convert date string from french format to ISO"
|
"""Convert date string from french format to ISO.
|
||||||
|
If null_is_empty (default false), returns "" if no input.
|
||||||
|
"""
|
||||||
if not dmy:
|
if not dmy:
|
||||||
if null_is_empty:
|
if null_is_empty:
|
||||||
return ""
|
return ""
|
||||||
@ -506,7 +508,7 @@ def DateISOtoDMY(isodate):
|
|||||||
return "%02d/%02d/%04d" % (day, month, year)
|
return "%02d/%02d/%04d" % (day, month, year)
|
||||||
|
|
||||||
|
|
||||||
def TimetoISO8601(t, null_is_empty=False):
|
def TimetoISO8601(t, null_is_empty=False) -> str:
|
||||||
"convert time string to ISO 8601 (allow 16:03, 16h03, 16)"
|
"convert time string to ISO 8601 (allow 16:03, 16h03, 16)"
|
||||||
if isinstance(t, datetime.time):
|
if isinstance(t, datetime.time):
|
||||||
return t.isoformat()
|
return t.isoformat()
|
||||||
@ -518,7 +520,7 @@ def TimetoISO8601(t, null_is_empty=False):
|
|||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
def TimefromISO8601(t):
|
def TimefromISO8601(t) -> str:
|
||||||
"convert time string from ISO 8601 to our display format"
|
"convert time string from ISO 8601 to our display format"
|
||||||
if not t:
|
if not t:
|
||||||
return t
|
return t
|
||||||
@ -532,19 +534,6 @@ def TimefromISO8601(t):
|
|||||||
return fs[0] + "h" + fs[1] # discard seconds
|
return fs[0] + "h" + fs[1] # discard seconds
|
||||||
|
|
||||||
|
|
||||||
def TimeDuration(heure_debut, heure_fin):
|
|
||||||
"""duree (nb entier de minutes) entre deux heures a notre format
|
|
||||||
ie 12h23
|
|
||||||
"""
|
|
||||||
if heure_debut and heure_fin:
|
|
||||||
h0, m0 = [int(x) for x in heure_debut.split("h")]
|
|
||||||
h1, m1 = [int(x) for x in heure_fin.split("h")]
|
|
||||||
d = (h1 - h0) * 60 + (m1 - m0)
|
|
||||||
return d
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def float_null_is_zero(x):
|
def float_null_is_zero(x):
|
||||||
if x is None or x == "":
|
if x is None or x == "":
|
||||||
return 0.0
|
return 0.0
|
||||||
|
@ -37,7 +37,7 @@ from app import db, ScoDocJSONEncoder
|
|||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import but_validations
|
from app.models import but_validations
|
||||||
from app.models import Matiere, ModuleImpl, UniteEns
|
from app.models import Evaluation, Matiere, ModuleImpl, UniteEns
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
|
||||||
@ -324,7 +324,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
def _list_modimpls(
|
def _list_modimpls(
|
||||||
nt: NotesTableCompat,
|
nt: NotesTableCompat,
|
||||||
etudid: int,
|
etudid: int,
|
||||||
modimpls: list[ModuleImpl],
|
modimpls: list[dict],
|
||||||
prefs: SemPreferences,
|
prefs: SemPreferences,
|
||||||
version: str,
|
version: str,
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
@ -398,24 +398,29 @@ def _list_modimpls(
|
|||||||
# Evaluations incomplètes ou futures:
|
# Evaluations incomplètes ou futures:
|
||||||
complete_eval_ids = set([e["evaluation_id"] for e in evals])
|
complete_eval_ids = set([e["evaluation_id"] for e in evals])
|
||||||
if prefs["bul_show_all_evals"]:
|
if prefs["bul_show_all_evals"]:
|
||||||
all_evals = sco_evaluation_db.do_evaluation_list(
|
evaluations = Evaluation.query.filter_by(
|
||||||
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
|
moduleimpl_id=modimpl["moduleimpl_id"]
|
||||||
)
|
).order_by(Evaluation.date_debut)
|
||||||
all_evals.reverse() # plus ancienne d'abord
|
# plus ancienne d'abord
|
||||||
for e in all_evals:
|
for e in evaluations:
|
||||||
if e["evaluation_id"] not in complete_eval_ids:
|
if e.id not in complete_eval_ids:
|
||||||
mod_dict["evaluation"].append(
|
mod_dict["evaluation"].append(
|
||||||
dict(
|
dict(
|
||||||
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
date_debut=e.date_debut.isoformat()
|
||||||
|
if e.date_debut
|
||||||
|
else None,
|
||||||
|
date_fin=e.date_fin.isoformat() if e.date_fin else None,
|
||||||
|
coefficient=e.coefficient,
|
||||||
|
description=quote_xml_attr(e.description or ""),
|
||||||
|
incomplete="1",
|
||||||
|
# Deprecated:
|
||||||
|
jour=e.date_debut.isoformat() if e.date_debut else "",
|
||||||
heure_debut=ndb.TimetoISO8601(
|
heure_debut=ndb.TimetoISO8601(
|
||||||
e["heure_debut"], null_is_empty=True
|
e["heure_debut"], null_is_empty=True
|
||||||
),
|
),
|
||||||
heure_fin=ndb.TimetoISO8601(
|
heure_fin=ndb.TimetoISO8601(
|
||||||
e["heure_fin"], null_is_empty=True
|
e["heure_fin"], null_is_empty=True
|
||||||
),
|
),
|
||||||
coefficient=e["coefficient"],
|
|
||||||
description=quote_xml_attr(e["description"]),
|
|
||||||
incomplete="1",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
modules_dict.append(mod_dict)
|
modules_dict.append(mod_dict)
|
||||||
|
@ -50,11 +50,11 @@ import app.scodoc.sco_utils as scu
|
|||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
from app.but.bulletin_but_xml_compat import bulletin_but_xml_compat
|
from app.but.bulletin_but_xml_compat import bulletin_but_xml_compat
|
||||||
|
from app.models.evaluations import Evaluation
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
from app.scodoc import sco_assiduites
|
from app.scodoc import sco_assiduites
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
@ -320,12 +320,11 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
if sco_preferences.get_preference(
|
if sco_preferences.get_preference(
|
||||||
"bul_show_all_evals", formsemestre_id
|
"bul_show_all_evals", formsemestre_id
|
||||||
):
|
):
|
||||||
all_evals = sco_evaluation_db.do_evaluation_list(
|
evaluations = Evaluation.query.filter_by(
|
||||||
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
|
moduleimpl_id=modimpl["moduleimpl_id"]
|
||||||
)
|
).order_by(Evaluation.date_debut)
|
||||||
all_evals.reverse() # plus ancienne d'abord
|
for e in evaluations:
|
||||||
for e in all_evals:
|
if e.id not in complete_eval_ids:
|
||||||
if e["evaluation_id"] not in complete_eval_ids:
|
|
||||||
x_eval = Element(
|
x_eval = Element(
|
||||||
"evaluation",
|
"evaluation",
|
||||||
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
||||||
|
@ -37,7 +37,7 @@ from flask_login import current_user
|
|||||||
from app import db, log
|
from app import db, log
|
||||||
|
|
||||||
from app.models import Evaluation, ModuleImpl, ScolarNews
|
from app.models import Evaluation, ModuleImpl, ScolarNews
|
||||||
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
|
from app.models.evaluations import evaluation_enrich_dict, check_convert_evaluation_args
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
@ -89,7 +89,6 @@ def do_evaluation_list(args, sortkey=None):
|
|||||||
'apresmidi' : 1 (termine après 12:00) ou 0
|
'apresmidi' : 1 (termine après 12:00) ou 0
|
||||||
'descrheure' : ' de 15h00 à 16h30'
|
'descrheure' : ' de 15h00 à 16h30'
|
||||||
"""
|
"""
|
||||||
# Attention: transformation fonction ScoDoc7 en SQLAlchemy
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
|
evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
|
||||||
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
|
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
|
||||||
@ -108,115 +107,33 @@ def do_evaluation_list_in_formsemestre(formsemestre_id):
|
|||||||
return evals
|
return evals
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_create(
|
|
||||||
moduleimpl_id=None,
|
|
||||||
jour=None,
|
|
||||||
heure_debut=None,
|
|
||||||
heure_fin=None,
|
|
||||||
description=None,
|
|
||||||
note_max=None,
|
|
||||||
coefficient=None,
|
|
||||||
visibulletin=None,
|
|
||||||
publish_incomplete=None,
|
|
||||||
evaluation_type=None,
|
|
||||||
numero=None,
|
|
||||||
**kw, # ceci pour absorber les arguments excedentaires de tf #sco8
|
|
||||||
):
|
|
||||||
"""Create an evaluation"""
|
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
|
||||||
raise AccessDenied(
|
|
||||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
|
||||||
)
|
|
||||||
args = locals()
|
|
||||||
log("do_evaluation_create: args=" + str(args))
|
|
||||||
modimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
|
||||||
if modimpl is None:
|
|
||||||
raise ValueError("module not found")
|
|
||||||
check_evaluation_args(args)
|
|
||||||
# Check numeros
|
|
||||||
moduleimpl_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
|
|
||||||
if not "numero" in args or args["numero"] is None:
|
|
||||||
n = None
|
|
||||||
# determine le numero avec la date
|
|
||||||
# Liste des eval existantes triees par date, la plus ancienne en tete
|
|
||||||
mod_evals = do_evaluation_list(
|
|
||||||
args={"moduleimpl_id": moduleimpl_id},
|
|
||||||
sortkey="jour asc, heure_debut asc",
|
|
||||||
)
|
|
||||||
if args["jour"]:
|
|
||||||
next_eval = None
|
|
||||||
t = (
|
|
||||||
ndb.DateDMYtoISO(args["jour"], null_is_empty=True),
|
|
||||||
ndb.TimetoISO8601(args["heure_debut"], null_is_empty=True),
|
|
||||||
)
|
|
||||||
for e in mod_evals:
|
|
||||||
if (
|
|
||||||
ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
|
||||||
ndb.TimetoISO8601(e["heure_debut"], null_is_empty=True),
|
|
||||||
) > t:
|
|
||||||
next_eval = e
|
|
||||||
break
|
|
||||||
if next_eval:
|
|
||||||
n = moduleimpl_evaluation_insert_before(mod_evals, next_eval)
|
|
||||||
else:
|
|
||||||
n = None # a placer en fin
|
|
||||||
if n is None: # pas de date ou en fin:
|
|
||||||
if mod_evals:
|
|
||||||
log(pprint.pformat(mod_evals[-1]))
|
|
||||||
n = mod_evals[-1]["numero"] + 1
|
|
||||||
else:
|
|
||||||
n = 0 # the only one
|
|
||||||
# log("creating with numero n=%d" % n)
|
|
||||||
args["numero"] = n
|
|
||||||
|
|
||||||
#
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
r = _evaluationEditor.create(cnx, args)
|
|
||||||
|
|
||||||
# news
|
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
|
|
||||||
url = url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
)
|
|
||||||
ScolarNews.add(
|
|
||||||
typ=ScolarNews.NEWS_NOTE,
|
|
||||||
obj=moduleimpl_id,
|
|
||||||
text=f"""Création d'une évaluation dans <a href="{url}">{
|
|
||||||
modimpl.module.titre or '(module sans titre)'}</a>""",
|
|
||||||
url=url,
|
|
||||||
)
|
|
||||||
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_edit(args):
|
def do_evaluation_edit(args):
|
||||||
"edit an evaluation"
|
"edit an evaluation"
|
||||||
evaluation_id = args["evaluation_id"]
|
evaluation_id = args["evaluation_id"]
|
||||||
the_evals = do_evaluation_list({"evaluation_id": evaluation_id})
|
evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
|
||||||
if not the_evals:
|
if evaluation is None:
|
||||||
raise ValueError("evaluation inexistante !")
|
raise ValueError("evaluation inexistante !")
|
||||||
moduleimpl_id = the_evals[0]["moduleimpl_id"]
|
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
||||||
raise AccessDenied(
|
raise AccessDenied(
|
||||||
"Modification évaluation impossible pour %s" % current_user.get_nomplogin()
|
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||||
)
|
)
|
||||||
args["moduleimpl_id"] = moduleimpl_id
|
args["moduleimpl_id"] = evaluation.moduleimpl.id
|
||||||
check_evaluation_args(args)
|
check_convert_evaluation_args(evaluation.moduleimpl, args)
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
_evaluationEditor.edit(cnx, args)
|
_evaluationEditor.edit(cnx, args)
|
||||||
# inval cache pour ce semestre
|
# inval cache pour ce semestre
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
sco_cache.invalidate_formsemestre(
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
|
formsemestre_id=evaluation.moduleimpl.formsemestre_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_delete(evaluation_id):
|
def do_evaluation_delete(evaluation_id):
|
||||||
"delete evaluation"
|
"delete evaluation"
|
||||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||||
modimpl: ModuleImpl = evaluation.moduleimpl
|
modimpl: ModuleImpl = evaluation.moduleimpl
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=modimpl.id):
|
if not modimpl.can_edit_evaluation(current_user):
|
||||||
raise AccessDenied(
|
raise AccessDenied(
|
||||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||||
)
|
)
|
||||||
@ -287,68 +204,6 @@ def do_evaluation_get_all_notes(
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_evaluation_renumber(moduleimpl_id, only_if_unumbered=False, redirect=0):
|
|
||||||
"""Renumber evaluations in this module, according to their date. (numero=0: oldest one)
|
|
||||||
Needed because previous versions of ScoDoc did not have eval numeros
|
|
||||||
Note: existing numeros are ignored
|
|
||||||
"""
|
|
||||||
redirect = int(redirect)
|
|
||||||
# log('moduleimpl_evaluation_renumber( moduleimpl_id=%s )' % moduleimpl_id )
|
|
||||||
# List sorted according to date/heure, ignoring numeros:
|
|
||||||
# (note that we place evaluations with NULL date at the end)
|
|
||||||
mod_evals = do_evaluation_list(
|
|
||||||
args={"moduleimpl_id": moduleimpl_id},
|
|
||||||
sortkey="jour asc, heure_debut asc",
|
|
||||||
)
|
|
||||||
|
|
||||||
all_numbered = False not in [x["numero"] > 0 for x in mod_evals]
|
|
||||||
if all_numbered and only_if_unumbered:
|
|
||||||
return # all ok
|
|
||||||
|
|
||||||
# Reset all numeros:
|
|
||||||
i = 1
|
|
||||||
for e in mod_evals:
|
|
||||||
e["numero"] = i
|
|
||||||
do_evaluation_edit(e)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# If requested, redirect to moduleimpl page:
|
|
||||||
if redirect:
|
|
||||||
return flask.redirect(
|
|
||||||
url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_evaluation_insert_before(mod_evals, next_eval):
|
|
||||||
"""Renumber evals such that an evaluation with can be inserted before next_eval
|
|
||||||
Returns numero suitable for the inserted evaluation
|
|
||||||
"""
|
|
||||||
if next_eval:
|
|
||||||
n = next_eval["numero"]
|
|
||||||
if not n:
|
|
||||||
log("renumbering old evals")
|
|
||||||
moduleimpl_evaluation_renumber(next_eval["moduleimpl_id"])
|
|
||||||
next_eval = do_evaluation_list(
|
|
||||||
args={"evaluation_id": next_eval["evaluation_id"]}
|
|
||||||
)[0]
|
|
||||||
n = next_eval["numero"]
|
|
||||||
else:
|
|
||||||
n = 1
|
|
||||||
# log('inserting at position numero %s' % n )
|
|
||||||
# all numeros >= n are incremented
|
|
||||||
for e in mod_evals:
|
|
||||||
if e["numero"] >= n:
|
|
||||||
e["numero"] += 1
|
|
||||||
# log('incrementing %s to %s' % (e['evaluation_id'], e['numero']))
|
|
||||||
do_evaluation_edit(e)
|
|
||||||
|
|
||||||
return n
|
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
||||||
"""Move before/after previous one (decrement/increment numero)
|
"""Move before/after previous one (decrement/increment numero)
|
||||||
(published)
|
(published)
|
||||||
@ -357,12 +212,13 @@ def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
|||||||
moduleimpl_id = evaluation.moduleimpl_id
|
moduleimpl_id = evaluation.moduleimpl_id
|
||||||
redirect = int(redirect)
|
redirect = int(redirect)
|
||||||
# access: can change eval ?
|
# access: can change eval ?
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
||||||
raise AccessDenied(
|
raise AccessDenied(
|
||||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||||
)
|
)
|
||||||
|
Evaluation.moduleimpl_evaluation_renumber(
|
||||||
moduleimpl_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
|
evaluation.moduleimpl, only_if_unumbered=True
|
||||||
|
)
|
||||||
e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
||||||
|
|
||||||
after = int(after) # 0: deplace avant, 1 deplace apres
|
after = int(after) # 0: deplace avant, 1 deplace apres
|
||||||
@ -379,8 +235,8 @@ def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
|||||||
if neigh: #
|
if neigh: #
|
||||||
if neigh["numero"] == e["numero"]:
|
if neigh["numero"] == e["numero"]:
|
||||||
log("Warning: moduleimpl_evaluation_move: forcing renumber")
|
log("Warning: moduleimpl_evaluation_move: forcing renumber")
|
||||||
moduleimpl_evaluation_renumber(
|
Evaluation.moduleimpl_evaluation_renumber(
|
||||||
e["moduleimpl_id"], only_if_unumbered=False
|
evaluation.moduleimpl, only_if_unumbered=False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# swap numero with neighbor
|
# swap numero with neighbor
|
||||||
|
@ -83,7 +83,7 @@ def evaluation_create_form(
|
|||||||
can_edit_poids = not preferences["but_disable_edit_poids_evaluations"]
|
can_edit_poids = not preferences["but_disable_edit_poids_evaluations"]
|
||||||
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
|
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
|
||||||
#
|
#
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
if not modimpl.can_edit_evaluation(current_user):
|
||||||
return f"""
|
return f"""
|
||||||
{html_sco_header.sco_header()}
|
{html_sco_header.sco_header()}
|
||||||
<h2>Opération non autorisée</h2>
|
<h2>Opération non autorisée</h2>
|
||||||
@ -356,8 +356,11 @@ def evaluation_create_form(
|
|||||||
if edit:
|
if edit:
|
||||||
sco_evaluation_db.do_evaluation_edit(tf[2])
|
sco_evaluation_db.do_evaluation_edit(tf[2])
|
||||||
else:
|
else:
|
||||||
# création d'une evaluation (via fonction ScoDoc7)
|
# création d'une evaluation
|
||||||
evaluation_id = sco_evaluation_db.do_evaluation_create(**tf[2])
|
evaluation = Evaluation.create(moduleimpl=modimpl, **tf[2])
|
||||||
|
db.session.add(evaluation)
|
||||||
|
db.session.commit()
|
||||||
|
evaluation_id = evaluation.id
|
||||||
if is_apc:
|
if is_apc:
|
||||||
# Set poids
|
# Set poids
|
||||||
evaluation = db.session.get(Evaluation, evaluation_id)
|
evaluation = db.session.get(Evaluation, evaluation_id)
|
||||||
|
@ -435,8 +435,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
top_table_links += f"""
|
top_table_links += f"""
|
||||||
<a class="stdlink" style="margin-left:2em;" href="{
|
<a class="stdlink" style="margin-left:2em;" href="{
|
||||||
url_for("notes.moduleimpl_evaluation_renumber",
|
url_for("notes.moduleimpl_evaluation_renumber",
|
||||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id,
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
||||||
redirect=1)
|
|
||||||
}">Trier par date</a>
|
}">Trier par date</a>
|
||||||
"""
|
"""
|
||||||
if nb_evaluations > 0:
|
if nb_evaluations > 0:
|
||||||
|
@ -54,34 +54,6 @@ def can_edit_notes(authuser, moduleimpl_id, allow_ens=True):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def can_edit_evaluation(moduleimpl_id=None):
|
|
||||||
"""Vérifie que l'on a le droit de modifier, créer ou détruire une
|
|
||||||
évaluation dans ce module.
|
|
||||||
Sinon, lance une exception.
|
|
||||||
(nb: n'implique pas le droit de saisir ou modifier des notes)
|
|
||||||
"""
|
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
|
|
||||||
# acces pour resp. moduleimpl et resp. form semestre (dir etud)
|
|
||||||
if moduleimpl_id is None:
|
|
||||||
raise ValueError("no moduleimpl specified") # bug
|
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
|
||||||
|
|
||||||
if (
|
|
||||||
current_user.has_permission(Permission.ScoEditAllEvals)
|
|
||||||
or current_user.id == M["responsable_id"]
|
|
||||||
or current_user.id in sem["responsables"]
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
elif sem["ens_can_edit_eval"]:
|
|
||||||
for ens in M["ens"]:
|
|
||||||
if ens["ens_id"] == current_user.id:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def can_suppress_annotation(annotation_id):
|
def can_suppress_annotation(annotation_id):
|
||||||
"""True if current user can suppress this annotation
|
"""True if current user can suppress this annotation
|
||||||
Seuls l'auteur de l'annotation et le chef de dept peuvent supprimer
|
Seuls l'auteur de l'annotation et le chef de dept peuvent supprimer
|
||||||
|
@ -60,7 +60,7 @@ from app.models.formsemestre import FormSemestre
|
|||||||
|
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import UniteEns
|
from app.models import Evaluation, ModuleImpl, UniteEns
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_edit_matiere
|
from app.scodoc import sco_edit_matiere
|
||||||
@ -154,6 +154,7 @@ def external_ue_inscrit_et_note(
|
|||||||
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
|
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
|
||||||
et enregistre les notes.
|
et enregistre les notes.
|
||||||
"""
|
"""
|
||||||
|
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||||
log(
|
log(
|
||||||
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})"
|
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})"
|
||||||
)
|
)
|
||||||
@ -163,18 +164,14 @@ def external_ue_inscrit_et_note(
|
|||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
list(notes_etuds.keys()),
|
list(notes_etuds.keys()),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Création d'une évaluation si il n'y en a pas déjà:
|
# Création d'une évaluation si il n'y en a pas déjà:
|
||||||
mod_evals = sco_evaluation_db.do_evaluation_list(
|
if moduleimpl.evaluations.count() > 0:
|
||||||
args={"moduleimpl_id": moduleimpl_id}
|
|
||||||
)
|
|
||||||
if len(mod_evals):
|
|
||||||
# met la note dans le première évaluation existante:
|
# met la note dans le première évaluation existante:
|
||||||
evaluation_id = mod_evals[0]["evaluation_id"]
|
evaluation: Evaluation = moduleimpl.evaluations.first()
|
||||||
else:
|
else:
|
||||||
# crée une évaluation:
|
# crée une évaluation:
|
||||||
evaluation_id = sco_evaluation_db.do_evaluation_create(
|
evaluation: Evaluation = Evaluation.create(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl=moduleimpl,
|
||||||
note_max=20.0,
|
note_max=20.0,
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
publish_incomplete=True,
|
publish_incomplete=True,
|
||||||
@ -185,7 +182,7 @@ def external_ue_inscrit_et_note(
|
|||||||
# Saisie des notes
|
# Saisie des notes
|
||||||
_, _, _ = sco_saisie_notes.notes_add(
|
_, _, _ = sco_saisie_notes.notes_add(
|
||||||
current_user,
|
current_user,
|
||||||
evaluation_id,
|
evaluation.id,
|
||||||
list(notes_etuds.items()),
|
list(notes_etuds.items()),
|
||||||
do_it=True,
|
do_it=True,
|
||||||
)
|
)
|
||||||
|
@ -68,6 +68,9 @@ from app.scodoc.codes_cursus import NOTES_TOLERANCE, CODES_EXPL
|
|||||||
from app.scodoc import sco_xml
|
from app.scodoc import sco_xml
|
||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
|
# En principe, aucun champ text ne devrait excéder cette taille
|
||||||
|
MAX_TEXT_LEN = 64 * 1024
|
||||||
|
|
||||||
# le répertoire static, lié à chaque release pour éviter les problèmes de caches
|
# le répertoire static, lié à chaque release pour éviter les problèmes de caches
|
||||||
STATIC_DIR = (
|
STATIC_DIR = (
|
||||||
os.environ.get("SCRIPT_NAME", "") + "/ScoDoc/static/links/" + sco_version.SCOVERSION
|
os.environ.get("SCRIPT_NAME", "") + "/ScoDoc/static/links/" + sco_version.SCOVERSION
|
||||||
|
@ -57,8 +57,8 @@ from app.but.forms import jury_but_forms
|
|||||||
from app.comp import jury, res_sem
|
from app.comp import jury, res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import (
|
from app.models import (
|
||||||
|
Evaluation,
|
||||||
Formation,
|
Formation,
|
||||||
ScolarFormSemestreValidation,
|
|
||||||
ScolarAutorisationInscription,
|
ScolarAutorisationInscription,
|
||||||
ScolarNews,
|
ScolarNews,
|
||||||
Scolog,
|
Scolog,
|
||||||
@ -134,6 +134,7 @@ from app.scodoc import sco_lycee
|
|||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_moduleimpl_inscriptions
|
from app.scodoc import sco_moduleimpl_inscriptions
|
||||||
from app.scodoc import sco_moduleimpl_status
|
from app.scodoc import sco_moduleimpl_status
|
||||||
|
from app.scodoc import sco_permissions_check
|
||||||
from app.scodoc import sco_placement
|
from app.scodoc import sco_placement
|
||||||
from app.scodoc import sco_poursuite_dut
|
from app.scodoc import sco_poursuite_dut
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
@ -378,11 +379,40 @@ sco_publish(
|
|||||||
sco_evaluations.formsemestre_evaluations_delai_correction,
|
sco_evaluations.formsemestre_evaluations_delai_correction,
|
||||||
Permission.ScoView,
|
Permission.ScoView,
|
||||||
)
|
)
|
||||||
sco_publish(
|
|
||||||
"/moduleimpl_evaluation_renumber",
|
|
||||||
sco_evaluation_db.moduleimpl_evaluation_renumber,
|
@bp.route("/moduleimpl_evaluation_renumber", methods=["GET", "POST"])
|
||||||
Permission.ScoView,
|
@scodoc
|
||||||
)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
|
@scodoc7func
|
||||||
|
def moduleimpl_evaluation_renumber(moduleimpl_id):
|
||||||
|
"Renumérote les évaluations, triant par date"
|
||||||
|
modimpl: ModuleImpl = (
|
||||||
|
ModuleImpl.query.filter_by(id=moduleimpl_id)
|
||||||
|
.join(FormSemestre)
|
||||||
|
.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
.first_or_404()
|
||||||
|
)
|
||||||
|
if not modimpl.can_edit_evaluation(current_user):
|
||||||
|
raise ScoPermissionDenied(
|
||||||
|
dest_url=url_for(
|
||||||
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=modimpl.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Evaluation.moduleimpl_evaluation_renumber(modimpl)
|
||||||
|
# redirect to moduleimpl page:
|
||||||
|
if redirect:
|
||||||
|
return flask.redirect(
|
||||||
|
url_for(
|
||||||
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/moduleimpl_evaluation_move",
|
"/moduleimpl_evaluation_move",
|
||||||
sco_evaluation_db.moduleimpl_evaluation_move,
|
sco_evaluation_db.moduleimpl_evaluation_move,
|
||||||
|
58
migrations/versions/5c44d0d215ca_evaluation_date.py
Normal file
58
migrations/versions/5c44d0d215ca_evaluation_date.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"""evaluation date: modifie le codage des dates d'évaluations
|
||||||
|
|
||||||
|
Revision ID: 5c44d0d215ca
|
||||||
|
Revises: 45e0a855b8eb
|
||||||
|
Create Date: 2023-08-22 14:39:23.831483
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "5c44d0d215ca"
|
||||||
|
down_revision = "45e0a855b8eb"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
"modifie les colonnes codant les dates d'évaluations"
|
||||||
|
with op.batch_alter_table("notes_evaluation", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column("date_debut", sa.DateTime(timezone=True), nullable=True)
|
||||||
|
)
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column("date_fin", sa.DateTime(timezone=True), nullable=True)
|
||||||
|
)
|
||||||
|
# recode les dates existantes
|
||||||
|
op.execute("UPDATE notes_evaluation SET date_debut = jour+heure_debut;")
|
||||||
|
op.execute("UPDATE notes_evaluation SET date_fin = jour+heure_fin;")
|
||||||
|
with op.batch_alter_table("notes_evaluation", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("jour")
|
||||||
|
batch_op.drop_column("heure_fin")
|
||||||
|
batch_op.drop_column("heure_debut")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
"modifie les colonnes codant les dates d'évaluations"
|
||||||
|
with op.batch_alter_table("notes_evaluation", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column(
|
||||||
|
"heure_debut", postgresql.TIME(), autoincrement=False, nullable=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column(
|
||||||
|
"heure_fin", postgresql.TIME(), autoincrement=False, nullable=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column("jour", sa.DATE(), autoincrement=False, nullable=True)
|
||||||
|
)
|
||||||
|
op.execute("UPDATE notes_evaluation SET jour = DATE(date_debut);")
|
||||||
|
op.execute("UPDATE notes_evaluation SET heure_debut = date_debut::time;")
|
||||||
|
op.execute("UPDATE notes_evaluation SET heure_fin = date_fin::time;")
|
||||||
|
with op.batch_alter_table("notes_evaluation", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("date_fin")
|
||||||
|
batch_op.drop_column("date_debut")
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
from pprint import pprint as pp
|
from pprint import pprint as pp
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@ -82,6 +82,7 @@ def make_shell_context():
|
|||||||
"ctx": app.test_request_context(),
|
"ctx": app.test_request_context(),
|
||||||
"current_app": flask.current_app,
|
"current_app": flask.current_app,
|
||||||
"current_user": current_user,
|
"current_user": current_user,
|
||||||
|
"datetime": datetime,
|
||||||
"Departement": Departement,
|
"Departement": Departement,
|
||||||
"db": db,
|
"db": db,
|
||||||
"Evaluation": Evaluation,
|
"Evaluation": Evaluation,
|
||||||
|
@ -315,13 +315,12 @@ pp(GET(f"/formsemestre/880/resultats", headers=HEADERS)[0])
|
|||||||
# jour = sem["date_fin"]
|
# jour = sem["date_fin"]
|
||||||
# evaluation_id = POST(
|
# evaluation_id = POST(
|
||||||
# s,
|
# s,
|
||||||
# "/Notes/do_evaluation_create",
|
# f"/moduleimpl/{mod['moduleimpl_id']}/evaluation/create",
|
||||||
# data={
|
# data={
|
||||||
# "moduleimpl_id": mod["moduleimpl_id"],
|
|
||||||
# "coefficient": 1,
|
# "coefficient": 1,
|
||||||
# "jour": jour, # "5/9/2019",
|
# "jour": jour, # "2023-08-23",
|
||||||
# "heure_debut": "9h00",
|
# "heure_debut": "9:00",
|
||||||
# "heure_fin": "10h00",
|
# "heure_fin": "10:00",
|
||||||
# "note_max": 20, # notes sur 20
|
# "note_max": 20, # notes sur 20
|
||||||
# "description": "essai",
|
# "description": "essai",
|
||||||
# },
|
# },
|
||||||
|
@ -165,37 +165,3 @@ assert isinstance(json.loads(r.text)[0]["billet_id"], int)
|
|||||||
# print(f"{len(inscrits)} inscrits dans ce module")
|
# print(f"{len(inscrits)} inscrits dans ce module")
|
||||||
# # prend le premier inscrit, au hasard:
|
# # prend le premier inscrit, au hasard:
|
||||||
# etudid = inscrits[0]["etudid"]
|
# etudid = inscrits[0]["etudid"]
|
||||||
|
|
||||||
# # ---- Création d'une evaluation le dernier jour du semestre
|
|
||||||
# jour = sem["date_fin"]
|
|
||||||
# evaluation_id = POST(
|
|
||||||
# "/Notes/do_evaluation_create",
|
|
||||||
# data={
|
|
||||||
# "moduleimpl_id": mod["moduleimpl_id"],
|
|
||||||
# "coefficient": 1,
|
|
||||||
# "jour": jour, # "5/9/2019",
|
|
||||||
# "heure_debut": "9h00",
|
|
||||||
# "heure_fin": "10h00",
|
|
||||||
# "note_max": 20, # notes sur 20
|
|
||||||
# "description": "essai",
|
|
||||||
# },
|
|
||||||
# errmsg="échec création évaluation",
|
|
||||||
# )
|
|
||||||
|
|
||||||
# print(
|
|
||||||
# f"Evaluation créée dans le module {mod['moduleimpl_id']}, evaluation_id={evaluation_id}"
|
|
||||||
# )
|
|
||||||
# print(
|
|
||||||
# f"Pour vérifier, aller sur: {DEPT_URL}/Notes/moduleimpl_status?moduleimpl_id={mod['moduleimpl_id']}",
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # ---- Saisie d'une note
|
|
||||||
# junk = POST(
|
|
||||||
# "/Notes/save_note",
|
|
||||||
# data={
|
|
||||||
# "etudid": etudid,
|
|
||||||
# "evaluation_id": evaluation_id,
|
|
||||||
# "value": 16.66, # la note !
|
|
||||||
# "comment": "test API",
|
|
||||||
# },
|
|
||||||
# )
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""Test Logos
|
"""Test APi evaluations
|
||||||
|
|
||||||
Utilisation :
|
Utilisation :
|
||||||
créer les variables d'environnement: (indiquer les valeurs
|
créer les variables d'environnement: (indiquer les valeurs
|
||||||
@ -20,7 +20,13 @@ Utilisation :
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
|
from tests.api.setup_test_api import (
|
||||||
|
API_URL,
|
||||||
|
CHECK_CERTIFICATE,
|
||||||
|
POST_JSON,
|
||||||
|
api_admin_headers,
|
||||||
|
api_headers,
|
||||||
|
)
|
||||||
from tests.api.tools_test_api import (
|
from tests.api.tools_test_api import (
|
||||||
verify_fields,
|
verify_fields,
|
||||||
EVALUATIONS_FIELDS,
|
EVALUATIONS_FIELDS,
|
||||||
@ -43,25 +49,25 @@ def test_evaluations(api_headers):
|
|||||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
list_eval = r.json()
|
evaluations = r.json()
|
||||||
assert list_eval
|
assert evaluations
|
||||||
assert isinstance(list_eval, list)
|
assert isinstance(evaluations, list)
|
||||||
for eval in list_eval:
|
for e in evaluations:
|
||||||
assert verify_fields(eval, EVALUATIONS_FIELDS) is True
|
assert verify_fields(e, EVALUATIONS_FIELDS)
|
||||||
assert isinstance(eval["id"], int)
|
assert isinstance(e["id"], int)
|
||||||
assert isinstance(eval["note_max"], float)
|
assert isinstance(e["note_max"], float)
|
||||||
assert isinstance(eval["visi_bulletin"], bool)
|
assert isinstance(e["visi_bulletin"], bool)
|
||||||
assert isinstance(eval["evaluation_type"], int)
|
assert isinstance(e["evaluation_type"], int)
|
||||||
assert isinstance(eval["moduleimpl_id"], int)
|
assert isinstance(e["moduleimpl_id"], int)
|
||||||
assert eval["description"] is None or isinstance(eval["description"], str)
|
assert e["description"] is None or isinstance(e["description"], str)
|
||||||
assert isinstance(eval["coefficient"], float)
|
assert isinstance(e["coefficient"], float)
|
||||||
assert isinstance(eval["publish_incomplete"], bool)
|
assert isinstance(e["publish_incomplete"], bool)
|
||||||
assert isinstance(eval["numero"], int)
|
assert isinstance(e["numero"], int)
|
||||||
assert eval["date_debut"] is None or isinstance(eval["date_debut"], str)
|
assert e["date_debut"] is None or isinstance(e["date_debut"], str)
|
||||||
assert eval["date_fin"] is None or isinstance(eval["date_fin"], str)
|
assert e["date_fin"] is None or isinstance(e["date_fin"], str)
|
||||||
assert isinstance(eval["poids"], dict)
|
assert isinstance(e["poids"], dict)
|
||||||
|
|
||||||
assert eval["moduleimpl_id"] == moduleimpl_id
|
assert e["moduleimpl_id"] == moduleimpl_id
|
||||||
|
|
||||||
|
|
||||||
def test_evaluation_notes(api_headers):
|
def test_evaluation_notes(api_headers):
|
||||||
@ -92,3 +98,31 @@ def test_evaluation_notes(api_headers):
|
|||||||
assert isinstance(note["uid"], int)
|
assert isinstance(note["uid"], int)
|
||||||
|
|
||||||
assert eval_id == note["evaluation_id"]
|
assert eval_id == note["evaluation_id"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_evaluation_create(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Test /moduleimpl/<int:moduleimpl_id>/evaluation/create
|
||||||
|
"""
|
||||||
|
moduleimpl_id = 20
|
||||||
|
e = POST_JSON(
|
||||||
|
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||||
|
{"description": "eval test"},
|
||||||
|
api_admin_headers,
|
||||||
|
)
|
||||||
|
assert isinstance(e, dict)
|
||||||
|
assert verify_fields(e, EVALUATIONS_FIELDS)
|
||||||
|
# Check default values
|
||||||
|
assert e["note_max"] == 20.0
|
||||||
|
assert e["evaluation_type"] == 0
|
||||||
|
assert e["jour"] is None
|
||||||
|
assert e["date_debut"] is None
|
||||||
|
assert e["date_fin"] is None
|
||||||
|
assert e["visibulletin"] is True
|
||||||
|
assert e["publish_incomplete"] is False
|
||||||
|
assert e["coefficient"] == 1.0
|
||||||
|
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# - tester creation UE externe
|
||||||
|
# - tester création base test et test API
|
||||||
|
@ -16,7 +16,14 @@ import typing
|
|||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models import Departement, Formation, FormationModalite, Matiere
|
from app.models import (
|
||||||
|
Departement,
|
||||||
|
Evaluation,
|
||||||
|
Formation,
|
||||||
|
FormationModalite,
|
||||||
|
Matiere,
|
||||||
|
ModuleImpl,
|
||||||
|
)
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_edit_matiere
|
from app.scodoc import sco_edit_matiere
|
||||||
@ -307,14 +314,15 @@ class ScoFake(object):
|
|||||||
publish_incomplete=None,
|
publish_incomplete=None,
|
||||||
evaluation_type=None,
|
evaluation_type=None,
|
||||||
numero=None,
|
numero=None,
|
||||||
):
|
) -> int:
|
||||||
args = locals()
|
args = locals()
|
||||||
del args["self"]
|
del args["self"]
|
||||||
oid = sco_evaluation_db.do_evaluation_create(**args)
|
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||||
oids = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": oid})
|
assert moduleimpl
|
||||||
if not oids:
|
evaluation: Evaluation = Evaluation.create(moduleimpl=moduleimpl, **args)
|
||||||
raise ScoValueError("evaluation not created !")
|
db.session.add(evaluation)
|
||||||
return oids[0]
|
db.session.commit()
|
||||||
|
return evaluation.id
|
||||||
|
|
||||||
@logging_meth
|
@logging_meth
|
||||||
def create_note(
|
def create_note(
|
||||||
|
@ -11,7 +11,6 @@ import datetime
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
@ -23,6 +22,7 @@ from app.models import (
|
|||||||
Absence,
|
Absence,
|
||||||
Assiduite,
|
Assiduite,
|
||||||
Departement,
|
Departement,
|
||||||
|
Evaluation,
|
||||||
Formation,
|
Formation,
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
FormSemestreEtape,
|
FormSemestreEtape,
|
||||||
@ -235,14 +235,13 @@ def inscrit_etudiants(etuds: list, formsemestre: FormSemestre):
|
|||||||
|
|
||||||
|
|
||||||
def create_evaluations(formsemestre: FormSemestre):
|
def create_evaluations(formsemestre: FormSemestre):
|
||||||
"creation d'une evaluation dans cahque modimpl du semestre"
|
"Création d'une evaluation dans chaque modimpl du semestre"
|
||||||
for modimpl in formsemestre.modimpls:
|
for moduleimpl in formsemestre.modimpls:
|
||||||
args = {
|
args = {
|
||||||
"moduleimpl_id": modimpl.id,
|
"jour": datetime.date(2022, 3, 1) + datetime.timedelta(days=moduleimpl.id),
|
||||||
"jour": datetime.date(2022, 3, 1) + datetime.timedelta(days=modimpl.id),
|
|
||||||
"heure_debut": "8h00",
|
"heure_debut": "8h00",
|
||||||
"heure_fin": "9h00",
|
"heure_fin": "9h00",
|
||||||
"description": f"Evaluation-{modimpl.module.code}",
|
"description": f"Evaluation-{moduleimpl.module.code}",
|
||||||
"note_max": 20,
|
"note_max": 20,
|
||||||
"coefficient": 1.0,
|
"coefficient": 1.0,
|
||||||
"visibulletin": True,
|
"visibulletin": True,
|
||||||
@ -250,7 +249,9 @@ def create_evaluations(formsemestre: FormSemestre):
|
|||||||
"evaluation_type": None,
|
"evaluation_type": None,
|
||||||
"numero": None,
|
"numero": None,
|
||||||
}
|
}
|
||||||
evaluation_id = sco_evaluation_db.do_evaluation_create(**args)
|
evaluation = Evaluation.create(moduleimpl=moduleimpl, **args)
|
||||||
|
db.session.add(evaluation)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def saisie_notes_evaluations(formsemestre: FormSemestre, user: User):
|
def saisie_notes_evaluations(formsemestre: FormSemestre, user: User):
|
||||||
|
@ -39,7 +39,14 @@ echo
|
|||||||
echo Server PID "$pid" running on port "$PORT"
|
echo Server PID "$pid" running on port "$PORT"
|
||||||
# ------------------
|
# ------------------
|
||||||
|
|
||||||
pytest tests/api
|
if [ "$#" -eq 1 ]
|
||||||
|
then
|
||||||
|
echo "Starting pytest tests/api"
|
||||||
|
pytest tests/api
|
||||||
|
else
|
||||||
|
echo "Starting pytest $@"
|
||||||
|
pytest "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
# ------------------
|
# ------------------
|
||||||
echo "Killing server"
|
echo "Killing server"
|
||||||
|
Loading…
Reference in New Issue
Block a user