Compare commits

..

13 Commits

60 changed files with 2820 additions and 1336 deletions

View File

@ -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):

54
app/comp/moy_ue.py Normal file
View File

@ -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

View File

@ -31,35 +31,42 @@ 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,
notes_modules_enseignants,
NotesModuleImplInscription,
NotesEvaluation,
NotesSemSet,
notes_semset_formsemestre,
)
from app.models.moduleimpls import (
ModuleImpl,
notes_modules_enseignants,
ModuleImplInscription,
)
from app.models.evaluations import (
Evaluation,
EvaluationUEPoids,
)
from app.models.but_pn import AppCrit
from app.models.groups import Partition, GroupDescr, group_membership
from app.models.notes import (
ScolarEvent,
ScolarFormsemestreValidation,
ScolarAutorisationInscription,
NotesAppreciations,
BulAppreciations,
NotesNotes,
NotesNotesLog,
)

View File

@ -1,4 +1,41 @@
"""ScoDoc 9 models : Formation BUT 2021
"""
from enum import unique
from typing import Any
# insérer ici idk
from app import db
from app.scodoc.sco_utils import ModuleType
Modules_ACs = db.Table(
"modules_acs",
db.Column("module_id", db.ForeignKey("notes_modules.id")),
db.Column("ac_id", db.ForeignKey("app_crit.id")),
)
class AppCrit(db.Model):
"Apprentissage Critique BUT"
__tablename__ = "app_crit"
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.Text(), nullable=False, info={"label": "Code"})
titre = db.Column(db.Text(), info={"label": "Titre"})
modules = db.relationship(
"Module", secondary=Modules_ACs, lazy="dynamic", backref="acs"
)
def to_dict(self):
result = dict(self.__dict__)
result.pop("_sa_instance_state", None)
return result
def get_label(self):
return self.code + " - " + self.titre
def __repr__(self):
return "<AC {}>".format(self.code)
def get_saes(self):
"""Liste des SAE associées"""
return [m for m in self.modules if m.module_type == ModuleType.SAE]

View File

@ -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"
)

123
app/models/evaluations.py Normal file
View File

@ -0,0 +1,123 @@
# -*- coding: UTF-8 -*
"""ScoDoc models: evaluations
"""
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.models import CODE_STR_LEN
from app.models import UniteEns
import app.scodoc.notesdb as ndb
from app.scodoc import sco_evaluation_db
class Evaluation(db.Model):
"""Evaluation (contrôle, examen, ...)"""
__tablename__ = "notes_evaluation"
id = db.Column(db.Integer, primary_key=True)
evaluation_id = db.synonym("id")
moduleimpl_id = db.Column(
db.Integer, db.ForeignKey("notes_moduleimpl.id"), index=True
)
jour = db.Column(db.Date)
heure_debut = db.Column(db.Time)
heure_fin = db.Column(db.Time)
description = db.Column(db.Text)
note_max = db.Column(db.Float)
coefficient = db.Column(db.Float)
visibulletin = db.Column(
db.Boolean, nullable=False, default=True, server_default="true"
)
publish_incomplete = db.Column(
db.Boolean, nullable=False, default=False, server_default="false"
)
# type d'evaluation: 0 normale, 1 rattrapage, 2 "2eme session"
evaluation_type = db.Column(
db.Integer, nullable=False, default=0, server_default="0"
)
# ordre de presentation (par défaut, le plus petit numero
# est la plus ancienne eval):
numero = db.Column(db.Integer)
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"""
self.update_ue_poids_dict({ue.id: poids})
def set_ue_poids_dict(self, ue_poids_dict: dict):
"""set poids vers les UE (remplace existants)
ue_poids_dict = { ue_id : poids }
"""
L = []
for ue_id, poids in ue_poids_dict.items():
ue = UniteEns.query.get(ue_id)
L.append(EvaluationUEPoids(evaluation=self, ue=ue, poids=poids))
self.ue_poids = L
def update_ue_poids_dict(self, ue_poids_dict: dict):
"""update poids vers UE (ajoute aux existants)"""
current = self.get_ue_poids_dict()
current.update(ue_poids_dict)
self.set_ue_poids_dict(current)
def get_ue_poids_dict(self):
"""returns { ue_id : poids }"""
return {p.ue.id: p.poids for p in self.ue_poids}
class EvaluationUEPoids(db.Model):
"""Poids des évaluations (BUT)
association many to many
"""
evaluation_id = db.Column(
db.Integer, db.ForeignKey("notes_evaluation.id"), primary_key=True
)
ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), primary_key=True)
poids = db.Column(
db.Float,
nullable=False,
)
evaluation = db.relationship(
Evaluation,
backref=db.backref("ue_poids", cascade="all, delete-orphan"),
)
ue = db.relationship(
UniteEns,
backref=db.backref("evaluation_ue_poids", cascade="all, delete-orphan"),
)
def __repr__(self):
return f"<EvaluationUEPoids {self.evaluation} {self.ue} poids={self.poids}>"

View File

@ -5,9 +5,12 @@ 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
from app.scodoc import sco_codes_parcours
class NotesFormation(db.Model):
class Formation(db.Model):
"""Programme pédagogique d'une formation"""
__tablename__ = "notes_formations"
@ -30,16 +33,20 @@ 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}')>"
def get_parcours(self):
"""get l'instance de TypeParcours de cette formation"""
return sco_codes_parcours.get_parcours_from_code(self.type_parcours)
class NotesUE(db.Model):
"""Unité d'Enseignement"""
class UniteEns(db.Model):
"""Unité d'Enseignement (UE)"""
__tablename__ = "notes_ue"
@ -49,6 +56,11 @@ class NotesUE(db.Model):
acronyme = db.Column(db.Text(), nullable=False)
numero = db.Column(db.Integer) # ordre de présentation
titre = db.Column(db.Text())
# Le semestre_idx n'est pas un id mais le numéro du semestre: 1, 2, ...
# En ScoDoc7 et pour les formations classiques, il est NULL
# (le numéro du semestre étant alors déterminé par celui des modules de l'UE)
# Pour les formations APC, il est obligatoire (de 1 à 6 pour le BUT):
semestre_idx = db.Column(db.Integer, nullable=True, index=True)
# Type d'UE: 0 normal ("fondamentale"), 1 "sport", 2 "projet et stage (LP)",
# 4 "élective"
type = db.Column(db.Integer, default=0, server_default="0")
@ -67,14 +79,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.
@ -89,10 +101,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"
@ -106,7 +118,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"))
@ -116,9 +128,86 @@ class NotesModule(db.Model):
numero = db.Column(db.Integer) # ordre de présentation
# id de l'element pedagogique Apogee correspondant:
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
module_type = db.Column(db.Integer) # NULL ou 0:defaut, 1: malus (NOTES_MALUS)
# 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"<Module{ModuleType(self.module_type).name} id={self.id} code={self.code}>"
)
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 }
Les coefs nuls (zéro) ne sont pas stockés: la relation est supprimée.
"""
ue_coefs = []
for ue_id, coef in ue_coef_dict.items():
ue = UniteEns.query.get(ue_id)
if coef == 0.0:
self.delete_ue_coef(ue)
else:
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))
if ue_coef:
db.session.delete(ue_coef)
def ue_coefs_descr(self):
"""List of tuples [ (ue_acronyme, coef) ]"""
return [(c.ue.acronyme, c.coef) for c in self.ue_coefs]
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):

View File

@ -1,6 +1,6 @@
# -*- coding: UTF-8 -*
"""ScoDoc models
"""ScoDoc models: formsemestre
"""
from typing import Any
@ -8,12 +8,14 @@ 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 UniteEns
import app.scodoc.notesdb as ndb
from app.scodoc import sco_evaluation_db
class FormSemestre(db.Model):
"""Mise en oeuvre d'un semestre de formation
was notes_formsemestre
"""
"""Mise en oeuvre d'un semestre de formation"""
__tablename__ = "notes_formsemestre"
@ -72,11 +74,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
@ -85,7 +85,10 @@ 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
def get_ues(self):
"UE des modules de ce semestre"
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
@ -100,7 +103,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"
@ -112,7 +115,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.)
"""
@ -139,7 +142,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"),
@ -150,9 +153,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)
@ -163,7 +166,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"
@ -182,7 +185,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"
@ -202,7 +205,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"
@ -218,7 +221,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"
@ -238,89 +241,6 @@ class NotesFormsemestreInscription(db.Model):
etape = db.Column(db.String(APO_CODE_STR_LEN))
class NotesModuleImpl(db.Model):
"""Mise en oeuvre d'un module pour une annee/semestre"""
__tablename__ = "notes_moduleimpl"
__table_args__ = (db.UniqueConstraint("formsemestre_id", "module_id"),)
id = db.Column(db.Integer, primary_key=True)
moduleimpl_id = db.synonym("id")
module_id = db.Column(
db.Integer,
db.ForeignKey("notes_modules.id"),
)
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
index=True,
)
responsable_id = db.Column("responsable_id", db.Integer, db.ForeignKey("user.id"))
# formule de calcul moyenne:
computation_expr = db.Column(db.Text())
# Enseignants (chargés de TD ou TP) d'un moduleimpl
notes_modules_enseignants = db.Table(
"notes_modules_enseignants",
db.Column(
"moduleimpl_id",
db.Integer,
db.ForeignKey("notes_moduleimpl.id"),
),
db.Column("ens_id", db.Integer, db.ForeignKey("user.id")),
# ? db.UniqueConstraint("moduleimpl_id", "ens_id"),
)
# XXX il manque probablement une relation pour gérer cela
class NotesModuleImplInscription(db.Model):
"""Inscription à un module (etudiants,moduleimpl)"""
__tablename__ = "notes_moduleimpl_inscription"
__table_args__ = (db.UniqueConstraint("moduleimpl_id", "etudid"),)
id = db.Column(db.Integer, primary_key=True)
moduleimpl_inscription_id = db.synonym("id")
moduleimpl_id = db.Column(
db.Integer,
db.ForeignKey("notes_moduleimpl.id"),
index=True,
)
etudid = db.Column(db.Integer, db.ForeignKey("identite.id"), index=True)
class NotesEvaluation(db.Model):
"""Evaluation (contrôle, examen, ...)"""
__tablename__ = "notes_evaluation"
id = db.Column(db.Integer, primary_key=True)
evaluation_id = db.synonym("id")
moduleimpl_id = db.Column(
db.Integer, db.ForeignKey("notes_moduleimpl.id"), index=True
)
jour = db.Column(db.Date)
heure_debut = db.Column(db.Time)
heure_fin = db.Column(db.Time)
description = db.Column(db.Text)
note_max = db.Column(db.Float)
coefficient = db.Column(db.Float)
visibulletin = db.Column(
db.Boolean, nullable=False, default=True, server_default="true"
)
publish_incomplete = db.Column(
db.Boolean, nullable=False, default=False, server_default="false"
)
# type d'evaluation: 0 normale, 1 rattrapage, 2 "2eme session"
evaluation_type = db.Column(
db.Integer, nullable=False, default=0, server_default="0"
)
# ordre de presentation (par défaut, le plus petit numero
# est la plus ancienne eval):
numero = db.Column(db.Integer)
class NotesSemSet(db.Model):
"""semsets: ensemble de formsemestres pour exports Apogée"""

66
app/models/moduleimpls.py Normal file
View File

