forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -374,115 +374,114 @@ def formsemestre_etudiants(
|
||||
return sorted(etuds, key=itemgetter("sort_key"))
|
||||
|
||||
|
||||
# retrait (temporaire ? à discuter)
|
||||
# @bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||
# @api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||
# @login_required
|
||||
# @scodoc
|
||||
# @permission_required(Permission.ScoView)
|
||||
# @as_json
|
||||
# def etat_evals(formsemestre_id: int):
|
||||
# """
|
||||
# Informations sur l'état des évaluations d'un formsemestre.
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def etat_evals(formsemestre_id: int):
|
||||
"""
|
||||
Informations sur l'état des évaluations d'un formsemestre.
|
||||
|
||||
# formsemestre_id : l'id d'un semestre
|
||||
formsemestre_id : l'id d'un semestre
|
||||
|
||||
# Exemple de résultat :
|
||||
# [
|
||||
# {
|
||||
# "id": 1, // moduleimpl_id
|
||||
# "titre": "Initiation aux réseaux informatiques",
|
||||
# "evaluations": [
|
||||
# {
|
||||
# "id": 1,
|
||||
# "description": null,
|
||||
# "datetime_epreuve": null,
|
||||
# "heure_fin": "09:00:00",
|
||||
# "coefficient": "02.00"
|
||||
# "is_complete": true,
|
||||
# "nb_inscrits": 16,
|
||||
# "nb_manquantes": 0,
|
||||
# "ABS": 0,
|
||||
# "ATT": 0,
|
||||
# "EXC": 0,
|
||||
# "saisie_notes": {
|
||||
# "datetime_debut": "2021-09-11T00:00:00+02:00",
|
||||
# "datetime_fin": "2022-08-25T00:00:00+02:00",
|
||||
# "datetime_mediane": "2022-03-19T00:00:00+01:00"
|
||||
# }
|
||||
# },
|
||||
# ...
|
||||
# ]
|
||||
# },
|
||||
# ]
|
||||
# """
|
||||
# query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
# if g.scodoc_dept:
|
||||
# query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
# formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
# app.set_sco_dept(formsemestre.departement.acronym)
|
||||
# nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"id": 1, // moduleimpl_id
|
||||
"titre": "Initiation aux réseaux informatiques",
|
||||
"evaluations": [
|
||||
{
|
||||
"id": 1,
|
||||
"description": null,
|
||||
"datetime_epreuve": null,
|
||||
"heure_fin": "09:00:00",
|
||||
"coefficient": "02.00"
|
||||
"is_complete": true,
|
||||
"nb_inscrits": 16,
|
||||
"nb_manquantes": 0,
|
||||
"ABS": 0,
|
||||
"ATT": 0,
|
||||
"EXC": 0,
|
||||
"saisie_notes": {
|
||||
"datetime_debut": "2021-09-11T00:00:00+02:00",
|
||||
"datetime_fin": "2022-08-25T00:00:00+02:00",
|
||||
"datetime_mediane": "2022-03-19T00:00:00+01:00"
|
||||
}
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
]
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
# result = []
|
||||
# for modimpl_id in nt.modimpls_results:
|
||||
# modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl_id]
|
||||
# modimpl: ModuleImpl = ModuleImpl.query.get_or_404(modimpl_id)
|
||||
# modimpl_dict = modimpl.to_dict(convert_objects=True)
|
||||
result = []
|
||||
for modimpl_id in nt.modimpls_results:
|
||||
modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl_id]
|
||||
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(modimpl_id)
|
||||
modimpl_dict = modimpl.to_dict(convert_objects=True, with_module=False)
|
||||
|
||||
# list_eval = []
|
||||
# for evaluation_id in modimpl_results.evaluations_etat:
|
||||
# eval_etat = modimpl_results.evaluations_etat[evaluation_id]
|
||||
# evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
# eval_dict = evaluation.to_dict_api()
|
||||
# eval_dict["etat"] = eval_etat.to_dict()
|
||||
list_eval = []
|
||||
for evaluation_id in modimpl_results.evaluations_etat:
|
||||
eval_etat = modimpl_results.evaluations_etat[evaluation_id]
|
||||
evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
eval_dict = evaluation.to_dict_api()
|
||||
eval_dict["etat"] = eval_etat.to_dict()
|
||||
|
||||
# eval_dict["nb_inscrits"] = modimpl_results.nb_inscrits_module
|
||||
# eval_dict["nb_notes_manquantes"] = len(
|
||||
# modimpl_results.evals_etudids_sans_note[evaluation.id]
|
||||
# )
|
||||
# eval_dict["nb_notes_abs"] = sum(
|
||||
# modimpl_results.evals_notes[evaluation.id] == scu.NOTES_ABSENCE
|
||||
# )
|
||||
# eval_dict["nb_notes_att"] = eval_etat.nb_attente
|
||||
# eval_dict["nb_notes_exc"] = sum(
|
||||
# modimpl_results.evals_notes[evaluation.id] == scu.NOTES_NEUTRALISE
|
||||
# )
|
||||
eval_dict["nb_inscrits"] = modimpl_results.nb_inscrits_module
|
||||
eval_dict["nb_notes_manquantes"] = len(
|
||||
modimpl_results.evals_etudids_sans_note[evaluation.id]
|
||||
)
|
||||
eval_dict["nb_notes_abs"] = sum(
|
||||
modimpl_results.evals_notes[evaluation.id] == scu.NOTES_ABSENCE
|
||||
)
|
||||
eval_dict["nb_notes_att"] = eval_etat.nb_attente
|
||||
eval_dict["nb_notes_exc"] = sum(
|
||||
modimpl_results.evals_notes[evaluation.id] == scu.NOTES_NEUTRALISE
|
||||
)
|
||||
|
||||
# # Récupération de toutes les notes de l'évaluation
|
||||
# # eval["notes"] = modimpl_results.get_eval_notes_dict(evaluation_id)
|
||||
# Récupération de toutes les notes de l'évaluation
|
||||
# eval["notes"] = modimpl_results.get_eval_notes_dict(evaluation_id)
|
||||
|
||||
# notes = NotesNotes.query.filter_by(evaluation_id=evaluation.id).all()
|
||||
notes = NotesNotes.query.filter_by(evaluation_id=evaluation.id).all()
|
||||
|
||||
# date_debut = None
|
||||
# date_fin = None
|
||||
# date_mediane = None
|
||||
date_debut = None
|
||||
date_fin = None
|
||||
date_mediane = None
|
||||
|
||||
# # Si il y a plus d'une note saisie pour l'évaluation
|
||||
# if len(notes) >= 1:
|
||||
# # Tri des notes en fonction de leurs dates
|
||||
# notes_sorted = sorted(notes, key=attrgetter("date"))
|
||||
# Si il y a plus d'une note saisie pour l'évaluation
|
||||
if len(notes) >= 1:
|
||||
# Tri des notes en fonction de leurs dates
|
||||
notes_sorted = sorted(notes, key=attrgetter("date"))
|
||||
|
||||
# date_debut = notes_sorted[0].date
|
||||
# date_fin = notes_sorted[-1].date
|
||||
date_debut = notes_sorted[0].date
|
||||
date_fin = notes_sorted[-1].date
|
||||
|
||||
# # Note médiane
|
||||
# date_mediane = notes_sorted[len(notes_sorted) // 2].date
|
||||
# Note médiane
|
||||
date_mediane = notes_sorted[len(notes_sorted) // 2].date
|
||||
|
||||
# eval_dict["saisie_notes"] = {
|
||||
# "datetime_debut": date_debut.isoformat()
|
||||
# if date_debut is not None
|
||||
# else None,
|
||||
# "datetime_fin": date_fin.isoformat() if date_fin is not None else None,
|
||||
# "datetime_mediane": date_mediane.isoformat()
|
||||
# if date_mediane is not None
|
||||
# else None,
|
||||
# }
|
||||
eval_dict["saisie_notes"] = {
|
||||
"datetime_debut": date_debut.isoformat()
|
||||
if date_debut is not None
|
||||
else None,
|
||||
"datetime_fin": date_fin.isoformat() if date_fin is not None else None,
|
||||
"datetime_mediane": date_mediane.isoformat()
|
||||
if date_mediane is not None
|
||||
else None,
|
||||
}
|
||||
|
||||
# list_eval.append(eval_dict)
|
||||
list_eval.append(eval_dict)
|
||||
|
||||
# modimpl_dict["evaluations"] = list_eval
|
||||
# result.append(modimpl_dict)
|
||||
# return result
|
||||
modimpl_dict["evaluations"] = list_eval
|
||||
result.append(modimpl_dict)
|
||||
return result
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/resultats")
|
||||
|
@ -102,6 +102,8 @@ class User(UserMixin, ScoDocModel):
|
||||
token = db.Column(db.Text(), index=True, unique=True)
|
||||
token_expiration = db.Column(db.DateTime)
|
||||
|
||||
# Define the back reference from User to ModuleImpl
|
||||
modimpls = db.relationship("ModuleImpl", back_populates="responsable")
|
||||
roles = db.relationship("Role", secondary="user_role", viewonly=True)
|
||||
Permission = Permission
|
||||
|
||||
@ -245,24 +247,26 @@ class User(UserMixin, ScoDocModel):
|
||||
def to_dict(self, include_email=True):
|
||||
"""l'utilisateur comme un dict, avec des champs supplémentaires"""
|
||||
data = {
|
||||
"date_expiration": self.date_expiration.isoformat() + "Z"
|
||||
if self.date_expiration
|
||||
else None,
|
||||
"date_modif_passwd": self.date_modif_passwd.isoformat() + "Z"
|
||||
"date_expiration": (
|
||||
self.date_expiration.isoformat() + "Z" if self.date_expiration else None
|
||||
),
|
||||
"date_modif_passwd": (
|
||||
self.date_modif_passwd.isoformat() + "Z"
|
||||
if self.date_modif_passwd
|
||||
else None,
|
||||
"date_created": self.date_created.isoformat() + "Z"
|
||||
if self.date_created
|
||||
else None,
|
||||
else None
|
||||
),
|
||||
"date_created": (
|
||||
self.date_created.isoformat() + "Z" if self.date_created else None
|
||||
),
|
||||
"dept": self.dept,
|
||||
"id": self.id,
|
||||
"active": self.active,
|
||||
"cas_id": self.cas_id,
|
||||
"cas_allow_login": self.cas_allow_login,
|
||||
"cas_allow_scodoc_login": self.cas_allow_scodoc_login,
|
||||
"cas_last_login": self.cas_last_login.isoformat() + "Z"
|
||||
if self.cas_last_login
|
||||
else None,
|
||||
"cas_last_login": (
|
||||
self.cas_last_login.isoformat() + "Z" if self.cas_last_login else None
|
||||
),
|
||||
"edt_id": self.edt_id,
|
||||
"status_txt": "actif" if self.active else "fermé",
|
||||
"last_seen": self.last_seen.isoformat() + "Z" if self.last_seen else None,
|
||||
@ -477,8 +481,8 @@ class User(UserMixin, ScoDocModel):
|
||||
return f"{nom} {scu.format_prenom(self.prenom)} ({self.user_name})"
|
||||
|
||||
@staticmethod
|
||||
def get_user_id_from_nomplogin(nomplogin: str) -> Optional[int]:
|
||||
"""Returns id from the string "Dupont Pierre (dupont)"
|
||||
def get_user_from_nomplogin(nomplogin: str) -> Optional["User"]:
|
||||
"""Returns User instance from the string "Dupont Pierre (dupont)"
|
||||
or None if user does not exist
|
||||
"""
|
||||
match = re.match(r".*\((.*)\)", nomplogin.strip())
|
||||
@ -486,7 +490,7 @@ class User(UserMixin, ScoDocModel):
|
||||
user_name = match.group(1)
|
||||
u = User.query.filter_by(user_name=user_name).first()
|
||||
if u:
|
||||
return u.id
|
||||
return u
|
||||
return None
|
||||
|
||||
def get_nom_fmt(self):
|
||||
|
@ -35,7 +35,6 @@ from app.decorators import (
|
||||
permission_required,
|
||||
)
|
||||
from app.models import FormSemestre, FormSemestreInscription, Identite
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc.codes_cursus import UE_STANDARD
|
||||
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
|
@ -194,7 +194,7 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
||||
"""Génère la partie "titre" du bulletin de notes.
|
||||
Renvoie une liste d'objets platypus
|
||||
"""
|
||||
# comme les bulletins standard, mais avec notre préférence
|
||||
# comme les bulletins standards, mais avec notre préférence
|
||||
return super().bul_title_pdf(preference_field=preference_field)
|
||||
|
||||
def bul_part_below(self, fmt="pdf") -> list:
|
||||
@ -406,6 +406,8 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
||||
|
||||
def boite_identite(self) -> list:
|
||||
"Les informations sur l'identité et l'inscription de l'étudiant"
|
||||
parcour = self.formsemestre.etuds_inscriptions[self.etud.id].parcour
|
||||
|
||||
return [
|
||||
Paragraph(
|
||||
SU(f"""{self.etud.nomprenom}"""),
|
||||
@ -416,6 +418,7 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
||||
f"""
|
||||
<b>{self.bul["demission"]}</b><br/>
|
||||
Formation: {self.formsemestre.titre_num()}<br/>
|
||||
{'Parcours ' + parcour.code + '<br/>' if parcour else ''}
|
||||
Année universitaire: {self.formsemestre.annee_scolaire_str()}<br/>
|
||||
"""
|
||||
),
|
||||
|
@ -97,7 +97,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
||||
<span class="avertissement_redoublement">{formsemestre_2.annee_scolaire_str()
|
||||
if formsemestre_2 else ""}</span>
|
||||
</div>
|
||||
<div class="titre">RCUE</div>
|
||||
<div class="titre" title="Décisions sur RCUEs enregistrées sur l'ensemble du cursus">RCUE</div>
|
||||
"""
|
||||
)
|
||||
for dec_rcue in deca.get_decisions_rcues_annee():
|
||||
|
@ -178,19 +178,25 @@ class ModuleImplResults:
|
||||
eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)]
|
||||
# Nombre de notes (non vides, incluant ATT etc) des inscrits:
|
||||
nb_notes = eval_notes_inscr.notna().sum()
|
||||
# Etudiants avec notes en attente:
|
||||
# = ceux avec note ATT
|
||||
eval_etudids_attente = set(
|
||||
eval_notes_inscr.iloc[
|
||||
(eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
|
||||
].index
|
||||
)
|
||||
if evaluation.publish_incomplete:
|
||||
# et en "imédiat", tous ceux sans note
|
||||
eval_etudids_attente |= etudids_sans_note
|
||||
# Synthèse pour état du module:
|
||||
self.etudids_attente |= eval_etudids_attente
|
||||
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
||||
evaluation_id=evaluation.id,
|
||||
nb_attente=len(eval_etudids_attente),
|
||||
nb_notes=nb_notes,
|
||||
nb_notes=int(nb_notes),
|
||||
is_complete=is_complete,
|
||||
)
|
||||
# au moins une note en ATT dans ce modimpl:
|
||||
# au moins une note en attente (ATT ou manquante en mode "immédiat") dans ce modimpl:
|
||||
self.en_attente = bool(self.etudids_attente)
|
||||
|
||||
# Force columns names to integers (evaluation ids)
|
||||
|
@ -52,7 +52,7 @@ class ScoDocModel(db.Model):
|
||||
def create_from_dict(cls, data: dict) -> "ScoDocModel":
|
||||
"""Create a new instance of the model with attributes given in dict.
|
||||
The instance is added to the session (but not flushed nor committed).
|
||||
Use only relevant arributes for the given model and ignore others.
|
||||
Use only relevant attributes for the given model and ignore others.
|
||||
"""
|
||||
if data:
|
||||
args = cls.convert_dict_fields(cls.filter_model_attributes(data))
|
||||
|
@ -445,10 +445,11 @@ class Identite(models.ScoDocModel):
|
||||
"prenom_etat_civil": self.prenom_etat_civil,
|
||||
}
|
||||
|
||||
def to_dict_scodoc7(self, restrict=False) -> dict:
|
||||
def to_dict_scodoc7(self, restrict=False, with_inscriptions=False) -> dict:
|
||||
"""Représentation dictionnaire,
|
||||
compatible ScoDoc7 mais sans infos admission.
|
||||
Si restrict, cache les infos "personnelles" si pas permission ViewEtudData
|
||||
Si with_inscriptions, inclut les champs "inscription"
|
||||
"""
|
||||
e_dict = self.__dict__.copy() # dict(self.__dict__)
|
||||
e_dict.pop("_sa_instance_state", None)
|
||||
@ -460,6 +461,8 @@ class Identite(models.ScoDocModel):
|
||||
adresse = self.adresses.first()
|
||||
if adresse:
|
||||
e_dict.update(adresse.to_dict(restrict=restrict))
|
||||
if with_inscriptions:
|
||||
e_dict.update(self.inscription_descr())
|
||||
return {k: v or "" for k, v in e_dict.items()} # convert_null_outputs_to_empty
|
||||
|
||||
def to_dict_bul(self, include_urls=True):
|
||||
@ -574,7 +577,9 @@ class Identite(models.ScoDocModel):
|
||||
return r[0] if r else None
|
||||
|
||||
def inscription_descr(self) -> dict:
|
||||
"""Description de l'état d'inscription"""
|
||||
"""Description de l'état d'inscription
|
||||
avec champs compatibles templates ScoDoc7
|
||||
"""
|
||||
inscription_courante = self.inscription_courante()
|
||||
if inscription_courante:
|
||||
titre_sem = inscription_courante.formsemestre.titre_mois()
|
||||
@ -585,7 +590,7 @@ class Identite(models.ScoDocModel):
|
||||
else:
|
||||
inscr_txt = "Inscrit en"
|
||||
|
||||
return {
|
||||
result = {
|
||||
"etat_in_cursem": inscription_courante.etat,
|
||||
"inscription_courante": inscription_courante,
|
||||
"inscription": titre_sem,
|
||||
@ -608,15 +613,20 @@ class Identite(models.ScoDocModel):
|
||||
inscription = "ancien"
|
||||
situation = "ancien élève"
|
||||
else:
|
||||
inscription = ("non inscrit",)
|
||||
inscription = "non inscrit"
|
||||
situation = inscription
|
||||
return {
|
||||
result = {
|
||||
"etat_in_cursem": "?",
|
||||
"inscription_courante": None,
|
||||
"inscription": inscription,
|
||||
"inscription_str": inscription,
|
||||
"situation": situation,
|
||||
}
|
||||
# aliases pour compat templates ScoDoc7
|
||||
result["etatincursem"] = result["etat_in_cursem"]
|
||||
result["inscriptionstr"] = result["inscription_str"]
|
||||
|
||||
return result
|
||||
|
||||
def inscription_etat(self, formsemestre_id: int) -> str:
|
||||
"""État de l'inscription de cet étudiant au semestre:
|
||||
@ -749,9 +759,7 @@ def check_etud_duplicate_code(args, code_name, edit=True):
|
||||
).all()
|
||||
duplicate = False
|
||||
if edit:
|
||||
duplicate = (len(etuds) > 1) or (
|
||||
(len(etuds) == 1) and etuds[0].id != args["etudid"]
|
||||
)
|
||||
duplicate = (len(etuds) > 1) or ((len(etuds) == 1) and etuds[0].id != etudid)
|
||||
else:
|
||||
duplicate = len(etuds) > 0
|
||||
if duplicate:
|
||||
|
@ -5,7 +5,7 @@
|
||||
import datetime
|
||||
from operator import attrgetter
|
||||
|
||||
from flask import g, url_for
|
||||
from flask import abort, g, url_for
|
||||
from flask_login import current_user
|
||||
import sqlalchemy as sa
|
||||
|
||||
@ -241,6 +241,25 @@ class Evaluation(db.Model):
|
||||
if k != "_sa_instance_state" and k != "id" and k in data:
|
||||
setattr(self, k, data[k])
|
||||
|
||||
@classmethod
|
||||
def get_evaluation(
|
||||
cls, evaluation_id: int | str, dept_id: int = None
|
||||
) -> "Evaluation":
|
||||
"""Evaluation ou 404, cherche uniquement dans le département spécifié ou le courant."""
|
||||
from app.models import FormSemestre, ModuleImpl
|
||||
|
||||
if not isinstance(evaluation_id, int):
|
||||
try:
|
||||
evaluation_id = int(evaluation_id)
|
||||
except (TypeError, ValueError):
|
||||
abort(404, "evaluation_id invalide")
|
||||
if g.scodoc_dept:
|
||||
dept_id = dept_id if dept_id is not None else g.scodoc_dept_id
|
||||
query = cls.query.filter_by(id=evaluation_id)
|
||||
if dept_id is not None:
|
||||
query = query.join(ModuleImpl).join(FormSemestre).filter_by(dept_id=dept_id)
|
||||
return query.first_or_404()
|
||||
|
||||
@classmethod
|
||||
def get_max_numero(cls, moduleimpl_id: int) -> int:
|
||||
"""Return max numero among evaluations in this
|
||||
@ -265,7 +284,9 @@ class Evaluation(db.Model):
|
||||
evaluations = moduleimpl.evaluations.order_by(
|
||||
Evaluation.date_debut, Evaluation.numero
|
||||
).all()
|
||||
all_numbered = all(e.numero is not None for e in evaluations)
|
||||
numeros_distincts = {e.numero for e in evaluations if e.numero is not None}
|
||||
# pas de None, pas de dupliqués
|
||||
all_numbered = len(numeros_distincts) == len(evaluations)
|
||||
if all_numbered and only_if_unumbered:
|
||||
return # all ok
|
||||
|
||||
|
@ -3,12 +3,13 @@
|
||||
"""
|
||||
import pandas as pd
|
||||
from flask import abort, g
|
||||
from flask_login import current_user
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
from app import db
|
||||
from app.auth.models import User
|
||||
from app.comp import df_cache
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import APO_CODE_STR_LEN, ScoDocModel
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.evaluations import Evaluation
|
||||
from app.models.modules import Module
|
||||
@ -17,7 +18,7 @@ from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
class ModuleImpl(db.Model):
|
||||
class ModuleImpl(ScoDocModel):
|
||||
"""Mise en oeuvre d'un module pour une annee/semestre"""
|
||||
|
||||
__tablename__ = "notes_moduleimpl"
|
||||
@ -36,7 +37,10 @@ class ModuleImpl(db.Model):
|
||||
index=True,
|
||||
nullable=False,
|
||||
)
|
||||
responsable_id = db.Column("responsable_id", db.Integer, db.ForeignKey("user.id"))
|
||||
responsable_id = db.Column(
|
||||
"responsable_id", db.Integer, db.ForeignKey("user.id", ondelete="SET NULL")
|
||||
)
|
||||
responsable = db.relationship("User", back_populates="modimpls")
|
||||
# formule de calcul moyenne:
|
||||
computation_expr = db.Column(db.Text())
|
||||
|
||||
@ -52,8 +56,8 @@ class ModuleImpl(db.Model):
|
||||
secondary="notes_modules_enseignants",
|
||||
lazy="dynamic",
|
||||
backref="moduleimpl",
|
||||
viewonly=True,
|
||||
)
|
||||
"enseignants du module (sans le responsable)"
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} {self.id} module={repr(self.module)}>"
|
||||
@ -85,7 +89,7 @@ class ModuleImpl(db.Model):
|
||||
|
||||
@classmethod
|
||||
def get_modimpl(cls, moduleimpl_id: int | str, dept_id: int = None) -> "ModuleImpl":
|
||||
"""FormSemestre ou 404, cherche uniquement dans le département spécifié ou le courant."""
|
||||
"""ModuleImpl ou 404, cherche uniquement dans le département spécifié ou le courant."""
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
if not isinstance(moduleimpl_id, int):
|
||||
@ -187,7 +191,7 @@ class ModuleImpl(db.Model):
|
||||
return allow_ens and user.id in (ens.id for ens in self.enseignants)
|
||||
return True
|
||||
|
||||
def can_change_ens_by(self, user: User, raise_exc=False) -> bool:
|
||||
def can_change_responsable(self, user: User, raise_exc=False) -> bool:
|
||||
"""Check if user can modify module resp.
|
||||
If raise_exc, raises exception (AccessDenied or ScoLockedSemError) if not.
|
||||
= Admin, et dir des etud. (si option l'y autorise)
|
||||
@ -208,6 +212,27 @@ class ModuleImpl(db.Model):
|
||||
raise AccessDenied(f"Modification impossible pour {user}")
|
||||
return False
|
||||
|
||||
def can_change_ens(self, user: User | None = None, raise_exc=True) -> bool:
|
||||
"""check if user can modify ens list (raise exception if not)"
|
||||
if user is None, current user.
|
||||
"""
|
||||
user = current_user if user is None else user
|
||||
if not self.formsemestre.etat:
|
||||
if raise_exc:
|
||||
raise ScoLockedSemError("Modification impossible: semestre verrouille")
|
||||
return False
|
||||
# -- check access
|
||||
# admin, resp. module ou resp. semestre
|
||||
if (
|
||||
user.id != self.responsable_id
|
||||
and not user.has_permission(Permission.EditFormSemestre)
|
||||
and user.id not in (u.id for u in self.formsemestre.responsables)
|
||||
):
|
||||
if raise_exc:
|
||||
raise AccessDenied(f"Modification impossible pour {user}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def est_inscrit(self, etud: Identite) -> bool:
|
||||
"""
|
||||
Vérifie si l'étudiant est bien inscrit au moduleimpl (même si DEM ou DEF au semestre).
|
||||
|
@ -33,7 +33,7 @@ from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app.models import Evaluation, GroupDescr, ModuleImpl, Partition
|
||||
from app.models import Evaluation, GroupDescr, Identite, ModuleImpl, Partition
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
@ -160,26 +160,31 @@ def sidebar(etudid: int = None):
|
||||
etudid = request.form.get("etudid", None)
|
||||
|
||||
if etudid is not None:
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
params.update(etud)
|
||||
params["fiche_url"] = url_for(
|
||||
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||
)
|
||||
etud = Identite.get_etud(etudid)
|
||||
# compte les absences du semestre en cours
|
||||
H.append(
|
||||
"""<h2 id="insidebar-etud"><a href="%(fiche_url)s" class="sidebar">
|
||||
<font color="#FF0000">%(civilite_str)s %(nom_disp)s</font></a>
|
||||
f"""<h2 id="insidebar-etud"><a href="{
|
||||
url_for(
|
||||
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||
)
|
||||
}" class="sidebar">
|
||||
<font color="#FF0000">{etud.civilite_str} {etud.nom_disp()}</font></a>
|
||||
</h2>
|
||||
<b>Absences</b>"""
|
||||
% params
|
||||
)
|
||||
if etud["cursem"]:
|
||||
cur_sem = etud["cursem"]
|
||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, cur_sem)
|
||||
inscription = etud.inscription_courante()
|
||||
if inscription:
|
||||
formsemestre = inscription.formsemestre
|
||||
nbabs, nbabsjust = sco_assiduites.formsemestre_get_assiduites_count(
|
||||
etudid, formsemestre
|
||||
)
|
||||
nbabsnj = nbabs - nbabsjust
|
||||
H.append(
|
||||
f"""<span title="absences du { cur_sem["date_debut"] } au {
|
||||
cur_sem["date_fin"] }">({
|
||||
f"""<span title="absences du {
|
||||
formsemestre.date_debut.strftime("%d/%m/%Y")
|
||||
} au {
|
||||
formsemestre.date_fin.strftime("%d/%m/%Y")
|
||||
}">({
|
||||
sco_preferences.get_preference("assi_metrique", None)})
|
||||
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
||||
)
|
||||
@ -189,10 +194,12 @@ def sidebar(etudid: int = None):
|
||||
cur_formsemestre_id = retreive_formsemestre_from_request()
|
||||
H.append(
|
||||
f"""
|
||||
<li><a href="{ url_for('assiduites.ajout_assiduite_etud',
|
||||
<li><a href="{
|
||||
url_for('assiduites.ajout_assiduite_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">Ajouter</a></li>
|
||||
<li><a href="{ url_for('assiduites.ajout_justificatif_etud',
|
||||
<li><a href="{
|
||||
url_for('assiduites.ajout_justificatif_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
||||
formsemestre_id=cur_formsemestre_id,
|
||||
)
|
||||
@ -201,7 +208,8 @@ def sidebar(etudid: int = None):
|
||||
)
|
||||
if sco_preferences.get_preference("handle_billets_abs"):
|
||||
H.append(
|
||||
f"""<li><a href="{ url_for('absences.billets_etud',
|
||||
f"""<li><a href="{
|
||||
url_for('absences.billets_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">Billets</a></li>"""
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -268,10 +268,12 @@ def abs_notification_message(
|
||||
"""
|
||||
from app.scodoc import sco_bulletins
|
||||
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
etud = Identite.get_etud(etudid)
|
||||
|
||||
# Variables accessibles dans les balises du template: %(nom_variable)s :
|
||||
values = sco_bulletins.make_context_dict(formsemestre, etud)
|
||||
values = sco_bulletins.make_context_dict(
|
||||
formsemestre, etud.to_dict_scodoc7(with_inscriptions=True)
|
||||
)
|
||||
|
||||
values["nbabs"] = nbabs
|
||||
values["nbabsjust"] = nbabsjust
|
||||
@ -287,7 +289,7 @@ def abs_notification_message(
|
||||
log("abs_notification_message: empty template, not sending message")
|
||||
return None
|
||||
|
||||
subject = f"""[ScoDoc] Trop d'absences pour {etud["nomprenom"]}"""
|
||||
subject = f"""[ScoDoc] Trop d'absences pour {etud.nomprenom}"""
|
||||
msg = Message(subject, sender=email.get_from_addr(formsemestre.departement.acronym))
|
||||
msg.body = txt
|
||||
return msg
|
||||
|
@ -138,21 +138,18 @@ def etud_upload_file_form(etudid):
|
||||
"""Page with a form to choose and upload a file, with a description."""
|
||||
# check permission
|
||||
if not can_edit_etud_archive(current_user):
|
||||
raise AccessDenied("opération non autorisée pour %s" % current_user)
|
||||
etuds = sco_etud.get_etud_info(filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
raise AccessDenied(f"opération non autorisée pour {current_user}")
|
||||
etud = Identite.get_etud(etudid)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Chargement d'un document associé à %(nomprenom)s" % etud,
|
||||
page_title=f"Chargement d'un document associé à {etud.nomprenom}",
|
||||
),
|
||||
"""<h2>Chargement d'un document associé à %(nomprenom)s</h2>
|
||||
"""
|
||||
% etud,
|
||||
"""<p>Le fichier ne doit pas dépasser %sMo.</p>
|
||||
"""
|
||||
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
|
||||
f"""<h2>Chargement d'un document associé à {etud.nomprenom}</h2>
|
||||
|
||||
<p>Le fichier ne doit pas dépasser {
|
||||
scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)}Mo.</p>
|
||||
""",
|
||||
]
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
@ -176,20 +173,13 @@ def etud_upload_file_form(etudid):
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
)
|
||||
else:
|
||||
return flask.redirect(etud.url_fiche())
|
||||
data = tf[2]["datafile"].read()
|
||||
descr = tf[2]["description"]
|
||||
filename = tf[2]["datafile"].filename
|
||||
etud_archive_id = etud["etudid"]
|
||||
_store_etud_file_to_new_archive(
|
||||
etud_archive_id, data, filename, description=descr
|
||||
)
|
||||
return flask.redirect(
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
)
|
||||
etud_archive_id = (etudid,)
|
||||
_store_etud_file_to_new_archive(etud_archive_id, data, filename, description=descr)
|
||||
return flask.redirect(etud.url_fiche())
|
||||
|
||||
|
||||
def _store_etud_file_to_new_archive(
|
||||
@ -209,23 +199,20 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
||||
# check permission
|
||||
if not can_edit_etud_archive(current_user):
|
||||
raise AccessDenied(f"opération non autorisée pour {current_user}")
|
||||
etuds = sco_etud.get_etud_info(filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etud["etudid"]
|
||||
etud = Identite.get_etud(etudid)
|
||||
etud_archive_id = etudid
|
||||
archive_id = ETUDS_ARCHIVER.get_id_from_name(
|
||||
etud_archive_id, archive_name, dept_id=etud["dept_id"]
|
||||
etud_archive_id, archive_name, dept_id=etud.dept_id
|
||||
)
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
"""<h2>Confirmer la suppression des fichiers ?</h2>
|
||||
<p>Fichier associé le %s à l'étudiant %s</p>
|
||||
<p>La suppression sera définitive.</p>"""
|
||||
% (
|
||||
ETUDS_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
|
||||
etud["nomprenom"],
|
||||
),
|
||||
f"""<h2>Confirmer la suppression des fichiers ?</h2>
|
||||
<p>Fichier associé le {
|
||||
ETUDS_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M")
|
||||
} à l'étudiant {etud.nomprenom}
|
||||
</p>
|
||||
<p>La suppression sera définitive.</p>
|
||||
""",
|
||||
dest_url="",
|
||||
cancel_url=url_for(
|
||||
"scolar.fiche_etud",
|
||||
@ -236,22 +223,17 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
||||
parameters={"etudid": etudid, "archive_name": archive_name},
|
||||
)
|
||||
|
||||
ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud["dept_id"])
|
||||
ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud.dept_id)
|
||||
flash("Archive supprimée")
|
||||
return flask.redirect(
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
)
|
||||
return flask.redirect(etud.url_fiche())
|
||||
|
||||
|
||||
def etud_get_archived_file(etudid, archive_name, filename):
|
||||
"""Send file to client."""
|
||||
etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etud["etudid"]
|
||||
etud = Identite.get_etud(etudid)
|
||||
etud_archive_id = etud.id
|
||||
return ETUDS_ARCHIVER.get_archived_file(
|
||||
etud_archive_id, archive_name, filename, dept_id=etud["dept_id"]
|
||||
etud_archive_id, archive_name, filename, dept_id=etud.dept_id
|
||||
)
|
||||
|
||||
|
||||
|
@ -735,17 +735,18 @@ def fill_etuds_info(etuds: list[dict], add_admission=True):
|
||||
etud["nomlycee"] = etud.get("nomlycee", "") or ""
|
||||
|
||||
|
||||
# voir Identite.inscription_descr et Identite.to_dict_scodoc7(with_inscriptions=True)
|
||||
def etud_inscriptions_infos(etudid: int, ne="") -> dict:
|
||||
"""Dict avec les informations sur les semestres passés et courant.
|
||||
{
|
||||
"sems" : , # trie les semestres par date de debut, le plus recent d'abord
|
||||
"ins" : ,
|
||||
"cursem" : ,
|
||||
"inscription" : ,
|
||||
"inscriptionstr" : ,
|
||||
"inscription_formsemestre_id" : ,
|
||||
"etatincursem" : ,
|
||||
"situation" : ,
|
||||
"inscription" : , # cursem["titremois"]
|
||||
"inscriptionstr" : , # "Inscrit en " + cursem["titremois"]
|
||||
"inscription_formsemestre_id" : , # cursem["formsemestre_id"]
|
||||
"etatincursem" : , # curi["etat"]
|
||||
"situation" : , # descr_situation_etud(etudid, ne)
|
||||
}
|
||||
"""
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
@ -162,50 +162,48 @@ def do_evaluation_get_all_notes(
|
||||
return d
|
||||
|
||||
|
||||
def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
||||
def moduleimpl_evaluation_move(evaluation_id: int, after=0):
|
||||
"""Move before/after previous one (decrement/increment numero)
|
||||
(published)
|
||||
"""
|
||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
redirect = int(redirect)
|
||||
evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||
# access: can change eval ?
|
||||
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
||||
raise AccessDenied(
|
||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||
)
|
||||
Evaluation.moduleimpl_evaluation_renumber(
|
||||
evaluation.moduleimpl, only_if_unumbered=True
|
||||
)
|
||||
e = get_evaluations_dict(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 = get_evaluations_dict({"moduleimpl_id": e["moduleimpl_id"]})
|
||||
if len(mod_evals) > 1:
|
||||
idx = [p["evaluation_id"] for p in mod_evals].index(evaluation_id)
|
||||
|
||||
Evaluation.moduleimpl_evaluation_renumber(
|
||||
evaluation.moduleimpl, only_if_unumbered=True
|
||||
)
|
||||
mod_evaluations = evaluation.moduleimpl.evaluations.all()
|
||||
if len(mod_evaluations) > 1:
|
||||
idx = [e.id for e in mod_evaluations].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]
|
||||
neigh = mod_evaluations[idx - 1]
|
||||
elif after == 1 and idx < len(mod_evaluations) - 1:
|
||||
neigh = mod_evaluations[idx + 1]
|
||||
if neigh: #
|
||||
if neigh["numero"] == e["numero"]:
|
||||
if neigh.numero == evaluation.numero:
|
||||
log("Warning: moduleimpl_evaluation_move: forcing renumber")
|
||||
Evaluation.moduleimpl_evaluation_renumber(
|
||||
evaluation.moduleimpl, 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)
|
||||
evaluation.numero, neigh.numero = neigh.numero, evaluation.numero
|
||||
db.session.add(evaluation)
|
||||
db.session.add(neigh)
|
||||
db.session.commit()
|
||||
# redirect to moduleimpl page:
|
||||
if redirect:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=e["moduleimpl_id"],
|
||||
moduleimpl_id=evaluation.moduleimpl.id,
|
||||
)
|
||||
)
|
||||
|
@ -37,7 +37,7 @@ from flask_login import current_user
|
||||
from flask import request
|
||||
|
||||
from app import db
|
||||
from app.models import Evaluation, FormSemestre, ModuleImpl
|
||||
from app.models import Evaluation, Module, ModuleImpl
|
||||
from app.models.evaluations import heure_to_time
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -47,7 +47,6 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
|
||||
@ -58,27 +57,20 @@ def evaluation_create_form(
|
||||
page_title="Évaluation",
|
||||
):
|
||||
"Formulaire création/édition d'une évaluation (pas de ses notes)"
|
||||
evaluation: Evaluation
|
||||
if evaluation_id is not None:
|
||||
evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
|
||||
evaluation = db.session.get(Evaluation, evaluation_id)
|
||||
if evaluation is None:
|
||||
raise ScoValueError("Cette évaluation n'existe pas ou plus !")
|
||||
moduleimpl_id = evaluation.moduleimpl_id
|
||||
#
|
||||
modimpl: ModuleImpl = (
|
||||
ModuleImpl.query.filter_by(id=moduleimpl_id)
|
||||
.join(FormSemestre)
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
.first_or_404()
|
||||
)
|
||||
modimpl_o = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[
|
||||
0
|
||||
]
|
||||
mod = modimpl_o["module"]
|
||||
formsemestre_id = modimpl_o["formsemestre_id"]
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
formsemestre_id = modimpl.formsemestre_id
|
||||
formsemestre = modimpl.formsemestre
|
||||
module: Module = modimpl.module
|
||||
sem_ues = formsemestre.get_ues(with_sport=False)
|
||||
is_malus = mod["module_type"] == ModuleType.MALUS
|
||||
is_apc = mod["module_type"] in (ModuleType.RESSOURCE, ModuleType.SAE)
|
||||
is_malus = module.module_type == ModuleType.MALUS
|
||||
is_apc = module.module_type in (ModuleType.RESSOURCE, ModuleType.SAE)
|
||||
preferences = sco_preferences.SemPreferences(formsemestre.id)
|
||||
can_edit_poids = not preferences["but_disable_edit_poids_evaluations"]
|
||||
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
|
||||
@ -98,9 +90,11 @@ def evaluation_create_form(
|
||||
# création nouvel
|
||||
if moduleimpl_id is None:
|
||||
raise ValueError("missing moduleimpl_id parameter")
|
||||
numeros = [(e.numero or 0) for e in modimpl.evaluations]
|
||||
initvalues = {
|
||||
"note_max": 20,
|
||||
"jour": time.strftime("%d/%m/%Y", time.localtime()),
|
||||
"note_max": 20,
|
||||
"numero": (max(numeros) + 1) if numeros else 0,
|
||||
"publish_incomplete": is_malus,
|
||||
"visibulletin": 1,
|
||||
}
|
||||
@ -128,18 +122,7 @@ def evaluation_create_form(
|
||||
min_note_max_str = scu.fmt_note(min_note_max)
|
||||
else:
|
||||
min_note_max_str = "0"
|
||||
#
|
||||
H = [
|
||||
f"""<h3>{action} en
|
||||
{scu.MODULE_TYPE_NAMES[mod["module_type"]]} <a class="stdlink" href="{
|
||||
url_for("notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
||||
}">{mod["code"] or "module sans code"} {mod["titre"]}</a> {link}</h3>
|
||||
"""
|
||||
]
|
||||
|
||||
heures = [f"{h:02d}h{m:02d}" for h in range(8, 19) for m in (0, 30)]
|
||||
#
|
||||
initvalues["coefficient"] = initvalues.get("coefficient", 1.0)
|
||||
vals = scu.get_request_args()
|
||||
#
|
||||
@ -164,6 +147,7 @@ def evaluation_create_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"}),
|
||||
("numero", {"default": initvalues["numero"], "input_type": "hidden"}),
|
||||
(
|
||||
"jour",
|
||||
{
|
||||
@ -323,6 +307,16 @@ def evaluation_create_form(
|
||||
dest_url = url_for(
|
||||
"notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id
|
||||
)
|
||||
H = [
|
||||
f"""<h3>{action} en
|
||||
{scu.MODULE_TYPE_NAMES[module.module_type]} <a class="stdlink" href="{
|
||||
url_for("notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
||||
}">{module.code or "module sans code"} {
|
||||
module.titre or module.abbrev or "(sans titre)"
|
||||
}</a> {link}</h3>
|
||||
"""
|
||||
]
|
||||
if tf[0] == 0:
|
||||
head = html_sco_header.sco_header(page_title=page_title)
|
||||
return (
|
||||
|
@ -280,18 +280,21 @@ def do_evaluation_etat(
|
||||
}
|
||||
|
||||
|
||||
def _summarize_evals_etats(evals: list[dict]) -> dict:
|
||||
def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
||||
"""Synthétise les états d'une liste d'évaluations
|
||||
evals: list of mappings (etats),
|
||||
utilise e["etat"]["evalcomplete"], e["etat"]["nb_notes"], e["etat"]["last_modif"]
|
||||
-> nb_eval_completes, nb_evals_en_cours,
|
||||
nb_evals_vides, date derniere modif
|
||||
->
|
||||
nb_eval_completes (= prises en compte)
|
||||
nb_evals_en_cours (= avec des notes, mais pas complete)
|
||||
nb_evals_vides (= sans aucune note)
|
||||
date derniere modif
|
||||
|
||||
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
|
||||
"""
|
||||
nb_evals_completes, nb_evals_en_cours, nb_evals_vides = 0, 0, 0
|
||||
dates = []
|
||||
for e in evals:
|
||||
for e in etat_evals:
|
||||
if e["etat"]["evalcomplete"]:
|
||||
nb_evals_completes += 1
|
||||
elif e["etat"]["nb_notes"] == 0:
|
||||
@ -345,8 +348,8 @@ def do_evaluation_etat_in_sem(formsemestre: FormSemestre) -> dict:
|
||||
|
||||
def do_evaluation_etat_in_mod(nt, modimpl: ModuleImpl):
|
||||
"""état des évaluations dans ce module"""
|
||||
evals = nt.get_mod_evaluation_etat_list(modimpl)
|
||||
etat = _summarize_evals_etats(evals)
|
||||
etat_evals = nt.get_mod_evaluation_etat_list(modimpl)
|
||||
etat = _summarize_evals_etats(etat_evals)
|
||||
# Il y a-t-il des notes en attente dans ce module ?
|
||||
etat["attente"] = nt.modimpls_results[modimpl.id].en_attente
|
||||
return etat
|
||||
|
@ -241,7 +241,7 @@ def do_formsemestre_create(args, silent=False):
|
||||
write_formsemestre_etapes(args)
|
||||
if args["responsables"]:
|
||||
args["formsemestre_id"] = formsemestre_id
|
||||
write_formsemestre_responsables(args)
|
||||
_write_formsemestre_responsables(args)
|
||||
|
||||
# create default partition
|
||||
partition_id = sco_groups.partition_create(
|
||||
@ -275,7 +275,7 @@ def do_formsemestre_edit(sem, cnx=None, **kw):
|
||||
|
||||
_formsemestreEditor.edit(cnx, sem, **kw)
|
||||
write_formsemestre_etapes(sem)
|
||||
write_formsemestre_responsables(sem)
|
||||
_write_formsemestre_responsables(sem)
|
||||
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=sem["formsemestre_id"]
|
||||
@ -296,8 +296,12 @@ def read_formsemestre_responsables(formsemestre_id: int) -> list[int]: # py3.9+
|
||||
return [x["responsable_id"] for x in r]
|
||||
|
||||
|
||||
def write_formsemestre_responsables(sem):
|
||||
return _write_formsemestre_aux(sem, "responsables", "responsable_id")
|
||||
def _write_formsemestre_responsables(sem): # TODO old, à ré-écrire avec models
|
||||
if sem and "responsables" in sem:
|
||||
sem["responsables"] = [
|
||||
uid for uid in sem["responsables"] if (uid is not None) and (uid != -1)
|
||||
]
|
||||
_write_formsemestre_aux(sem, "responsables", "responsable_id")
|
||||
|
||||
|
||||
# ---------------------- Coefs des UE
|
||||
@ -362,10 +366,11 @@ def read_formsemestre_etapes(formsemestre_id): # OBSOLETE
|
||||
return [ApoEtapeVDI(x["etape_apo"]) for x in r if x["etape_apo"]]
|
||||
|
||||
|
||||
def write_formsemestre_etapes(sem):
|
||||
def write_formsemestre_etapes(sem): # TODO old, à ré-écrire avec models
|
||||
return _write_formsemestre_aux(sem, "etapes", "etape_apo")
|
||||
|
||||
|
||||
# TODO old, à ré-écrire avec models
|
||||
def _write_formsemestre_aux(sem, fieldname, valuename):
|
||||
"""fieldname: 'etapes' ou 'responsables'
|
||||
valuename: 'etape_apo' ou 'responsable_id'
|
||||
|
@ -857,17 +857,20 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
tf[2]["bul_hide_xml"] = True
|
||||
# remap les identifiants de responsables:
|
||||
for field in resp_fields:
|
||||
tf[2][field] = User.get_user_id_from_nomplogin(tf[2][field])
|
||||
resp = User.get_user_from_nomplogin(tf[2][field])
|
||||
tf[2][field] = resp.id if resp else -1
|
||||
tf[2]["responsables"] = []
|
||||
for field in resp_fields:
|
||||
if tf[2][field]:
|
||||
tf[2]["responsables"].append(tf[2][field])
|
||||
for module_id in tf[2]["tf-checked"]:
|
||||
mod_resp_id = User.get_user_id_from_nomplogin(tf[2][module_id])
|
||||
if mod_resp_id is None:
|
||||
mod_resp = User.get_user_from_nomplogin(tf[2][module_id])
|
||||
if mod_resp is None:
|
||||
# Si un module n'a pas de responsable (ou inconnu),
|
||||
# l'affecte au 1er directeur des etudes:
|
||||
mod_resp_id = tf[2]["responsable_id"]
|
||||
else:
|
||||
mod_resp_id = mod_resp.id
|
||||
tf[2][module_id] = mod_resp_id
|
||||
|
||||
# etapes:
|
||||
@ -1227,9 +1230,12 @@ def formsemestre_clone(formsemestre_id):
|
||||
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
||||
)
|
||||
else:
|
||||
resp = User.get_user_from_nomplogin(tf[2]["responsable_id"])
|
||||
if not resp:
|
||||
raise ScoValueError("id responsable invalide")
|
||||
new_formsemestre_id = do_formsemestre_clone(
|
||||
formsemestre_id,
|
||||
User.get_user_id_from_nomplogin(tf[2]["responsable_id"]),
|
||||
resp.id,
|
||||
tf[2]["date_debut"],
|
||||
tf[2]["date_fin"],
|
||||
clone_evaluations=tf[2]["clone_evaluations"],
|
||||
@ -1273,29 +1279,24 @@ def do_formsemestre_clone(
|
||||
log(f"created formsemestre {formsemestre_id}")
|
||||
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||
# 2- create moduleimpls
|
||||
mods_orig = sco_moduleimpl.moduleimpl_list(formsemestre_id=orig_formsemestre_id)
|
||||
for mod_orig in mods_orig:
|
||||
args = mod_orig.copy()
|
||||
modimpl_orig: ModuleImpl
|
||||
for modimpl_orig in formsemestre_orig.modimpls:
|
||||
args = modimpl_orig.to_dict(with_module=False)
|
||||
args["formsemestre_id"] = formsemestre_id
|
||||
mid = sco_moduleimpl.do_moduleimpl_create(args)
|
||||
# copy notes_modules_enseignants
|
||||
ens = sco_moduleimpl.do_ens_list(
|
||||
args={"moduleimpl_id": mod_orig["moduleimpl_id"]}
|
||||
)
|
||||
for e in ens:
|
||||
args = e.copy()
|
||||
args["moduleimpl_id"] = mid
|
||||
sco_moduleimpl.do_ens_create(args)
|
||||
modimpl_new = ModuleImpl.create_from_dict(args)
|
||||
db.session.flush()
|
||||
# copy enseignants
|
||||
for ens in modimpl_orig.enseignants:
|
||||
modimpl_new.enseignants.append(ens)
|
||||
db.session.add(modimpl_new)
|
||||
# optionally, copy evaluations
|
||||
if clone_evaluations:
|
||||
for e in Evaluation.query.filter_by(
|
||||
moduleimpl_id=mod_orig["moduleimpl_id"]
|
||||
):
|
||||
for e in Evaluation.query.filter_by(moduleimpl_id=modimpl_orig.id):
|
||||
# copie en enlevant la date
|
||||
new_eval = e.clone(
|
||||
not_copying=("date_debut", "date_fin", "moduleimpl_id")
|
||||
)
|
||||
new_eval.moduleimpl_id = mid
|
||||
new_eval.moduleimpl_id = modimpl_new.id
|
||||
# Copie les poids APC de l'évaluation
|
||||
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
|
||||
db.session.commit()
|
||||
|
@ -1262,6 +1262,7 @@ def formsemestre_tableau_modules(
|
||||
etat["nb_evals_completes"] > 0
|
||||
and etat["nb_evals_en_cours"] == 0
|
||||
and etat["nb_evals_vides"] == 0
|
||||
and not etat["attente"]
|
||||
):
|
||||
H.append(f'<tr class="formsemestre_status_green{fontorange}">')
|
||||
else:
|
||||
@ -1315,6 +1316,7 @@ def formsemestre_tableau_modules(
|
||||
if nb_evals != 0:
|
||||
H.append(
|
||||
f"""<a href="{moduleimpl_status_url}"
|
||||
title="les évaluations 'ok' sont celles prises en compte dans les calculs"
|
||||
class="formsemestre_status_link">{nb_evals} prévues,
|
||||
{etat["nb_evals_completes"]} ok</a>"""
|
||||
)
|
||||
|
@ -25,22 +25,18 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Fonctions sur les moduleimpl
|
||||
"""Fonctions sur les moduleimpl (legacy: use models.moduleimpls instead)
|
||||
"""
|
||||
|
||||
from flask_login import current_user
|
||||
import psycopg2
|
||||
|
||||
from app import db
|
||||
|
||||
from app.models import Formation
|
||||
from app.scodoc import scolog
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_cache
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import ScoValueError, AccessDenied
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
# --- Gestion des "Implémentations de Modules"
|
||||
# Un "moduleimpl" correspond a la mise en oeuvre d'un module
|
||||
@ -57,15 +53,6 @@ _moduleimplEditor = ndb.EditableTable(
|
||||
),
|
||||
)
|
||||
|
||||
_modules_enseignantsEditor = ndb.EditableTable(
|
||||
"notes_modules_enseignants",
|
||||
None, # pas d'id dans cette Table d'association
|
||||
(
|
||||
"moduleimpl_id", # associe moduleimpl
|
||||
"ens_id", # a l'id de l'enseignant (User.id)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def do_moduleimpl_create(args):
|
||||
"create a moduleimpl"
|
||||
@ -108,9 +95,6 @@ def moduleimpl_list(moduleimpl_id=None, formsemestre_id=None, module_id=None):
|
||||
args = locals()
|
||||
cnx = ndb.GetDBConnexion()
|
||||
modimpls = _moduleimplEditor.list(cnx, args)
|
||||
# Ajoute la liste des enseignants
|
||||
for mo in modimpls:
|
||||
mo["ens"] = do_ens_list(args={"moduleimpl_id": mo["moduleimpl_id"]})
|
||||
return modimpls
|
||||
|
||||
|
||||
@ -342,65 +326,3 @@ def do_moduleimpl_inscrit_etuds(moduleimpl_id, formsemestre_id, etudids, reset=F
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=formsemestre_id
|
||||
) # > moduleimpl_inscrit_etuds
|
||||
|
||||
|
||||
def do_ens_list(*args, **kw):
|
||||
"liste les enseignants d'un moduleimpl (pas le responsable)"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
ens = _modules_enseignantsEditor.list(cnx, *args, **kw)
|
||||
return ens
|
||||
|
||||
|
||||
def do_ens_edit(*args, **kw):
|
||||
"edit ens"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
_modules_enseignantsEditor.edit(cnx, *args, **kw)
|
||||
|
||||
|
||||
def do_ens_create(args):
|
||||
"create ens"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
r = _modules_enseignantsEditor.create(cnx, args)
|
||||
return r
|
||||
|
||||
|
||||
def can_change_module_resp(moduleimpl_id):
|
||||
"""Check if current user can modify module resp. (raise exception if not).
|
||||
= Admin, et dir des etud. (si option l'y autorise)
|
||||
"""
|
||||
M = moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
# -- check lock
|
||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||
if not sem["etat"]:
|
||||
raise ScoValueError("Modification impossible: semestre verrouille")
|
||||
# -- check access
|
||||
# admin ou resp. semestre avec flag resp_can_change_resp
|
||||
if not current_user.has_permission(Permission.EditFormSemestre) and (
|
||||
(current_user.id not in sem["responsables"]) or (not sem["resp_can_change_ens"])
|
||||
):
|
||||
raise AccessDenied(f"Modification impossible pour {current_user}")
|
||||
return M, sem
|
||||
|
||||
|
||||
def can_change_ens(moduleimpl_id, raise_exc=True):
|
||||
"check if current user can modify ens list (raise exception if not)"
|
||||
M = moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
# -- check lock
|
||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||
if not sem["etat"]:
|
||||
if raise_exc:
|
||||
raise ScoValueError("Modification impossible: semestre verrouille")
|
||||
else:
|
||||
return False
|
||||
# -- check access
|
||||
# admin, resp. module ou resp. semestre
|
||||
if (
|
||||
current_user.id != M["responsable_id"]
|
||||
and not current_user.has_permission(Permission.EditFormSemestre)
|
||||
and (current_user.id not in sem["responsables"])
|
||||
):
|
||||
if raise_exc:
|
||||
raise AccessDenied("Modification impossible pour %s" % current_user)
|
||||
else:
|
||||
return False
|
||||
return M, sem
|
||||
|
@ -209,15 +209,10 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
#
|
||||
sem_locked = not formsemestre.etat
|
||||
can_edit_evals = (
|
||||
sco_permissions_check.can_edit_notes(
|
||||
current_user, moduleimpl_id, allow_ens=formsemestre.ens_can_edit_eval
|
||||
)
|
||||
and not sem_locked
|
||||
)
|
||||
can_edit_notes = (
|
||||
sco_permissions_check.can_edit_notes(current_user, moduleimpl_id)
|
||||
modimpl.can_edit_notes(current_user, allow_ens=formsemestre.ens_can_edit_eval)
|
||||
and not sem_locked
|
||||
)
|
||||
can_edit_notes = modimpl.can_edit_notes(current_user) and not sem_locked
|
||||
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
|
||||
#
|
||||
module_resp = db.session.get(User, modimpl.responsable_id)
|
||||
@ -244,7 +239,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
<span class="blacktt">({module_resp.user_name})</span>
|
||||
""",
|
||||
]
|
||||
if modimpl.can_change_ens_by(current_user):
|
||||
if modimpl.can_change_responsable(current_user):
|
||||
H.append(
|
||||
f"""<a class="stdlink" href="{url_for("notes.edit_moduleimpl_resp",
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
||||
@ -253,14 +248,15 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
H.append("""</td><td>""")
|
||||
H.append(", ".join([u.get_nomprenom() for u in modimpl.enseignants]))
|
||||
H.append("""</td><td>""")
|
||||
try:
|
||||
sco_moduleimpl.can_change_ens(moduleimpl_id)
|
||||
if modimpl.can_change_ens(raise_exc=False):
|
||||
H.append(
|
||||
"""<a class="stdlink" href="edit_enseignants_form?moduleimpl_id=%s">modifier les enseignants</a>"""
|
||||
% moduleimpl_id
|
||||
f"""<a class="stdlink" href="{
|
||||
url_for("notes.edit_enseignants_form",
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id
|
||||
)
|
||||
except:
|
||||
pass
|
||||
}">modifier les enseignants</a>"""
|
||||
)
|
||||
|
||||
H.append("""</td></tr>""")
|
||||
|
||||
# 2ieme ligne: Semestre, Coef
|
||||
|
@ -8,50 +8,11 @@ from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app.auth.models import User
|
||||
from app.models import FormSemestre
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_exceptions
|
||||
from app.scodoc import sco_moduleimpl
|
||||
|
||||
|
||||
def can_edit_notes(authuser, moduleimpl_id, allow_ens=True):
|
||||
"""True if authuser can enter or edit notes in this module.
|
||||
If allow_ens, grant access to all ens in this module
|
||||
|
||||
Si des décisions de jury ont déjà été saisies dans ce semestre,
|
||||
seul le directeur des études peut saisir des notes (et il ne devrait pas).
|
||||
"""
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_cursus_dut
|
||||
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||
if not sem["etat"]:
|
||||
return False # semestre verrouillé
|
||||
|
||||
if sco_cursus_dut.formsemestre_has_decisions(sem["formsemestre_id"]):
|
||||
# il y a des décisions de jury dans ce semestre !
|
||||
return (
|
||||
authuser.has_permission(Permission.EditAllNotes)
|
||||
or authuser.id in sem["responsables"]
|
||||
)
|
||||
else:
|
||||
if (
|
||||
(not authuser.has_permission(Permission.EditAllNotes))
|
||||
and authuser.id != M["responsable_id"]
|
||||
and authuser.id not in sem["responsables"]
|
||||
):
|
||||
# enseignant (chargé de TD) ?
|
||||
if allow_ens:
|
||||
for ens in M["ens"]:
|
||||
if ens["ens_id"] == authuser.id:
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def can_suppress_annotation(annotation_id):
|
||||
|
@ -48,6 +48,7 @@ from wtforms import (
|
||||
HiddenField,
|
||||
SelectMultipleField,
|
||||
)
|
||||
from app.models import ModuleImpl
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import ScoValueError
|
||||
@ -58,7 +59,6 @@ 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
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_permissions_check
|
||||
@ -247,6 +247,8 @@ class PlacementRunner:
|
||||
# gr_title = sco_groups.listgroups_abbrev(d['groups'])
|
||||
self.current_user = current_user
|
||||
self.moduleimpl_id = self.eval_data["moduleimpl_id"]
|
||||
self.moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(self.moduleimpl_id)
|
||||
# TODO: à revoir pour utiliser modèle ModuleImpl
|
||||
self.moduleimpl_data = sco_moduleimpl.moduleimpl_list(
|
||||
moduleimpl_id=self.moduleimpl_id
|
||||
)[0]
|
||||
@ -280,9 +282,7 @@ class PlacementRunner:
|
||||
def check_placement(self):
|
||||
"""Vérifie que l'utilisateur courant a le droit d'édition sur les notes"""
|
||||
# Check access (admin, respformation, and responsable_id)
|
||||
return sco_permissions_check.can_edit_notes(
|
||||
self.current_user, self.moduleimpl_id
|
||||
)
|
||||
return self.moduleimpl.can_edit_notes(self.current_user)
|
||||
|
||||
def exec_placement(self):
|
||||
"""Excéute l'action liée au formulaire"""
|
||||
|
@ -198,8 +198,8 @@ def do_evaluation_upload_xls():
|
||||
evaluation_id = int(vals["evaluation_id"])
|
||||
comment = vals["comment"]
|
||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
# Check access (admin, respformation, and responsable_id)
|
||||
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl_id):
|
||||
# Check access (admin, respformation, responsable_id, ens)
|
||||
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
||||
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
|
||||
#
|
||||
diag, lines = sco_excel.excel_file_to_list(vals["notefile"])
|
||||
@ -315,7 +315,7 @@ def do_evaluation_set_etud_note(evaluation: Evaluation, etud: Identite, value) -
|
||||
"""Enregistre la note d'un seul étudiant
|
||||
value: valeur externe (float ou str)
|
||||
"""
|
||||
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl.id):
|
||||
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
||||
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
|
||||
# Convert and check value
|
||||
L, invalids, _, _, _ = _check_notes([(etud.id, value)], evaluation)
|
||||
@ -336,7 +336,7 @@ def do_evaluation_set_missing(
|
||||
modimpl = evaluation.moduleimpl
|
||||
# Check access
|
||||
# (admin, respformation, and responsable_id)
|
||||
if not sco_permissions_check.can_edit_notes(current_user, modimpl.id):
|
||||
if not modimpl.can_edit_notes(current_user):
|
||||
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
|
||||
#
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
@ -433,15 +433,11 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
"suppress all notes in this eval"
|
||||
evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
|
||||
if sco_permissions_check.can_edit_notes(
|
||||
current_user, evaluation.moduleimpl_id, allow_ens=False
|
||||
):
|
||||
if evaluation.moduleimpl.can_edit_notes(current_user, allow_ens=False):
|
||||
# On a le droit de modifier toutes les notes
|
||||
# recupere les etuds ayant une note
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
elif sco_permissions_check.can_edit_notes(
|
||||
current_user, evaluation.moduleimpl_id, allow_ens=True
|
||||
):
|
||||
elif evaluation.moduleimpl.can_edit_notes(current_user, allow_ens=True):
|
||||
# Enseignant associé au module: ne peut supprimer que les notes qu'il a saisi
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
|
||||
evaluation_id, by_uid=current_user.id
|
||||
@ -682,7 +678,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
|
||||
evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
moduleimpl_id = evaluation.moduleimpl.id
|
||||
formsemestre_id = evaluation.moduleimpl.formsemestre_id
|
||||
if not sco_permissions_check.can_edit_notes(current_user, moduleimpl_id):
|
||||
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
||||
return (
|
||||
html_sco_header.sco_header()
|
||||
+ f"""
|
||||
@ -813,9 +809,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
|
||||
|
||||
#
|
||||
H.append("""</div><h3>Autres opérations</h3><ul>""")
|
||||
if sco_permissions_check.can_edit_notes(
|
||||
current_user, moduleimpl_id, allow_ens=False
|
||||
):
|
||||
if evaluation.moduleimpl.can_edit_notes(current_user, allow_ens=False):
|
||||
H.append(
|
||||
f"""
|
||||
<li>
|
||||
@ -967,7 +961,7 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
)
|
||||
# Check access
|
||||
# (admin, respformation, and responsable_id)
|
||||
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl_id):
|
||||
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
||||
return f"""
|
||||
{html_sco_header.sco_header()}
|
||||
<h2>Modification des notes impossible pour {current_user.user_name}</h2>
|
||||
@ -1361,7 +1355,7 @@ def save_notes(
|
||||
_external=True,
|
||||
)
|
||||
# Check access: admin, respformation, or responsable_id
|
||||
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl_id):
|
||||
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
||||
return json_error(403, "modification notes non autorisee pour cet utilisateur")
|
||||
#
|
||||
valid_notes, _, _, _, _ = _check_notes(notes, evaluation)
|
||||
|
@ -1834,9 +1834,9 @@ def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
||||
)
|
||||
|
||||
# Sinon on créé l'assiduité
|
||||
|
||||
assiduite_unique: Assiduite | None = None
|
||||
try:
|
||||
assiduite_unique: Assiduite = Assiduite.create_assiduite(
|
||||
assiduite_unique = Assiduite.create_assiduite(
|
||||
etud=etud,
|
||||
date_debut=scu.localize_datetime(evaluation.date_debut),
|
||||
date_fin=scu.localize_datetime(evaluation.date_fin),
|
||||
@ -1862,7 +1862,7 @@ def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
||||
duplication="oui",
|
||||
)
|
||||
raise ScoValueError(msg, dest) from exc
|
||||
|
||||
if assiduite_unique is not None:
|
||||
db.session.add(assiduite_unique)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -539,9 +539,9 @@ def ue_sharing_code():
|
||||
return sco_edit_ue.ue_sharing_code(
|
||||
ue_code=ue_code,
|
||||
ue_id=None if ((ue_id is None) or ue_id == "") else int(ue_id),
|
||||
hide_ue_id=None
|
||||
if ((hide_ue_id is None) or hide_ue_id == "")
|
||||
else int(hide_ue_id),
|
||||
hide_ue_id=(
|
||||
None if ((hide_ue_id is None) or hide_ue_id == "") else int(hide_ue_id)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@ -961,12 +961,15 @@ def formsemestre_custommenu_edit(formsemestre_id):
|
||||
@scodoc7func
|
||||
def edit_enseignants_form(moduleimpl_id):
|
||||
"modif liste enseignants/moduleimpl"
|
||||
M, sem = sco_moduleimpl.can_change_ens(moduleimpl_id)
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
modimpl.can_change_ens(raise_exc=True)
|
||||
# --
|
||||
header = html_sco_header.html_sem_header(
|
||||
'Enseignants du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
|
||||
% (moduleimpl_id, M["module"]["titre"]),
|
||||
page_title="Enseignants du module %s" % M["module"]["titre"],
|
||||
f"""Enseignants du <a href="{
|
||||
url_for("notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
||||
}">module {modimpl.module.titre or modimpl.module.code}</a>""",
|
||||
page_title=f"Enseignants du module {modimpl.module.titre or modimpl.module.code}",
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
@ -981,21 +984,18 @@ def edit_enseignants_form(moduleimpl_id):
|
||||
allowed_user_names = list(uid2display.values())
|
||||
|
||||
H = [
|
||||
"<ul><li><b>%s</b> (responsable)</li>"
|
||||
% uid2display.get(M["responsable_id"], M["responsable_id"])
|
||||
f"""<ul><li><b>{
|
||||
uid2display.get(modimpl.responsable_id, modimpl.responsable_id)
|
||||
}</b> (responsable)</li>"""
|
||||
]
|
||||
for ens in M["ens"]:
|
||||
u = db.session.get(User, ens["ens_id"])
|
||||
if u:
|
||||
nom = u.get_nomcomplet()
|
||||
else:
|
||||
nom = "? (compte inconnu)"
|
||||
u: User
|
||||
for u in modimpl.enseignants:
|
||||
H.append(
|
||||
f"""
|
||||
<li>{nom} (<a class="stdlink" href="{
|
||||
<li>{u.get_nomcomplet()} (<a class="stdlink" href="{
|
||||
url_for('notes.edit_enseignants_form_delete',
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id,
|
||||
ens_id=ens["ens_id"])
|
||||
ens_id=u.id)
|
||||
}">supprimer</a>)
|
||||
</li>"""
|
||||
)
|
||||
@ -1006,7 +1006,7 @@ def edit_enseignants_form(moduleimpl_id):
|
||||
<p class="help">Pour changer le responsable du module, passez par la
|
||||
page "<a class="stdlink" href="{
|
||||
url_for("notes.formsemestre_editwithmodules", scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=M["formsemestre_id"])
|
||||
formsemestre_id=modimpl.formsemestre_id)
|
||||
}">Modification du semestre</a>",
|
||||
accessible uniquement au responsable de la formation (chef de département)
|
||||
</p>
|
||||
@ -1053,24 +1053,24 @@ def edit_enseignants_form(moduleimpl_id):
|
||||
)
|
||||
)
|
||||
else:
|
||||
ens_id = User.get_user_id_from_nomplogin(tf[2]["ens_id"])
|
||||
if not ens_id:
|
||||
ens = User.get_user_from_nomplogin(tf[2]["ens_id"])
|
||||
if ens is None:
|
||||
H.append(
|
||||
'<p class="help">Pour ajouter un enseignant, choisissez un nom dans le menu</p>'
|
||||
)
|
||||
else:
|
||||
# et qu'il n'est pas deja:
|
||||
if (
|
||||
ens_id in [x["ens_id"] for x in M["ens"]]
|
||||
or ens_id == M["responsable_id"]
|
||||
ens.id in (x.id for x in modimpl.enseignants)
|
||||
or ens.id == modimpl.responsable_id
|
||||
):
|
||||
H.append(
|
||||
f"""<p class="help">Enseignant {ens_id} déjà dans la liste !</p>"""
|
||||
f"""<p class="help">Enseignant {ens.user_name} déjà dans la liste !</p>"""
|
||||
)
|
||||
else:
|
||||
sco_moduleimpl.do_ens_create(
|
||||
{"moduleimpl_id": moduleimpl_id, "ens_id": ens_id}
|
||||
)
|
||||
modimpl.enseignants.append(ens)
|
||||
db.session.add(modimpl)
|
||||
db.session.commit()
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.edit_enseignants_form",
|
||||
@ -1090,10 +1090,10 @@ def edit_moduleimpl_resp(moduleimpl_id: int):
|
||||
Accessible par Admin et dir des etud si flag resp_can_change_ens
|
||||
"""
|
||||
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
||||
modimpl.can_change_ens_by(current_user, raise_exc=True) # access control
|
||||
modimpl.can_change_responsable(current_user, raise_exc=True) # access control
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
f"""Modification du responsable du <a href="{
|
||||
f"""Modification du responsable du <a class="stdlink" href="{
|
||||
url_for("notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
||||
}">module {modimpl.module.titre or ""}</a>""",
|
||||
@ -1156,8 +1156,8 @@ def edit_moduleimpl_resp(moduleimpl_id: int):
|
||||
)
|
||||
)
|
||||
else:
|
||||
responsable_id = User.get_user_id_from_nomplogin(tf[2]["responsable_id"])
|
||||
if not responsable_id:
|
||||
responsable = User.get_user_from_nomplogin(tf[2]["responsable_id"])
|
||||
if not responsable:
|
||||
# presque impossible: tf verifie les valeurs (mais qui peuvent changer entre temps)
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
@ -1167,7 +1167,7 @@ def edit_moduleimpl_resp(moduleimpl_id: int):
|
||||
)
|
||||
)
|
||||
|
||||
modimpl.responsable_id = responsable_id
|
||||
modimpl.responsable = responsable
|
||||
db.session.add(modimpl)
|
||||
db.session.commit()
|
||||
flash("Responsable modifié")
|
||||
@ -1192,8 +1192,6 @@ def view_module_abs(moduleimpl_id, fmt="html"):
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
).first_or_404()
|
||||
|
||||
debut_sem = modimpl.formsemestre.date_debut
|
||||
fin_sem = modimpl.formsemestre.date_fin
|
||||
inscrits: list[Identite] = sorted(
|
||||
[i.etud for i in modimpl.inscriptions], key=lambda e: e.sort_key
|
||||
)
|
||||
@ -1286,9 +1284,8 @@ def formsemestre_enseignants_list(formsemestre_id, fmt="html"):
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
# resp. de modules et charges de TD
|
||||
sem_ens: dict[
|
||||
int, list[ModuleImpl]
|
||||
] = {} # uid : { "mods" : liste des modimpls, ... }
|
||||
# uid : { "mods" : liste des modimpls, ... }
|
||||
sem_ens: dict[int, list[ModuleImpl]] = {}
|
||||
modimpls = formsemestre.modimpls_sorted
|
||||
for modimpl in modimpls:
|
||||
if not modimpl.responsable_id in sem_ens:
|
||||
@ -1372,22 +1369,17 @@ def edit_enseignants_form_delete(moduleimpl_id, ens_id: int):
|
||||
|
||||
ens_id: user.id
|
||||
"""
|
||||
M, _ = sco_moduleimpl.can_change_ens(moduleimpl_id)
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
modimpl.can_change_ens(raise_exc=True)
|
||||
# search ens_id
|
||||
ok = False
|
||||
for ens in M["ens"]:
|
||||
if ens["ens_id"] == ens_id:
|
||||
ok = True
|
||||
ens: User | None = None
|
||||
for ens in modimpl.enseignants:
|
||||
if ens.id == ens_id:
|
||||
break
|
||||
if not ok:
|
||||
if ens is None:
|
||||
raise ScoValueError(f"invalid ens_id ({ens_id})")
|
||||
ndb.SimpleQuery(
|
||||
"""DELETE FROM notes_modules_enseignants
|
||||
WHERE moduleimpl_id = %(moduleimpl_id)s
|
||||
AND ens_id = %(ens_id)s
|
||||
""",
|
||||
{"moduleimpl_id": moduleimpl_id, "ens_id": ens_id},
|
||||
)
|
||||
modimpl.enseignants.remove(ens)
|
||||
db.session.commit()
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.edit_enseignants_form",
|
||||
@ -1399,18 +1391,6 @@ def edit_enseignants_form_delete(moduleimpl_id, ens_id: int):
|
||||
|
||||
# --- Gestion des inscriptions aux semestres
|
||||
|
||||
# Ancienne API, pas certain de la publier en ScoDoc8
|
||||
# sco_publish(
|
||||
# "/do_formsemestre_inscription_create",
|
||||
# sco_formsemestre_inscriptions.do_formsemestre_inscription_create,
|
||||
# Permission.EtudInscrit,
|
||||
# )
|
||||
# sco_publish(
|
||||
# "/do_formsemestre_inscription_edit",
|
||||
# sco_formsemestre_inscriptions.do_formsemestre_inscription_edit,
|
||||
# Permission.EtudInscrit,
|
||||
# )
|
||||
|
||||
sco_publish(
|
||||
"/do_formsemestre_inscription_list",
|
||||
sco_formsemestre_inscriptions.do_formsemestre_inscription_list,
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.92"
|
||||
SCOVERSION = "9.6.934"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
@ -619,7 +619,6 @@ def test_formsemestre_programme(api_headers):
|
||||
assert verify_fields(sae, MODIMPL_FIELDS)
|
||||
|
||||
|
||||
@pytest.mark.skip # XXX WIP
|
||||
def test_etat_evals(api_headers): # voir si on maintient cette route ?
|
||||
"""
|
||||
Route : /formsemestre/<int:formsemestre_id>/etat_evals
|
||||
|
@ -375,7 +375,6 @@ def test_import_formation(test_client, filename="formation-exemple-1.xml"):
|
||||
formsemestre_id=formsemestre_ids[mod["semestre_id"] - 1],
|
||||
)
|
||||
mi = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
assert mi["ens"] == []
|
||||
assert mi["module_id"] == mod["module_id"]
|
||||
|
||||
# --- Export formation en XML
|
||||
|
Loading…
Reference in New Issue
Block a user