1
0
forked from ScoDoc/ScoDoc

- Coef evaluations (modèles).

- Refactoring.
- Changement des noms des classes (modèles) des formations.
- Début intégration calculs BUT.
- Requiert numpy et pandas.
This commit is contained in:
Emmanuel Viennet 2021-11-12 22:17:46 +01:00
parent d71b399c3d
commit 89e7250f4a
51 changed files with 2025 additions and 1102 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,26 +31,27 @@ from app.models.etudiants import (
)
from app.models.events import Scolog, ScolarNews
from app.models.formations import (
NotesFormation,
NotesUE,
NotesMatiere,
NotesModule,
Formation,
UniteEns,
Matiere,
Module,
ModuleUECoef,
NotesTag,
notes_modules_tags,
)
from app.models.formsemestre import (
FormSemestre,
NotesFormsemestreEtape,
NotesFormModalite,
NotesFormsemestreUECoef,
NotesFormsemestreUEComputationExpr,
NotesFormsemestreCustomMenu,
NotesFormsemestreInscription,
FormsemestreEtape,
FormationModalite,
FormsemestreUECoef,
FormsemestreUEComputationExpr,
FormsemestreCustomMenu,
FormsemestreInscription,
notes_formsemestre_responsables,
NotesModuleImpl,
ModuleImpl,
notes_modules_enseignants,
NotesModuleImplInscription,
NotesEvaluation,
ModuleImplInscription,
Evaluation,
EvaluationUEPoids,
NotesSemSet,
notes_semset_formsemestre,
@ -61,7 +62,7 @@ from app.models.notes import (
ScolarEvent,
ScolarFormsemestreValidation,
ScolarAutorisationInscription,
NotesAppreciations,
BulAppreciations,
NotesNotes,
NotesNotesLog,
)

View File

@ -22,7 +22,7 @@ class AppCrit(db.Model):
titre = db.Column(db.Text(), info={"label": "Titre"})
modules = db.relationship(
"NotesModule", secondary=Modules_ACs, lazy="dynamic", backref="acs"
"Module", secondary=Modules_ACs, lazy="dynamic", backref="acs"
)
def to_dict(self):

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

View File

@ -5,10 +5,11 @@ from typing import Any
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.scodoc import sco_utils as scu
from app.scodoc.sco_utils import ModuleType
class NotesFormation(db.Model):
class Formation(db.Model):
"""Programme pédagogique d'une formation"""
__tablename__ = "notes_formations"
@ -31,16 +32,16 @@ class NotesFormation(db.Model):
type_parcours = db.Column(db.Integer, default=0, server_default="0")
code_specialite = db.Column(db.String(SHORT_STR_LEN))
ues = db.relationship("NotesUE", backref="formation", lazy="dynamic")
ues = db.relationship("UniteEns", backref="formation", lazy="dynamic")
formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
ues = db.relationship("NotesUE", lazy="dynamic", backref="formation")
ues = db.relationship("UniteEns", lazy="dynamic", backref="formation")
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme}')>"
class NotesUE(db.Model):
"""Unité d'Enseignement"""
class UniteEns(db.Model):
"""Unité d'Enseignement (UE)"""
__tablename__ = "notes_ue"
@ -68,14 +69,14 @@ class NotesUE(db.Model):
coefficient = db.Column(db.Float)
# relations
matieres = db.relationship("NotesMatiere", lazy="dynamic", backref="ue")
modules = db.relationship("NotesModule", lazy="dynamic", backref="ue")
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
modules = db.relationship("Module", lazy="dynamic", backref="ue")
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id}, formation_id={self.formation_id}, acronyme='{self.acronyme}')>"
class NotesMatiere(db.Model):
class Matiere(db.Model):
"""Matières: regroupe les modules d'une UE
La matière a peu d'utilité en dehors de la présentation des modules
d'une UE.
@ -90,10 +91,10 @@ class NotesMatiere(db.Model):
titre = db.Column(db.Text())
numero = db.Column(db.Integer) # ordre de présentation
modules = db.relationship("NotesModule", lazy="dynamic", backref="matiere")
modules = db.relationship("Module", lazy="dynamic", backref="matiere")
class NotesModule(db.Model):
class Module(db.Model):
"""Module"""
__tablename__ = "notes_modules"
@ -107,7 +108,7 @@ class NotesModule(db.Model):
heures_cours = db.Column(db.Float)
heures_td = db.Column(db.Float)
heures_tp = db.Column(db.Float)
coefficient = db.Column(db.Float) # coef PPN
coefficient = db.Column(db.Float) # coef PPN (sauf en APC)
ects = db.Column(db.Float) # Crédits ECTS
ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), index=True)
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
@ -120,13 +121,75 @@ class NotesModule(db.Model):
# Type: ModuleType: DEFAULT, MALUS, RESSOURCE, MODULE_SAE (enum)
module_type = db.Column(db.Integer)
# Relations:
modimpls = db.relationship("NotesModuleImpl", backref="module", lazy="dynamic")
modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)
def __init__(self, **kwargs):
self.ue_coefs = []
super(Module, self).__init__(**kwargs)
def __repr__(self):
return (
f"<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 }
"""
ue_coefs = []
for ue_id, coef in ue_coef_dict.items():
ue = UniteEns.query.get(ue_id)
ue_coefs.append(ModuleUECoef(module=self, ue=ue, coef=coef))
self.ue_coefs = ue_coefs
def update_ue_coef_dict(self, ue_coef_dict: dict):
"""update coefs vers UE (ajoute aux existants)"""
current = self.get_ue_coef_dict()
current.update(ue_coef_dict)
self.set_ue_coef_dict(current)
def get_ue_coef_dict(self):
"""returns { ue_id : coef }"""
return {p.ue.id: p.coef for p in self.ue_coefs}
def delete_ue_coef(self, ue):
"""delete coef"""
ue_coef = ModuleUECoef.query.get((self.id, ue.id))
db.session.delete(ue_coef)
class ModuleUECoef(db.Model):
"""Coefficients des modules vers les UE (APC, BUT)
En mode APC, ces coefs remplacent le coefficient "PPN" du module.
"""
__tablename__ = "module_ue_coef"
module_id = db.Column(
db.Integer, db.ForeignKey("notes_modules.id"), primary_key=True
)
ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), primary_key=True)
coef = db.Column(
db.Float,
nullable=False,
)
module = db.relationship(
Module,
backref=db.backref("ue_coefs", cascade="all, delete-orphan"),
)
ue = db.relationship(
UniteEns,
backref=db.backref("module_ue_coefs", cascade="all, delete-orphan"),
)
class NotesTag(db.Model):
"""Tag sur un module"""