@ -0,0 +1,66 @@
# -*- coding: UTF-8 -*
"""ScoDoc models: moduleimpls
"""
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.models import CODE_STR_LEN
from app.models import UniteEns
import app.scodoc.notesdb as ndb
from app.scodoc import sco_evaluation_db
class ModuleImpl(db.Model):
"""Mise en oeuvre d'un module pour une annee/semestre"""
__tablename__ = "notes_moduleimpl"
__table_args__ = (db.UniqueConstraint("formsemestre_id", "module_id"),)
id = db.Column(db.Integer, primary_key=True)
moduleimpl_id = db.synonym("id")
module_id = db.Column(
db.Integer,
db.ForeignKey("notes_modules.id"),
)
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
index=True,
)
responsable_id = db.Column("responsable_id", db.Integer, db.ForeignKey("user.id"))
# formule de calcul moyenne:
computation_expr = db.Column(db.Text())
# Enseignants (chargés de TD ou TP) d'un moduleimpl
notes_modules_enseignants = db.Table(
"notes_modules_enseignants",
db.Column(
"moduleimpl_id",
db.Integer,
db.ForeignKey("notes_moduleimpl.id"),
),
db.Column("ens_id", db.Integer, db.ForeignKey("user.id")),
# ? db.UniqueConstraint("moduleimpl_id", "ens_id"),
)
# XXX il manque probablement une relation pour gérer cela
class ModuleImplInscription(db.Model):
"""Inscription à un module (etudiants,moduleimpl)"""
__tablename__ = "notes_moduleimpl_inscription"
__table_args__ = (db.UniqueConstraint("moduleimpl_id", "etudid"),)
id = db.Column(db.Integer, primary_key=True)
moduleimpl_inscription_id = db.synonym("id")
moduleimpl_id = db.Column(
db.Integer,
db.ForeignKey("notes_moduleimpl.id"),
index=True,
)
etudid = db.Column(db.Integer, db.ForeignKey("identite.id"), index=True)

View File

@ -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(

View File

@ -51,7 +51,7 @@ def TrivialFormulator(
allow_null : if true, field can be left empty (default true)
type : 'string', 'int', 'float' (default to string), 'list' (only for hidden)
readonly : default False. if True, no form element, display current value.
convert_numbers: covert int and float values (from string)
convert_numbers: convert int and float values (from string)
allowed_values : list of possible values (default: any value)
validator : function validating the field (called with (value,field)).
min_value : minimum value (for floats and ints)

View File

@ -40,7 +40,7 @@ from app.scodoc.sco_permissions import Permission
def sidebar_common():
"partie commune à toutes les sidebar"
H = [
f"""<a class="scodoc_title" href="{url_for("scodoc.index", scodoc_dept=g.scodoc_dept)}">ScoDoc 9</a>
f"""<a class="scodoc_title" href="{url_for("scodoc.index", scodoc_dept=g.scodoc_dept)}">ScoDoc 9.1</a>
<div id="authuser"><a id="authuserlink" href="{
url_for("users.user_info_page",
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -28,8 +28,42 @@
"""Semestres: Codes gestion parcours (constantes)
"""
import collections
import enum
from app import log
@enum.unique
class CodesParcours(enum.IntEnum):
"""Codes numériques de sparcours, enregistrés en base
dans notes_formations.type_parcours
Ne pas modifier.
"""
Legacy = 0
DUT = 100
DUT4 = 110
DUTMono = 120
DUT2 = 130
LP = 200
LP2sem = 210
LP2semEvry = 220
LP2014 = 230
LP2sem2014 = 240
M2 = 250
M2noncomp = 251
Mono = 300
MasterLMD = 402
MasterIG = 403
LicenceUCAC3 = 501
MasterUCAC2 = 502
MonoUCAC = 503
GEN_6_SEM = 600
BUT = 700
ISCID6 = 1001
ISCID4 = 1002
NOTES_TOLERANCE = 0.00499999999999 # si note >= (BARRE-TOLERANCE), considere ok
# (permet d'eviter d'afficher 10.00 sous barre alors que la moyenne vaut 9.999)
@ -217,6 +251,7 @@ class TypeParcours(object):
ALLOWED_UE_TYPES = list(
UE_TYPE_NAME.keys()
) # par defaut, autorise tous les types d'UE
APC_SAE = False # Approche par compétences avec ressources et SAÉs
def check(self, formation=None):
return True, "" # status, diagnostic_message
@ -265,6 +300,20 @@ def register_parcours(Parcours):
TYPES_PARCOURS[Parcours.TYPE_PARCOURS] = Parcours
class ParcoursBUT(TypeParcours):
"""BUT Bachelor Universitaire de Technologie"""
TYPE_PARCOURS = 700
NAME = "BUT"
NB_SEM = 6
COMPENSATION_UE = False
APC_SAE = True
ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT]
register_parcours(ParcoursBUT())
class ParcoursDUT(TypeParcours):
"""DUT selon l'arrêté d'août 2005"""
@ -305,7 +354,7 @@ register_parcours(ParcoursDUTMono())
class ParcoursDUT2(ParcoursDUT):
"""DUT en deux semestres (par ex.: années spéciales semestrialisées)"""
TYPE_PARCOURS = 130
TYPE_PARCOURS = CodesParcours.DUT2
NAME = "DUT2"
NB_SEM = 2
@ -318,7 +367,7 @@ class ParcoursLP(TypeParcours):
(pour anciennes LP. Après 2014, préférer ParcoursLP2014)
"""
TYPE_PARCOURS = 200
TYPE_PARCOURS = CodesParcours.LP
NAME = "LP"
NB_SEM = 1
COMPENSATION_UE = False
@ -335,7 +384,7 @@ register_parcours(ParcoursLP())
class ParcoursLP2sem(ParcoursLP):
"""Licence Pro (en deux "semestres")"""
TYPE_PARCOURS = 210
TYPE_PARCOURS = CodesParcours.LP2sem
NAME = "LP2sem"
NB_SEM = 2
COMPENSATION_UE = True
@ -348,7 +397,7 @@ register_parcours(ParcoursLP2sem())
class ParcoursLP2semEvry(ParcoursLP):
"""Licence Pro (en deux "semestres", U. Evry)"""
TYPE_PARCOURS = 220
TYPE_PARCOURS = CodesParcours.LP2semEvry
NAME = "LP2semEvry"
NB_SEM = 2
COMPENSATION_UE = True
@ -374,7 +423,7 @@ class ParcoursLP2014(TypeParcours):
# l'établissement d'un coefficient qui peut varier dans un rapport de 1 à 3. ", etc ne sont _pas_
# vérifiés par ScoDoc)
TYPE_PARCOURS = 230
TYPE_PARCOURS = CodesParcours.LP2014
NAME = "LP2014"
NB_SEM = 1
ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT, UE_STAGE_LP]
@ -418,7 +467,7 @@ register_parcours(ParcoursLP2014())
class ParcoursLP2sem2014(ParcoursLP):
"""Licence Pro (en deux "semestres", selon arrêté du 22/01/2014)"""
TYPE_PARCOURS = 240
TYPE_PARCOURS = CodesParcours.LP2sem2014
NAME = "LP2014_2sem"
NB_SEM = 2
@ -430,7 +479,7 @@ register_parcours(ParcoursLP2sem2014())
class ParcoursM2(TypeParcours):
"""Master 2 (en deux "semestres")"""
TYPE_PARCOURS = 250
TYPE_PARCOURS = CodesParcours.M2
NAME = "M2sem"
NB_SEM = 2
COMPENSATION_UE = True
@ -443,7 +492,7 @@ register_parcours(ParcoursM2())
class ParcoursM2noncomp(ParcoursM2):
"""Master 2 (en deux "semestres") sans compensation"""
TYPE_PARCOURS = 251
TYPE_PARCOURS = CodesParcours.M2noncomp
NAME = "M2noncomp"
COMPENSATION_UE = False
UNUSED_CODES = set((ADC, ATT, ATB))
@ -455,7 +504,7 @@ register_parcours(ParcoursM2noncomp())
class ParcoursMono(TypeParcours):
"""Formation générique en une session"""
TYPE_PARCOURS = 300
TYPE_PARCOURS = CodesParcours.Mono
NAME = "Mono"
NB_SEM = 1
COMPENSATION_UE = False
@ -468,7 +517,7 @@ register_parcours(ParcoursMono())
class ParcoursLegacy(TypeParcours):
"""DUT (ancien ScoDoc, ne plus utiliser)"""
TYPE_PARCOURS = 0
TYPE_PARCOURS = CodesParcours.Legacy
NAME = "DUT"
NB_SEM = 4
COMPENSATION_UE = None # backward compat: defini dans formsemestre
@ -502,7 +551,7 @@ class ParcoursBachelorISCID6(ParcoursISCID):
"""ISCID: Bachelor en 3 ans (6 sem.)"""
NAME = "ParcoursBachelorISCID6"
TYPE_PARCOURS = 1001
TYPE_PARCOURS = CodesParcours.ISCID6
NAME = ""
NB_SEM = 6
ECTS_PROF_DIPL = 8 # crédits professionnels requis pour obtenir le diplôme
@ -513,7 +562,7 @@ register_parcours(ParcoursBachelorISCID6())
class ParcoursMasterISCID4(ParcoursISCID):
"ISCID: Master en 2 ans (4 sem.)"
TYPE_PARCOURS = 1002
TYPE_PARCOURS = CodesParcours.ISCID4
NAME = "ParcoursMasterISCID4"
NB_SEM = 4
ECTS_PROF_DIPL = 15 # crédits professionnels requis pour obtenir le diplôme
@ -567,7 +616,7 @@ class ParcoursUCAC(TypeParcours):
class ParcoursLicenceUCAC3(ParcoursUCAC):
"""UCAC: Licence en 3 sessions d'un an"""
TYPE_PARCOURS = 501
TYPE_PARCOURS = CodesParcours.LicenceUCAC3
NAME = "Licence UCAC en 3 sessions d'un an"
NB_SEM = 3
@ -578,7 +627,7 @@ register_parcours(ParcoursLicenceUCAC3())
class ParcoursMasterUCAC2(ParcoursUCAC):
"""UCAC: Master en 2 sessions d'un an"""
TYPE_PARCOURS = 502
TYPE_PARCOURS = CodesParcours.MasterUCAC2
NAME = "Master UCAC en 2 sessions d'un an"
NB_SEM = 2
@ -589,7 +638,7 @@ register_parcours(ParcoursMasterUCAC2())
class ParcoursMonoUCAC(ParcoursUCAC):
"""UCAC: Formation en 1 session de durée variable"""
TYPE_PARCOURS = 503
TYPE_PARCOURS = CodesParcours.MonoUCAC
NAME = "Formation UCAC en 1 session de durée variable"
NB_SEM = 1
UNUSED_CODES = set((ADC, ATT, ATB))
@ -601,7 +650,7 @@ register_parcours(ParcoursMonoUCAC())
class Parcours6Sem(TypeParcours):
"""Parcours générique en 6 semestres"""
TYPE_PARCOURS = 600
TYPE_PARCOURS = CodesParcours.GEN_6_SEM
NAME = "Formation en 6 semestres"
NB_SEM = 6
COMPENSATION_UE = True
@ -623,7 +672,7 @@ register_parcours(Parcours6Sem())
class ParcoursMasterLMD(TypeParcours):
"""Master générique en 4 semestres dans le LMD"""
TYPE_PARCOURS = 402
TYPE_PARCOURS = CodesParcours.MasterLMD
NAME = "Master LMD"
NB_SEM = 4
COMPENSATION_UE = True # variabale inutilisée
@ -636,7 +685,7 @@ register_parcours(ParcoursMasterLMD())
class ParcoursMasterIG(ParcoursMasterLMD):
"""Master de l'Institut Galilée (U. Paris 13) en 4 semestres (LMD)"""
TYPE_PARCOURS = 403
TYPE_PARCOURS = CodesParcours.MasterIG
NAME = "Master IG P13"
BARRE_MOY = 10.0
NOTES_BARRE_VALID_UE_TH = 10.0 # seuil pour valider UE

View File

@ -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

View File

@ -29,12 +29,15 @@
(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 import models
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError, ScoGenError
@ -44,22 +47,6 @@ from app.scodoc import sco_edit_matiere
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_news
_MODULE_HELP = """<p class="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 <em>évaluations</em>. <br/>
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 <em>coefficient</em> associé au module.
</p>
<p class="help">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.
</p> """
_moduleEditor = ndb.EditableTable(
"notes_modules",
"module_id",
@ -120,27 +107,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"),
"""<h2>Création d'un module dans la matière %(titre)s""" % M,
"""<h2>Création d'un module dans la matière %(titre)s""" % matiere,
""" (UE %(acronyme)s)</h2>""" % 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 +143,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 +156,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 +191,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 +340,37 @@ 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_id = module["formation_id"]
formation = sco_formations.formation_list(args={"formation_id": 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": 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(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,28 +378,23 @@ def module_edit(module_id=None):
"js/module_tag_editor.js",
],
),
"""<h2>Modification du module %(titre)s""" % Mod,
""" (formation %(acronyme)s, version %(version)s)</h2>""" % Fo,
_MODULE_HELP,
"""<h2>Modification du module %(titre)s""" % module,
""" (formation %(acronyme)s, version %(version)s)</h2>""" % formation,
render_template("scodoc/help/modules.html", is_apc=is_apc),
]
if not unlocked:
H.append(
"""<div class="ue_warning"><span>Formation verrouillée, seuls certains éléments peuvent être modifiés</span></div>"""
)
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
(
descr = [
(
"code",
{
"size": 10,
"explanation": "code du module (doit être unique dans la formation)",
"allow_null": False,
"validator": lambda val, field, formation_id=Mod[
"formation_id"
]: check_module_code_unicity(
"validator": lambda val, field, formation_id=formation_id: check_module_code_unicity(
val, field, formation_id, module_id=module_id
),
},
@ -420,8 +407,8 @@ def module_edit(module_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],
"enabled": unlocked,
},
),
@ -445,6 +432,27 @@ def module_edit(module_id=None):
"explanation": "nombre d'heures de Travaux Pratiques",
},
),
]
if is_apc:
a_module = models.Module.query.get(module_id)
coefs_descr = a_module.ue_coefs_descr()
if coefs_descr:
coefs_descr_txt = ", ".join(["%s: %s" % x for x in coefs_descr])
else:
coefs_descr_txt = """<span class="missing_value">non définis</span>"""
descr += [
(
"ue_coefs",
{
"readonly": True,
"title": "Coefficients vers les UE",
"default": coefs_descr_txt,
"explanation": "passer par la page d'édition de la formation pour modifier les coefficients",
},
)
]
else: # Module classique avec coef scalaire:
descr += [
(
"coefficient",
{
@ -455,7 +463,8 @@ def module_edit(module_id=None):
"enabled": unlocked,
},
),
# ('ects', { 'size' : 4, 'type' : 'float', 'title' : 'ECTS', 'explanation' : 'nombre de crédits ECTS', 'enabled' : unlocked }),
]
descr += [
("formation_id", {"input_type": "hidden"}),
("ue_id", {"input_type": "hidden"}),
("module_id", {"input_type": "hidden"}),
@ -465,8 +474,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,
},
),
@ -499,11 +508,16 @@ def module_edit(module_id=None):
"type": "int",
},
),
),
]
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
descr,
html_foot_markup="""<div style="width: 90%;"><span class="sco_tag_edit"><textarea data-module_id="{}" class="module_tag_editor">{}</textarea></span></div>""".format(
module_id, ",".join(sco_tag_module.module_tag_list(module_id))
),
initvalues=Mod,
initvalues=module,
submitlabel="Modifier ce module",
)
@ -602,7 +616,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 +668,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,
},
)

