ScoDoc/app/scodoc/sco_ue_external.py

397 lines
14 KiB
Python

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 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
#
##############################################################################
"""Fonction de gestion des UE "externes" (effectuees dans un cursus exterieur)
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
au préalable une forme d'équivalence entre ces UE et celles du
programme. Les UE effectuées à l'extérieur sont par nature variable
d'un étudiant à l'autre et d'une année à l'autre, et ne peuvent pas
être introduites dans le programme pédagogique ScoDoc sans alourdir
considérablement les opérations (saisie, affichage du programme,
gestion des inscriptions).
En outre, un suivi détaillé de ces UE n'est pas nécessaire: il suffit
de pouvoir y associer une note et une quantité de crédits ECTS.
Solution proposée (nov 2014):
- un nouveau type d'UE qui
- s'affichera à part dans le programme pédagogique
et les bulletins
- pas présentées lors de la mise en place de semestres
- affichage sur bulletin des étudiants qui y sont inscrit
- création en même temps que la saisie de la note
(chaine creation: UE/matière/module, inscription étudiant, entrée valeur note)
avec auto-suggestion du nom pour limiter la création de doublons
- seront aussi présentées (à part) sur la page "Voir les inscriptions aux modules"
"""
import flask
from flask import flash, g, request, render_template, url_for
from flask_login import current_user
from app.formations import edit_module, edit_ue
from app.models.formsemestre import FormSemestre
from app import db, log
from app.models import Evaluation, Identite, Matiere, ModuleImpl, UniteEns
from app.scodoc import codes_cursus
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_saisie_notes
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_permissions import Permission
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
def external_ue_create(
formsemestre_id,
titre="",
acronyme="",
ue_type=codes_cursus.UE_STANDARD,
ects=0.0,
) -> ModuleImpl:
"""Crée UE/matiere/module dans la formation du formsemestre
puis un moduleimpl.
Return: moduleimpl
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
log(f"creating external UE in {formsemestre}: {acronyme}")
# Contrôle d'accès:
if not current_user.has_permission(Permission.EditFormSemestre):
if (not formsemestre.resp_can_edit) or (
current_user.id not in [u.id for u in formsemestre.responsables]
):
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
#
formation_id = formsemestre.formation.id
numero = edit_ue.next_ue_numero(formation_id, semestre_id=formsemestre.semestre_id)
ue_id = edit_ue.do_ue_create(
{
"formation_id": formation_id,
"semestre_idx": formsemestre.semestre_id,
"titre": titre,
"acronyme": acronyme,
"numero": numero,
"type": ue_type,
"ects": ects,
"is_external": True,
},
)
ue = db.session.get(UniteEns, ue_id)
flash(f"UE créée (code {ue.ue_code})")
matiere = Matiere.create_from_dict(
{"ue_id": ue_id, "titre": titre or acronyme, "numero": 1}
)
module_id = edit_module.do_module_create(
{
"titre": "UE extérieure",
"code": acronyme,
"coefficient": ects, # tous le coef. module est egal à la quantite d'ECTS
"ue_id": ue_id,
"matiere_id": matiere.id,
"formation_id": formation_id,
"semestre_id": formsemestre.semestre_id,
"module_type": scu.ModuleType.STANDARD,
},
)
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(
{
"module_id": module_id,
"formsemestre_id": formsemestre_id,
# affecte le 1er responsable du semestre comme resp. du module
"responsable_id": (
formsemestre.responsables[0].id
if len(formsemestre.responsables)
else None
),
},
)
modimpl = db.session.get(ModuleImpl, moduleimpl_id)
assert modimpl
return modimpl
def external_ue_inscrit_et_note(
moduleimpl: ModuleImpl, formsemestre_id: int, notes_etuds: dict
):
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
et enregistre les notes.
"""
log(
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl.id}, notes_etuds={notes_etuds})"
)
# Inscription des étudiants
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
moduleimpl.id,
formsemestre_id,
list(notes_etuds.keys()),
)
# Création d'une évaluation si il n'y en a pas déjà:
if moduleimpl.evaluations.count() > 0:
# met la note dans le première évaluation existante:
evaluation: Evaluation = moduleimpl.evaluations.first()
else:
# crée une évaluation:
evaluation: Evaluation = Evaluation.create(
moduleimpl=moduleimpl,
note_max=20.0,
coefficient=1.0,
publish_incomplete=True,
evaluation_type=Evaluation.EVALUATION_NORMALE,
visibulletin=False,
description="note externe",
)
# Saisie des notes
_, _, _, _ = sco_saisie_notes.notes_add(
current_user,
evaluation.id,
list(notes_etuds.items()),
do_it=True,
)
def get_existing_external_ue(formation_id: int) -> list[UniteEns]:
"Liste de toutes les UEs externes définies dans cette formation"
return UniteEns.query.filter_by(formation_id=formation_id, is_external=True).all()
def get_external_moduleimpl(formsemestre_id: int, ue_id: int) -> ModuleImpl:
"moduleimpl correspondant à l'UE externe indiquée de ce formsemestre"
r = ndb.SimpleDictFetch(
"""
SELECT mi.id AS moduleimpl_id FROM notes_moduleimpl mi, notes_modules mo
WHERE mi.formsemestre_id = %(formsemestre_id)s
AND mi.module_id = mo.id
AND mo.ue_id = %(ue_id)s
""",
{"ue_id": ue_id, "formsemestre_id": formsemestre_id},
)
if r:
modimpl_id = r[0]["moduleimpl_id"]
modimpl = db.session.get(ModuleImpl, modimpl_id)
assert modimpl
return modimpl
else:
raise ScoValueError(
f"""Aucun module externe ne correspond
(formsemestre_id={formsemestre_id}, ue_id={ue_id})"""
)
# Web view
def external_ue_create_form(formsemestre_id: int, etudid: int):
"""Formulaire création UE externe + inscription étudiant et saisie note
- Demande UE: peut-être existante (liste les UE externes de cette formation),
ou sinon spécifier titre, acronyme, type, ECTS
- Demande note à enregistrer.
Note: pour l'édition éventuelle de ces informations, on utilisera les
fonctions standards sur les UE/modules/notes
En BUT, pas d'UEs externes. Voir https://scodoc.org/git/ScoDoc/ScoDoc/issues/542
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Contrôle d'accès:
if not formsemestre.can_be_edited_by(current_user):
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
if formsemestre.formation.is_apc():
raise ScoValueError("Impossible d'ajouter une UE externe en BUT")
etud = Identite.get_etud(etudid)
formation_id = formsemestre.formation.id
existing_external_ue = get_existing_external_ue(formation_id)
H = [
f"""
<h2 class="formsemestre">Ajout d'une UE externe pour {etud.nomprenom}</h2>
<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE
dans un autre établissement et qu'elle doit être intégrée dans le semestre courant.<br>
La note (/20) obtenue par l'étudiant doit toujours être spécifiée.</br>
On peut choisir une UE externe existante (dans le menu), ou bien en créer une, qui sera
alors ajoutée à la formation.
</p>
""",
]
parcours = formsemestre.formation.get_cursus()
ue_types = [
typ for typ in parcours.ALLOWED_UE_TYPES if typ != codes_cursus.UE_SPORT
]
ue_types.sort()
ue_types_names = [codes_cursus.UE_TYPE_NAME[k] for k in ue_types]
ue_types = [str(x) for x in ue_types]
if existing_external_ue:
default_label = "Nouvelle UE"
else:
default_label = "Aucune UE externe existante"
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
(
("formsemestre_id", {"input_type": "hidden"}),
("etudid", {"input_type": "hidden"}),
(
"existing_ue",
{
"input_type": "menu",
"title": "UE externe existante:",
"allowed_values": [""]
+ [str(ue.id) for ue in existing_external_ue],
"labels": [default_label]
+ [
f"{ue.titre or ''} ({ue.acronyme})"
for ue in existing_external_ue
],
"attributes": ['onchange="update_external_ue_form();"'],
"explanation": "inscrire cet étudiant dans cette UE",
},
),
(
"sep",
{
"input_type": "separator",
"title": "Ou bien déclarer une nouvelle UE externe:",
"dom_id": "tf_extue_decl",
},
),
# champs a desactiver si une UE existante est choisie
(
"titre",
{"size": 30, "explanation": "nom de l'UE", "dom_id": "tf_extue_titre"},
),
(
"acronyme",
{
"size": 8,
"explanation": "abbréviation",
"allow_null": True, # attention: verifier
"dom_id": "tf_extue_acronyme",
},
),
(
"type",
{
"explanation": "type d'UE",
"input_type": "menu",
"allowed_values": ue_types,
"labels": ue_types_names,
"dom_id": "tf_extue_type",
},
),
(
"ects",
{
"size": 4,
"type": "float",
"min_value": 0,
"max_value": 1000,
"title": "ECTS",
"explanation": "nombre de crédits ECTS",
"dom_id": "tf_extue_ects",
},
),
#
(
"note",
{"size": 4, "explanation": "note sur 20", "dom_id": "tf_extue_note"},
),
),
submitlabel="Enregistrer",
cancelbutton="Annuler",
)
bull_url = url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
etudid=etudid,
)
if tf[0] == 0:
return render_template(
"sco_page.j2",
title=f"Ajout d'une UE externe pour {etud.nomprenom}",
javascripts=["js/sco_ue_external.js"],
content="\n".join(H) + "\n" + tf[1],
)
elif tf[0] == -1:
return flask.redirect(bull_url)
else:
note = tf[2]["note"].strip().upper()
note_value, invalid = sco_saisie_notes.convert_note_from_string(
note, 20.0, etudid=etudid, absents=[], invalids=[]
)
if invalid:
return render_template(
"sco_page.j2",
title=f"Ajout d'une UE externe pour {etud.nomprenom}",
javascripts=["js/sco_ue_external.js"],
content="\n".join(H)
+ "\n"
+ tf_error_message("valeur note invalide")
+ tf[1],
)
if tf[2]["existing_ue"]:
ue_id = int(tf[2]["existing_ue"])
modimpl = get_external_moduleimpl(formsemestre_id, ue_id)
else:
acronyme = tf[2]["acronyme"].strip()
if not acronyme:
return render_template(
"sco_page.j2",
title=f"Ajout d'une UE externe pour {etud.nomprenom}",
javascripts=["js/sco_ue_external.js"],
content="\n".join(H)
+ "\n"
+ tf_error_message("spécifier acronyme d'UE")
+ tf[1],
)
modimpl = external_ue_create(
formsemestre_id,
titre=tf[2]["titre"],
acronyme=acronyme,
ue_type=tf[2]["type"], # type de l'UE
ects=tf[2]["ects"],
)
external_ue_inscrit_et_note(
modimpl,
formsemestre_id,
{etudid: note_value},
)
flash("Ajout effectué")
return flask.redirect(bull_url)