View File

@ -8,7 +8,10 @@ from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
from app.models import NotesUE
from app.models import UniteEns
import app.scodoc.notesdb as ndb
from app.scodoc import sco_evaluation_db
class FormSemestre(db.Model):
@ -73,11 +76,9 @@ class FormSemestre(db.Model):
# Relations:
etapes = db.relationship(
"NotesFormsemestreEtape", cascade="all,delete", backref="formsemestre"
)
formsemestres = db.relationship(
"NotesModuleImpl", backref="formsemestre", lazy="dynamic"
"FormsemestreEtape", cascade="all,delete", backref="formsemestre"
)
modimpls = db.relationship("ModuleImpl", backref="formsemestre", lazy="dynamic")
# Ancien id ScoDoc7 pour les migrations de bases anciennes
# ne pas utiliser après migrate_scodoc7_dept_archives
@ -86,7 +87,7 @@ class FormSemestre(db.Model):
def __init__(self, **kwargs):
super(FormSemestre, self).__init__(**kwargs)
if self.modalite is None:
self.modalite = NotesFormModalite.DEFAULT_MODALITE
self.modalite = FormationModalite.DEFAULT_MODALITE
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
@ -101,7 +102,7 @@ notes_formsemestre_responsables = db.Table(
)
class NotesFormsemestreEtape(db.Model):
class FormsemestreEtape(db.Model):
"""Étape Apogée associées au semestre"""
__tablename__ = "notes_formsemestre_etapes"
@ -113,7 +114,7 @@ class NotesFormsemestreEtape(db.Model):
etape_apo = db.Column(db.String(APO_CODE_STR_LEN))
class NotesFormModalite(db.Model):
class FormationModalite(db.Model):
"""Modalités de formation, utilisées pour la présentation
(grouper les semestres, générer des codes, etc.)
"""
@ -140,7 +141,7 @@ class NotesFormModalite(db.Model):
numero = 0
try:
for (code, titre) in (
(NotesFormModalite.DEFAULT_MODALITE, "Formation Initiale"),
(FormationModalite.DEFAULT_MODALITE, "Formation Initiale"),
("FAP", "Apprentissage"),
("FC", "Formation Continue"),
("DEC", "Formation Décalées"),
@ -151,9 +152,9 @@ class NotesFormModalite(db.Model):
("EXT", "Extérieur"),
("OTHER", "Autres formations"),
):
modalite = NotesFormModalite.query.filter_by(modalite=code).first()
modalite = FormationModalite.query.filter_by(modalite=code).first()
if modalite is None:
modalite = NotesFormModalite(
modalite = FormationModalite(
modalite=code, titre=titre, numero=numero
)
db.session.add(modalite)
@ -164,7 +165,7 @@ class NotesFormModalite(db.Model):
raise
class NotesFormsemestreUECoef(db.Model):
class FormsemestreUECoef(db.Model):
"""Coef des UE capitalisees arrivant dans ce semestre"""
__tablename__ = "notes_formsemestre_uecoef"
@ -183,7 +184,7 @@ class NotesFormsemestreUECoef(db.Model):
coefficient = db.Column(db.Float, nullable=False)
class NotesFormsemestreUEComputationExpr(db.Model):
class FormsemestreUEComputationExpr(db.Model):
"""Formules utilisateurs pour calcul moyenne UE"""
__tablename__ = "notes_formsemestre_ue_computation_expr"
@ -203,7 +204,7 @@ class NotesFormsemestreUEComputationExpr(db.Model):
computation_expr = db.Column(db.Text())
class NotesFormsemestreCustomMenu(db.Model):
class FormsemestreCustomMenu(db.Model):
"""Menu custom associe au semestre"""
__tablename__ = "notes_formsemestre_custommenu"
@ -219,7 +220,7 @@ class NotesFormsemestreCustomMenu(db.Model):
idx = db.Column(db.Integer, default=0, server_default="0") # rang dans le menu
class NotesFormsemestreInscription(db.Model):
class FormsemestreInscription(db.Model):
"""Inscription à un semestre de formation"""
__tablename__ = "notes_formsemestre_inscription"
@ -239,7 +240,7 @@ class NotesFormsemestreInscription(db.Model):
etape = db.Column(db.String(APO_CODE_STR_LEN))
class NotesModuleImpl(db.Model):
class ModuleImpl(db.Model):
"""Mise en oeuvre d'un module pour une annee/semestre"""
__tablename__ = "notes_moduleimpl"
@ -275,7 +276,7 @@ notes_modules_enseignants = db.Table(
# XXX il manque probablement une relation pour gérer cela
class NotesModuleImplInscription(db.Model):
class ModuleImplInscription(db.Model):
"""Inscription à un module (etudiants,moduleimpl)"""
__tablename__ = "notes_moduleimpl_inscription"
@ -291,7 +292,7 @@ class NotesModuleImplInscription(db.Model):
etudid = db.Column(db.Integer, db.ForeignKey("identite.id"), index=True)
class NotesEvaluation(db.Model):
class Evaluation(db.Model):
"""Evaluation (contrôle, examen, ...)"""
__tablename__ = "notes_evaluation"
@ -306,7 +307,7 @@ class NotesEvaluation(db.Model):
heure_fin = db.Column(db.Time)
description = db.Column(db.Text)
note_max = db.Column(db.Float)
coefficient = db.Column(db.Float) # non BUT
coefficient = db.Column(db.Float)
visibulletin = db.Column(
db.Boolean, nullable=False, default=True, server_default="true"
)
@ -320,7 +321,34 @@ class NotesEvaluation(db.Model):
# ordre de presentation (par défaut, le plus petit numero
# est la plus ancienne eval):
numero = db.Column(db.Integer)
ues = db.relationship("NotesUE", secondary="evaluation_ue_poids", viewonly=True)
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
def to_dict(self):
e = dict(self.__dict__)
e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators
e["jour"] = ndb.DateISOtoDMY(e["jour"])
e["numero"] = ndb.int_null_is_zero(e["numero"])
return sco_evaluation_db.evaluation_enrich_dict(e)
# def from_dict(self, data):
# """Set evaluation attributes from given dict values."""
# sco_evaluation_db._check_evaluation_args(data)
# for field in [
# "moduleimpl_id",
# "jour",
# "heure_debut",
# "heure_fin",
# "description",
# "note_max",
# "coefficient",
# "visibulletin",
# "publish_incomplete",
# "evaluation_type",
# "numero",
# ]:
# if field in data:
# setattr(self, field, data[field] or None)
def set_ue_poids(self, ue, poids: float):
"""Set poids évaluation vers cette UE"""
@ -332,7 +360,7 @@ class NotesEvaluation(db.Model):
"""
L = []
for ue_id, poids in ue_poids_dict.items():
ue = NotesUE.query.get(ue_id)
ue = UniteEns.query.get(ue_id)
L.append(EvaluationUEPoids(evaluation=self, ue=ue, poids=poids))
self.ue_poids = L
@ -361,11 +389,12 @@ class EvaluationUEPoids(db.Model):
nullable=False,
)
evaluation = db.relationship(
NotesEvaluation,
Evaluation,
backref=db.backref("ue_poids", cascade="all, delete-orphan"),
)
ue = db.relationship(
NotesUE, backref=db.backref("evaluation_ue_poids", cascade="all, delete-orphan")
UniteEns,
backref=db.backref("evaluation_ue_poids", cascade="all, delete-orphan"),
)
def __repr__(self):

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

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

@ -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,11 +29,13 @@
(portage from DTML)
"""
import flask
from flask import url_for, g, request
from flask import url_for, render_template
from flask import g, request
from flask_login import current_user
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_permissions import Permission
@ -44,22 +46,6 @@ from app.scodoc import sco_edit_matiere
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_news
_MODULE_HELP = """<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 +106,30 @@ def do_module_create(args) -> int:
def module_create(matiere_id=None):
"""Creation d'un module"""
"""Création d'un module"""
from app.scodoc import sco_formations
from app.scodoc import sco_edit_ue
if matiere_id is None:
raise ScoValueError("invalid matiere !")
M = sco_edit_matiere.matiere_list(args={"matiere_id": matiere_id})[0]
UE = sco_edit_ue.ue_list(args={"ue_id": M["ue_id"]})[0]
Fo = sco_formations.formation_list(args={"formation_id": UE["formation_id"]})[0]
parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
matiere = sco_edit_matiere.matiere_list(args={"matiere_id": matiere_id})[0]
UE = sco_edit_ue.ue_list(args={"ue_id": matiere["ue_id"]})[0]
formation = sco_formations.formation_list(
args={"formation_id": UE["formation_id"]}
)[0]
parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"])
is_apc = parcours.APC_SAE
semestres_indices = list(range(1, parcours.NB_SEM + 1))
H = [
html_sco_header.sco_header(page_title="Création d'un module"),
"""<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 +142,7 @@ def module_create(matiere_id=None):
"size": 10,
"explanation": "code du module (doit être unique dans la formation)",
"allow_null": False,
"validator": lambda val, field, formation_id=Fo[
"validator": lambda val, field, formation_id=formation[
"formation_id"
]: check_module_code_unicity(val, field, formation_id),
},
@ -166,8 +155,8 @@ def module_create(matiere_id=None):
"input_type": "menu",
"title": "Type",
"explanation": "",
"labels": ("Standard", "Malus"),
"allowed_values": (str(scu.MODULE_STANDARD), str(scu.MODULE_MALUS)),
"labels": [x.name.capitalize() for x in scu.ModuleType],
"allowed_values": [str(int(x)) for x in scu.ModuleType],
},
),
(
@ -201,8 +190,8 @@ def module_create(matiere_id=None):
),
# ('ects', { 'size' : 4, 'type' : 'float', 'title' : 'ECTS', 'explanation' : 'nombre de crédits ECTS (inutilisés: les crédits sont associés aux UE)' }),
("formation_id", {"default": UE["formation_id"], "input_type": "hidden"}),
("ue_id", {"default": M["ue_id"], "input_type": "hidden"}),
("matiere_id", {"default": M["matiere_id"], "input_type": "hidden"}),
("ue_id", {"default": matiere["ue_id"], "input_type": "hidden"}),
("matiere_id", {"default": matiere["matiere_id"], "input_type": "hidden"}),
(
"semestre_id",
{
@ -350,35 +339,38 @@ def module_edit(module_id=None):
if not module_id:
raise ScoValueError("invalid module !")
Mod = module_list(args={"module_id": module_id})
if not Mod:
modules = module_list(args={"module_id": module_id})
if not modules:
raise ScoValueError("invalid module !")
Mod = Mod[0]
module = modules[0]
unlocked = not module_is_locked(module_id)
Fo = sco_formations.formation_list(args={"formation_id": Mod["formation_id"]})[0]
parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
M = ndb.SimpleDictFetch(
formation = sco_formations.formation_list(
args={"formation_id": module["formation_id"]}
)[0]
parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"])
is_apc = parcours.APC_SAE
ues_matieres = ndb.SimpleDictFetch(
"""SELECT ue.acronyme, mat.*, mat.id AS matiere_id
FROM notes_matieres mat, notes_ue ue
WHERE mat.ue_id = ue.id
AND ue.formation_id = %(formation_id)s
ORDER BY ue.numero, mat.numero
""",
{"formation_id": Mod["formation_id"]},
{"formation_id": module["formation_id"]},
)
Mnames = ["%s / %s" % (x["acronyme"], x["titre"]) for x in M]
Mids = ["%s!%s" % (x["ue_id"], x["matiere_id"]) for x in M]
Mod["ue_matiere_id"] = "%s!%s" % (Mod["ue_id"], Mod["matiere_id"])
mat_names = ["%s / %s" % (x["acronyme"], x["titre"]) for x in ues_matieres]
ue_mat_ids = ["%s!%s" % (x["ue_id"], x["matiere_id"]) for x in ues_matieres]
module["ue_matiere_id"] = "%s!%s" % (module["ue_id"], module["matiere_id"])
semestres_indices = list(range(1, parcours.NB_SEM + 1))
dest_url = url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=str(Mod["formation_id"]),
formation_id=str(module["formation_id"]),
)
H = [
html_sco_header.sco_header(
page_title="Modification du module %(titre)s" % Mod,
page_title="Modification du module %(titre)s" % module,
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"],
javascripts=[
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
@ -386,9 +378,9 @@ def module_edit(module_id=None):
"js/module_tag_editor.js",
],
),
"""<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(
@ -405,7 +397,7 @@ def module_edit(module_id=None):
"size": 10,
"explanation": "code du module (doit être unique dans la formation)",
"allow_null": False,
"validator": lambda val, field, formation_id=Mod[
"validator": lambda val, field, formation_id=module[
"formation_id"
]: check_module_code_unicity(
val, field, formation_id, module_id=module_id
@ -465,8 +457,8 @@ def module_edit(module_id=None):
"input_type": "menu",
"title": "Matière",
"explanation": "un module appartient à une seule matière.",
"labels": Mnames,
"allowed_values": Mids,
"labels": mat_names,
"allowed_values": ue_mat_ids,
"enabled": unlocked,
},
),
@ -503,7 +495,7 @@ def module_edit(module_id=None):
html_foot_markup="""<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 +594,7 @@ def formation_add_malus_modules(formation_id, titre=None, redirect=True):
[
mod
for mod in module_list(args={"ue_id": ue["ue_id"]})
if mod["module_type"] == scu.MODULE_MALUS
if mod["module_type"] == ModuleType.MALUS
]
)
if nb_mod_malus == 0:
@ -654,7 +646,7 @@ def ue_add_malus_module(ue_id, titre=None, code=None):
"matiere_id": matiere_id,
"formation_id": ue["formation_id"],
"semestre_id": semestre_id,
"module_type": scu.MODULE_MALUS,
"module_type": ModuleType.MALUS,
},
)

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 UniteEns
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
from app.scodoc.gen_tables import GenTable
@ -955,7 +956,7 @@ def _ue_table_modules(
mod["module_id"]
)
klass = "notes_module_list"
if mod["module_type"] == scu.MODULE_MALUS:
if mod["module_type"] == ModuleType.MALUS:
klass += " module_malus"
H.append('<li class="%s">' % klass)
@ -1066,20 +1067,20 @@ def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
formation_code = F["formation_code"]
# UE du même code, code formation et departement:
q_ues = (
NotesUE.query.filter_by(ue_code=ue_code)
.join(NotesUE.formation, aliased=True)
UniteEns.query.filter_by(ue_code=ue_code)
.join(UniteEns.formation, aliased=True)
.filter_by(dept_id=g.scodoc_dept_id, formation_code=formation_code)
)
else:
# Toutes les UE du departement avec ce code:
q_ues = (
NotesUE.query.filter_by(ue_code=ue_code)
.join(NotesUE.formation, aliased=True)
UniteEns.query.filter_by(ue_code=ue_code)
.join(UniteEns.formation, aliased=True)
.filter_by(dept_id=g.scodoc_dept_id)
)
if hide_ue_id: # enlève l'ue de depart
q_ues = q_ues.filter(NotesUE.id != hide_ue_id)
q_ues = q_ues.filter(UniteEns.id != hide_ue_id)
ues = q_ues.all()
if not ues:

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"])
]
)
H.append(
"""<td class="malus">
<a href="moduleimpl_status?moduleimpl_id=%s" class="formsemestre_status_link">malus (%d notes)</a>
if nt.parcours.APC_SAE:
# BUT: tableau ressources puis SAE
ressources = [
m for m in modimpls if m["module"]["module_type"] == ModuleType.RESSOURCE
]
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)
)
else:
raise ValueError("Invalid module_type") # a bug
<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:
# 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">
<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>""",
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()}
<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)

View File

@ -49,7 +49,7 @@ 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
@ -71,20 +71,23 @@ 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 NotesModule.module_type: ne pas modifier ces valeurs
# 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
@ -667,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;
}

View File

@ -0,0 +1,93 @@
/* table_editor, par Sébastien L.
*/
body {
font-family: Arial, Helvetica, sans-serif;
}
/***************************/
/* Le tableau */
/***************************/
.tableau {
display: grid;
grid-auto-rows: minmax(24px, auto);
}
.entete {
background: #09c;
font-weight: bold;
}
.tableau>div {
padding: 4px;
border: 1px solid #999;
grid-column: var(--x) / span var(--nbX);
grid-row: var(--y) / span var(--nbY);
}
/***************************/
/* Attente */
/***************************/
.wait {
position: fixed;
top: 32px;
left: 50%;
height: 4px;
width: 32px;
margin-left: -16px;
background: #424242;
animation: attente 0.4s infinite alternate;
display: none;
}
.go {
display: block;
}
@keyframes attente {
100% {
transform: translateY(-16px) rotate(360deg);
}
}
/***************************/
/* Système de modification */
/***************************/
.modifOnOff {
position: relative;
display: table;
margin: 16px;
padding-right: 8px;
cursor: pointer;
}
.modifOnOff::before {
content: '';
width: 40px;
height: 20px;
background: #c90;
position: absolute;
top: 0;
left: 100%;
border-radius: 20px;
transition: 0.2s;
}
.modifOnOff::after {
content: '';
width: 16px;
height: 16px;
background: #FFF;
position: absolute;
top: 2px;
left: calc(100% + 2px);
border-radius: 100%;
transition: 0.2s;
}
.modifOnOff.active::before {
background: #9c0;
}
.modifOnOff.active::after {
transform: translateX(20px);
}

View File

@ -0,0 +1,56 @@
/* table_editor, par Sébastien L.
*/
/******************************/
/* Gestion de la modification */
/******************************/
function editableOnOff() {
if (this.classList.toggle("active")) {
document.querySelectorAll("[data-editable=true]").forEach(cellule => {
cellule.contentEditable = true;
cellule.addEventListener("input", delayBeforeSave);
cellule.addEventListener("blur", save);
})
} else {
document.querySelectorAll("[data-editable=true]").forEach(cellule => {
cellule.removeAttribute("contentEditable");
cellule.removeEventListener("input", delayBeforeSave);
cellule.removeEventListener("blur", save);
})
}
}
let timeout = 0;
function delayBeforeSave() {
clearTimeout(timeout);
document.querySelector(".wait").classList.add("go");
timeout = setTimeout(() => { save(this) }, 2000);
}
/*****************************/
/* 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}"
style="
--x:${cellule.x};
--y:${cellule.y};
--nbX:${cellule.nbX};
--nbY: ${cellule.nbY};
">
${cellule.data}
</div>`;
})
document.querySelector(".tableau").innerHTML = output;
document.querySelector(".modifOnOff").addEventListener("click", editableOnOff);
}

View File

@ -0,0 +1,55 @@
<!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=wait></div>
<div class=modifOnOff>Modifier</div>
<div class="tableau"></div>
<script>
$(function () {
$.getJSON("{{data_source}}", function (data) {
build_table(data);
});
});
function save(obj) {
if (event?.currentTarget) {
obj = event.currentTarget;
}
document.querySelector(".wait").classList.remove("go");
// XXX DEBUG
console.log(`
x : ${getComputedStyle(obj).getPropertyValue("--x")}
y : ${getComputedStyle(obj).getPropertyValue("--y")}
data : ${obj.innerText}
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: `${obj.innerText}`
},
function (result) {
console.log("enregistré");
}
);
}
</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
)

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

@ -0,0 +1,171 @@
# -*- 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
"nbX": 1,
"nbY": 1,
"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],
"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

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

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

@ -15,8 +15,7 @@ login_user(admin_user)
"""
def test_evaluation_poids(test_client):
"""Association de poids vers les UE"""
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"
@ -33,14 +32,20 @@ def test_evaluation_poids(test_client):
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=_f["formation_id"],
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=_mod["module_id"],
module_id=module_id,
formsemestre_id=sem["formsemestre_id"],
)
moduleimpl_id = mi["id"]
@ -51,17 +56,17 @@ def test_evaluation_poids(test_client):
coefficient=0,
)
evaluation_id = _e1["evaluation_id"] # evaluation_id=25246
ue1_id = _ue1["id"] # ue1_id=1684
formation_id = _f["id"] # formation_id=199
# ue1_id=1684
# formation_id=199
#
e1 = models.NotesEvaluation.query.get(evaluation_id)
ue1 = models.NotesUE.query.get(ue1_id)
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.NotesUE.query.filter_by(formation_id=formation_id).all()
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)
@ -81,3 +86,22 @@ def test_evaluation_poids(test_client):
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

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