View File

@ -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 Formation, 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
@ -65,6 +66,7 @@ _ueEditor = ndb.EditableTable(
"acronyme",
"numero",
"titre",
"semestre_idx",
"type",
"ue_code",
"ects",
@ -81,6 +83,7 @@ _ueEditor = ndb.EditableTable(
"numero": ndb.int_null_is_zero,
"ects": ndb.float_null_is_null,
"coefficient": ndb.float_null_is_zero,
"semestre_idx": ndb.int_null_is_null,
},
)
@ -194,7 +197,7 @@ def ue_create(formation_id=None):
def ue_edit(ue_id=None, create=False, formation_id=None):
"""Modification ou creation d'une UE"""
"""Modification ou création d'une UE"""
from app.scodoc import sco_formations
create = int(create)
@ -211,25 +214,25 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
title = "Création d'une UE"
initvalues = {}
submitlabel = "Créer cette UE"
Fol = sco_formations.formation_list(args={"formation_id": formation_id})
if not Fol:
raise ScoValueError(
"Formation %s inexistante ! (si vous avez suivi un lien valide, merci de signaler le problème)"
% formation_id
)
Fo = Fol[0]
parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
formation = Formation.query.get(formation_id)
if not formation:
raise ScoValueError(f"Formation inexistante ! (id={formation_id})")
parcours = formation.get_parcours()
is_apc = parcours.APC_SAE
semestres_indices = list(range(1, parcours.NB_SEM + 1))
H = [
html_sco_header.sco_header(page_title=title, javascripts=["js/edit_ue.js"]),
"<h2>" + title,
" (formation %(acronyme)s, version %(version)s)</h2>" % Fo,
f" (formation {formation.acronyme}, version {formation.version})</h2>",
"""
<p class="help">Les UE sont des groupes de modules dans une formation donnée, utilisés pour l'évaluation (on calcule des moyennes par UE et applique des seuils ("barres")).
</p>
<p class="help">Les UE sont des groupes de modules dans une formation donnée,
utilisés pour la validation (on calcule des moyennes par UE et applique des
seuils ("barres")).
</p>
<p class="help">Note: L'UE n'a pas de coefficient associé. Seuls les <em>modules</em> ont des coefficients.
</p>""",
<p class="help">Note: sauf exception, l'UE n'a pas de coefficient associé.
Seuls les <em>modules</em> ont des coefficients.
</p>""",
]
ue_types = parcours.ALLOWED_UE_TYPES
@ -251,6 +254,18 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
"type": "int",
},
),
(
"semestre_idx",
{
"input_type": "menu",
"type": "int",
"allow_null": True,
"title": parcours.SESSION_NAME.capitalize(),
"explanation": "%s de l'UE dans la formation" % parcours.SESSION_NAME,
"labels": ["non spécifié"] + [str(x) for x in semestres_indices],
"allowed_values": [""] + semestres_indices,
},
),
(
"type",
{
@ -275,10 +290,11 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
"size": 4,
"type": "float",
"title": "Coefficient",
"explanation": """les coefficients d'UE ne sont utilisés que lorsque
l'option <em>Utiliser les coefficients d'UE pour calculer la moyenne générale</em>
est activée. Par défaut, le coefficient d'une UE est simplement la somme des
coefficients des modules dans lesquels l'étudiant a des notes.
"explanation": """les coefficients d'UE ne sont utilisés que
lorsque l'option <em>Utiliser les coefficients d'UE pour calculer
la moyenne générale</em> est activée. Par défaut, le coefficient
d'une UE est simplement la somme des coefficients des modules dans
lesquels l'étudiant a des notes.
""",
},
),
@ -307,30 +323,13 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
},
),
]
if parcours.UE_IS_MODULE:
# demande le semestre pour creer le module immediatement:
semestres_indices = list(range(1, parcours.NB_SEM + 1))
fw.append(
(
"semestre_id",
{
"input_type": "menu",
"type": "int",
"title": parcours.SESSION_NAME.capitalize(),
"explanation": "%s de début du module dans la formation"
% parcours.SESSION_NAME,
"labels": [str(x) for x in semestres_indices],
"allowed_values": semestres_indices,
},
)
)
if create and not parcours.UE_IS_MODULE:
fw.append(
(
"create_matiere",
{
"input_type": "boolcheckbox",
"default": False,
"default": True,
"title": "Créer matière identique",
"explanation": "créer immédiatement une matière dans cette UE (utile si on n'utilise pas de matières)",
},
@ -352,13 +351,10 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
if not tf[2]["ue_code"]:
del tf[2]["ue_code"]
if not tf[2]["numero"]:
if not "semestre_id" in tf[2]:
tf[2]["semestre_id"] = 0
# numero regroupant par semestre ou année:
tf[2]["numero"] = next_ue_numero(
formation_id, int(tf[2]["semestre_id"] or 0)
formation_id, int(tf[2]["semestre_idx"])
)
ue_id = do_ue_create(tf[2])
if parcours.UE_IS_MODULE or tf[2]["create_matiere"]:
matiere_id = sco_edit_matiere.do_matiere_create(
@ -374,7 +370,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
"ue_id": ue_id,
"matiere_id": matiere_id,
"formation_id": formation_id,
"semestre_id": tf[2]["semestre_id"],
"semestre_id": tf[2]["semestre_idx"],
},
)
else:
@ -387,14 +383,18 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
def _add_ue_semestre_id(ues):
"""ajoute semestre_id dans les ue, en regardant le premier module de chacune.
"""ajoute semestre_id dans les ue, en regardant
semestre_idx ou à défaut le premier module de chacune.
Les UE sans modules se voient attribuer le numero UE_SEM_DEFAULT (1000000),
qui les place à la fin de la liste.
"""
for ue in ues:
Modlist = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
if Modlist:
ue["semestre_id"] = Modlist[0]["semestre_id"]
if ue["semestre_idx"] is not None:
ue["semestre_id"] = ue["semestre_idx"]
else:
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
if modules:
ue["semestre_id"] = modules[0]["semestre_id"]
else:
ue["semestre_id"] = 1000000
@ -452,6 +452,7 @@ def ue_table(formation_id=None, msg=""): # was ue_list
raise ScoValueError("invalid formation_id")
F = F[0]
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
is_apc = parcours.APC_SAE
locked = sco_formations.formation_has_locked_sems(formation_id)
ues = ue_list(args={"formation_id": formation_id, "is_external": False})
@ -567,7 +568,18 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
)
H.append("</div>")
# Formation APC (BUT) ?
if is_apc:
H.append(
f"""<div class="formation_apc_infos">
<div class="ue_list_tit">Formation par compétences (BUT)</div>
<ul>
<li><a class="stdlink" href="{
url_for('notes.edit_modules_ue_coefs', scodoc_dept=g.scodoc_dept, formation_id=formation_id)
}">éditer les coefficients des ressources et SAÉs</a></li>
</ul>
</div>"""
)
# Description des UE/matières/modules
H.append('<div class="formation_ue_list">')
H.append('<div class="ue_list_tit">Programme pédagogique:</div>')
@ -775,8 +787,12 @@ def _ue_table_ues(
)
else:
H.append('<span class="locked">[verrouillé]</span>')
if parcours.APC_SAE:
func_html_list = _ue_table_ressources_saes
else:
func_html_list = _ue_table_matieres
H.append(
_ue_table_matieres(
func_html_list(
parcours,
ue,
editable,
@ -837,6 +853,8 @@ def _ue_table_matieres(
delete_disabled_icon,
)
)
if not parcours.UE_IS_MODULE:
H.append("</li>")
if not matieres:
H.append("<li>Aucune matière dans cette UE ! ")
if editable:
@ -855,6 +873,76 @@ def _ue_table_matieres(
return "\n".join(H)
def _ue_table_ressources_saes(
parcours,
ue,
editable,
tag_editable,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
):
"""Édition de programme: liste des ressources et SAÉs d'une UE.
(pour les parcours APC_SAE)
"""
matieres = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
if not matieres:
# Les formations APC (BUT) n'utilisent pas de matières
# mais il doit y en avoir une par UE
# silently fix this on-the-fly to ease migration
_ = sco_edit_matiere.do_matiere_create(
{"ue_id": ue["ue_id"], "titre": "APC", "numero": 1},
)
matieres = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
assert matieres
mat = matieres[0]
H = [
"""
<ul class="notes_matiere_list but_matiere_list">
"""
]
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
for titre, element_name, module_type in (
("Ressources", "ressource", scu.ModuleType.RESSOURCE),
("SAÉs", "SAÉ", scu.ModuleType.SAE),
("Autres modules", "xxx", None),
):
H.append(f'<li class="notes_matiere_list">{titre}')
elements = [
m
for m in modules
if module_type == m["module_type"]
or (
(module_type is None)
and m["module_type"]
not in (scu.ModuleType.RESSOURCE, scu.ModuleType.SAE)
)
]
H.append(
_ue_table_modules(
parcours,
mat,
elements,
editable,
tag_editable,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
empty_list_msg="Aucune " + element_name,
create_element_msg="créer une " + element_name,
add_suppress_link=False,
)
)
H.append("</li></ul>")
return "\n".join(H)
def _ue_table_modules(
parcours,
mat,
@ -866,6 +954,10 @@ def _ue_table_modules(
arrow_none,
delete_icon,
delete_disabled_icon,
unit_name="matière",
add_suppress_link=True, # lien "supprimer cette matière"
empty_list_msg="Aucun élément dans cette matière",
create_element_msg="créer un module",
):
"""Édition de programme: liste des modules d'une matière d'une UE"""
H = ['<ul class="notes_module_list">']
@ -875,7 +967,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('<li class="%s">' % klass)
@ -948,8 +1040,8 @@ def _ue_table_modules(
)
H.append("</li>")
if not modules:
H.append("<li>Aucun module dans cette matière ! ")
if editable:
H.append(f"<li>{empty_list_msg} ! ")
if editable and add_suppress_link:
H.append(
f"""<a class="stdlink" href="{
url_for("notes.matiere_delete",
@ -963,11 +1055,10 @@ def _ue_table_modules(
f"""<li> <a class="stdlink" href="{
url_for("notes.module_create",
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"])}"
>créer un module</a></li>
>{create_element_msg}</a></li>
"""
)
H.append("</ul>")
H.append("</li>")
return "\n".join(H)
@ -987,20 +1078,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:

View File

@ -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 <a href="%(url)s">%(titre)s</a>' % 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 <a href="%(url)s">%(titre)s</a>' % 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"],
)
)

