From 89e7250f4a28ef7cc3ae7dca2a1731f99e6bef47 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 12 Nov 2021 22:17:46 +0100
Subject: [PATCH] =?UTF-8?q?-=20Coef=20evaluations=20(mod=C3=A8les).=20-=20?=
=?UTF-8?q?Refactoring.=20-=20Changement=20des=20noms=20des=20classes=20(m?=
=?UTF-8?q?od=C3=A8les)=20des=20formations.=20-=20D=C3=A9but=20int=C3=A9gr?=
=?UTF-8?q?ation=20calculs=20BUT.=20-=20Requiert=20numpy=20et=20pandas.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/__init__.py | 2 +-
app/comp/moy_ue.py | 54 ++
app/models/__init__.py | 29 +-
app/models/but_pn.py | 2 +-
app/models/departements.py | 4 +-
app/models/formations.py | 87 ++-
app/models/formsemestre.py | 75 +-
app/models/notes.py | 3 +-
app/scodoc/notes_table.py | 13 +-
app/scodoc/notesdb.py | 2 +-
app/scodoc/sco_bulletins.py | 7 +-
app/scodoc/sco_bulletins_json.py | 4 +-
app/scodoc/sco_bulletins_xml.py | 4 +-
app/scodoc/sco_compute_moy.py | 10 +-
app/scodoc/sco_edit_module.py | 100 ++-
app/scodoc/sco_edit_ue.py | 15 +-
app/scodoc/sco_evaluation_db.py | 482 ++++++++++++
app/scodoc/sco_evaluation_edit.py | 332 ++++++++
app/scodoc/sco_evaluations.py | 714 +-----------------
app/scodoc/sco_formsemestre.py | 5 +-
app/scodoc/sco_formsemestre_edit.py | 11 +-
app/scodoc/sco_formsemestre_status.py | 383 ++++++----
app/scodoc/sco_formsemestre_validation.py | 4 +-
app/scodoc/sco_import_etuds.py | 2 +-
app/scodoc/sco_liste_notes.py | 17 +-
app/scodoc/sco_moduleimpl_status.py | 42 +-
app/scodoc/sco_news.py | 4 +-
app/scodoc/sco_parcours_dut.py | 4 +-
app/scodoc/sco_placement.py | 7 +-
app/scodoc/sco_recapcomplet.py | 8 +-
app/scodoc/sco_report.py | 4 +-
app/scodoc/sco_saisie_notes.py | 60 +-
app/scodoc/sco_ue_external.py | 13 +-
app/scodoc/sco_undo_notes.py | 7 +-
app/scodoc/sco_utils.py | 26 +-
app/static/css/scodoc.css | 13 +-
app/static/css/table_editor.css | 93 +++
app/static/js/table_editor.js | 56 ++
app/templates/pn/form_modules_ue_coefs.html | 55 ++
app/templates/scodoc/help/evaluations.html | 53 ++
app/templates/scodoc/help/modules.html | 27 +
app/views/__init__.py | 2 +-
app/views/notes.py | 18 +-
app/views/pn_modules.py | 171 +++++
app/views/scodoc.py | 6 +-
.../6cfc21a7ae1b_coefs_modules_but.py | 35 +
requirements-3.9.txt | 5 +-
tests/unit/sco_fake_gen.py | 9 +-
tests/unit/test_but_modules.py | 42 +-
tests/unit/test_caches.py | 3 +-
tests/unit/test_sco_basic.py | 3 +-
51 files changed, 2025 insertions(+), 1102 deletions(-)
create mode 100644 app/comp/moy_ue.py
create mode 100644 app/scodoc/sco_evaluation_db.py
create mode 100644 app/scodoc/sco_evaluation_edit.py
create mode 100644 app/static/css/table_editor.css
create mode 100644 app/static/js/table_editor.js
create mode 100644 app/templates/pn/form_modules_ue_coefs.html
create mode 100644 app/templates/scodoc/help/evaluations.html
create mode 100644 app/templates/scodoc/help/modules.html
create mode 100644 app/views/pn_modules.py
create mode 100644 migrations/versions/6cfc21a7ae1b_coefs_modules_but.py
diff --git a/app/__init__.py b/app/__init__.py
index 41875f33b2..0943a91f6a 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -357,7 +357,7 @@ def sco_db_insert_constants():
current_app.logger.info("Init Sco db")
# Modalités:
- models.NotesFormModalite.insert_modalites()
+ models.FormationModalite.insert_modalites()
def initialize_scodoc_database(erase=False, create_all=False):
diff --git a/app/comp/moy_ue.py b/app/comp/moy_ue.py
new file mode 100644
index 0000000000..b2eb29e062
--- /dev/null
+++ b/app/comp/moy_ue.py
@@ -0,0 +1,54 @@
+# -*- mode: python -*-
+# -*- coding: utf-8 -*-
+
+##############################################################################
+#
+# Gestion scolarite IUT
+#
+# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Emmanuel Viennet emmanuel.viennet@viennet.net
+#
+##############################################################################
+
+"""Fonctions de calcul des moyennes d'UE
+"""
+import numpy as np
+import pandas as pd
+
+from app import db
+from app import models
+
+
+def df_load_ue_coefs(formation_id):
+ """Load coefs of all modules in formation and returns a DataFrame
+ rows = modules, columns = UE, value = coef.
+ Unspecified coefs (not defined in db) are set to zero.
+ """
+ ues = models.UniteEns.query.filter_by(formation_id=formation_id).all()
+ modules = models.Module.query.filter_by(formation_id=formation_id).all()
+ ue_ids = [ue.id for ue in ues]
+ module_ids = [module.id for module in modules]
+ df = pd.DataFrame(columns=ue_ids, index=module_ids, dtype=float)
+ for mod_coef in (
+ db.session.query(models.ModuleUECoef)
+ .filter(models.UniteEns.formation_id == formation_id)
+ .filter(models.ModuleUECoef.ue_id == models.UniteEns.id)
+ ):
+ df[mod_coef.ue_id][mod_coef.module_id] = mod_coef.coef
+ df.fillna(value=0, inplace=True)
+ return df
diff --git a/app/models/__init__.py b/app/models/__init__.py
index 3d164fe442..100b0a933c 100644
--- a/app/models/__init__.py
+++ b/app/models/__init__.py
@@ -31,26 +31,27 @@ from app.models.etudiants import (
)
from app.models.events import Scolog, ScolarNews
from app.models.formations import (
- NotesFormation,
- NotesUE,
- NotesMatiere,
- NotesModule,
+ Formation,
+ UniteEns,
+ Matiere,
+ Module,
+ ModuleUECoef,
NotesTag,
notes_modules_tags,
)
from app.models.formsemestre import (
FormSemestre,
- NotesFormsemestreEtape,
- NotesFormModalite,
- NotesFormsemestreUECoef,
- NotesFormsemestreUEComputationExpr,
- NotesFormsemestreCustomMenu,
- NotesFormsemestreInscription,
+ FormsemestreEtape,
+ FormationModalite,
+ FormsemestreUECoef,
+ FormsemestreUEComputationExpr,
+ FormsemestreCustomMenu,
+ FormsemestreInscription,
notes_formsemestre_responsables,
- NotesModuleImpl,
+ ModuleImpl,
notes_modules_enseignants,
- NotesModuleImplInscription,
- NotesEvaluation,
+ ModuleImplInscription,
+ Evaluation,
EvaluationUEPoids,
NotesSemSet,
notes_semset_formsemestre,
@@ -61,7 +62,7 @@ from app.models.notes import (
ScolarEvent,
ScolarFormsemestreValidation,
ScolarAutorisationInscription,
- NotesAppreciations,
+ BulAppreciations,
NotesNotes,
NotesNotesLog,
)
diff --git a/app/models/but_pn.py b/app/models/but_pn.py
index 75f83a35fa..1e142d23d7 100644
--- a/app/models/but_pn.py
+++ b/app/models/but_pn.py
@@ -22,7 +22,7 @@ class AppCrit(db.Model):
titre = db.Column(db.Text(), info={"label": "Titre"})
modules = db.relationship(
- "NotesModule", secondary=Modules_ACs, lazy="dynamic", backref="acs"
+ "Module", secondary=Modules_ACs, lazy="dynamic", backref="acs"
)
def to_dict(self):
diff --git a/app/models/departements.py b/app/models/departements.py
index 36aa8d4c62..aa9c1006a6 100644
--- a/app/models/departements.py
+++ b/app/models/departements.py
@@ -21,9 +21,7 @@ class Departement(db.Model):
entreprises = db.relationship("Entreprise", lazy="dynamic", backref="departement")
etudiants = db.relationship("Identite", lazy="dynamic", backref="departement")
- formations = db.relationship(
- "NotesFormation", lazy="dynamic", backref="departement"
- )
+ formations = db.relationship("Formation", lazy="dynamic", backref="departement")
formsemestres = db.relationship(
"FormSemestre", lazy="dynamic", backref="departement"
)
diff --git a/app/models/formations.py b/app/models/formations.py
index 046a3e7371..fd1f018950 100644
--- a/app/models/formations.py
+++ b/app/models/formations.py
@@ -5,10 +5,11 @@ from typing import Any
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
+from app.scodoc import sco_utils as scu
from app.scodoc.sco_utils import ModuleType
-class NotesFormation(db.Model):
+class Formation(db.Model):
"""Programme pédagogique d'une formation"""
__tablename__ = "notes_formations"
@@ -31,16 +32,16 @@ class NotesFormation(db.Model):
type_parcours = db.Column(db.Integer, default=0, server_default="0")
code_specialite = db.Column(db.String(SHORT_STR_LEN))
- ues = db.relationship("NotesUE", backref="formation", lazy="dynamic")
+ ues = db.relationship("UniteEns", backref="formation", lazy="dynamic")
formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
- ues = db.relationship("NotesUE", lazy="dynamic", backref="formation")
+ ues = db.relationship("UniteEns", lazy="dynamic", backref="formation")
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme}')>"
-class NotesUE(db.Model):
- """Unité d'Enseignement"""
+class UniteEns(db.Model):
+ """Unité d'Enseignement (UE)"""
__tablename__ = "notes_ue"
@@ -68,14 +69,14 @@ class NotesUE(db.Model):
coefficient = db.Column(db.Float)
# relations
- matieres = db.relationship("NotesMatiere", lazy="dynamic", backref="ue")
- modules = db.relationship("NotesModule", lazy="dynamic", backref="ue")
+ matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
+ modules = db.relationship("Module", lazy="dynamic", backref="ue")
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id}, formation_id={self.formation_id}, acronyme='{self.acronyme}')>"
-class NotesMatiere(db.Model):
+class Matiere(db.Model):
"""Matières: regroupe les modules d'une UE
La matière a peu d'utilité en dehors de la présentation des modules
d'une UE.
@@ -90,10 +91,10 @@ class NotesMatiere(db.Model):
titre = db.Column(db.Text())
numero = db.Column(db.Integer) # ordre de présentation
- modules = db.relationship("NotesModule", lazy="dynamic", backref="matiere")
+ modules = db.relationship("Module", lazy="dynamic", backref="matiere")
-class NotesModule(db.Model):
+class Module(db.Model):
"""Module"""
__tablename__ = "notes_modules"
@@ -107,7 +108,7 @@ class NotesModule(db.Model):
heures_cours = db.Column(db.Float)
heures_td = db.Column(db.Float)
heures_tp = db.Column(db.Float)
- coefficient = db.Column(db.Float) # coef PPN
+ coefficient = db.Column(db.Float) # coef PPN (sauf en APC)
ects = db.Column(db.Float) # Crédits ECTS
ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), index=True)
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
@@ -120,13 +121,75 @@ class NotesModule(db.Model):
# Type: ModuleType: DEFAULT, MALUS, RESSOURCE, MODULE_SAE (enum)
module_type = db.Column(db.Integer)
# Relations:
- modimpls = db.relationship("NotesModuleImpl", backref="module", lazy="dynamic")
+ modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
+ ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)
+
+ def __init__(self, **kwargs):
+ self.ue_coefs = []
+ super(Module, self).__init__(**kwargs)
def __repr__(self):
return (
f""
)
+ def type_name(self):
+ return scu.MODULE_TYPE_NAMES[self.module_type]
+
+ def set_ue_coef(self, ue, coef: float) -> None:
+ """Set coef module vers cette UE"""
+ self.update_ue_coef_dict({ue.id: coef})
+
+ def set_ue_coef_dict(self, ue_coef_dict: dict) -> None:
+ """set coefs vers les UE (remplace existants)
+ ue_coef_dict = { ue_id : coef }
+ """
+ ue_coefs = []
+ for ue_id, coef in ue_coef_dict.items():
+ ue = UniteEns.query.get(ue_id)
+ ue_coefs.append(ModuleUECoef(module=self, ue=ue, coef=coef))
+ self.ue_coefs = ue_coefs
+
+ def update_ue_coef_dict(self, ue_coef_dict: dict):
+ """update coefs vers UE (ajoute aux existants)"""
+ current = self.get_ue_coef_dict()
+ current.update(ue_coef_dict)
+ self.set_ue_coef_dict(current)
+
+ def get_ue_coef_dict(self):
+ """returns { ue_id : coef }"""
+ return {p.ue.id: p.coef for p in self.ue_coefs}
+
+ def delete_ue_coef(self, ue):
+ """delete coef"""
+ ue_coef = ModuleUECoef.query.get((self.id, ue.id))
+ db.session.delete(ue_coef)
+
+
+class ModuleUECoef(db.Model):
+ """Coefficients des modules vers les UE (APC, BUT)
+ En mode APC, ces coefs remplacent le coefficient "PPN" du module.
+ """
+
+ __tablename__ = "module_ue_coef"
+
+ module_id = db.Column(
+ db.Integer, db.ForeignKey("notes_modules.id"), primary_key=True
+ )
+ ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), primary_key=True)
+ coef = db.Column(
+ db.Float,
+ nullable=False,
+ )
+ module = db.relationship(
+ Module,
+ backref=db.backref("ue_coefs", cascade="all, delete-orphan"),
+ )
+ ue = db.relationship(
+ UniteEns,
+ backref=db.backref("module_ue_coefs", cascade="all, delete-orphan"),
+ )
+
class NotesTag(db.Model):
"""Tag sur un module"""
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index b92e2241aa..f4eb8da3fc 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -8,7 +8,10 @@ from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
-from app.models import NotesUE
+from app.models import UniteEns
+
+import app.scodoc.notesdb as ndb
+from app.scodoc import sco_evaluation_db
class FormSemestre(db.Model):
@@ -73,11 +76,9 @@ class FormSemestre(db.Model):
# Relations:
etapes = db.relationship(
- "NotesFormsemestreEtape", cascade="all,delete", backref="formsemestre"
- )
- formsemestres = db.relationship(
- "NotesModuleImpl", backref="formsemestre", lazy="dynamic"
+ "FormsemestreEtape", cascade="all,delete", backref="formsemestre"
)
+ modimpls = db.relationship("ModuleImpl", backref="formsemestre", lazy="dynamic")
# Ancien id ScoDoc7 pour les migrations de bases anciennes
# ne pas utiliser après migrate_scodoc7_dept_archives
@@ -86,7 +87,7 @@ class FormSemestre(db.Model):
def __init__(self, **kwargs):
super(FormSemestre, self).__init__(**kwargs)
if self.modalite is None:
- self.modalite = NotesFormModalite.DEFAULT_MODALITE
+ self.modalite = FormationModalite.DEFAULT_MODALITE
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
@@ -101,7 +102,7 @@ notes_formsemestre_responsables = db.Table(
)
-class NotesFormsemestreEtape(db.Model):
+class FormsemestreEtape(db.Model):
"""Étape Apogée associées au semestre"""
__tablename__ = "notes_formsemestre_etapes"
@@ -113,7 +114,7 @@ class NotesFormsemestreEtape(db.Model):
etape_apo = db.Column(db.String(APO_CODE_STR_LEN))
-class NotesFormModalite(db.Model):
+class FormationModalite(db.Model):
"""Modalités de formation, utilisées pour la présentation
(grouper les semestres, générer des codes, etc.)
"""
@@ -140,7 +141,7 @@ class NotesFormModalite(db.Model):
numero = 0
try:
for (code, titre) in (
- (NotesFormModalite.DEFAULT_MODALITE, "Formation Initiale"),
+ (FormationModalite.DEFAULT_MODALITE, "Formation Initiale"),
("FAP", "Apprentissage"),
("FC", "Formation Continue"),
("DEC", "Formation Décalées"),
@@ -151,9 +152,9 @@ class NotesFormModalite(db.Model):
("EXT", "Extérieur"),
("OTHER", "Autres formations"),
):
- modalite = NotesFormModalite.query.filter_by(modalite=code).first()
+ modalite = FormationModalite.query.filter_by(modalite=code).first()
if modalite is None:
- modalite = NotesFormModalite(
+ modalite = FormationModalite(
modalite=code, titre=titre, numero=numero
)
db.session.add(modalite)
@@ -164,7 +165,7 @@ class NotesFormModalite(db.Model):
raise
-class NotesFormsemestreUECoef(db.Model):
+class FormsemestreUECoef(db.Model):
"""Coef des UE capitalisees arrivant dans ce semestre"""
__tablename__ = "notes_formsemestre_uecoef"
@@ -183,7 +184,7 @@ class NotesFormsemestreUECoef(db.Model):
coefficient = db.Column(db.Float, nullable=False)
-class NotesFormsemestreUEComputationExpr(db.Model):
+class FormsemestreUEComputationExpr(db.Model):
"""Formules utilisateurs pour calcul moyenne UE"""
__tablename__ = "notes_formsemestre_ue_computation_expr"
@@ -203,7 +204,7 @@ class NotesFormsemestreUEComputationExpr(db.Model):
computation_expr = db.Column(db.Text())
-class NotesFormsemestreCustomMenu(db.Model):
+class FormsemestreCustomMenu(db.Model):
"""Menu custom associe au semestre"""
__tablename__ = "notes_formsemestre_custommenu"
@@ -219,7 +220,7 @@ class NotesFormsemestreCustomMenu(db.Model):
idx = db.Column(db.Integer, default=0, server_default="0") # rang dans le menu
-class NotesFormsemestreInscription(db.Model):
+class FormsemestreInscription(db.Model):
"""Inscription à un semestre de formation"""
__tablename__ = "notes_formsemestre_inscription"
@@ -239,7 +240,7 @@ class NotesFormsemestreInscription(db.Model):
etape = db.Column(db.String(APO_CODE_STR_LEN))
-class NotesModuleImpl(db.Model):
+class ModuleImpl(db.Model):
"""Mise en oeuvre d'un module pour une annee/semestre"""
__tablename__ = "notes_moduleimpl"
@@ -275,7 +276,7 @@ notes_modules_enseignants = db.Table(
# XXX il manque probablement une relation pour gérer cela
-class NotesModuleImplInscription(db.Model):
+class ModuleImplInscription(db.Model):
"""Inscription à un module (etudiants,moduleimpl)"""
__tablename__ = "notes_moduleimpl_inscription"
@@ -291,7 +292,7 @@ class NotesModuleImplInscription(db.Model):
etudid = db.Column(db.Integer, db.ForeignKey("identite.id"), index=True)
-class NotesEvaluation(db.Model):
+class Evaluation(db.Model):
"""Evaluation (contrôle, examen, ...)"""
__tablename__ = "notes_evaluation"
@@ -306,7 +307,7 @@ class NotesEvaluation(db.Model):
heure_fin = db.Column(db.Time)
description = db.Column(db.Text)
note_max = db.Column(db.Float)
- coefficient = db.Column(db.Float) # non BUT
+ coefficient = db.Column(db.Float)
visibulletin = db.Column(
db.Boolean, nullable=False, default=True, server_default="true"
)
@@ -320,7 +321,34 @@ class NotesEvaluation(db.Model):
# ordre de presentation (par défaut, le plus petit numero
# est la plus ancienne eval):
numero = db.Column(db.Integer)
- ues = db.relationship("NotesUE", secondary="evaluation_ue_poids", viewonly=True)
+ ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
+
+ def to_dict(self):
+ e = dict(self.__dict__)
+ e.pop("_sa_instance_state", None)
+ # ScoDoc7 output_formators
+ e["jour"] = ndb.DateISOtoDMY(e["jour"])
+ e["numero"] = ndb.int_null_is_zero(e["numero"])
+ return sco_evaluation_db.evaluation_enrich_dict(e)
+
+ # def from_dict(self, data):
+ # """Set evaluation attributes from given dict values."""
+ # sco_evaluation_db._check_evaluation_args(data)
+ # for field in [
+ # "moduleimpl_id",
+ # "jour",
+ # "heure_debut",
+ # "heure_fin",
+ # "description",
+ # "note_max",
+ # "coefficient",
+ # "visibulletin",
+ # "publish_incomplete",
+ # "evaluation_type",
+ # "numero",
+ # ]:
+ # if field in data:
+ # setattr(self, field, data[field] or None)
def set_ue_poids(self, ue, poids: float):
"""Set poids évaluation vers cette UE"""
@@ -332,7 +360,7 @@ class NotesEvaluation(db.Model):
"""
L = []
for ue_id, poids in ue_poids_dict.items():
- ue = NotesUE.query.get(ue_id)
+ ue = UniteEns.query.get(ue_id)
L.append(EvaluationUEPoids(evaluation=self, ue=ue, poids=poids))
self.ue_poids = L
@@ -361,11 +389,12 @@ class EvaluationUEPoids(db.Model):
nullable=False,
)
evaluation = db.relationship(
- NotesEvaluation,
+ Evaluation,
backref=db.backref("ue_poids", cascade="all, delete-orphan"),
)
ue = db.relationship(
- NotesUE, backref=db.backref("evaluation_ue_poids", cascade="all, delete-orphan")
+ UniteEns,
+ backref=db.backref("evaluation_ue_poids", cascade="all, delete-orphan"),
)
def __repr__(self):
diff --git a/app/models/notes.py b/app/models/notes.py
index 0d0be63766..98fe5f5af2 100644
--- a/app/models/notes.py
+++ b/app/models/notes.py
@@ -100,9 +100,10 @@ class ScolarAutorisationInscription(db.Model):
)
-class NotesAppreciations(db.Model):
+class BulAppreciations(db.Model):
"""Appréciations sur bulletins"""
+ __tablename__ = "notes_appreciations"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
etudid = db.Column(
diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py
index 9f9314e2f6..fbfae8bc20 100644
--- a/app/scodoc/notes_table.py
+++ b/app/scodoc/notes_table.py
@@ -34,6 +34,7 @@ from flask import g, url_for
from app.models import ScoDocSiteConfig
import app.scodoc.sco_utils as scu
+from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.sco_formulas import NoteVector
@@ -607,7 +608,7 @@ class NotesTable(object):
# si 'NI', etudiant non inscrit a ce module
if val != "NI":
est_inscrit = True
- if modimpl["module"]["module_type"] == scu.MODULE_STANDARD:
+ if modimpl["module"]["module_type"] == ModuleType.STANDARD:
coef = modimpl["module"]["coefficient"]
if modimpl["ue"]["type"] != UE_SPORT:
notes.append(val, name=modimpl["module"]["code"])
@@ -644,11 +645,17 @@ class NotesTable(object):
except:
# log('comp_etud_moy_ue: exception: val=%s coef=%s' % (val,coef))
pass
- elif modimpl["module"]["module_type"] == scu.MODULE_MALUS:
+ elif modimpl["module"]["module_type"] == ModuleType.MALUS:
try:
ue_malus += val
except:
pass # si non inscrit ou manquant, ignore
+ elif modimpl["module"]["module_type"] in (
+ ModuleType.RESSOURCE,
+ ModuleType.SAE,
+ ):
+ # XXX temporaire pour ne pas bloquer durant le dev
+ pass
else:
raise ValueError(
"invalid module type (%s)" % modimpl["module"]["module_type"]
@@ -672,7 +679,7 @@ class NotesTable(object):
# Recalcule la moyenne en utilisant une formule utilisateur
expr_diag = {}
- formula = sco_compute_moy.get_ue_expression(self.formsemestre_id, ue_id, cnx)
+ formula = sco_compute_moy.get_ue_expression(self.formsemestre_id, ue_id)
if formula:
moy = sco_compute_moy.compute_user_formula(
self.sem,
diff --git a/app/scodoc/notesdb.py b/app/scodoc/notesdb.py
index 9be12f3954..5418c3ce28 100644
--- a/app/scodoc/notesdb.py
+++ b/app/scodoc/notesdb.py
@@ -53,7 +53,7 @@ def close_db_connection():
del g.db_conn
-def GetDBConnexion(autocommit=True): # on n'utilise plus autocommit
+def GetDBConnexion():
return g.db_conn
diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index 98f3a917e4..91c912c437 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -45,6 +45,7 @@ from flask_login import current_user
from flask_mail import Message
import app.scodoc.sco_utils as scu
+from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.sco_permissions import Permission
@@ -59,7 +60,7 @@ from app.scodoc import sco_bulletins_xml
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
from app.scodoc import sco_etud
-from app.scodoc import sco_evaluations
+from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
@@ -428,7 +429,7 @@ def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
mod_moy = nt.get_etud_mod_moy(
modimpl["moduleimpl_id"], etudid
) # peut etre 'NI'
- is_malus = mod["module"]["module_type"] == scu.MODULE_MALUS
+ is_malus = mod["module"]["module_type"] == ModuleType.MALUS
if bul_show_abs_modules:
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
mod_abs = [nbabs, nbabsjust]
@@ -558,7 +559,7 @@ def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
mod["evaluations_incompletes"] = []
if sco_preferences.get_preference("bul_show_all_evals", formsemestre_id):
complete_eval_ids = set([e["evaluation_id"] for e in evals])
- all_evals = sco_evaluations.do_evaluation_list(
+ all_evals = sco_evaluation_db.do_evaluation_list(
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
)
all_evals.reverse() # plus ancienne d'abord
diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py
index 4bcf839e18..7ad1653b93 100644
--- a/app/scodoc/sco_bulletins_json.py
+++ b/app/scodoc/sco_bulletins_json.py
@@ -36,7 +36,7 @@ import app.scodoc.notesdb as ndb
from app.scodoc import sco_abs
from app.scodoc import sco_cache
from app.scodoc import sco_edit_ue
-from app.scodoc import sco_evaluations
+from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_photos
@@ -277,7 +277,7 @@ def formsemestre_bulletinetud_published_dict(
if sco_preferences.get_preference(
"bul_show_all_evals", formsemestre_id
):
- all_evals = sco_evaluations.do_evaluation_list(
+ all_evals = sco_evaluation_db.do_evaluation_list(
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
)
all_evals.reverse() # plus ancienne d'abord
diff --git a/app/scodoc/sco_bulletins_xml.py b/app/scodoc/sco_bulletins_xml.py
index efdbe8c08a..a0aff43054 100644
--- a/app/scodoc/sco_bulletins_xml.py
+++ b/app/scodoc/sco_bulletins_xml.py
@@ -51,7 +51,7 @@ from app.scodoc import sco_abs
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
from app.scodoc import sco_edit_ue
-from app.scodoc import sco_evaluations
+from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_photos
@@ -289,7 +289,7 @@ def make_xml_formsemestre_bulletinetud(
if sco_preferences.get_preference(
"bul_show_all_evals", formsemestre_id
):
- all_evals = sco_evaluations.do_evaluation_list(
+ all_evals = sco_evaluation_db.do_evaluation_list(
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
)
all_evals.reverse() # plus ancienne d'abord
diff --git a/app/scodoc/sco_compute_moy.py b/app/scodoc/sco_compute_moy.py
index 3e50590125..898fd9adf8 100644
--- a/app/scodoc/sco_compute_moy.py
+++ b/app/scodoc/sco_compute_moy.py
@@ -34,6 +34,7 @@ from flask import url_for, g
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.sco_utils import (
+ ModuleType,
NOTES_ATTENTE,
NOTES_NEUTRALISE,
EVALUATION_NORMALE,
@@ -44,7 +45,7 @@ from app.scodoc.sco_exceptions import ScoValueError
from app import log
from app.scodoc import sco_abs
from app.scodoc import sco_edit_module
-from app.scodoc import sco_evaluations
+from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_formulas
@@ -103,8 +104,9 @@ formsemestre_ue_computation_expr_list = _formsemestre_ue_computation_exprEditor.
formsemestre_ue_computation_expr_edit = _formsemestre_ue_computation_exprEditor.edit
-def get_ue_expression(formsemestre_id, ue_id, cnx, html_quote=False):
+def get_ue_expression(formsemestre_id, ue_id, html_quote=False):
"""Returns UE expression (formula), or None if no expression has been defined"""
+ cnx = ndb.GetDBConnexion()
el = formsemestre_ue_computation_expr_list(
cnx, {"formsemestre_id": formsemestre_id, "ue_id": ue_id}
)
@@ -203,7 +205,7 @@ def compute_moduleimpl_moyennes(nt, modimpl):
"""
diag_info = {} # message d'erreur formule
moduleimpl_id = modimpl["moduleimpl_id"]
- is_malus = modimpl["module"]["module_type"] == scu.MODULE_MALUS
+ is_malus = modimpl["module"]["module_type"] == ModuleType.MALUS
sem = sco_formsemestre.get_formsemestre(modimpl["formsemestre_id"])
etudids = sco_moduleimpl.moduleimpl_listeetuds(
moduleimpl_id
@@ -230,7 +232,7 @@ def compute_moduleimpl_moyennes(nt, modimpl):
eval_rattr = None
for e in evals:
e["nb_inscrits"] = e["etat"]["nb_inscrits"]
- NotesDB = sco_evaluations.do_evaluation_get_all_notes(
+ NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(
e["evaluation_id"]
) # toutes, y compris demissions
# restreint aux étudiants encore inscrits à ce module
diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py
index ae19b8b777..9fcb75a48f 100644
--- a/app/scodoc/sco_edit_module.py
+++ b/app/scodoc/sco_edit_module.py
@@ -29,11 +29,13 @@
(portage from DTML)
"""
import flask
-from flask import url_for, g, request
+from flask import url_for, render_template
+from flask import g, request
from flask_login import current_user
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
+from app.scodoc.sco_utils import ModuleType
from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_permissions import Permission
@@ -44,22 +46,6 @@ from app.scodoc import sco_edit_matiere
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_news
-_MODULE_HELP = """
-Les modules sont décrits dans le programme pédagogique. Un module est pour ce
-logiciel l'unité pédagogique élémentaire. On va lui associer une note
-à travers des évaluations.
-Cette note (moyenne de module) sera utilisée pour calculer la moyenne
-générale (et la moyenne de l'UE à laquelle appartient le module). Pour
-cela, on utilisera le coefficient associé au module.
-
-
-Un module possède un enseignant responsable
-(typiquement celui qui dispense le cours magistral). On peut associer
-au module une liste d'enseignants (typiquement les chargés de TD).
-Tous ces enseignants, plus le responsable du semestre, pourront
-saisir et modifier les notes de ce module.
-
"""
-
_moduleEditor = ndb.EditableTable(
"notes_modules",
"module_id",
@@ -120,27 +106,30 @@ def do_module_create(args) -> int:
def module_create(matiere_id=None):
- """Creation d'un module"""
+ """Création d'un module"""
from app.scodoc import sco_formations
from app.scodoc import sco_edit_ue
if matiere_id is None:
raise ScoValueError("invalid matiere !")
- M = sco_edit_matiere.matiere_list(args={"matiere_id": matiere_id})[0]
- UE = sco_edit_ue.ue_list(args={"ue_id": M["ue_id"]})[0]
- Fo = sco_formations.formation_list(args={"formation_id": UE["formation_id"]})[0]
- parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
+ matiere = sco_edit_matiere.matiere_list(args={"matiere_id": matiere_id})[0]
+ UE = sco_edit_ue.ue_list(args={"ue_id": matiere["ue_id"]})[0]
+ formation = sco_formations.formation_list(
+ args={"formation_id": UE["formation_id"]}
+ )[0]
+ parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"])
+ is_apc = parcours.APC_SAE
semestres_indices = list(range(1, parcours.NB_SEM + 1))
H = [
html_sco_header.sco_header(page_title="Création d'un module"),
- """Création d'un module dans la matière %(titre)s""" % M,
+ """Création d'un module dans la matière %(titre)s""" % matiere,
""" (UE %(acronyme)s)
""" % UE,
- _MODULE_HELP,
+ render_template("scodoc/help/modules.html", is_apc=is_apc),
]
- # cherche le numero adequat (pour placer le module en fin de liste)
- Mods = module_list(args={"matiere_id": matiere_id})
- if Mods:
- default_num = max([m["numero"] for m in Mods]) + 10
+ # cherche le numero adéquat (pour placer le module en fin de liste)
+ modules = module_list(args={"matiere_id": matiere_id})
+ if modules:
+ default_num = max([m["numero"] for m in modules]) + 10
else:
default_num = 10
tf = TrivialFormulator(
@@ -153,7 +142,7 @@ def module_create(matiere_id=None):
"size": 10,
"explanation": "code du module (doit être unique dans la formation)",
"allow_null": False,
- "validator": lambda val, field, formation_id=Fo[
+ "validator": lambda val, field, formation_id=formation[
"formation_id"
]: check_module_code_unicity(val, field, formation_id),
},
@@ -166,8 +155,8 @@ def module_create(matiere_id=None):
"input_type": "menu",
"title": "Type",
"explanation": "",
- "labels": ("Standard", "Malus"),
- "allowed_values": (str(scu.MODULE_STANDARD), str(scu.MODULE_MALUS)),
+ "labels": [x.name.capitalize() for x in scu.ModuleType],
+ "allowed_values": [str(int(x)) for x in scu.ModuleType],
},
),
(
@@ -201,8 +190,8 @@ def module_create(matiere_id=None):
),
# ('ects', { 'size' : 4, 'type' : 'float', 'title' : 'ECTS', 'explanation' : 'nombre de crédits ECTS (inutilisés: les crédits sont associés aux UE)' }),
("formation_id", {"default": UE["formation_id"], "input_type": "hidden"}),
- ("ue_id", {"default": M["ue_id"], "input_type": "hidden"}),
- ("matiere_id", {"default": M["matiere_id"], "input_type": "hidden"}),
+ ("ue_id", {"default": matiere["ue_id"], "input_type": "hidden"}),
+ ("matiere_id", {"default": matiere["matiere_id"], "input_type": "hidden"}),
(
"semestre_id",
{
@@ -350,35 +339,38 @@ def module_edit(module_id=None):
if not module_id:
raise ScoValueError("invalid module !")
- Mod = module_list(args={"module_id": module_id})
- if not Mod:
+ modules = module_list(args={"module_id": module_id})
+ if not modules:
raise ScoValueError("invalid module !")
- Mod = Mod[0]
+ module = modules[0]
unlocked = not module_is_locked(module_id)
- Fo = sco_formations.formation_list(args={"formation_id": Mod["formation_id"]})[0]
- parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
- M = ndb.SimpleDictFetch(
+ formation = sco_formations.formation_list(
+ args={"formation_id": module["formation_id"]}
+ )[0]
+ parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"])
+ is_apc = parcours.APC_SAE
+ ues_matieres = ndb.SimpleDictFetch(
"""SELECT ue.acronyme, mat.*, mat.id AS matiere_id
FROM notes_matieres mat, notes_ue ue
WHERE mat.ue_id = ue.id
AND ue.formation_id = %(formation_id)s
ORDER BY ue.numero, mat.numero
""",
- {"formation_id": Mod["formation_id"]},
+ {"formation_id": module["formation_id"]},
)
- Mnames = ["%s / %s" % (x["acronyme"], x["titre"]) for x in M]
- Mids = ["%s!%s" % (x["ue_id"], x["matiere_id"]) for x in M]
- Mod["ue_matiere_id"] = "%s!%s" % (Mod["ue_id"], Mod["matiere_id"])
+ mat_names = ["%s / %s" % (x["acronyme"], x["titre"]) for x in ues_matieres]
+ ue_mat_ids = ["%s!%s" % (x["ue_id"], x["matiere_id"]) for x in ues_matieres]
+ module["ue_matiere_id"] = "%s!%s" % (module["ue_id"], module["matiere_id"])
semestres_indices = list(range(1, parcours.NB_SEM + 1))
dest_url = url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
- formation_id=str(Mod["formation_id"]),
+ formation_id=str(module["formation_id"]),
)
H = [
html_sco_header.sco_header(
- page_title="Modification du module %(titre)s" % Mod,
+ page_title="Modification du module %(titre)s" % module,
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"],
javascripts=[
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
@@ -386,9 +378,9 @@ def module_edit(module_id=None):
"js/module_tag_editor.js",
],
),
- """Modification du module %(titre)s""" % Mod,
- """ (formation %(acronyme)s, version %(version)s)
""" % Fo,
- _MODULE_HELP,
+ """Modification du module %(titre)s""" % module,
+ """ (formation %(acronyme)s, version %(version)s)
""" % formation,
+ render_template("scodoc/help/modules.html", is_apc=is_apc),
]
if not unlocked:
H.append(
@@ -405,7 +397,7 @@ def module_edit(module_id=None):
"size": 10,
"explanation": "code du module (doit être unique dans la formation)",
"allow_null": False,
- "validator": lambda val, field, formation_id=Mod[
+ "validator": lambda val, field, formation_id=module[
"formation_id"
]: check_module_code_unicity(
val, field, formation_id, module_id=module_id
@@ -465,8 +457,8 @@ def module_edit(module_id=None):
"input_type": "menu",
"title": "Matière",
"explanation": "un module appartient à une seule matière.",
- "labels": Mnames,
- "allowed_values": Mids,
+ "labels": mat_names,
+ "allowed_values": ue_mat_ids,
"enabled": unlocked,
},
),
@@ -503,7 +495,7 @@ def module_edit(module_id=None):
html_foot_markup="""
""".format(
module_id, ",".join(sco_tag_module.module_tag_list(module_id))
),
- initvalues=Mod,
+ initvalues=module,
submitlabel="Modifier ce module",
)
@@ -602,7 +594,7 @@ def formation_add_malus_modules(formation_id, titre=None, redirect=True):
[
mod
for mod in module_list(args={"ue_id": ue["ue_id"]})
- if mod["module_type"] == scu.MODULE_MALUS
+ if mod["module_type"] == ModuleType.MALUS
]
)
if nb_mod_malus == 0:
@@ -654,7 +646,7 @@ def ue_add_malus_module(ue_id, titre=None, code=None):
"matiere_id": matiere_id,
"formation_id": ue["formation_id"],
"semestre_id": semestre_id,
- "module_type": scu.MODULE_MALUS,
+ "module_type": ModuleType.MALUS,
},
)
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index 0911eec87a..232e3cb87b 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -32,9 +32,10 @@ import flask
from flask import g, url_for, request
from flask_login import current_user
-from app.models.formations import NotesUE
+from app.models.formations import UniteEns
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
+from app.scodoc.sco_utils import ModuleType
from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
from app.scodoc.gen_tables import GenTable
@@ -955,7 +956,7 @@ def _ue_table_modules(
mod["module_id"]
)
klass = "notes_module_list"
- if mod["module_type"] == scu.MODULE_MALUS:
+ if mod["module_type"] == ModuleType.MALUS:
klass += " module_malus"
H.append('' % klass)
@@ -1066,20 +1067,20 @@ def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
formation_code = F["formation_code"]
# UE du même code, code formation et departement:
q_ues = (
- NotesUE.query.filter_by(ue_code=ue_code)
- .join(NotesUE.formation, aliased=True)
+ UniteEns.query.filter_by(ue_code=ue_code)
+ .join(UniteEns.formation, aliased=True)
.filter_by(dept_id=g.scodoc_dept_id, formation_code=formation_code)
)
else:
# Toutes les UE du departement avec ce code:
q_ues = (
- NotesUE.query.filter_by(ue_code=ue_code)
- .join(NotesUE.formation, aliased=True)
+ UniteEns.query.filter_by(ue_code=ue_code)
+ .join(UniteEns.formation, aliased=True)
.filter_by(dept_id=g.scodoc_dept_id)
)
if hide_ue_id: # enlève l'ue de depart
- q_ues = q_ues.filter(NotesUE.id != hide_ue_id)
+ q_ues = q_ues.filter(UniteEns.id != hide_ue_id)
ues = q_ues.all()
if not ues:
diff --git a/app/scodoc/sco_evaluation_db.py b/app/scodoc/sco_evaluation_db.py
new file mode 100644
index 0000000000..55f9e68513
--- /dev/null
+++ b/app/scodoc/sco_evaluation_db.py
@@ -0,0 +1,482 @@
+# -*- mode: python -*-
+# -*- coding: utf-8 -*-
+
+##############################################################################
+#
+# Gestion scolarite IUT
+#
+# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Emmanuel Viennet emmanuel.viennet@gmail.com
+#
+##############################################################################
+
+"""Gestion evaluations (ScoDoc7, sans SQlAlchemy)
+"""
+
+import datetime
+import pprint
+
+import flask
+from flask import url_for, g
+from flask_login import current_user
+
+from app import log
+import app.scodoc.sco_utils as scu
+import app.scodoc.notesdb as ndb
+from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
+
+from app.scodoc import sco_cache
+from app.scodoc import sco_edit_module
+from app.scodoc import sco_formsemestre
+from app.scodoc import sco_moduleimpl
+from app.scodoc import sco_news
+from app.scodoc import sco_permissions_check
+
+
+_evaluationEditor = ndb.EditableTable(
+ "notes_evaluation",
+ "evaluation_id",
+ (
+ "evaluation_id",
+ "moduleimpl_id",
+ "jour",
+ "heure_debut",
+ "heure_fin",
+ "description",
+ "note_max",
+ "coefficient",
+ "visibulletin",
+ "publish_incomplete",
+ "evaluation_type",
+ "numero",
+ ),
+ sortkey="numero desc, jour desc, heure_debut desc", # plus recente d'abord
+ output_formators={
+ "jour": ndb.DateISOtoDMY,
+ "numero": ndb.int_null_is_zero,
+ },
+ input_formators={
+ "jour": ndb.DateDMYtoISO,
+ "heure_debut": ndb.TimetoISO8601, # converti par evaluation_enrich_dict
+ "heure_fin": ndb.TimetoISO8601, # converti par evaluation_enrich_dict
+ "visibulletin": bool,
+ "publish_incomplete": bool,
+ "evaluation_type": int,
+ },
+)
+
+
+def evaluation_enrich_dict(e):
+ """add or convert some fileds in an evaluation dict"""
+ # For ScoDoc7 compat
+ heure_debut_dt = e["heure_debut"] or datetime.time(
+ 8, 00
+ ) # au cas ou pas d'heure (note externe?)
+ heure_fin_dt = e["heure_fin"] or datetime.time(8, 00)
+ e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"])
+ e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"])
+ e["jouriso"] = ndb.DateDMYtoISO(e["jour"])
+ heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
+ d = ndb.TimeDuration(heure_debut, heure_fin)
+ if d is not None:
+ m = d % 60
+ e["duree"] = "%dh" % (d / 60)
+ if m != 0:
+ e["duree"] += "%02d" % m
+ else:
+ e["duree"] = ""
+ if heure_debut and (not heure_fin or heure_fin == heure_debut):
+ e["descrheure"] = " à " + heure_debut
+ elif heure_debut and heure_fin:
+ e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin)
+ else:
+ e["descrheure"] = ""
+ # matin, apresmidi: utile pour se referer aux absences:
+ if heure_debut_dt < datetime.time(12, 00):
+ e["matin"] = 1
+ else:
+ e["matin"] = 0
+ if heure_fin_dt > datetime.time(12, 00):
+ e["apresmidi"] = 1
+ else:
+ e["apresmidi"] = 0
+ return e
+
+
+def do_evaluation_list(args, sortkey=None):
+ """List evaluations, sorted by numero (or most recent date first).
+
+ Ajoute les champs:
+ 'duree' : '2h30'
+ 'matin' : 1 (commence avant 12:00) ou 0
+ 'apresmidi' : 1 (termine après 12:00) ou 0
+ 'descrheure' : ' de 15h00 à 16h30'
+ """
+ # Attention: transformation fonction ScoDc7 en SQLAlchemy
+ cnx = ndb.GetDBConnexion()
+ evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
+ # calcule duree (chaine de car.) de chaque evaluation et ajoute jouriso, matin, apresmidi
+ for e in evals:
+ evaluation_enrich_dict(e)
+
+ return evals
+
+
+def do_evaluation_list_in_formsemestre(formsemestre_id):
+ "list evaluations in this formsemestre"
+ mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
+ evals = []
+ for modimpl in mods:
+ evals += do_evaluation_list(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
+ return evals
+
+
+def _check_evaluation_args(args):
+ "Check coefficient, dates and duration, raises exception if invalid"
+ moduleimpl_id = args["moduleimpl_id"]
+ # check bareme
+ note_max = args.get("note_max", None)
+ if note_max is None:
+ raise ScoValueError("missing note_max")
+ try:
+ note_max = float(note_max)
+ except ValueError:
+ raise ScoValueError("Invalid note_max value")
+ if note_max < 0:
+ raise ScoValueError("Invalid note_max value (must be positive or null)")
+ # check coefficient
+ coef = args.get("coefficient", None)
+ if coef is None:
+ raise ScoValueError("missing coefficient")
+ try:
+ coef = float(coef)
+ except ValueError:
+ raise ScoValueError("Invalid coefficient value")
+ if coef < 0:
+ raise ScoValueError("Invalid coefficient value (must be positive or null)")
+ # check date
+ jour = args.get("jour", None)
+ args["jour"] = jour
+ if jour:
+ M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
+ sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
+ d, m, y = [int(x) for x in sem["date_debut"].split("/")]
+ date_debut = datetime.date(y, m, d)
+ d, m, y = [int(x) for x in sem["date_fin"].split("/")]
+ date_fin = datetime.date(y, m, d)
+ # passe par ndb.DateDMYtoISO pour avoir date pivot
+ y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
+ jour = datetime.date(y, m, d)
+ if (jour > date_fin) or (jour < date_debut):
+ raise ScoValueError(
+ "La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
+ % (d, m, y)
+ )
+ heure_debut = args.get("heure_debut", None)
+ args["heure_debut"] = heure_debut
+ heure_fin = args.get("heure_fin", None)
+ args["heure_fin"] = heure_fin
+ if jour and ((not heure_debut) or (not heure_fin)):
+ raise ScoValueError("Les heures doivent être précisées")
+ d = ndb.TimeDuration(heure_debut, heure_fin)
+ if d and ((d < 0) or (d > 60 * 12)):
+ raise ScoValueError("Heures de l'évaluation incohérentes !")
+
+
+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(
+ "Modification évaluation impossible pour %s" % current_user.get_nomplogin()
+ )
+ args = locals()
+ log("do_evaluation_create: args=" + str(args))
+ _check_evaluation_args(args)
+ # Check numeros
+ module_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 = module_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
+ M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
+ mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
+ mod["moduleimpl_id"] = M["moduleimpl_id"]
+ mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
+ sco_news.add(
+ typ=sco_news.NEWS_NOTE,
+ object=moduleimpl_id,
+ text='Création d\'une évaluation dans %(titre)s' % mod,
+ url=mod["url"],
+ )
+
+ return r
+
+
+def do_evaluation_edit(args):
+ "edit an evaluation"
+ evaluation_id = args["evaluation_id"]
+ the_evals = do_evaluation_list({"evaluation_id": evaluation_id})
+ if not the_evals:
+ raise ValueError("evaluation inexistante !")
+ moduleimpl_id = the_evals[0]["moduleimpl_id"]
+ if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
+ raise AccessDenied(
+ "Modification évaluation impossible pour %s" % current_user.get_nomplogin()
+ )
+ args["moduleimpl_id"] = moduleimpl_id
+ _check_evaluation_args(args)
+
+ cnx = ndb.GetDBConnexion()
+ _evaluationEditor.edit(cnx, args)
+ # inval cache pour ce semestre
+ M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
+ sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
+
+
+def do_evaluation_delete(evaluation_id):
+ "delete evaluation"
+ the_evals = do_evaluation_list({"evaluation_id": evaluation_id})
+ if not the_evals:
+ raise ValueError("evaluation inexistante !")
+ moduleimpl_id = the_evals[0]["moduleimpl_id"]
+ if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
+ raise AccessDenied(
+ "Modification évaluation impossible pour %s" % current_user.get_nomplogin()
+ )
+ NotesDB = do_evaluation_get_all_notes(evaluation_id) # { etudid : value }
+ notes = [x["value"] for x in NotesDB.values()]
+ if notes:
+ raise ScoValueError(
+ "Impossible de supprimer cette évaluation: il reste des notes"
+ )
+
+ cnx = ndb.GetDBConnexion()
+
+ _evaluationEditor.delete(cnx, evaluation_id)
+ # inval cache pour ce semestre
+ M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
+ sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
+ # news
+
+ mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
+ mod["moduleimpl_id"] = M["moduleimpl_id"]
+ mod["url"] = (
+ scu.NotesURL() + "/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
+ )
+ sco_news.add(
+ typ=sco_news.NEWS_NOTE,
+ object=moduleimpl_id,
+ text='Suppression d\'une évaluation dans %(titre)s' % mod,
+ url=mod["url"],
+ )
+
+
+# ancien _notes_getall
+def do_evaluation_get_all_notes(
+ evaluation_id, table="notes_notes", filter_suppressed=True, by_uid=None
+):
+ """Toutes les notes pour une evaluation: { etudid : { 'value' : value, 'date' : date ... }}
+ Attention: inclut aussi les notes des étudiants qui ne sont plus inscrits au module.
+ """
+ do_cache = (
+ filter_suppressed and table == "notes_notes" and (by_uid is None)
+ ) # pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant
+ if do_cache:
+ r = sco_cache.EvaluationCache.get(evaluation_id)
+ if r != None:
+ return r
+ cnx = ndb.GetDBConnexion()
+ cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
+ cond = " where evaluation_id=%(evaluation_id)s"
+ if by_uid:
+ cond += " and uid=%(by_uid)s"
+
+ cursor.execute(
+ "select * from " + table + cond,
+ {"evaluation_id": evaluation_id, "by_uid": by_uid},
+ )
+ res = cursor.dictfetchall()
+ d = {}
+ if filter_suppressed:
+ for x in res:
+ if x["value"] != scu.NOTES_SUPPRESS:
+ d[x["etudid"]] = x
+ else:
+ for x in res:
+ d[x["etudid"]] = x
+ if do_cache:
+ status = sco_cache.EvaluationCache.set(evaluation_id, d)
+ if not status:
+ log(f"Warning: EvaluationCache.set: {evaluation_id}\t{status}")
+ return d
+
+
+def module_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('module_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 module_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")
+ module_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 module_evaluation_move(evaluation_id, after=0, redirect=1):
+ """Move before/after previous one (decrement/increment numero)
+ (published)
+ """
+ e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
+ redirect = int(redirect)
+ # access: can change eval ?
+ if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=e["moduleimpl_id"]):
+ raise AccessDenied(
+ "Modification évaluation impossible pour %s" % current_user.get_nomplogin()
+ )
+
+ module_evaluation_renumber(e["moduleimpl_id"], only_if_unumbered=True)
+ e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
+
+ after = int(after) # 0: deplace avant, 1 deplace apres
+ if after not in (0, 1):
+ raise ValueError('invalid value for "after"')
+ mod_evals = do_evaluation_list({"moduleimpl_id": e["moduleimpl_id"]})
+ if len(mod_evals) > 1:
+ idx = [p["evaluation_id"] for p in mod_evals].index(evaluation_id)
+ neigh = None # object to swap with
+ if after == 0 and idx > 0:
+ neigh = mod_evals[idx - 1]
+ elif after == 1 and idx < len(mod_evals) - 1:
+ neigh = mod_evals[idx + 1]
+ if neigh: #
+ if neigh["numero"] == e["numero"]:
+ log("Warning: module_evaluation_move: forcing renumber")
+ module_evaluation_renumber(e["moduleimpl_id"], only_if_unumbered=False)
+ else:
+ # swap numero with neighbor
+ e["numero"], neigh["numero"] = neigh["numero"], e["numero"]
+ do_evaluation_edit(e)
+ do_evaluation_edit(neigh)
+ # redirect to moduleimpl page:
+ if redirect:
+ return flask.redirect(
+ url_for(
+ "notes.moduleimpl_status",
+ scodoc_dept=g.scodoc_dept,
+ moduleimpl_id=e["moduleimpl_id"],
+ )
+ )
diff --git a/app/scodoc/sco_evaluation_edit.py b/app/scodoc/sco_evaluation_edit.py
new file mode 100644
index 0000000000..a310ecdd5f
--- /dev/null
+++ b/app/scodoc/sco_evaluation_edit.py
@@ -0,0 +1,332 @@
+# -*- mode: python -*-
+# -*- coding: utf-8 -*-
+
+##############################################################################
+#
+# Gestion scolarite IUT
+#
+# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Emmanuel Viennet emmanuel.viennet@gmail.com
+#
+##############################################################################
+
+"""Formulaire ajout/édition d'une évaluation
+"""
+
+import time
+
+import flask
+from flask import url_for, render_template
+from flask import g
+from flask_login import current_user
+from flask import request
+
+from app import db
+from app import log
+from app import models
+import app.scodoc.sco_utils as scu
+from app.scodoc.sco_utils import ModuleType
+from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
+from app.scodoc.TrivialFormulator import TrivialFormulator
+from app.scodoc import html_sco_header
+from app.scodoc import sco_evaluations
+from app.scodoc import sco_evaluation_db
+from app.scodoc import sco_moduleimpl
+from app.scodoc import sco_permissions_check
+
+
+def evaluation_create_form(
+ moduleimpl_id=None,
+ evaluation_id=None,
+ edit=False,
+ page_title="Évaluation",
+):
+ "Formulaire création/édition d'une évaluation (pas de ses notes)"
+ if evaluation_id is not None:
+ evaluation = models.Evaluation.query.get(evaluation_id)
+ moduleimpl_id = evaluation.moduleimpl_id
+ #
+ modimpl = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
+ mod = modimpl["module"]
+ formsemestre_id = modimpl["formsemestre_id"]
+ sem_ues = db.session.query(models.UniteEns).filter(
+ models.ModuleImpl.formsemestre_id == formsemestre_id,
+ models.Module.id == models.ModuleImpl.module_id,
+ models.UniteEns.id == models.Module.ue_id,
+ )
+ is_malus = mod["module_type"] == ModuleType.MALUS
+ is_apc = mod["module_type"] in (ModuleType.RESSOURCE, ModuleType.SAE)
+
+ min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
+ #
+ if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
+ return (
+ html_sco_header.sco_header()
+ + "Opération non autorisée
"
+ + "Modification évaluation impossible pour %s"
+ % current_user.get_nomplogin()
+ + "
"
+ + 'Revenir
'
+ % (moduleimpl_id,)
+ + html_sco_header.sco_footer()
+ )
+ if not edit:
+ # creation nouvel
+ if moduleimpl_id is None:
+ raise ValueError("missing moduleimpl_id parameter")
+ initvalues = {
+ "note_max": 20,
+ "jour": time.strftime("%d/%m/%Y", time.localtime()),
+ "publish_incomplete": is_malus,
+ }
+ submitlabel = "Créer cette évaluation"
+ action = "Création d'une évaluation"
+ link = ""
+ else:
+ # édition données existantes
+ # setup form init values
+ if evaluation_id is None:
+ raise ValueError("missing evaluation_id parameter")
+ initvalues = evaluation.to_dict()
+ moduleimpl_id = initvalues["moduleimpl_id"]
+ submitlabel = "Modifier les données"
+ action = "Modification d'une évaluation"
+ link = ""
+ # Note maximale actuelle dans cette éval ?
+ etat = sco_evaluations.do_evaluation_etat(evaluation_id)
+ if etat["maxi_num"] is not None:
+ min_note_max = max(scu.NOTES_PRECISION, etat["maxi_num"])
+ else:
+ min_note_max = scu.NOTES_PRECISION
+ #
+ if min_note_max > scu.NOTES_PRECISION:
+ min_note_max_str = scu.fmt_note(min_note_max)
+ else:
+ min_note_max_str = "0"
+ #
+ mod_descr = '%s %s %s' % (
+ moduleimpl_id,
+ mod["code"],
+ mod["titre"],
+ link,
+ )
+ H = [
+ f"""{action} en
+ {scu.MODULE_TYPE_NAMES[mod["module_type"]]} {mod_descr}
+ """
+ ]
+
+ heures = ["%02dh%02d" % (h, m) for h in range(8, 19) for m in (0, 30)]
+ #
+ initvalues["visibulletin"] = initvalues.get("visibulletin", True)
+ if initvalues["visibulletin"]:
+ initvalues["visibulletinlist"] = ["X"]
+ else:
+ initvalues["visibulletinlist"] = []
+ vals = scu.get_request_args()
+ if vals.get("tf_submitted", False) and "visibulletinlist" not in vals:
+ vals["visibulletinlist"] = []
+ #
+ if is_apc: # BUT: poids vers les UE
+ for ue in sem_ues:
+ if edit:
+ existing_poids = models.EvaluationUEPoids.query.filter_by(
+ ue=ue, evaluation=evaluation
+ ).first()
+ else:
+ existing_poids = None
+ if existing_poids:
+ poids = existing_poids.poids
+ else:
+ poids = 1.0 # par defaut au départ
+ initvalues[f"poids_{ue.id}"] = poids
+ #
+ form = [
+ ("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
+ ("formsemestre_id", {"default": formsemestre_id, "input_type": "hidden"}),
+ ("moduleimpl_id", {"default": moduleimpl_id, "input_type": "hidden"}),
+ # ('jour', { 'title' : 'Date (j/m/a)', 'size' : 12, 'explanation' : 'date de l\'examen, devoir ou contrôle' }),
+ (
+ "jour",
+ {
+ "input_type": "date",
+ "title": "Date",
+ "size": 12,
+ "explanation": "date de l'examen, devoir ou contrôle",
+ },
+ ),
+ (
+ "heure_debut",
+ {
+ "title": "Heure de début",
+ "explanation": "heure du début de l'épreuve",
+ "input_type": "menu",
+ "allowed_values": heures,
+ "labels": heures,
+ },
+ ),
+ (
+ "heure_fin",
+ {
+ "title": "Heure de fin",
+ "explanation": "heure de fin de l'épreuve",
+ "input_type": "menu",
+ "allowed_values": heures,
+ "labels": heures,
+ },
+ ),
+ ]
+ if is_malus: # pas de coefficient
+ form.append(("coefficient", {"input_type": "hidden", "default": "1."}))
+ elif not is_apc: # modules standard hors BUT
+ form.append(
+ (
+ "coefficient",
+ {
+ "size": 6,
+ "type": "float",
+ "explanation": "coef. dans le module (choisi librement par l'enseignant)",
+ "allow_null": False,
+ },
+ )
+ )
+ form += [
+ (
+ "note_max",
+ {
+ "size": 4,
+ "type": "float",
+ "title": "Notes de 0 à",
+ "explanation": "barème (note max actuelle: %s)" % min_note_max_str,
+ "allow_null": False,
+ "max_value": scu.NOTES_MAX,
+ "min_value": min_note_max,
+ },
+ ),
+ (
+ "description",
+ {
+ "size": 36,
+ "type": "text",
+ "explanation": 'type d\'évaluation, apparait sur le bulletins longs. Exemples: "contrôle court", "examen de TP", "examen final".',
+ },
+ ),
+ (
+ "visibulletinlist",
+ {
+ "input_type": "checkbox",
+ "allowed_values": ["X"],
+ "labels": [""],
+ "title": "Visible sur bulletins",
+ "explanation": "(pour les bulletins en version intermédiaire)",
+ },
+ ),
+ (
+ "publish_incomplete",
+ {
+ "input_type": "boolcheckbox",
+ "title": "Prise en compte immédiate",
+ "explanation": "notes utilisées même si incomplètes",
+ },
+ ),
+ (
+ "evaluation_type",
+ {
+ "input_type": "menu",
+ "title": "Modalité",
+ "allowed_values": (
+ scu.EVALUATION_NORMALE,
+ scu.EVALUATION_RATTRAPAGE,
+ scu.EVALUATION_SESSION2,
+ ),
+ "type": "int",
+ "labels": (
+ "Normale",
+ "Rattrapage (remplace si meilleure note)",
+ "Deuxième session (remplace toujours)",
+ ),
+ },
+ ),
+ ]
+ if is_apc: # ressources et SAÉs
+ form += [
+ (
+ "coefficient",
+ {
+ "size": 6,
+ "type": "float",
+ "explanation": "importance de l'évaluation (multiplie les poids ci-dessous)",
+ "allow_null": False,
+ },
+ ),
+ ]
+ # Liste des UE utilisées dans des modules de ce semestre:
+ for ue in sem_ues:
+ form.append(
+ (
+ f"poids_{ue.id}",
+ {
+ "title": f"Poids {ue.acronyme}",
+ "size": 2,
+ "type": "float",
+ "explanation": f"{ue.titre}",
+ "allow_null": False,
+ },
+ ),
+ )
+ tf = TrivialFormulator(
+ request.base_url,
+ vals,
+ form,
+ cancelbutton="Annuler",
+ submitlabel=submitlabel,
+ initvalues=initvalues,
+ readonly=False,
+ )
+
+ dest_url = "moduleimpl_status?moduleimpl_id=%s" % modimpl["moduleimpl_id"]
+ if tf[0] == 0:
+ head = html_sco_header.sco_header(page_title=page_title)
+ return (
+ head
+ + "\n".join(H)
+ + "\n"
+ + tf[1]
+ + render_template("scodoc/help/evaluations.html", is_apc=is_apc)
+ + html_sco_header.sco_footer()
+ )
+ elif tf[0] == -1:
+ return flask.redirect(dest_url)
+ else:
+ # form submission
+ if tf[2]["visibulletinlist"]:
+ tf[2]["visibulletin"] = True
+ else:
+ tf[2]["visibulletin"] = False
+ if edit:
+ sco_evaluation_db.do_evaluation_edit(tf[2])
+ else:
+ # creation d'une evaluation
+ evaluation_id = sco_evaluation_db.do_evaluation_create(**tf[2])
+ # Set poids
+ evaluation = models.Evaluation.query.get(evaluation_id)
+ for ue in sem_ues:
+ evaluation.set_ue_poids(ue, tf[2][f"poids_{ue.id}"])
+ db.session.add(evaluation)
+ db.session.commit()
+ return flask.redirect(dest_url)
diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py
index 0fba0b3c0d..e650d69508 100644
--- a/app/scodoc/sco_evaluations.py
+++ b/app/scodoc/sco_evaluations.py
@@ -29,9 +29,7 @@
"""
import datetime
import operator
-import pprint
import time
-import urllib
import flask
from flask import url_for
@@ -41,12 +39,13 @@ from flask import request
from app import log
import app.scodoc.sco_utils as scu
+from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
import sco_version
from app.scodoc.gen_tables import GenTable
-from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc import html_sco_header
+from app.scodoc import sco_evaluation_db
from app.scodoc import sco_abs
from app.scodoc import sco_cache
from app.scodoc import sco_edit_module
@@ -96,281 +95,6 @@ def ListMedian(L):
# --------------------------------------------------------------------
-_evaluationEditor = ndb.EditableTable(
- "notes_evaluation",
- "evaluation_id",
- (
- "evaluation_id",
- "moduleimpl_id",
- "jour",
- "heure_debut",
- "heure_fin",
- "description",
- "note_max",
- "coefficient",
- "visibulletin",
- "publish_incomplete",
- "evaluation_type",
- "numero",
- ),
- sortkey="numero desc, jour desc, heure_debut desc", # plus recente d'abord
- output_formators={
- "jour": ndb.DateISOtoDMY,
- "numero": ndb.int_null_is_zero,
- },
- input_formators={
- "jour": ndb.DateDMYtoISO,
- "heure_debut": ndb.TimetoISO8601, # converti par do_evaluation_list
- "heure_fin": ndb.TimetoISO8601, # converti par do_evaluation_list
- "visibulletin": bool,
- "publish_incomplete": bool,
- "evaluation_type": int,
- },
-)
-
-
-def do_evaluation_list(args, sortkey=None):
- """List evaluations, sorted by numero (or most recent date first).
-
- Ajoute les champs:
- 'duree' : '2h30'
- 'matin' : 1 (commence avant 12:00) ou 0
- 'apresmidi' : 1 (termine après 12:00) ou 0
- 'descrheure' : ' de 15h00 à 16h30'
- """
- cnx = ndb.GetDBConnexion()
- evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
- # calcule duree (chaine de car.) de chaque evaluation et ajoute jouriso, matin, apresmidi
- for e in evals:
- heure_debut_dt = e["heure_debut"] or datetime.time(
- 8, 00
- ) # au cas ou pas d'heure (note externe?)
- heure_fin_dt = e["heure_fin"] or datetime.time(8, 00)
- e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"])
- e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"])
- e["jouriso"] = ndb.DateDMYtoISO(e["jour"])
- heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
- d = ndb.TimeDuration(heure_debut, heure_fin)
- if d is not None:
- m = d % 60
- e["duree"] = "%dh" % (d / 60)
- if m != 0:
- e["duree"] += "%02d" % m
- else:
- e["duree"] = ""
- if heure_debut and (not heure_fin or heure_fin == heure_debut):
- e["descrheure"] = " à " + heure_debut
- elif heure_debut and heure_fin:
- e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin)
- else:
- e["descrheure"] = ""
- # matin, apresmidi: utile pour se referer aux absences:
- if heure_debut_dt < datetime.time(12, 00):
- e["matin"] = 1
- else:
- e["matin"] = 0
- if heure_fin_dt > datetime.time(12, 00):
- e["apresmidi"] = 1
- else:
- e["apresmidi"] = 0
-
- return evals
-
-
-def do_evaluation_list_in_formsemestre(formsemestre_id):
- "list evaluations in this formsemestre"
- mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
- evals = []
- for mod in mods:
- evals += do_evaluation_list(args={"moduleimpl_id": mod["moduleimpl_id"]})
- return evals
-
-
-def _check_evaluation_args(args):
- "Check coefficient, dates and duration, raises exception if invalid"
- moduleimpl_id = args["moduleimpl_id"]
- # check bareme
- note_max = args.get("note_max", None)
- if note_max is None:
- raise ScoValueError("missing note_max")
- try:
- note_max = float(note_max)
- except ValueError:
- raise ScoValueError("Invalid note_max value")
- if note_max < 0:
- raise ScoValueError("Invalid note_max value (must be positive or null)")
- # check coefficient
- coef = args.get("coefficient", None)
- if coef is None:
- raise ScoValueError("missing coefficient")
- try:
- coef = float(coef)
- except ValueError:
- raise ScoValueError("Invalid coefficient value")
- if coef < 0:
- raise ScoValueError("Invalid coefficient value (must be positive or null)")
- # check date
- jour = args.get("jour", None)
- args["jour"] = jour
- if jour:
- M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
- sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
- d, m, y = [int(x) for x in sem["date_debut"].split("/")]
- date_debut = datetime.date(y, m, d)
- d, m, y = [int(x) for x in sem["date_fin"].split("/")]
- date_fin = datetime.date(y, m, d)
- # passe par ndb.DateDMYtoISO pour avoir date pivot
- y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
- jour = datetime.date(y, m, d)
- if (jour > date_fin) or (jour < date_debut):
- raise ScoValueError(
- "La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
- % (d, m, y)
- )
- heure_debut = args.get("heure_debut", None)
- args["heure_debut"] = heure_debut
- heure_fin = args.get("heure_fin", None)
- args["heure_fin"] = heure_fin
- if jour and ((not heure_debut) or (not heure_fin)):
- raise ScoValueError("Les heures doivent être précisées")
- d = ndb.TimeDuration(heure_debut, heure_fin)
- if d and ((d < 0) or (d > 60 * 12)):
- raise ScoValueError("Heures de l'évaluation incohérentes !")
-
-
-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(
- "Modification évaluation impossible pour %s" % current_user.get_nomplogin()
- )
- args = locals()
- log("do_evaluation_create: args=" + str(args))
- _check_evaluation_args(args)
- # Check numeros
- module_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
- ModEvals = 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 ModEvals:
- 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 = module_evaluation_insert_before(ModEvals, next_eval)
- else:
- n = None # a placer en fin
- if n is None: # pas de date ou en fin:
- if ModEvals:
- log(pprint.pformat(ModEvals[-1]))
- n = ModEvals[-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
- M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
- mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
- mod["moduleimpl_id"] = M["moduleimpl_id"]
- mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
- sco_news.add(
- typ=sco_news.NEWS_NOTE,
- object=moduleimpl_id,
- text='Création d\'une évaluation dans %(titre)s' % mod,
- url=mod["url"],
- )
-
- return r
-
-
-def do_evaluation_edit(args):
- "edit an evaluation"
- evaluation_id = args["evaluation_id"]
- the_evals = do_evaluation_list({"evaluation_id": evaluation_id})
- if not the_evals:
- raise ValueError("evaluation inexistante !")
- moduleimpl_id = the_evals[0]["moduleimpl_id"]
- if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
- raise AccessDenied(
- "Modification évaluation impossible pour %s" % current_user.get_nomplogin()
- )
- args["moduleimpl_id"] = moduleimpl_id
- _check_evaluation_args(args)
-
- cnx = ndb.GetDBConnexion()
- _evaluationEditor.edit(cnx, args)
- # inval cache pour ce semestre
- M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
- sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
-
-
-def do_evaluation_delete(evaluation_id):
- "delete evaluation"
- the_evals = do_evaluation_list({"evaluation_id": evaluation_id})
- if not the_evals:
- raise ValueError("evaluation inexistante !")
- moduleimpl_id = the_evals[0]["moduleimpl_id"]
- if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
- raise AccessDenied(
- "Modification évaluation impossible pour %s" % current_user.get_nomplogin()
- )
- NotesDB = do_evaluation_get_all_notes(evaluation_id) # { etudid : value }
- notes = [x["value"] for x in NotesDB.values()]
- if notes:
- raise ScoValueError(
- "Impossible de supprimer cette évaluation: il reste des notes"
- )
-
- cnx = ndb.GetDBConnexion()
-
- _evaluationEditor.delete(cnx, evaluation_id)
- # inval cache pour ce semestre
- M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
- sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
- # news
- mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
- mod["moduleimpl_id"] = M["moduleimpl_id"]
- mod["url"] = (
- scu.NotesURL() + "/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
- )
- sco_news.add(
- typ=sco_news.NEWS_NOTE,
- object=moduleimpl_id,
- text='Suppression d\'une évaluation dans %(titre)s' % mod,
- url=mod["url"],
- )
def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=False):
@@ -385,7 +109,9 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=
nb_inscrits = len(
sco_groups.do_evaluation_listeetuds_groups(evaluation_id, getallstudents=True)
)
- NotesDB = do_evaluation_get_all_notes(evaluation_id) # { etudid : value }
+ NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(
+ evaluation_id
+ ) # { etudid : value }
notes = [x["value"] for x in NotesDB.values()]
nb_abs = len([x for x in notes if x is None])
nb_neutre = len([x for x in notes if x == scu.NOTES_NEUTRALISE])
@@ -408,10 +134,10 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=
else:
last_modif = None
# ---- Liste des groupes complets et incomplets
- E = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
+ E = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
- is_malus = Mod["module_type"] == scu.MODULE_MALUS # True si module de malus
+ is_malus = Mod["module_type"] == ModuleType.MALUS # True si module de malus
formsemestre_id = M["formsemestre_id"]
# Si partition_id is None, prend 'all' ou bien la premiere:
if partition_id is None:
@@ -611,46 +337,6 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
return res
-# ancien _notes_getall
-def do_evaluation_get_all_notes(
- evaluation_id, table="notes_notes", filter_suppressed=True, by_uid=None
-):
- """Toutes les notes pour une evaluation: { etudid : { 'value' : value, 'date' : date ... }}
- Attention: inclut aussi les notes des étudiants qui ne sont plus inscrits au module.
- """
- do_cache = (
- filter_suppressed and table == "notes_notes" and (by_uid is None)
- ) # pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant
- if do_cache:
- r = sco_cache.EvaluationCache.get(evaluation_id)
- if r != None:
- return r
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- cond = " where evaluation_id=%(evaluation_id)s"
- if by_uid:
- cond += " and uid=%(by_uid)s"
-
- cursor.execute(
- "select * from " + table + cond,
- {"evaluation_id": evaluation_id, "by_uid": by_uid},
- )
- res = cursor.dictfetchall()
- d = {}
- if filter_suppressed:
- for x in res:
- if x["value"] != scu.NOTES_SUPPRESS:
- d[x["etudid"]] = x
- else:
- for x in res:
- d[x["etudid"]] = x
- if do_cache:
- status = sco_cache.EvaluationCache.set(evaluation_id, d)
- if not status:
- log(f"Warning: EvaluationCache.set: {evaluation_id}\t{status}")
- return d
-
-
def _eval_etat(evals):
"""evals: list of mappings (etats)
-> nb_eval_completes, nb_evals_en_cours,
@@ -818,10 +504,12 @@ def evaluation_date_first_completion(evaluation_id):
# ins = [i for i in insem if i["etudid"] in insmodset]
notes = list(
- do_evaluation_get_all_notes(evaluation_id, filter_suppressed=False).values()
+ sco_evaluation_db.do_evaluation_get_all_notes(
+ evaluation_id, filter_suppressed=False
+ ).values()
)
notes_log = list(
- do_evaluation_get_all_notes(
+ sco_evaluation_db.do_evaluation_get_all_notes(
evaluation_id, filter_suppressed=False, table="notes_notes_log"
).values()
)
@@ -854,7 +542,7 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
if (e["evaluation_type"] != scu.EVALUATION_NORMALE) or (
- Mod["module_type"] == scu.MODULE_MALUS
+ Mod["module_type"] == ModuleType.MALUS
):
continue
e["date_first_complete"] = evaluation_date_first_completion(e["evaluation_id"])
@@ -917,112 +605,6 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
return tab.make_page(format=format)
-def module_evaluation_insert_before(ModEvals, 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")
- module_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 ModEvals:
- if e["numero"] >= n:
- e["numero"] += 1
- # log('incrementing %s to %s' % (e['evaluation_id'], e['numero']))
- do_evaluation_edit(e)
-
- return n
-
-
-def module_evaluation_move(evaluation_id, after=0, redirect=1):
- """Move before/after previous one (decrement/increment numero)
- (published)
- """
- e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
- redirect = int(redirect)
- # access: can change eval ?
- if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=e["moduleimpl_id"]):
- raise AccessDenied(
- "Modification évaluation impossible pour %s" % current_user.get_nomplogin()
- )
-
- module_evaluation_renumber(e["moduleimpl_id"], only_if_unumbered=True)
- e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
-
- after = int(after) # 0: deplace avant, 1 deplace apres
- if after not in (0, 1):
- raise ValueError('invalid value for "after"')
- ModEvals = do_evaluation_list({"moduleimpl_id": e["moduleimpl_id"]})
- # log('ModEvals=%s' % [ x['evaluation_id'] for x in ModEvals] )
- if len(ModEvals) > 1:
- idx = [p["evaluation_id"] for p in ModEvals].index(evaluation_id)
- neigh = None # object to swap with
- if after == 0 and idx > 0:
- neigh = ModEvals[idx - 1]
- elif after == 1 and idx < len(ModEvals) - 1:
- neigh = ModEvals[idx + 1]
- if neigh: #
- # swap numero with neighbor
- e["numero"], neigh["numero"] = neigh["numero"], e["numero"]
- do_evaluation_edit(e)
- do_evaluation_edit(neigh)
- # redirect to moduleimpl page:
- if redirect:
- return flask.redirect(
- url_for(
- "notes.moduleimpl_status",
- scodoc_dept=g.scodoc_dept,
- moduleimpl_id=e["moduleimpl_id"],
- )
- )
-
-
-def module_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('module_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)
- ModEvals = 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 ModEvals]
- if all_numbered and only_if_unumbered:
- return # all ok
-
- # log('module_evaluation_renumber')
- # Reset all numeros:
- i = 1
- for e in ModEvals:
- 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,
- )
- )
-
-
# -------------- VIEWS
def evaluation_describe(evaluation_id="", edit_in_place=True):
"""HTML description of evaluation, for page headers
@@ -1030,7 +612,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
"""
from app.scodoc import sco_saisie_notes
- E = do_evaluation_list({"evaluation_id": evaluation_id})[0]
+ E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
moduleimpl_id = E["moduleimpl_id"]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
@@ -1054,13 +636,13 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
etit = E["description"] or ""
if etit:
etit = ' "' + etit + '"'
- if Mod["module_type"] == scu.MODULE_MALUS:
+ if Mod["module_type"] == ModuleType.MALUS:
etit += ' (points de malus)'
H = [
'Evaluation%sModule : %s
'
% (etit, mod_descr)
]
- if Mod["module_type"] == scu.MODULE_MALUS:
+ if Mod["module_type"] == ModuleType.MALUS:
# Indique l'UE
ue = sco_edit_ue.ue_list(args={"ue_id": Mod["ue_id"]})[0]
H.append("UE : %(acronyme)s
" % ue)
@@ -1099,269 +681,3 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
H.append("
")
return '' + "\n".join(H) + "
"
-
-
-def evaluation_create_form(
- moduleimpl_id=None,
- evaluation_id=None,
- edit=False,
- readonly=False,
- page_title="Evaluation",
-):
- "formulaire creation/edition des evaluations (pas des notes)"
- if evaluation_id != None:
- the_eval = do_evaluation_list({"evaluation_id": evaluation_id})[0]
- moduleimpl_id = the_eval["moduleimpl_id"]
- #
- M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
- is_malus = M["module"]["module_type"] == scu.MODULE_MALUS # True si module de malus
- formsemestre_id = M["formsemestre_id"]
- min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
- if not readonly:
- if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
- return (
- html_sco_header.sco_header()
- + "Opération non autorisée
"
- + "Modification évaluation impossible pour %s"
- % current_user.get_nomplogin()
- + "
"
- + 'Revenir
'
- % (moduleimpl_id,)
- + html_sco_header.sco_footer()
- )
- if readonly:
- edit = True # montre les donnees existantes
- if not edit:
- # creation nouvel
- if moduleimpl_id is None:
- raise ValueError("missing moduleimpl_id parameter")
- initvalues = {
- "note_max": 20,
- "jour": time.strftime("%d/%m/%Y", time.localtime()),
- "publish_incomplete": is_malus,
- }
- submitlabel = "Créer cette évaluation"
- action = "Création d'une é"
- link = ""
- else:
- # edition donnees existantes
- # setup form init values
- if evaluation_id is None:
- raise ValueError("missing evaluation_id parameter")
- initvalues = the_eval
- moduleimpl_id = initvalues["moduleimpl_id"]
- submitlabel = "Modifier les données"
- if readonly:
- action = "E"
- link = (
- 'voir toutes les notes du module'
- % M["moduleimpl_id"]
- )
- else:
- action = "Modification d'une é"
- link = ""
- # Note maximale actuelle dans cette eval ?
- etat = do_evaluation_etat(evaluation_id)
- if etat["maxi_num"] is not None:
- min_note_max = max(scu.NOTES_PRECISION, etat["maxi_num"])
- else:
- min_note_max = scu.NOTES_PRECISION
- #
- if min_note_max > scu.NOTES_PRECISION:
- min_note_max_str = scu.fmt_note(min_note_max)
- else:
- min_note_max_str = "0"
- #
- Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
- #
- help = """
- Le coefficient d'une évaluation n'est utilisé que pour pondérer les évaluations au sein d'un module.
- Il est fixé librement par l'enseignant pour refléter l'importance de ses différentes notes
- (examens, projets, travaux pratiques...). Ce coefficient est utilisé pour calculer la note
- moyenne de chaque étudiant dans ce module.
-
- Ne pas confondre ce coefficient avec le coefficient du module, qui est lui fixé par le programme
- pédagogique (le PPN pour les DUT) et pondère les moyennes de chaque module pour obtenir
- les moyennes d'UE et la moyenne générale.
-
- L'option Visible sur bulletins indique que la note sera reportée sur les bulletins
- en version dite "intermédiaire" (dans cette version, on peut ne faire apparaitre que certaines
- notes, en sus des moyennes de modules. Attention, cette option n'empêche pas la publication sur
- les bulletins en version "longue" (la note est donc visible par les étudiants sur le portail).
-
- Les modalités "rattrapage" et "deuxième session" définissent des évaluations prises en compte de
- façon spéciale:
-
- - les notes d'une évaluation de "rattrapage" remplaceront les moyennes du module
- si elles sont meilleures que celles calculées.
- - les notes de "deuxième session" remplacent, lorsqu'elles sont saisies, la moyenne de l'étudiant
- à ce module, même si la note de deuxième session est plus faible.
-
-
- Dans ces deux cas, le coefficient est ignoré, et toutes les notes n'ont
- pas besoin d'être rentrées.
-
-
- Par ailleurs, les évaluations des modules de type "malus" sont toujours spéciales: le coefficient n'est pas utilisé.
- Les notes de malus sont toujours comprises entre -20 et 20. Les points sont soustraits à la moyenne
- de l'UE à laquelle appartient le module malus (si la note est négative, la moyenne est donc augmentée).
-
- """
- mod_descr = '
%s %s %s' % (
- moduleimpl_id,
- Mod["code"],
- Mod["titre"],
- link,
- )
- if not readonly:
- H = ["
%svaluation en %s
" % (action, mod_descr)]
- else:
- return evaluation_describe(evaluation_id)
-
- heures = ["%02dh%02d" % (h, m) for h in range(8, 19) for m in (0, 30)]
- #
- initvalues["visibulletin"] = initvalues.get("visibulletin", True)
- if initvalues["visibulletin"]:
- initvalues["visibulletinlist"] = ["X"]
- else:
- initvalues["visibulletinlist"] = []
- vals = scu.get_request_args()
- if vals.get("tf_submitted", False) and "visibulletinlist" not in vals:
- vals["visibulletinlist"] = []
- #
- form = [
- ("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
- ("formsemestre_id", {"default": formsemestre_id, "input_type": "hidden"}),
- ("moduleimpl_id", {"default": moduleimpl_id, "input_type": "hidden"}),
- # ('jour', { 'title' : 'Date (j/m/a)', 'size' : 12, 'explanation' : 'date de l\'examen, devoir ou contrôle' }),
- (
- "jour",
- {
- "input_type": "date",
- "title": "Date",
- "size": 12,
- "explanation": "date de l'examen, devoir ou contrôle",
- },
- ),
- (
- "heure_debut",
- {
- "title": "Heure de début",
- "explanation": "heure du début de l'épreuve",
- "input_type": "menu",
- "allowed_values": heures,
- "labels": heures,
- },
- ),
- (
- "heure_fin",
- {
- "title": "Heure de fin",
- "explanation": "heure de fin de l'épreuve",
- "input_type": "menu",
- "allowed_values": heures,
- "labels": heures,
- },
- ),
- ]
- if is_malus: # pas de coefficient
- form.append(("coefficient", {"input_type": "hidden", "default": "1."}))
- else:
- form.append(
- (
- "coefficient",
- {
- "size": 10,
- "type": "float",
- "explanation": "coef. dans le module (choisi librement par l'enseignant)",
- "allow_null": False,
- },
- )
- )
- form += [
- (
- "note_max",
- {
- "size": 4,
- "type": "float",
- "title": "Notes de 0 à",
- "explanation": "barème (note max actuelle: %s)" % min_note_max_str,
- "allow_null": False,
- "max_value": scu.NOTES_MAX,
- "min_value": min_note_max,
- },
- ),
- (
- "description",
- {
- "size": 36,
- "type": "text",
- "explanation": 'type d\'évaluation, apparait sur le bulletins longs. Exemples: "contrôle court", "examen de TP", "examen final".',
- },
- ),
- (
- "visibulletinlist",
- {
- "input_type": "checkbox",
- "allowed_values": ["X"],
- "labels": [""],
- "title": "Visible sur bulletins",
- "explanation": "(pour les bulletins en version intermédiaire)",
- },
- ),
- (
- "publish_incomplete",
- {
- "input_type": "boolcheckbox",
- "title": "Prise en compte immédiate",
- "explanation": "notes utilisées même si incomplètes",
- },
- ),
- (
- "evaluation_type",
- {
- "input_type": "menu",
- "title": "Modalité",
- "allowed_values": (
- scu.EVALUATION_NORMALE,
- scu.EVALUATION_RATTRAPAGE,
- scu.EVALUATION_SESSION2,
- ),
- "type": "int",
- "labels": (
- "Normale",
- "Rattrapage (remplace si meilleure note)",
- "Deuxième session (remplace toujours)",
- ),
- },
- ),
- ]
- tf = TrivialFormulator(
- request.base_url,
- vals,
- form,
- cancelbutton="Annuler",
- submitlabel=submitlabel,
- initvalues=initvalues,
- readonly=readonly,
- )
-
- dest_url = "moduleimpl_status?moduleimpl_id=%s" % M["moduleimpl_id"]
- if tf[0] == 0:
- head = html_sco_header.sco_header(page_title=page_title)
- return head + "\n".join(H) + "\n" + tf[1] + help + html_sco_header.sco_footer()
- elif tf[0] == -1:
- return flask.redirect(dest_url)
- else:
- # form submission
- if tf[2]["visibulletinlist"]:
- tf[2]["visibulletin"] = True
- else:
- tf[2]["visibulletin"] = False
- if not edit:
- # creation d'une evaluation
- evaluation_id = do_evaluation_create(**tf[2])
- return flask.redirect(dest_url)
- else:
- do_evaluation_edit(tf[2])
- return flask.redirect(dest_url)
diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py
index 6d29b8cf2f..307d118838 100644
--- a/app/scodoc/sco_formsemestre.py
+++ b/app/scodoc/sco_formsemestre.py
@@ -39,7 +39,6 @@ from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
from app.scodoc import sco_formations
from app.scodoc import sco_preferences
-from app.scodoc import sco_users
from app.scodoc.gen_tables import GenTable
from app import log
from app.scodoc.sco_codes_parcours import NO_SEMESTRE_ID
@@ -369,7 +368,7 @@ def _write_formsemestre_aux(sem, fieldname, valuename):
# uniquify
values = set([str(x) for x in sem[fieldname]])
- cnx = ndb.GetDBConnexion(autocommit=False)
+ cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
tablename = "notes_formsemestre_" + fieldname
try:
@@ -398,6 +397,8 @@ def _write_formsemestre_aux(sem, fieldname, valuename):
def sem_set_responsable_name(sem):
"ajoute champs responsable_name"
+ from app.scodoc import sco_users
+
sem["responsable_name"] = ", ".join(
[
sco_users.user_info(responsable_id)["nomprenom"]
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index bd36427d3e..ce4278dbd7 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -49,6 +49,7 @@ from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_etud
from app.scodoc import sco_evaluations
+from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups_copy
@@ -851,7 +852,7 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
)[0]["moduleimpl_id"]
mod = sco_edit_module.module_list({"module_id": module_id})[0]
# Evaluations dans ce module ?
- evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
+ evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
if evals:
msg += [
'
impossible de supprimer %s (%s) car il y a %d évaluations définies (supprimer les d\'abord)'
@@ -1030,14 +1031,14 @@ def do_formsemestre_clone(
sco_moduleimpl.do_ens_create(args)
# optionally, copy evaluations
if clone_evaluations:
- evals = sco_evaluations.do_evaluation_list(
+ evals = sco_evaluation_db.do_evaluation_list(
args={"moduleimpl_id": mod_orig["moduleimpl_id"]}
)
for e in evals:
args = e.copy()
del args["jour"] # erase date
args["moduleimpl_id"] = mid
- _ = sco_evaluations.do_evaluation_create(**args)
+ _ = sco_evaluation_db.do_evaluation_create(**args)
# 3- copy uecoefs
objs = sco_formsemestre.formsemestre_uecoef_list(
@@ -1229,7 +1230,7 @@ def formsemestre_delete(formsemestre_id):
""",
]
- evals = sco_evaluations.do_evaluation_list_in_formsemestre(formsemestre_id)
+ evals = sco_evaluation_db.do_evaluation_list_in_formsemestre(formsemestre_id)
if evals:
H.append(
"""Attention: il y a %d évaluations dans ce semestre (sa suppression entrainera l'effacement définif des notes) !
"""
@@ -1311,7 +1312,7 @@ def do_formsemestre_delete(formsemestre_id):
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
for mod in mods:
# evaluations
- evals = sco_evaluations.do_evaluation_list(
+ evals = sco_evaluation_db.do_evaluation_list(
args={"moduleimpl_id": mod["moduleimpl_id"]}
)
for e in evals:
diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py
index 04f17d5b9b..48af5949c5 100644
--- a/app/scodoc/sco_formsemestre_status.py
+++ b/app/scodoc/sco_formsemestre_status.py
@@ -36,6 +36,7 @@ from flask_login import current_user
from app import log
import app.scodoc.sco_utils as scu
+from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidDateError
@@ -50,6 +51,7 @@ from app.scodoc import sco_compute_moy
from app.scodoc import sco_cache
from app.scodoc import sco_edit_ue
from app.scodoc import sco_evaluations
+from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_edit
@@ -459,7 +461,9 @@ def retreive_formsemestre_from_request() -> int:
modimpl = modimpl[0]
formsemestre_id = modimpl["formsemestre_id"]
elif "evaluation_id" in args:
- E = sco_evaluations.do_evaluation_list({"evaluation_id": args["evaluation_id"]})
+ E = sco_evaluation_db.do_evaluation_list(
+ {"evaluation_id": args["evaluation_id"]}
+ )
if not E:
return None # evaluation suppressed ?
E = E[0]
@@ -979,13 +983,22 @@ def formsemestre_status(formsemestre_id=None):
# porté du DTML
cnx = ndb.GetDBConnexion()
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
- Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
- # inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
- # args={"formsemestre_id": formsemestre_id}
- # )
- prev_ue_id = None
+ modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
+ formsemestre_id=formsemestre_id
+ )
+ nt = sco_cache.NotesTableCache.get(formsemestre_id)
+ # Construit la liste de tous les enseignants de ce semestre:
+ mails_enseignants = set(
+ [sco_users.user_info(ens_id)["email"] for ens_id in sem["responsables"]]
+ )
+ for modimpl in modimpls:
+ mails_enseignants.add(sco_users.user_info(modimpl["responsable_id"])["email"])
+ mails_enseignants |= set(
+ [sco_users.user_info(m["ens_id"])["email"] for m in modimpl["ens"]]
+ )
can_edit = sco_formsemestre_edit.can_edit_sem(formsemestre_id, sem=sem)
+ use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
H = [
html_sco_header.sco_header(page_title="Semestre %s" % sem["titreannee"]),
@@ -995,158 +1008,69 @@ def formsemestre_status(formsemestre_id=None):
),
"""Tableau de bord: cliquez sur un module pour saisir des notes
""",
]
- nt = sco_cache.NotesTableCache.get(formsemestre_id)
+
if nt.expr_diagnostics:
H.append(html_expr_diagnostic(nt.expr_diagnostics))
- H.append(
- """
-
-
")
- if sco_preferences.get_preference("use_ue_coefs", formsemestre_id):
+ if use_ue_coefs:
H.append(
"""
utilise les coefficients d'UE pour calculer la moyenne générale.
@@ -1166,3 +1090,156 @@ def formsemestre_status(formsemestre_id=None):
% (",".join(adrlist), len(adrlist))
)
return "".join(H) + html_sco_header.sco_footer()
+
+
+_TABLEAU_MODULES_HEAD = """
+"""
+
+
+def formsemestre_tableau_modules(
+ modimpls, nt, formsemestre_id, can_edit=True, show_ues=True, use_ue_coefs=False
+) -> str:
+ "Lignes table HTML avec modules du semestre"
+ H = []
+ prev_ue_id = None
+ for modimpl in modimpls:
+ mod = modimpl["module"]
+ mod_descr = (
+ "Module "
+ + modimpl["module"]["titre"]
+ + ", coef. "
+ + str(modimpl["module"]["coefficient"])
+ )
+ mod_ens = sco_users.user_info(modimpl["responsable_id"])["nomcomplet"]
+ if modimpl["ens"]:
+ mod_ens += " (resp.), " + ", ".join(
+ [sco_users.user_info(e["ens_id"])["nomcomplet"] for e in modimpl["ens"]]
+ )
+ mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
+ moduleimpl_id=modimpl["moduleimpl_id"]
+ )
+
+ ue = modimpl["ue"]
+ if show_ues and (prev_ue_id != ue["ue_id"]):
+ prev_ue_id = ue["ue_id"]
+ titre = ue["titre"]
+ if use_ue_coefs:
+ titre += " (coef. %s)" % (ue["coefficient"] or 0.0)
+ H.append(
+ f"""
+ {ue["acronyme"]}
+ {titre}
+ | """
+ )
+ if can_edit:
+ H.append(
+ ' '
+ % (formsemestre_id, ue["ue_id"])
+ )
+ H.append(
+ scu.icontag(
+ "formula",
+ title="Mode calcul moyenne d'UE",
+ style="vertical-align:middle",
+ )
+ )
+ if can_edit:
+ H.append("")
+
+ expr = sco_compute_moy.get_ue_expression(
+ formsemestre_id, ue["ue_id"], html_quote=True
+ )
+ if expr:
+ H.append(
+ """ %s"""
+ % expr
+ )
+
+ H.append(" |
")
+
+ if modimpl["ue"]["type"] != sco_codes_parcours.UE_STANDARD:
+ fontorange = " fontorange" # style css additionnel
+ else:
+ fontorange = ""
+
+ etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl["moduleimpl_id"])
+ if (
+ etat["nb_evals_completes"] > 0
+ and etat["nb_evals_en_cours"] == 0
+ and etat["nb_evals_vides"] == 0
+ ):
+ H.append('' % fontorange)
+ else:
+ H.append('
' % fontorange)
+
+ H.append(
+ '%s | '
+ % (modimpl["moduleimpl_id"], mod_descr, mod["code"])
+ )
+ H.append(
+ '%s | '
+ % (modimpl["moduleimpl_id"], mod_descr, mod["abbrev"] or mod["titre"])
+ )
+ H.append('%s | ' % len(mod_inscrits))
+ H.append(
+ '%s | '
+ % (
+ modimpl["moduleimpl_id"],
+ mod_ens,
+ sco_users.user_info(modimpl["responsable_id"])["prenomnom"],
+ )
+ )
+
+ if mod["module_type"] in (
+ ModuleType.STANDARD,
+ ModuleType.RESSOURCE,
+ ModuleType.SAE,
+ ):
+ H.append('')
+ nb_evals = (
+ etat["nb_evals_completes"]
+ + etat["nb_evals_en_cours"]
+ + etat["nb_evals_vides"]
+ )
+ if nb_evals != 0:
+ H.append(
+ '%s prévues, %s ok'
+ % (modimpl["moduleimpl_id"], nb_evals, etat["nb_evals_completes"])
+ )
+ if etat["nb_evals_en_cours"] > 0:
+ H.append(
+ ', %s en cours'
+ % (modimpl["moduleimpl_id"], etat["nb_evals_en_cours"])
+ )
+ if etat["attente"]:
+ H.append(
+ ' [en attente]'
+ % modimpl["moduleimpl_id"]
+ )
+ elif mod["module_type"] == ModuleType.MALUS:
+ nb_malus_notes = sum(
+ [
+ e["etat"]["nb_notes"]
+ for e in nt.get_mod_evaluation_etat_list(modimpl["moduleimpl_id"])
+ ]
+ )
+ H.append(
+ """ |
+ malus (%d notes)
+ """
+ % (modimpl["moduleimpl_id"], nb_malus_notes)
+ )
+ else:
+ raise ValueError("Invalid module_type") # a bug
+
+ H.append(" |
")
+ return "\n".join(H)
diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py
index 310173fd3b..91bbdec3f4 100644
--- a/app/scodoc/sco_formsemestre_validation.py
+++ b/app/scodoc/sco_formsemestre_validation.py
@@ -956,7 +956,7 @@ def do_formsemestre_validation_auto(formsemestre_id):
def formsemestre_validation_suppress_etud(formsemestre_id, etudid):
"""Suppression des decisions de jury pour un etudiant."""
log("formsemestre_validation_suppress_etud( %s, %s)" % (formsemestre_id, etudid))
- cnx = ndb.GetDBConnexion(autocommit=False)
+ cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
try:
@@ -1123,7 +1123,7 @@ def do_formsemestre_validate_previous_ue(
cette UE (utile seulement pour les semestres extérieurs).
"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- cnx = ndb.GetDBConnexion(autocommit=False)
+ cnx = ndb.GetDBConnexion()
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_ue_status
if ue_coefficient != None:
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py
index fe34484fc1..657e3acc82 100644
--- a/app/scodoc/sco_import_etuds.py
+++ b/app/scodoc/sco_import_etuds.py
@@ -262,7 +262,7 @@ def scolars_import_excel_file(
et les inscrit dans le semestre indiqué (et à TOUS ses modules)
"""
log("scolars_import_excel_file: formsemestre_id=%s" % formsemestre_id)
- cnx = ndb.GetDBConnexion(autocommit=False)
+ cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
annee_courante = time.localtime()[0]
always_require_ine = sco_preferences.get_preference("always_require_ine")
diff --git a/app/scodoc/sco_liste_notes.py b/app/scodoc/sco_liste_notes.py
index 627e918514..8df208faa7 100644
--- a/app/scodoc/sco_liste_notes.py
+++ b/app/scodoc/sco_liste_notes.py
@@ -37,13 +37,12 @@ import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator
-from app.scodoc import htmlutils
from app.scodoc import html_sco_header
from app.scodoc import sco_abs
from app.scodoc import sco_cache
from app.scodoc import sco_edit_module
from app.scodoc import sco_evaluations
-from app.scodoc import sco_excel
+from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
@@ -68,11 +67,11 @@ def do_evaluation_listenotes():
if "evaluation_id" in vals:
evaluation_id = int(vals["evaluation_id"])
mode = "eval"
- evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
+ evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
if "moduleimpl_id" in vals and vals["moduleimpl_id"]:
moduleimpl_id = int(vals["moduleimpl_id"])
mode = "module"
- evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
+ evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
if not mode:
raise ValueError("missing argument: evaluation or module")
if not evals:
@@ -545,7 +544,7 @@ def _add_eval_columns(
sum_notes = 0
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
evaluation_id = e["evaluation_id"]
- NotesDB = sco_evaluations.do_evaluation_get_all_notes(evaluation_id)
+ NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
for row in rows:
etudid = row["etudid"]
if etudid in NotesDB:
@@ -710,7 +709,7 @@ def evaluation_check_absences(evaluation_id):
EXC et pas justifie
Ramene 3 listes d'etudid
"""
- E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
+ E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
if not E["jour"]:
return [], [], [], [], [] # evaluation sans date
@@ -731,7 +730,7 @@ def evaluation_check_absences(evaluation_id):
Justs = set([x["etudid"] for x in Just]) # ensemble des etudiants avec justif
# Les notes:
- NotesDB = sco_evaluations.do_evaluation_get_all_notes(evaluation_id)
+ NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
ValButAbs = [] # une note mais noté absent
AbsNonSignalee = [] # note ABS mais pas noté absent
ExcNonSignalee = [] # note EXC mais pas noté absent
@@ -764,7 +763,7 @@ def evaluation_check_absences(evaluation_id):
def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
"""Affiche etat verification absences d'une evaluation"""
- E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
+ E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
am, pm, demijournee = _eval_demijournee(E)
(
@@ -876,7 +875,7 @@ def formsemestre_check_absences_html(formsemestre_id):
# Modules, dans l'ordre
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
for M in Mlist:
- evals = sco_evaluations.do_evaluation_list(
+ evals = sco_evaluation_db.do_evaluation_list(
{"moduleimpl_id": M["moduleimpl_id"]}
)
if evals:
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py
index 41dcfb6898..bbaf29d152 100644
--- a/app/scodoc/sco_moduleimpl_status.py
+++ b/app/scodoc/sco_moduleimpl_status.py
@@ -28,7 +28,6 @@
"""Tableau de bord module
"""
import time
-import urllib
from flask import g, url_for
from flask_login import current_user
@@ -44,6 +43,7 @@ from app.scodoc import sco_compute_moy
from app.scodoc import sco_cache
from app.scodoc import sco_edit_module
from app.scodoc import sco_evaluations
+from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_status
@@ -57,7 +57,7 @@ from app.scodoc import sco_users
# menu evaluation dans moduleimpl
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0):
"Menu avec actions sur une evaluation"
- E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
+ E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"])
@@ -161,13 +161,13 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
- ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
+ mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=M["moduleimpl_id"]
)
nt = sco_cache.NotesTableCache.get(formsemestre_id)
- ModEvals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
- ModEvals.sort(
+ mod_evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
+ mod_evals.sort(
key=lambda x: (x["numero"], x["jour"], x["heure_debut"]), reverse=True
) # la plus RECENTE en tête
@@ -179,15 +179,19 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
#
module_resp = User.query.get(M["responsable_id"])
+ mod_type_name = scu.MODULE_TYPE_NAMES[Mod["module_type"]]
H = [
- html_sco_header.sco_header(page_title="Module %(titre)s" % Mod),
- """""" % Mod,
- """
-
-
- Responsable: | """,
- module_resp.get_nomcomplet(), # sco_users.user_info(M["responsable_id"])["nomprenom"],
- f"""({module_resp.user_name})""",
+ html_sco_header.sco_header(page_title=f"{mod_type_name} {Mod['titre']}"),
+ f"""
+
+
+
+ Responsable: |
+ {module_resp.get_nomcomplet()}
+ ({module_resp.user_name})
+ """,
]
try:
sco_moduleimpl.can_change_module_resp(moduleimpl_id)
@@ -231,7 +235,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
# Ligne: Inscrits
H.append(
""" | Inscrits: | %d étudiants"""
- % len(ModInscrits)
+ % len(mod_inscrits)
)
if current_user.has_permission(Permission.ScoEtudInscrit):
H.append(
@@ -297,7 +301,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
""" | |