View File

@ -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()
+ "<h2>Opération non autorisée</h2><p>"
+ "Modification évaluation impossible pour %s"
% current_user.get_nomplogin()
+ "</p>"
+ '<p><a href="moduleimpl_status?moduleimpl_id=%s">Revenir</a></p>'
% (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 = '<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> %s' % (
moduleimpl_id,
mod["code"],
mod["titre"],
link,
)
H = [
f"""<h3>{action} en
{scu.MODULE_TYPE_NAMES[mod["module_type"]]} {mod_descr}</h3>
"""
]
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)

View File

@ -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 <a href="%(url)s">%(titre)s</a>' % 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 <a href="%(url)s">%(titre)s</a>' % 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 += ' <span class="eval_malus">(points de malus)</span>'
H = [
'<span class="eval_title">Evaluation%s</span><p><b>Module : %s</b></p>'
% (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("<p><b>UE : %(acronyme)s</b></p>" % ue)
@ -1099,269 +681,3 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
H.append("</p>")
return '<div class="eval_description">' + "\n".join(H) + "</div>"
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()
+ "<h2>Opération non autorisée</h2><p>"
+ "Modification évaluation impossible pour %s"
% current_user.get_nomplogin()
+ "</p>"
+ '<p><a href="moduleimpl_status?moduleimpl_id=%s">Revenir</a></p>'
% (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 = (
'<span class="evallink"><a class="stdlink" href="evaluation_listenotes?moduleimpl_id=%s">voir toutes les notes du module</a></span>'
% 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 = """<div class="help"><p class="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.
</p><p class="help">
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.
</p><p class="help">
L'option <em>Visible sur bulletins</em> 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).
</p><p class="help">
Les modalités "rattrapage" et "deuxième session" définissent des évaluations prises en compte de
façon spéciale: </p>
<ul>
<li>les notes d'une évaluation de "rattrapage" remplaceront les moyennes du module
<em>si elles sont meilleures que celles calculées</em>.</li>
<li>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.</li>
</ul>
<p class="help">
Dans ces deux cas, le coefficient est ignoré, et toutes les notes n'ont
pas besoin d'être rentrées.
</p>
<p class="help">
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).
</p>
"""
mod_descr = '<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> %s' % (
moduleimpl_id,
Mod["code"],
Mod["titre"],
link,
)
if not readonly:
H = ["<h3>%svaluation en %s</h3>" % (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)

View File

@ -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"]

View File

@ -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 += [
'<b>impossible de supprimer %s (%s) car il y a %d évaluations définies (<a href="moduleimpl_status?moduleimpl_id=%s" class="stdlink">supprimer les d\'abord</a>)</b>'
@ -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):
</ol></div>""",
]
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(
"""<p class="warning">Attention: il y a %d évaluations dans ce semestre (sa suppression entrainera l'effacement définif des notes) !</p>"""
@ -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:

View File

@ -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):
),
"""<p><b style="font-size: 130%">Tableau de bord: </b><span class="help">cliquez sur un module pour saisir des notes</span></p>""",
]
nt = sco_cache.NotesTableCache.get(formsemestre_id)
if nt.expr_diagnostics:
H.append(html_expr_diagnostic(nt.expr_diagnostics))
H.append(
"""
<p>
<table class="formsemestre_status">
<tr>
<th class="formsemestre_status">Code</th>
<th class="formsemestre_status">Module</th>
<th class="formsemestre_status">Inscrits</th>
<th class="resp">Responsable</th>
<th class="evals">Evaluations</th></tr>"""
)
mails_enseignants = set(
[sco_users.user_info(ens_id)["email"] for ens_id in sem["responsables"]]
) # adr. mail des enseignants
for M in Mlist:
Mod = M["module"]
ModDescr = (
"Module "
+ M["module"]["titre"]
+ ", coef. "
+ str(M["module"]["coefficient"])
)
ModEns = sco_users.user_info(M["responsable_id"])["nomcomplet"]
if M["ens"]:
ModEns += " (resp.), " + ", ".join(
[sco_users.user_info(e["ens_id"])["nomcomplet"] for e in M["ens"]]
)
ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=M["moduleimpl_id"]
)
mails_enseignants.add(sco_users.user_info(M["responsable_id"])["email"])
mails_enseignants |= set(
[sco_users.user_info(m["ens_id"])["email"] for m in M["ens"]]
)
ue = M["ue"]
if prev_ue_id != ue["ue_id"]:
prev_ue_id = ue["ue_id"]
acronyme = ue["acronyme"]
titre = ue["titre"]
if sco_preferences.get_preference("use_ue_coefs", formsemestre_id):
titre += " <b>(coef. %s)</b>" % (ue["coefficient"] or 0.0)
H.append(
"""<tr class="formsemestre_status_ue"><td colspan="4">
<span class="status_ue_acro">%s</span>
<span class="status_ue_title">%s</span>
</td><td>"""
% (acronyme, titre)
)
expr = sco_compute_moy.get_ue_expression(
formsemestre_id, ue["ue_id"], cnx, html_quote=True
)
if can_edit:
H.append(
' <a href="edit_ue_expr?formsemestre_id=%s&ue_id=%s">'
% (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("</a>")
if expr:
H.append(
""" <span class="formula" title="mode de calcul de la moyenne d'UE">%s</span>"""
% expr
)
H.append("</td></tr>")
if M["ue"]["type"] != sco_codes_parcours.UE_STANDARD:
fontorange = " fontorange" # style css additionnel
else:
fontorange = ""
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, M["moduleimpl_id"])
if (
etat["nb_evals_completes"] > 0
and etat["nb_evals_en_cours"] == 0
and etat["nb_evals_vides"] == 0
):
H.append('<tr class="formsemestre_status_green%s">' % fontorange)
else:
H.append('<tr class="formsemestre_status%s">' % fontorange)
H.append(
'<td class="formsemestre_status_code"><a href="moduleimpl_status?moduleimpl_id=%s" title="%s" class="stdlink">%s</a></td>'
% (M["moduleimpl_id"], ModDescr, Mod["code"])
)
H.append(
'<td class="scotext"><a href="moduleimpl_status?moduleimpl_id=%s" title="%s" class="formsemestre_status_link">%s</a></td>'
% (M["moduleimpl_id"], ModDescr, Mod["abbrev"] or Mod["titre"])
)
H.append('<td class="formsemestre_status_inscrits">%s</td>' % len(ModInscrits))
H.append(
'<td class="resp scotext"><a class="discretelink" href="moduleimpl_status?moduleimpl_id=%s" title="%s">%s</a></td>'
% (
M["moduleimpl_id"],
ModEns,
sco_users.user_info(M["responsable_id"])["prenomnom"],
)
)
if Mod["module_type"] == scu.MODULE_STANDARD:
H.append('<td class="evals">')
nb_evals = (
etat["nb_evals_completes"]
+ etat["nb_evals_en_cours"]
+ etat["nb_evals_vides"]
)
if nb_evals != 0:
H.append(
'<a href="moduleimpl_status?moduleimpl_id=%s" class="formsemestre_status_link">%s prévues, %s ok</a>'
% (M["moduleimpl_id"], nb_evals, etat["nb_evals_completes"])
)
if etat["nb_evals_en_cours"] > 0:
H.append(
', <span><a class="redlink" href="moduleimpl_status?moduleimpl_id=%s" title="Il manque des notes">%s en cours</a></span>'
% (M["moduleimpl_id"], etat["nb_evals_en_cours"])
)
if etat["attente"]:
H.append(
' <span><a class="redlink" href="moduleimpl_status?moduleimpl_id=%s" title="Il y a des notes en attente">[en attente]</a></span>'
% M["moduleimpl_id"]
)
elif Mod["module_type"] == scu.MODULE_MALUS:
nb_malus_notes = sum(
[
e["etat"]["nb_notes"]
for e in nt.get_mod_evaluation_etat_list(M["moduleimpl_id"])
if nt.parcours.APC_SAE:
# BUT: tableau ressources puis SAE
ressources = [
m for m in modimpls if m["module"]["module_type"] == ModuleType.RESSOURCE
]
)
H.append(
"""<td class="malus">
<a href="moduleimpl_status?moduleimpl_id=%s" class="formsemestre_status_link">malus (%d notes)</a>
saes = [m for m in modimpls if m["module"]["module_type"] == ModuleType.SAE]
autres = [
m
for m in modimpls
if m["module"]["module_type"] not in (ModuleType.RESSOURCE, ModuleType.SAE)
]
H += [
"""
% (M["moduleimpl_id"], nb_malus_notes)
)
<div class="tableau_modules">
""",
_TABLEAU_MODULES_HEAD,
f"""<tr class="formsemestre_status_cat">
<td colspan="5">
<span class="status_module_cat">Ressources</span>
</td></tr>""",
formsemestre_tableau_modules(
ressources, nt, formsemestre_id, can_edit=can_edit, show_ues=False
),
f"""<tr class="formsemestre_status_cat">
<td colspan="5">
<span class="status_module_cat">SAÉs</span>
</td></tr>""",
formsemestre_tableau_modules(
saes, nt, formsemestre_id, can_edit=can_edit, show_ues=False
),
]
if autres:
H += [
f"""<tr class="formsemestre_status_cat">
<td colspan="5">
<span class="status_module_cat">Autres modules</span>
</td></tr>""",
formsemestre_tableau_modules(
autres, nt, formsemestre_id, can_edit=can_edit, show_ues=False
),
]
H += [_TABLEAU_MODULES_FOOT, "</div>"]
else:
raise ValueError("Invalid module_type") # a bug
# formations classiques: groupe par UE
H += [
"<p>",
_TABLEAU_MODULES_HEAD,
formsemestre_tableau_modules(
modimpls,
nt,
formsemestre_id,
can_edit=can_edit,
use_ue_coefs=use_ue_coefs,
),
_TABLEAU_MODULES_FOOT,
"</p>",
]
H.append("</td></tr>")
H.append("</table></p>")
if sco_preferences.get_preference("use_ue_coefs", formsemestre_id):
if use_ue_coefs:
H.append(
"""
<p class="infop">utilise les coefficients d'UE pour calculer la moyenne générale.</p>
@ -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 = """
<table class="formsemestre_status">
<tr>
<th class="formsemestre_status">Code</th>
<th class="formsemestre_status">Module</th>
<th class="formsemestre_status">Inscrits</th>
<th class="resp">Responsable</th>
<th class="evals">Évaluations</th>
</tr>
"""
_TABLEAU_MODULES_FOOT = """</table>"""
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 += " <b>(coef. %s)</b>" % (ue["coefficient"] or 0.0)
H.append(
f"""<tr class="formsemestre_status_ue"><td colspan="4">
<span class="status_ue_acro">{ue["acronyme"]}</span>
<span class="status_ue_title">{titre}</span>
</td><td>"""
)
if can_edit:
H.append(
' <a href="edit_ue_expr?formsemestre_id=%s&ue_id=%s">'
% (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("</a>")
expr = sco_compute_moy.get_ue_expression(
formsemestre_id, ue["ue_id"], html_quote=True
)
if expr:
H.append(
""" <span class="formula" title="mode de calcul de la moyenne d'UE">%s</span>"""
% expr
)
H.append("</td></tr>")
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('<tr class="formsemestre_status_green%s">' % fontorange)
else:
H.append('<tr class="formsemestre_status%s">' % fontorange)
H.append(
'<td class="formsemestre_status_code"><a href="moduleimpl_status?moduleimpl_id=%s" title="%s" class="stdlink">%s</a></td>'
% (modimpl["moduleimpl_id"], mod_descr, mod["code"])
)
H.append(
'<td class="scotext"><a href="moduleimpl_status?moduleimpl_id=%s" title="%s" class="formsemestre_status_link">%s</a></td>'
% (modimpl["moduleimpl_id"], mod_descr, mod["abbrev"] or mod["titre"])
)
H.append('<td class="formsemestre_status_inscrits">%s</td>' % len(mod_inscrits))
H.append(
'<td class="resp scotext"><a class="discretelink" href="moduleimpl_status?moduleimpl_id=%s" title="%s">%s</a></td>'
% (
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('<td class="evals">')
nb_evals = (
etat["nb_evals_completes"]
+ etat["nb_evals_en_cours"]
+ etat["nb_evals_vides"]
)
if nb_evals != 0:
H.append(
'<a href="moduleimpl_status?moduleimpl_id=%s" class="formsemestre_status_link">%s prévues, %s ok</a>'
% (modimpl["moduleimpl_id"], nb_evals, etat["nb_evals_completes"])
)
if etat["nb_evals_en_cours"] > 0:
H.append(
', <span><a class="redlink" href="moduleimpl_status?moduleimpl_id=%s" title="Il manque des notes">%s en cours</a></span>'
% (modimpl["moduleimpl_id"], etat["nb_evals_en_cours"])
)
if etat["attente"]:
H.append(
' <span><a class="redlink" href="moduleimpl_status?moduleimpl_id=%s" title="Il y a des notes en attente">[en attente]</a></span>'
% 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(
"""<td class="malus">
<a href="moduleimpl_status?moduleimpl_id=%s" class="formsemestre_status_link">malus (%d notes)</a>
"""
% (modimpl["moduleimpl_id"], nb_malus_notes)
)
else:
raise ValueError("Invalid module_type") # a bug
H.append("</td></tr>")
return "\n".join(H)

View File

@ -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(

View File

@ -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")

View File

@ -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:

View File

@ -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),
"""<h2 class="formsemestre">Module <tt>%(code)s</tt> %(titre)s</h2>""" % Mod,
"""<div class="moduleimpl_tableaubord">
html_sco_header.sco_header(page_title=f"{mod_type_name} {Mod['titre']}"),
f"""<h2 class="formsemestre">{mod_type_name}
<tt>{Mod['code']}</tt> {Mod['titre']}</h2>
<div class="moduleimpl_tableaubord moduleimpl_type_{
scu.ModuleType(Mod['module_type']).name.lower()}">
<table>
<tr>
<td class="fichetitre2">Responsable: </td><td class="redboldtext">""",
module_resp.get_nomcomplet(), # sco_users.user_info(M["responsable_id"])["nomprenom"],
f"""<span class="blacktt">({module_resp.user_name})</span>""",
<td class="fichetitre2">Responsable: </td><td class="redboldtext">
{module_resp.get_nomcomplet()}
<span class="blacktt">({module_resp.user_name})</span>
""",
]
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(
"""<tr><td class="fichetitre2">Inscrits: </td><td> %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):
"""<p><form name="f"><span style="font-size:120%%; font-weight: bold;">%d évaluations :</span>
<span style="padding-left: 30px;">
<input type="hidden" name="moduleimpl_id" value="%s"/>"""
% (len(ModEvals), moduleimpl_id)
% (len(mod_evals), moduleimpl_id)
)
#
# Liste les noms de partitions
@ -341,16 +345,16 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
"""
% M
)
if ModEvals:
if mod_evals:
H.append(
'<div class="moduleimpl_evaluations_top_links">'
+ top_table_links
+ "</div>"
)
H.append("""<table class="moduleimpl_evaluations">""")
eval_index = len(ModEvals) - 1
eval_index = len(mod_evals) - 1
first = True
for eval in ModEvals:
for eval in mod_evals:
etat = sco_evaluations.do_evaluation_etat(
eval["evaluation_id"],
partition_id=partition_id,
@ -399,7 +403,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
)
# Fleches:
H.append('<span class="eval_arrows_chld">')
if eval_index != (len(ModEvals) - 1) and caneditevals:
if eval_index != (len(mod_evals) - 1) and caneditevals:
H.append(
'<a href="module_evaluation_move?evaluation_id=%s&after=0" class="aud">%s</a>'
% (eval["evaluation_id"], arrow_up)

View File

@ -42,7 +42,6 @@ from app import log
from app.scodoc import sco_formsemestre
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_preferences
from app.scodoc import sco_users
from app import email
@ -82,6 +81,8 @@ def add(typ, object=None, text="", url=None, max_frequency=False):
Si max_frequency, ne genere pas 2 nouvelles identiques à moins de max_frequency
secondes d'intervalle.
"""
from app.scodoc import sco_users
authuser_name = current_user.user_name
cnx = ndb.GetDBConnexion()
args = {
@ -112,6 +113,7 @@ def scolar_news_summary(n=5):
News are "compressed", ie redondant events are joined.
"""
from app.scodoc import sco_etud
from app.scodoc import sco_users
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)

View File

@ -539,7 +539,7 @@ class SituationEtudParcoursGeneric(object):
"""Enregistre la decision (instance de DecisionSem)
Enregistre codes semestre et UE, et autorisations inscription.
"""
cnx = ndb.GetDBConnexion(autocommit=False)
cnx = ndb.GetDBConnexion()
# -- check
if decision.code_etat in self.parcours.UNUSED_CODES:
raise ScoValueError("code decision invalide dans ce parcours")
@ -902,7 +902,7 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
Les UE des semestres NON ASSIDUS ne sont jamais validées (code AJ).
"""
valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
cnx = ndb.GetDBConnexion(autocommit=False)
cnx = ndb.GetDBConnexion()
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_ues, get_etud_ue_status
ue_ids = [x["ue_id"] for x in nt.get_ues(etudid=etudid, filter_sport=True)]
for ue_id in ue_ids:

View File

@ -54,6 +54,7 @@ from app import ScoValueError
from app.scodoc import html_sco_header, sco_preferences
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_excel
from app.scodoc.sco_excel import ScoExcelBook, COLORS
from app.scodoc import sco_formsemestre
@ -137,7 +138,9 @@ class PlacementForm(FlaskForm):
def set_evaluation_infos(self, evaluation_id):
"""Initialise les données du formulaire avec les données de l'évaluation."""
eval_data = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
eval_data = sco_evaluation_db.do_evaluation_list(
{"evaluation_id": evaluation_id}
)
if not eval_data:
raise ScoValueError("invalid evaluation_id")
self.groups_tree, self.has_groups, self.nb_groups = _get_group_info(
@ -236,7 +239,7 @@ class PlacementRunner:
self.groups_ids = [
gid if gid != TOUS else form.tous_id for gid in form["groups"].data
]
self.eval_data = sco_evaluations.do_evaluation_list(
self.eval_data = sco_evaluation_db.do_evaluation_list(
{"evaluation_id": self.evaluation_id}
)[0]
self.groups = sco_groups.listgroups(self.groups_ids)

View File

@ -45,11 +45,11 @@ from app.scodoc import sco_bulletins, sco_excel
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
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
from app.scodoc import sco_groups
from app.scodoc import sco_permissions
from app.scodoc import sco_permissions_check
from app.scodoc import sco_preferences
from app.scodoc import sco_etud
@ -818,9 +818,9 @@ def _list_notes_evals(evals, etudid):
or e["etat"]["evalattente"]
or e["publish_incomplete"]
):
NotesDB = sco_evaluations.do_evaluation_get_all_notes(e["evaluation_id"])
if etudid in NotesDB:
val = NotesDB[etudid]["value"]
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(e["evaluation_id"])
if etudid in notes_db:
val = notes_db[etudid]["value"]
else:
# Note manquante mais prise en compte immédiate: affiche ATT
val = scu.NOTES_ATTENTE

View File

@ -40,7 +40,7 @@ from flask import url_for, g, request
import pydot
import app.scodoc.sco_utils as scu
from app.models import NotesFormModalite
from app.models import FormationModalite
from app.scodoc import notesdb as ndb
from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours
@ -1340,7 +1340,7 @@ def graph_parcours(
log("n=%s" % n)
log("get=%s" % g.get_node(sem_node_name(s)))
log("nodes names = %s" % [x.get_name() for x in g.get_node_list()])
if s["modalite"] and s["modalite"] != NotesFormModalite.DEFAULT_MODALITE:
if s["modalite"] and s["modalite"] != FormationModalite.DEFAULT_MODALITE:
modalite = " " + s["modalite"]
else:
modalite = ""

View File

@ -39,6 +39,7 @@ from flask import g, url_for, request
from flask_login import current_user
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_exceptions import (
@ -56,6 +57,7 @@ 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_evaluation_db
from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
@ -133,9 +135,13 @@ def _check_notes(notes, evaluation, mod):
and 4 lists of etudid: invalids, withoutnotes, absents, tosuppress, existingjury
"""
note_max = evaluation["note_max"]
if mod["module_type"] == scu.MODULE_STANDARD:
if mod["module_type"] in (
scu.ModuleType.STANDARD,
scu.ModuleType.RESSOURCE,
scu.ModuleType.SAE,
):
note_min = scu.NOTES_MIN
elif mod["module_type"] == scu.MODULE_MALUS:
elif mod["module_type"] == ModuleType.MALUS:
note_min = -20.0
else:
raise ValueError("Invalid module type") # bug
@ -176,7 +182,7 @@ def do_evaluation_upload_xls():
vals = scu.get_request_args()
evaluation_id = int(vals["evaluation_id"])
comment = vals["comment"]
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
# Check access
# (admin, respformation, and responsable_id)
@ -253,7 +259,9 @@ def do_evaluation_upload_xls():
authuser, evaluation_id, L, comment
)
# news
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.do_evaluation_list({"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]
mod["moduleimpl_id"] = M["moduleimpl_id"]
@ -292,7 +300,7 @@ def do_evaluation_upload_xls():
def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
"""Initialisation des notes manquantes"""
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
# Check access
# (admin, respformation, and responsable_id)
@ -300,7 +308,7 @@ def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
# XXX imaginer un redirect + msg erreur
raise AccessDenied("Modification des notes impossible pour %s" % current_user)
#
NotesDB = sco_evaluations.do_evaluation_get_all_notes(evaluation_id)
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
etudids = sco_groups.do_evaluation_listeetuds_groups(
evaluation_id, getallstudents=True, include_dems=False
)
@ -378,19 +386,19 @@ def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
"suppress all notes in this eval"
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
if sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=False
):
# On a le droit de modifier toutes les notes
# recupere les etuds ayant une note
NotesDB = sco_evaluations.do_evaluation_get_all_notes(evaluation_id)
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
elif sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=True
):
# Enseignant associé au module: ne peut supprimer que les notes qu'il a saisi
NotesDB = sco_evaluations.do_evaluation_get_all_notes(
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(
evaluation_id, by_uid=current_user.id
)
else:
@ -471,13 +479,13 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
"etudiant %s: valeur de note invalide (%s)" % (etudid, value)
)
# Recherche notes existantes
NotesDB = sco_evaluations.do_evaluation_get_all_notes(evaluation_id)
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
# Met a jour la base
cnx = ndb.GetDBConnexion(autocommit=False)
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
nb_changed = 0
nb_suppress = 0
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
existing_decisions = (
[]
@ -596,7 +604,7 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
def saisie_notes_tableur(evaluation_id, group_ids=()):
"""Saisie des notes via un fichier Excel"""
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
raise ScoValueError("invalid evaluation_id")
E = evals[0]
@ -767,7 +775,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
def feuille_saisie_notes(evaluation_id, group_ids=[]):
"""Document Excel pour saisie notes dans l'évaluation et les groupes indiqués"""
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
raise ScoValueError("invalid evaluation_id")
E = evals[0]
@ -866,7 +874,7 @@ def has_existing_decision(M, E, etudid):
def saisie_notes(evaluation_id, group_ids=[]):
"""Formulaire saisie notes d'une évaluation pour un groupe"""
group_ids = [int(group_id) for group_id in group_ids]
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
raise ScoValueError("invalid evaluation_id")
E = evals[0]
@ -977,7 +985,7 @@ def saisie_notes(evaluation_id, group_ids=[]):
def _get_sorted_etuds(E, etudids, formsemestre_id):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
NotesDB = sco_evaluations.do_evaluation_get_all_notes(
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
E["evaluation_id"]
) # Notes existantes
cnx = ndb.GetDBConnexion()
@ -1017,14 +1025,14 @@ def _get_sorted_etuds(E, etudids, formsemestre_id):
e["absinfo"] = '<span class="sn_abs">' + " ".join(warn_abs_lst) + "</span> "
# Note actuelle de l'étudiant:
if etudid in NotesDB:
e["val"] = _displayNote(NotesDB[etudid]["value"])
comment = NotesDB[etudid]["comment"]
if etudid in notes_db:
e["val"] = _displayNote(notes_db[etudid]["value"])
comment = notes_db[etudid]["comment"]
if comment is None:
comment = ""
e["explanation"] = "%s (%s) %s" % (
NotesDB[etudid]["date"].strftime("%d/%m/%y %Hh%M"),
NotesDB[etudid]["uid"],
notes_db[etudid]["date"].strftime("%d/%m/%y %Hh%M"),
notes_db[etudid]["uid"],
comment,
)
else:
@ -1073,7 +1081,11 @@ def _form_saisie_notes(E, M, group_ids, destination=""):
("comment", {"size": 44, "title": "Commentaire", "return_focus_next": True}),
("changed", {"default": "0", "input_type": "hidden"}), # changed in JS
]
if M["module"]["module_type"] == scu.MODULE_STANDARD:
if M["module"]["module_type"] in (
ModuleType.STANDARD,
ModuleType.RESSOURCE,
ModuleType.SAE,
):
descr.append(
(
"s3",
@ -1086,7 +1098,7 @@ def _form_saisie_notes(E, M, group_ids, destination=""):
},
)
)
elif M["module"]["module_type"] == scu.MODULE_MALUS:
elif M["module"]["module_type"] == ModuleType.MALUS:
descr.append(
(
"s3",
@ -1229,7 +1241,7 @@ def save_note(etudid=None, evaluation_id=None, value=None, comment=""):
"save_note: evaluation_id=%s etudid=%s uid=%s value=%s"
% (evaluation_id, etudid, authuser, value)
)
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.do_evaluation_list({"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]
Mod["url"] = url_for(

View File

@ -27,7 +27,7 @@
"""Fonction de gestion des UE "externes" (effectuees dans un cursus exterieur)
On rapatrie (saisit) les notes (et crédits ECTS).
On rapatrie (saisie) les notes (et crédits ECTS).
Cas d'usage: les étudiants d'une formation gérée par ScoDoc peuvent
suivre un certain nombre d'UE à l'extérieur. L'établissement a reconnu
@ -66,6 +66,7 @@ from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module
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_moduleimpl
@ -148,13 +149,15 @@ def external_ue_inscrit_et_note(moduleimpl_id, formsemestre_id, notes_etuds):
)
# Création d'une évaluation si il n'y en a pas déjà:
ModEvals = sco_evaluations.do_evaluation_list(args={"moduleimpl_id": moduleimpl_id})
if len(ModEvals):
mod_evals = sco_evaluation_db.do_evaluation_list(
args={"moduleimpl_id": moduleimpl_id}
)
if len(mod_evals):
# met la note dans le première évaluation existante:
evaluation_id = ModEvals[0]["evaluation_id"]
evaluation_id = mod_evals[0]["evaluation_id"]
else:
# crée une évaluation:
evaluation_id = sco_evaluations.do_evaluation_create(
evaluation_id = sco_evaluation_db.do_evaluation_create(
moduleimpl_id=moduleimpl_id,
note_max=20.0,
coefficient=1.0,

View File

@ -53,6 +53,7 @@ from app.scodoc.intervals import intervalmap
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_preferences
@ -105,12 +106,12 @@ class NotesOperation(dict):
def list_operations(evaluation_id):
"""returns list of NotesOperation for this evaluation"""
notes = list(
sco_evaluations.do_evaluation_get_all_notes(
sco_evaluation_db.do_evaluation_get_all_notes(
evaluation_id, filter_suppressed=False
).values()
)
notes_log = list(
sco_evaluations.do_evaluation_get_all_notes(
sco_evaluation_db.do_evaluation_get_all_notes(
evaluation_id, filter_suppressed=False, table="notes_notes_log"
).values()
)
@ -148,7 +149,7 @@ def list_operations(evaluation_id):
def evaluation_list_operations(evaluation_id):
"""Page listing operations on evaluation"""
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
Ops = list_operations(evaluation_id)
@ -178,7 +179,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
r = ndb.SimpleDictFetch(
"""SELECT i.nom, code_nip, n.*, mod.titre, e.description, e.jour
"""SELECT i.nom, i.id as etudid, n.*, mod.titre, e.description, e.jour
FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi,
notes_modules mod, identite i
WHERE mi.id = e.moduleimpl_id

View File

@ -32,13 +32,12 @@ import base64
import bisect
import copy
import datetime
from enum import IntEnum
import json
from hashlib import md5
import numbers
import os
import pydot
import re
import requests
import _thread
import time
import unicodedata
@ -46,9 +45,11 @@ import urllib
from urllib.parse import urlparse, parse_qsl, urlunparse, urlencode
from PIL import Image as PILImage
import pydot
import requests
from flask import g, request
from flask import url_for, make_response
from flask import url_for, make_response, jsonify
from config import Config
from app import log
@ -70,8 +71,22 @@ NOTES_ATTENTE = -1002.0 # note "en attente" (se calcule comme une note neutrali
# Types de modules
MODULE_STANDARD = 0
MODULE_MALUS = 1
class ModuleType(IntEnum):
"""Code des types de module."""
# Stockés en BD dans Module.module_type: ne pas modifier ces valeurs
STANDARD = 0
MALUS = 1
RESSOURCE = 2 # BUT
SAE = 3 # BUT
MODULE_TYPE_NAMES = {
ModuleType.STANDARD: "Module",
ModuleType.MALUS: "Malus",
ModuleType.RESSOURCE: "Ressource",
ModuleType.SAE: "SAÉ",
}
MALUS_MAX = 20.0
MALUS_MIN = -20.0
@ -655,6 +670,17 @@ def get_request_args():
return vals
def json_error(message, success=False, status=404):
"""Simple JSON response, for errors"""
response = {
"success": success,
"status": status,
"message": message,
}
log(f"Error: {response}")
return jsonify(response), status
def get_scodoc_version():
"return a string identifying ScoDoc version"
return sco_version.SCOVERSION

View File

@ -1,5 +1,5 @@
/* # -*- mode: css -*-
ScoDoc, (c) Emmanuel Viennet 1998 - 2020
ScoDoc, (c) Emmanuel Viennet 1998 - 2021
*/
html,body {
@ -1192,6 +1192,7 @@ table.formsemestre_status {
tr.formsemestre_status { background-color: rgb(90%,90%,90%); }
tr.formsemestre_status_green { background-color: #EFF7F2; }
tr.formsemestre_status_ue { background-color: rgb(90%,90%,90%); }
tr.formsemestre_status_cat td { padding-top: 2ex;}
table.formsemestre_status td {
border-top: 1px solid rgb(80%,80%,80%);
border-bottom: 1px solid rgb(80%,80%,80%);
@ -1236,6 +1237,7 @@ td.formsemestre_status_cell {
span.status_ue_acro { font-weight: bold; }
span.status_ue_title { font-style: italic; padding-left: 1cm;}
span.status_module_cat { font-weight: bold; }
table.formsemestre_inscr td {
padding-right: 1.25em;
@ -1268,11 +1270,18 @@ ul.ue_inscr_list li.etud {
border-spacing: 1px;
}
/* Modules */
/* Tableau de bord module */
div.moduleimpl_tableaubord {
padding: 7px;
border: 2px solid gray;
}
div.moduleimpl_type_sae {
background-color:#cfeccf;
}
div.moduleimpl_type_ressource {
background-color:#f5e9d2;
}
span.moduleimpl_abs_link {
padding-right: 2em;
}
@ -1597,6 +1606,11 @@ div.ue_warning span {
font-weight: bold;
}
span.missing_value {
font-weight: bold;
color: red;
}
/* tableau recap notes */
table.notes_recapcomplet {
border: 2px solid blue;

View File

@ -0,0 +1,59 @@
/* table_editor, par Sébastien L.
*/
body {
font-family: Arial, Helvetica, sans-serif;
}
/***************************/
/* Le tableau */
/***************************/
.tableau{
display: grid;
grid-auto-rows: minmax(24px, auto);
gap: 2px;
}
.entete{
background: #09c;
font-weight: bold;
}
.tableau>div{
padding: 4px 8px;
border-radius: 4px;
border: 1px solid #999;
grid-column: var(--x) / span var(--nbX);
grid-row: var(--y) / span var(--nbY);
}
[data-editable="true"]{
cursor: pointer;
}
/***************************/
/* Statut des cellules */
/***************************/
.selected{ outline: 1px solid #c09; }
.modifying{ outline: 2px dashed #c09; }
.wait{ outline: 2px solid #c90; }
.good{ outline: 2px solid #9c0; }
.modified { font-weight: bold; color:indigo}
/***************************/
/* Message */
/***************************/
.message{
position: fixed;
bottom: 100%;
left: 50%;
z-index: 10;
padding: 20px;
border-radius: 0 0 10px 10px;
background: #ec7068;
background: #90c;
color: #FFF;
font-size: 24px;
animation: message 3s;
transform: translate(-50%, 0);
}
@keyframes message{
20%{transform: translate(-50%, 100%)}
80%{transform: translate(-50%, 100%)}
}

View File

@ -0,0 +1,128 @@
/* table_editor, par Sébastien L. 2021-11-12
*/
/*****************************/
/* Mise en place des données */
/*****************************/
function build_table(data) {
let output = "";
data.forEach((cellule) => {
output += `
<div
class="${cellule.style || ""}"
data-editable="${cellule.editable || "false"}"
data-module_id="${cellule.module_id}"
data-ue_id="${cellule.ue_id}"
data-x="${cellule.x}"
data-y="${cellule.y}"
data-nbX="${cellule.nbX || 1}"
data-nbY="${cellule.nbY || 1}"
data-data="${cellule.data}"
data-orig="${cellule.data}"
style="
--x:${cellule.x};
--y:${cellule.y};
--nbX:${cellule.nbX || 1};
--nbY: ${cellule.nbY || 1};
">
${cellule.data}
</div>`;
})
document.querySelector(".tableau").innerHTML = output;
installListeners();
}
/*****************************/
/* Gestion des évènements */
/*****************************/
$(function () {
document.body.addEventListener("keydown", key);
});
function installListeners() {
document.querySelectorAll("[data-editable=true]").forEach(cellule => {
cellule.addEventListener("click", function () { selectCell(this) });
cellule.addEventListener("dblclick", function () { modifCell(this) });
});
}
/*********************************/
/* Interaction avec les cellules */
/*********************************/
function selectCell(obj) {
if (obj.classList.contains("modifying")) {
return; // Cellule en cours de modification, ne pas sélectionner.
}
let currentModif = document.querySelector(".modifying");
if (currentModif) {
if (!save(currentModif)) {
return;
}
}
document.querySelectorAll(".selected, .modifying").forEach(cellule => {
cellule.classList.remove("selected", "modifying");
cellule.removeAttribute("contentEditable");
cellule.removeEventListener("keydown", keyCell);
})
obj.classList.add("selected");
}
function modifCell(obj) {
if (obj) {
obj.classList.add("modifying");
obj.contentEditable = true;
obj.addEventListener("keydown", keyCell);
obj.focus();
}
}
function key(event) {
switch (event.key) {
case "Enter": modifCell(document.querySelector(".selected")); event.preventDefault(); break;
case "ArrowRight": ArrowMove(1, 0); break;
case "ArrowLeft": ArrowMove(-1, 0); break;
case "ArrowUp": ArrowMove(0, -1); break;
case "ArrowDown": ArrowMove(0, 1); break;
}
}
function ArrowMove(x, y) {
if (document.querySelector(".modifying") || !document.querySelector(".selected")) {
return; // S'il n'y a aucune cellule selectionnée ou si une cellule est encours de modification, on ne change pas
}
let selected = document.querySelector(".selected");
let next = document.querySelector(`[data-x="${parseInt(selected.dataset.x) + x}"][data-y="${parseInt(selected.dataset.y) + y}"][data-editable="true"]`);
if (next) {
selectCell(next);
}
}
function keyCell(event) {
if (event.key == "Enter") {
event.preventDefault();
event.stopPropagation();
if (!save(this)) {
return
}
this.classList.remove("modifying");
ArrowMove(0, 1);
modifCell(document.querySelector(".selected"));
}
}
/******************************/
/* Affichage d'un message */
/******************************/
function message(msg) {
var div = document.createElement("div");
div.className = "message";
div.innerHTML = msg;
document.querySelector("body").appendChild(div);
setTimeout(() => {
div.remove();
}, 3000);
}

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
<script src="/ScoDoc/static/js/table_editor.js"></script>
<link href="/ScoDoc/static/css/table_editor.css" rel="stylesheet" type="text/css" />
<title>Édition coef. formation</title>
</head>
<body>
<h2>Formation {{formation.titre}} ({{formation.acronyme}})
[version {{formation.version}}] code {{formation.code}}</h2>
<div class="tableau"></div>
<script>
$(function () {
$.getJSON("{{data_source}}", function (data) {
build_table(data);
});
});
function save(obj) {
var value = obj.innerText.trim();
if (value.length == 0) {
value = "0";
}
if (!/^[\d.,]+$/.test(value)) {
message("Il est attendu un nombre");
return false;
}
if (value == obj.dataset.data) {
return true; // Aucune modification, pas d'enregistrement mais on continue normalement
}
obj.dataset.data = value;
obj.classList.add("wait");
// XXX DEBUG
// console.log(`
// x : ${getComputedStyle(obj).getPropertyValue("--x")}
// y : ${getComputedStyle(obj).getPropertyValue("--y")}
// data : ${value}
// ue_id: ${obj.dataset.ue_id}
// module_id : ${obj.dataset.module_id}
// `);
$.post("{{data_save}}",
{
module_id: obj.dataset.module_id,
ue_id: obj.dataset.ue_id,
coef: value
},
function (result) {
obj.classList.remove("wait");
if (obj.dataset.orig != value)
obj.classList.add("modified");
else
obj.classList.remove("modified");
// Lorsque les données sont bien enregistrées, on enlève
// l'indication que c'est bon au bout d'un temps
//setTimeout(() => {
// obj.classList.remove("modified");
//}, 1000);
}
);
return true;
}
</script>
</body>
</html>

View File

@ -0,0 +1,53 @@
<div class="help">
<p class="help">
Le coefficient d'une évaluation est utilisé 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.
</p>
{%if is_apc%}
<p class="help help_but">
Dans le BUT, une évaluation peut évaluer différents apprentissages critiques... (à compléter)
Le coefficient est multiplié par les poids vers chaque UE.
</p>
{%endif%}
<p class="help">
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.
</p>
<p class="help">
L'option <em>Visible sur bulletins</em> 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).
</p>
<p class="help">
Les modalités "rattrapage" et "deuxième session" définissent des
évaluations prises en compte de façon spéciale:
</p>
<ul>
<li>les notes d'une évaluation de "rattrapage" remplaceront les moyennes
du module <em>si elles sont meilleures que celles calculées</em>.
</li>
<li>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.
</li>
</ul>
<p class="help">
Dans ces deux cas, le coefficient est ignoré, et toutes les notes n'ont
pas besoin d'être rentrées.
</p>
<p class="help">
Par ailleurs, les évaluations des modules de type "<b>malus</b>" 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).
</p>
</div>

View File

@ -0,0 +1,27 @@
<div class="help">
<p class="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 <em>évaluations</em>. <br />
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 <em>coefficient</em> associé au module.
</p>
<p class="help">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, et le responsable du semestre, pourront
saisir et modifier les notes de ce module.
</p>
{%if is_apc%}
<p class="help help_but">
Dans le BUT, les modules peuvent être de type "ressource" ou "Situation
d'apprentissage et d'évaluation" (SAÉ). Ne pas oublier de préciser le
type, et de saisir les coefficients pondérant l'influence de la
ressource ou SAÉ vers les Unités d'Enseignement (UE).
Voir les détails sur
<a href="https://scodoc.org/BUT" target="_blank">la documentation</a>.
</p>
{%endif%}
</div>

View File

@ -16,7 +16,7 @@ notes_bp = Blueprint("notes", __name__)
users_bp = Blueprint("users", __name__)
absences_bp = Blueprint("absences", __name__)
from app.views import scodoc, notes, scolar, absences, users
from app.views import scodoc, notes, scolar, absences, users, pn_modules
# Cette fonction est bien appelée avant toutes les requêtes

View File

@ -83,7 +83,6 @@ from app.scodoc import sco_archives
from app.scodoc import sco_bulletins
from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_compute_moy
from app.scodoc import sco_cost_formation
from app.scodoc import sco_debouche
@ -94,7 +93,8 @@ from app.scodoc import sco_edit_ue
from app.scodoc import sco_etape_apogee_view
from app.scodoc import sco_etud
from app.scodoc import sco_evaluations
from app.scodoc import sco_excel
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_evaluation_edit
from app.scodoc import sco_export_results
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
@ -306,12 +306,12 @@ sco_publish(
)
sco_publish(
"/module_evaluation_renumber",
sco_evaluations.module_evaluation_renumber,
sco_evaluation_db.module_evaluation_renumber,
Permission.ScoView,
)
sco_publish(
"/module_evaluation_move",
sco_evaluations.module_evaluation_move,
sco_evaluation_db.module_evaluation_move,
Permission.ScoView,
)
sco_publish(
@ -354,7 +354,7 @@ def ue_table(formation_id=None, msg=""):
@scodoc7func
def ue_set_internal(ue_id):
""""""
ue = models.formations.NotesUE.query.get(ue_id)
ue = models.formations.UniteEns.query.get(ue_id)
if not ue:
raise ScoValueError("invalid ue_id")
ue.is_external = False
@ -1557,7 +1557,7 @@ sco_publish(
@scodoc7func
def evaluation_delete(evaluation_id):
"""Form delete evaluation"""
El = sco_evaluations.do_evaluation_list(args={"evaluation_id": evaluation_id})
El = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})
if not El:
raise ValueError("Evalution inexistante ! (%s)" % evaluation_id)
E = El[0]
@ -1630,7 +1630,7 @@ def evaluation_delete(evaluation_id):
sco_publish(
"/do_evaluation_list",
sco_evaluations.do_evaluation_list,
sco_evaluation_db.do_evaluation_list,
Permission.ScoView,
)
@ -1641,7 +1641,7 @@ sco_publish(
@scodoc7func
def evaluation_edit(evaluation_id):
"form edit evaluation"
return sco_evaluations.evaluation_create_form(
return sco_evaluation_edit.evaluation_create_form(
evaluation_id=evaluation_id, edit=True
)
@ -1652,7 +1652,7 @@ def evaluation_edit(evaluation_id):
@scodoc7func
def evaluation_create(moduleimpl_id):
"form create evaluation"
return sco_evaluations.evaluation_create_form(
return sco_evaluation_edit.evaluation_create_form(
moduleimpl_id=moduleimpl_id, edit=False
)

169
app/views/pn_modules.py Normal file
View File

@ -0,0 +1,169 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# ScoDoc
#
# 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
#
##############################################################################
"""
PN / Edition des coefs
Emmanuel Viennet, 2021
"""
from flask import url_for
from flask import jsonify
from flask import current_app, g, request
from flask.templating import render_template
from flask_login import current_user
from werkzeug.utils import redirect
from config import Config
from app import db
from app import models
from app.auth.models import User
from app.comp import moy_ue
from app.decorators import scodoc, permission_required
from app.scodoc import sco_edit_formation
from app.views import notes_bp as bp
# ---------------
from app.scodoc import sco_utils as scu
from app.scodoc import notesdb as ndb
from app import log
from app.models.formations import Formation, UniteEns, Module
from app.scodoc.sco_exceptions import (
ScoValueError,
ScoLockedFormError,
ScoGenError,
AccessDenied,
)
from app.scodoc import html_sco_header
from app.scodoc.sco_permissions import Permission
@bp.route("/table_modules_ue_coefs/<formation_id>")
@scodoc
@permission_required(Permission.ScoView)
def table_modules_ue_coefs(formation_id):
"""Description JSON de la table des coefs modules/UE dans une formation"""
_ = models.Formation.query.get_or_404(formation_id) # check
df = moy_ue.df_load_ue_coefs(formation_id)
ues = models.UniteEns.query.filter_by(formation_id=formation_id).all()
modules = models.Module.query.filter_by(formation_id=formation_id).all()
# Titre des modules, en ligne
col_titres_mods = [
{
"x": 1, # 1ere colonne
"y": row,
# "nbX": 1,
# "nbY": 1,
"style": "title_mod " + scu.ModuleType(mod.module_type).name,
"data": mod.code,
"title": mod.titre,
}
for (row, mod) in enumerate(modules, start=2)
]
row_titres_ue = [
{
"x": col,
"y": 1, # 1ere ligne
"style": "title_ue",
"data": ue.acronyme,
"title": ue.titre,
}
for (col, ue) in enumerate(ues, start=2)
]
# Les champs de saisie
cells = []
for (row, mod) in enumerate(modules, start=2):
for (col, ue) in enumerate(ues, start=2):
cells.append(
{
"x": col,
"y": row,
"style": "champs",
"data": df[ue.id][mod.id] or "",
"editable": True,
"module_id": mod.id,
"ue_id": ue.id,
}
)
return jsonify(col_titres_mods + row_titres_ue + cells)
@bp.route("/set_module_ue_coef", methods=["POST"])
@scodoc
@permission_required(Permission.ScoChangeFormation)
def set_module_ue_coef():
"""Set coef from module to UE"""
try:
module_id = int(request.form["module_id"])
except ValueError:
return scu.json_error("invalid module_id", 400)
try:
ue_id = int(request.form["ue_id"])
except ValueError:
return scu.json_error("invalid ue_id", 400)
try:
coef = float(request.form["coef"].replace(",", "."))
except ValueError:
return scu.json_error("invalid coef", 400)
module = models.Module.query.get(module_id)
if module is None:
return scu.json_error(f"module not found ({module_id})", 404)
ue = models.UniteEns.query.get(ue_id)
if not ue:
return scu.json_error(f"UE not found ({ue_id})", 404)
module.set_ue_coef(ue, coef)
db.session.commit()
sco_edit_formation.invalidate_sems_in_formation(module.formation_id)
return scu.json_error("ok", success=True, status=201)
@bp.route("/edit_modules_ue_coefs/<formation_id>")
@scodoc
@permission_required(Permission.ScoChangeFormation)
def edit_modules_ue_coefs(formation_id):
"""Formulaire édition grille coefs EU/modules"""
formation = models.Formation.query.filter_by(
formation_id=formation_id
).first_or_404()
return render_template(
"pn/form_modules_ue_coefs.html",
formation=formation,
data_source=url_for(
"notes.table_modules_ue_coefs",
scodoc_dept=g.scodoc_dept,
formation_id=formation_id,
),
data_save=url_for(
"notes.set_module_ue_coef",
scodoc_dept=g.scodoc_dept,
),
)

View File

@ -49,7 +49,7 @@ from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
import app
from app.models import Departement, Identite
from app.models import FormSemestre, NotesFormsemestreInscription
from app.models import FormSemestre, FormsemestreInscription
from app.models import ScoDocSiteConfig
import sco_version
from app.scodoc import sco_logos
@ -124,9 +124,7 @@ def get_etud_dept():
last_etud = None
last_date = None
for etud in etuds:
inscriptions = NotesFormsemestreInscription.query.filter_by(
etudid=etud.id
).all()
inscriptions = FormsemestreInscription.query.filter_by(etudid=etud.id).all()
for ins in inscriptions:
date_fin = FormSemestre.query.get(ins.formsemestre_id).date_fin
if (last_date is None) or date_fin > last_date:

View File

@ -0,0 +1,35 @@
"""coefs modules but
Revision ID: 6cfc21a7ae1b
Revises: ada0d1f3d84f
Create Date: 2021-11-11 21:04:05.573172
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6cfc21a7ae1b'
down_revision = 'ada0d1f3d84f'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('module_ue_coef',
sa.Column('module_id', sa.Integer(), nullable=False),
sa.Column('ue_id', sa.Integer(), nullable=False),
sa.Column('coef', sa.Float(), nullable=False),
sa.ForeignKeyConstraint(['module_id'], ['notes_modules.id'], ),
sa.ForeignKeyConstraint(['ue_id'], ['notes_ue.id'], ),
sa.PrimaryKeyConstraint('module_id', 'ue_id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('module_ue_coef')
# ### end Alembic commands ###

View File

@ -0,0 +1,64 @@
"""BUT: poids evaluations, AC
Revision ID: ada0d1f3d84f
Revises: 1efe07413835
Create Date: 2021-11-07 22:49:22.697211
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "ada0d1f3d84f"
down_revision = "1efe07413835"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"app_crit",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("code", sa.Text(), nullable=False),
sa.Column("titre", sa.Text(), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"modules_acs",
sa.Column("module_id", sa.Integer(), nullable=True),
sa.Column("ac_id", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(
["ac_id"],
["app_crit.id"],
),
sa.ForeignKeyConstraint(
["module_id"],
["notes_modules.id"],
),
)
op.create_table(
"evaluation_ue_poids",
sa.Column("evaluation_id", sa.Integer(), nullable=False),
sa.Column("ue_id", sa.Integer(), nullable=False),
sa.Column("poids", sa.Float(), nullable=False),
sa.ForeignKeyConstraint(
["evaluation_id"],
["notes_evaluation.id"],
),
sa.ForeignKeyConstraint(
["ue_id"],
["notes_ue.id"],
),
sa.PrimaryKeyConstraint("evaluation_id", "ue_id"),
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("evaluation_ue_poids")
op.drop_table("modules_acs")
op.drop_table("app_crit")
# ### end Alembic commands ###

View File

@ -0,0 +1,30 @@
"""UE.semestre_idx
Revision ID: c8efc54586d8
Revises: 6cfc21a7ae1b
Create Date: 2021-11-14 17:35:39.602613
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'c8efc54586d8'
down_revision = '6cfc21a7ae1b'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('notes_ue', sa.Column('semestre_idx', sa.Integer(), nullable=True))
op.create_index(op.f('ix_notes_ue_semestre_idx'), 'notes_ue', ['semestre_idx'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_notes_ue_semestre_idx'), table_name='notes_ue')
op.drop_column('notes_ue', 'semestre_idx')
# ### end Alembic commands ###

View File

@ -37,8 +37,10 @@ lazy-object-proxy==1.6.0
Mako==1.1.4
MarkupSafe==2.0.1
mccabe==0.6.1
numpy==1.21.4
openpyxl==3.0.7
packaging==21.0
pandas==1.3.4
Pillow==8.3.1
pluggy==0.13.1
psycopg2==2.9.1
@ -46,7 +48,6 @@ py==1.10.0
pycparser==2.20
pydot==1.4.2
PyJWT==2.1.0
pylint==2.9.6
pyOpenSSL==20.0.1
pyparsing==2.4.7
pytest==6.2.4
@ -58,8 +59,10 @@ redis==3.5.3
reportlab==3.6.1
requests==2.26.0
rq==1.9.0
six==1.16.0
SQLAlchemy==1.4.22
toml==0.10.2
tornado==6.1
urllib3==1.26.6
visitor==0.1.3
Werkzeug==2.0.1

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.0.62"
SCOVERSION = "9.1.0"
SCONAME = "ScoDoc"

View File

@ -17,7 +17,7 @@ import typing
from config import Config
from app.auth.models import User
from app.models import NotesFormModalite
from app.models import FormationModalite
from app.scodoc import notesdb as ndb
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_formation
@ -26,6 +26,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_formsemestre_inscriptions
@ -222,7 +223,7 @@ class ScoFake(object):
block_moyennes=None,
gestion_semestrielle=None,
bul_bgcolor=None,
modalite=NotesFormModalite.DEFAULT_MODALITE,
modalite=FormationModalite.DEFAULT_MODALITE,
resp_can_edit=None,
resp_can_change_ens=None,
ens_can_edit_eval=None,
@ -283,8 +284,8 @@ class ScoFake(object):
):
args = locals()
del args["self"]
oid = sco_evaluations.do_evaluation_create(**args)
oids = sco_evaluations.do_evaluation_list(args={"evaluation_id": oid})
oid = sco_evaluation_db.do_evaluation_create(**args)
oids = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": oid})
if not oids:
raise ScoValueError("evaluation not created !")
return oids[0]

View File

@ -0,0 +1,111 @@
"""
Test modèles évaluations avec poids BUT
"""
from tests.unit import sco_fake_gen
from app import db
from app import models
"""
mapp.set_sco_dept("RT")
from app.auth.models import get_super_admin
admin_user = get_super_admin()
ctx.push()
login_user(admin_user)
"""
def setup_formation_test():
G = sco_fake_gen.ScoFake(verbose=False)
_f = G.create_formation(
acronyme="F3", titre="Formation 2", titre_officiel="Titre officiel 2"
)
_ue1 = G.create_ue(formation_id=_f["formation_id"], acronyme="UE1", titre="ue 1")
_ue2 = G.create_ue(formation_id=_f["formation_id"], acronyme="UE2", titre="ue 2")
_ue3 = G.create_ue(formation_id=_f["formation_id"], acronyme="UE3", titre="ue 3")
_mat = G.create_matiere(ue_id=_ue1["ue_id"], titre="matière test")
_mod = G.create_module(
matiere_id=_mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=_ue1["ue_id"],
formation_id=_f["formation_id"],
)
return G, _f["id"], _ue1["id"], _ue2["id"], _ue3["id"], _mod["id"]
def test_evaluation_poids(test_client):
"""Association de poids vers les UE"""
G, formation_id, ue1_id, ue2_id, ue3_id, module_id = setup_formation_test()
sem = G.create_formsemestre(
formation_id=formation_id,
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
) # formsemestre_id=716
mi = G.create_moduleimpl(
module_id=module_id,
formsemestre_id=sem["formsemestre_id"],
)
moduleimpl_id = mi["id"]
_e1 = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="01/01/2021",
description="evaluation 1",
coefficient=0,
)
evaluation_id = _e1["evaluation_id"] # evaluation_id=25246
# ue1_id=1684
# formation_id=199
#
e1 = models.Evaluation.query.get(evaluation_id)
ue1 = models.UniteEns.query.get(ue1_id)
assert e1.ue_poids == []
p1 = 3.14
e1.set_ue_poids(ue1, p1)
db.session.commit()
assert e1.get_ue_poids_dict()[ue1_id] == p1
ues = models.UniteEns.query.filter_by(formation_id=formation_id).all()
poids = [1.0, 2.0, 3.0]
for (ue, p) in zip(ues, poids):
e1.set_ue_poids(ue, p)
assert len(e1.ue_poids) == len(ues)
assert e1.get_ue_poids_dict()[ues[1].id] == poids[1]
e1.set_ue_poids(ue1, p1)
db.session.commit()
poids2 = [10, 20]
e1.update_ue_poids_dict({ue.id: p for (ue, p) in zip(ues[:-1], poids2)})
assert e1.get_ue_poids_dict()[ues[0].id] == poids2[0]
assert e1.get_ue_poids_dict()[ues[1].id] == poids2[1]
assert e1.get_ue_poids_dict()[ues[2].id] == poids[2]
# Delete UE
db.session.delete(ues[2])
db.session.commit()
# Delete eval
db.session.delete(e1)
db.session.commit()
assert len(models.EvaluationUEPoids.query.all()) == 0
def test_modules_coefs(test_client):
"""Coefs vers les UE (BUT)"""
G, formation_id, ue1_id, ue2_id, ue3_id, module_id = setup_formation_test()
ue1 = models.UniteEns.query.get(ue1_id)
ue2 = models.UniteEns.query.get(ue2_id)
mod = models.Module.query.get(module_id)
coef = 2.5
mod.set_ue_coef(ue1, coef)
db.session.commit()
assert mod.ue_coefs[0].coef == coef
mod.set_ue_coef(ue2, 2 * coef)
db.session.commit()
assert set(mod.get_ue_coef_dict().values()) == {coef, 2 * coef}
assert set(mod.get_ue_coef_dict().keys()) == {ue1_id, ue2_id}
mod.delete_ue_coef(ue1)
db.session.commit()
assert len(mod.ue_coefs) == 1
# Gestion des coefs nuls:
mod.set_ue_coef(ue2, 0.0)
db.session.commit()
assert len(mod.ue_coefs) == 0

View File

@ -15,6 +15,7 @@ import app
from app import db
from app.scodoc import sco_cache
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import notesdb as ndb
from config import TestConfig
@ -66,7 +67,7 @@ def test_cache_evaluations(test_client):
raise Exception("no evaluations")
#
evaluation_id = sem_evals[0]["evaluation_id"]
eval_notes = sco_evaluations.do_evaluation_get_all_notes(evaluation_id)
eval_notes = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
# should have been be cached, except if empty
if eval_notes:
assert sco_cache.EvaluationCache.get(evaluation_id)

View File

@ -25,6 +25,7 @@ from app.scodoc import sco_abs_views
from app.scodoc import sco_bulletins
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre_validation
from app.scodoc import sco_parcours_dut
from app.scodoc import sco_cache
@ -134,7 +135,7 @@ def run_sco_basic(verbose=False):
# Modifie l'évaluation 2 pour "prise en compte immédiate"
e2["publish_incomplete"] = True
sco_evaluations.do_evaluation_edit(e2)
sco_evaluation_db.do_evaluation_edit(e2)
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
assert etat["evalcomplete"] == False
assert etat["nb_att"] == 0 # il n'y a pas de notes (explicitement) en attente

View File

@ -4,4 +4,4 @@ Architecture: amd64
Maintainer: Emmanuel Viennet <emmanuel@viennet.net>
Description: ScoDoc 9
Un logiciel pour le suivi de la scolarité universitaire.
Depends: adduser, curl, gcc, graphviz, libpq-dev, postfix|exim4, cracklib-runtime, libcrack2-dev, python3-dev, python3-venv, python3-pip, python3-wheel, nginx, postgresql, redis, ufw, systemctl
Depends: adduser, curl, gcc, graphviz, libpq-dev, postfix|exim4, cracklib-runtime, libcrack2-dev, python3-dev, python3-venv, python3-pip, python3-wheel, nginx, postgresql, redis, ufw