forked from ScoDoc/ScoDoc
Compare commits
139 Commits
Author | SHA1 | Date |
---|---|---|
leonard_montalbano | a43f1e0e22 | |
leonard_montalbano | 8e36201482 | |
Emmanuel Viennet | f61a528d12 | |
Emmanuel Viennet | 5a56138e55 | |
Emmanuel Viennet | 462c084bf4 | |
leonard_montalbano | 433b4b8f5c | |
Emmanuel Viennet | 8be3ecfeaf | |
leonard_montalbano | 288cad21cc | |
Emmanuel Viennet | 5efebb1336 | |
Emmanuel Viennet | 9587159692 | |
Emmanuel Viennet | 793eca017a | |
Emmanuel Viennet | a83d491874 | |
Emmanuel Viennet | fe6790738f | |
leonard_montalbano | f9817966cf | |
leonard_montalbano | 28ec8a482a | |
Emmanuel Viennet | 10230d20ef | |
Emmanuel Viennet | d2fe27b67c | |
Emmanuel Viennet | a4035411d9 | |
Emmanuel Viennet | a09418329f | |
Emmanuel Viennet | 2220b617b8 | |
leonard_montalbano | c4f2f0925d | |
Emmanuel Viennet | 8f911234b2 | |
Emmanuel Viennet | c923a5015b | |
Emmanuel Viennet | ec9cdfe50a | |
Emmanuel Viennet | 3d0509de64 | |
Emmanuel Viennet | b4a7749e5a | |
Emmanuel Viennet | e04a187a01 | |
leonard_montalbano | 47123aeb1e | |
leonard_montalbano | 90e292341e | |
Emmanuel Viennet | bcbace0120 | |
Emmanuel Viennet | 4a03887120 | |
Emmanuel Viennet | 3b7370f6df | |
Emmanuel Viennet | a0d8f89b18 | |
leonard_montalbano | b1e0def55a | |
leonard_montalbano | c1b11bd9d1 | |
Emmanuel Viennet | e6be8d9ecb | |
Emmanuel Viennet | 09111d9455 | |
Emmanuel Viennet | 577cac00ee | |
leonard_montalbano | 4d49de397c | |
leonard_montalbano | e9656dc07f | |
Emmanuel Viennet | 912ee8b4da | |
leonard_montalbano | 6bab2c00ad | |
Emmanuel Viennet | 750744094e | |
Emmanuel Viennet | a19a54e054 | |
Emmanuel Viennet | 9d53e38992 | |
Emmanuel Viennet | c0719df0c0 | |
leonard_montalbano | 1cd7a84b15 | |
leonard_montalbano | 1c271bbad4 | |
Emmanuel Viennet | 523ad7ad2a | |
Emmanuel Viennet | f0e731d151 | |
Emmanuel Viennet | 10c96ad683 | |
Emmanuel Viennet | 6943ccb872 | |
Emmanuel Viennet | c5c0b510ec | |
Emmanuel Viennet | 7edd051183 | |
Emmanuel Viennet | 23ea53294d | |
Emmanuel Viennet | 13b40936b8 | |
Emmanuel Viennet | b56a20643d | |
Emmanuel Viennet | e993599b39 | |
Emmanuel Viennet | 8b5a996571 | |
Emmanuel Viennet | 0e7f2f4deb | |
Emmanuel Viennet | 8330009dcf | |
Emmanuel Viennet | 732a4c5ce5 | |
leonard_montalbano | f0bdb5e9bd | |
Emmanuel Viennet | df9ec49568 | |
Emmanuel Viennet | bee7b74f17 | |
Emmanuel Viennet | f7c90397a8 | |
Emmanuel Viennet | 5aa896f793 | |
Emmanuel Viennet | 546e10c83a | |
Emmanuel Viennet | e1db9c542b | |
Emmanuel Viennet | ef408e5d8e | |
Emmanuel Viennet | 68680e89d3 | |
Emmanuel Viennet | 00fa91e598 | |
Emmanuel Viennet | 29b5d54d22 | |
Emmanuel Viennet | 6b8410e43b | |
Emmanuel Viennet | 091d34dd88 | |
Emmanuel Viennet | 1dfccb6737 | |
Emmanuel Viennet | 40f823ee7c | |
Emmanuel Viennet | c0494d8d71 | |
Emmanuel Viennet | c1c9f22a31 | |
Emmanuel Viennet | dbab59039c | |
Emmanuel Viennet | 9b27503d01 | |
Emmanuel Viennet | aa609aa0cf | |
leonard_montalbano | afe43f98e3 | |
leonard_montalbano | 8ea9f04ea6 | |
Emmanuel Viennet | 9bc5f27b16 | |
leonard_montalbano | cd961e6e3e | |
leonard_montalbano | 35be7ebb4c | |
Emmanuel Viennet | 6a07bb85a0 | |
Emmanuel Viennet | 2cac0031f6 | |
leonard_montalbano | d468a3f49e | |
leonard_montalbano | ba164481a6 | |
Emmanuel Viennet | e9ad417f1f | |
Emmanuel Viennet | bd33b288db | |
Jean-Marie PLACE | 875c12d703 | |
Emmanuel Viennet | e7b980bff7 | |
Emmanuel Viennet | 276d7977a7 | |
Emmanuel Viennet | 0e42df55c9 | |
leonard_montalbano | 976fdf5b4e | |
leonard_montalbano | 3fab8300a1 | |
Emmanuel Viennet | 6b8b0f9c24 | |
Emmanuel Viennet | d314d47dc5 | |
Emmanuel Viennet | 0801919b80 | |
leonard_montalbano | 677094aaac | |
leonard_montalbano | ba0062135b | |
Emmanuel Viennet | ba974df04f | |
leonard_montalbano | 401a43378d | |
Emmanuel Viennet | aa3a2fb3e0 | |
Emmanuel Viennet | f2c3841db9 | |
Emmanuel Viennet | 44123c022e | |
Emmanuel Viennet | 63784e341a | |
Emmanuel Viennet | cca72dfed2 | |
Emmanuel Viennet | 5c951d58e7 | |
Emmanuel Viennet | 202ce4e73e | |
Emmanuel Viennet | c81c4efb40 | |
Emmanuel Viennet | d877648546 | |
Emmanuel Viennet | 58a8bcb83d | |
Emmanuel Viennet | fae11d82ce | |
Emmanuel Viennet | 7a85ec7466 | |
Emmanuel Viennet | 091a49cb0d | |
Emmanuel Viennet | ab212a5b2b | |
Emmanuel Viennet | a67515d560 | |
leonard_montalbano | 84f43e1b36 | |
leonard_montalbano | 79b5530813 | |
leonard_montalbano | 25b0648284 | |
Emmanuel Viennet | 39e31983ee | |
Emmanuel Viennet | 175c66c834 | |
leonard_montalbano | 6842669ffe | |
Emmanuel Viennet | fffba011ea | |
Emmanuel Viennet | 89db5cf858 | |
Emmanuel Viennet | 9e4c19a292 | |
leonard_montalbano | 2e1dcce69d | |
Emmanuel Viennet | 716a6bf41f | |
Emmanuel Viennet | bfd0a4b311 | |
Emmanuel Viennet | f8630b3cdb | |
Emmanuel Viennet | 98e7f7a710 | |
Emmanuel Viennet | 1dbb199d2c | |
Emmanuel Viennet | 5bd60d9c34 | |
Sébastien Lehmann | b165bc2659 | |
Emmanuel Viennet | 60a77b8ba7 |
|
@ -20,10 +20,10 @@ Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
|
|||
|
||||
### État actuel (26 jan 22)
|
||||
|
||||
- 9.1 (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
||||
- 9.1.5x (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
||||
- ancien module "Entreprises" (obsolète) et ajoute la gestion du BUT.
|
||||
|
||||
- 9.2 (branche refactor_nt) est la version de développement.
|
||||
- 9.2 (branche dev92) est la version de développement.
|
||||
|
||||
|
||||
### Lignes de commandes
|
||||
|
|
|
@ -13,7 +13,7 @@ from logging.handlers import SMTPHandler, WatchedFileHandler
|
|||
|
||||
from flask import current_app, g, request
|
||||
from flask import Flask
|
||||
from flask import abort, has_request_context, jsonify
|
||||
from flask import abort, flash, has_request_context, jsonify
|
||||
from flask import render_template
|
||||
from flask.logging import default_handler
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
@ -201,7 +201,7 @@ def create_app(config_class=DevConfig):
|
|||
app.register_blueprint(auth_bp, url_prefix="/auth")
|
||||
|
||||
from app.entreprises import bp as entreprises_bp
|
||||
|
||||
|
||||
app.register_blueprint(entreprises_bp, url_prefix="/ScoDoc/entreprises")
|
||||
|
||||
from app.views import scodoc_bp
|
||||
|
@ -295,10 +295,12 @@ def create_app(config_class=DevConfig):
|
|||
|
||||
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
|
||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||
from app.but.bulletin_but_pdf import BulletinGeneratorStandardBUT
|
||||
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
|
||||
|
||||
# l'ordre est important, le premeir sera le "défaut" pour les nouveaux départements.
|
||||
# l'ordre est important, le premier sera le "défaut" pour les nouveaux départements.
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandardBUT)
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
||||
if app.testing or app.debug:
|
||||
|
@ -457,15 +459,12 @@ from app.models import Departement
|
|||
from app.scodoc import notesdb as ndb, sco_preferences
|
||||
from app.scodoc import sco_cache
|
||||
|
||||
# admin_role = Role.query.filter_by(name="SuperAdmin").first()
|
||||
# if admin_role:
|
||||
# admin = (
|
||||
# User.query.join(UserRole)
|
||||
# .filter((UserRole.user_id == User.id) & (UserRole.role_id == admin_role.id))
|
||||
# .first()
|
||||
# )
|
||||
# else:
|
||||
# click.echo(
|
||||
# "Warning: user database not initialized !\n (use: flask user-db-init)"
|
||||
# )
|
||||
# admin = None
|
||||
|
||||
def scodoc_flash_status_messages():
|
||||
"""Should be called on each page: flash messages indicating specific ScoDoc status"""
|
||||
email_test_mode_address = sco_preferences.get_preference("email_test_mode_address")
|
||||
if email_test_mode_address:
|
||||
flash(
|
||||
f"Mode test: mails redirigés vers {email_test_mode_address}",
|
||||
category="warning",
|
||||
)
|
||||
|
|
|
@ -23,4 +23,14 @@ def requested_format(default_format="json", allowed_formats=None):
|
|||
|
||||
from app.api import tokens
|
||||
from app.api import sco_api
|
||||
from app.api import test_api
|
||||
from app.api import departements
|
||||
from app.api import etudiants
|
||||
from app.api import formations
|
||||
from app.api import formsemestres
|
||||
from app.api import partitions
|
||||
from app.api import evaluations
|
||||
from app.api import jury
|
||||
from app.api import absences
|
||||
from app.api import logos
|
||||
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
#################################################### Absences #########################################################
|
||||
from datetime import datetime
|
||||
|
||||
from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.auth import token_auth
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.scodoc.sco_abs import add_absence, add_justif, annule_absence, annule_justif, list_abs_date
|
||||
from app.scodoc.sco_groups import get_group_members
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
|
||||
@bp.route("/absences/nip/<int:nip>", methods=["GET"])
|
||||
@bp.route("/absences/ine/<int:ine>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def absences(etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Retourne la liste des absences d'un étudiant donné
|
||||
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip: le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
abs = None
|
||||
if etudid is not None: # Si route etudid
|
||||
# Récupération des absences de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etudid).all()
|
||||
else:
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
# Récupération des absences de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etu.etudid).all()
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
# Récupération des absences de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etu.etudid).all()
|
||||
|
||||
if abs is not None: # Si des absences ont bien été trouvé
|
||||
# Mise en forme des données
|
||||
data = [d.to_dict() for d in abs]
|
||||
|
||||
return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/absences/etudid/<int:etudid>/abs_just_only", methods=["GET"])
|
||||
@bp.route("/absences/nip/<int:nip>/abs_just_only", methods=["GET"])
|
||||
@bp.route("/absences/ine/<int:ine>/abs_just_only", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def absences_justify(etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Retourne la liste des absences justifiées d'un étudiant donné
|
||||
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip: le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
abs = None
|
||||
if etudid is not None: # Si route etudid
|
||||
# Récupération des absences justifiées de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etudid, estjust=True).all()
|
||||
else:
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
# Récupération des absences justifiées de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etu.etudid, estjust=True).all()
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
# Récupération des absences justifiées de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etu.etudid, estjust=True).all()
|
||||
|
||||
if abs is not None: # Si des absences ont bien été trouvé
|
||||
# Mise en forme des données
|
||||
data = [d.to_dict() for d in abs]
|
||||
|
||||
return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/absences/abs_signale?etudid=<int:etudid>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>", methods=["POST"])
|
||||
@bp.route("/absences/abs_signale?nip=<int:nip>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>", methods=["POST"])
|
||||
@bp.route("/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>", methods=["POST"])
|
||||
@bp.route("/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>&moduleimpl_id=<int:moduleimpl_id>", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
@permission_required(Permission.APIAbsChange)
|
||||
def abs_signale(date: datetime, matin: bool, justif: bool, etudid: int = None, nip: int = None, ine: int = None,
|
||||
description: str = None, moduleimpl_id: int = None):
|
||||
"""
|
||||
Permet d'ajouter une absence en base
|
||||
|
||||
date : la date de l'absence
|
||||
matin : True ou False
|
||||
justif : True ou False
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip: le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
description : description possible à ajouter sur l'absence
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
"""
|
||||
# Fonctions utilisées : app.scodoc.sco_abs.add_absence() et app.scodoc.sco_abs.add_justif()
|
||||
|
||||
if description is not None: # Si la description a été renseignée
|
||||
if moduleimpl_id is not None: # Si le moduleimpl a été renseigné
|
||||
if etudid is not None: # Si route etudid
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etudid, date, matin, justif, description, moduleimpl_id)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif, description, moduleimpl_id)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif, description, moduleimpl_id)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
else: # Si le moduleimpl n'a pas été renseigné
|
||||
if etudid is not None: # Si route etudid
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etudid, date, matin, justif, description)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif, description)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif, description)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
else:
|
||||
if etudid is not None: # Si route etudid
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etudid, date, matin, justif)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etudid, date, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return error_response(200, message="OK")
|
||||
|
||||
|
||||
@bp.route("/absences/abs_annule?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@bp.route("/absences/abs_annule?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@bp.route("/absences/abs_annule?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
@permission_required(Permission.APIAbsChange)
|
||||
def abs_annule(jour: datetime, matin: str, etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Retourne un html
|
||||
|
||||
jour : la date de l'absence a annulé
|
||||
matin : True ou False
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip: le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_abs.annule_absence()
|
||||
|
||||
if etudid is None:
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
# Récupération de l'etudid de l'étudiant
|
||||
etudid = etu.etudid
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
# Récupération de l'etudid de l'étudiant
|
||||
etudid = etu.etudid
|
||||
try:
|
||||
# Utilisation de la fonction annule_absence
|
||||
annule_absence(etudid, jour, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return error_response(200, message="OK")
|
||||
|
||||
|
||||
@bp.route("/absences/abs_annule_justif?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@bp.route("/absences/abs_annule_justif?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@bp.route("/absences/abs_annule_justif?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
@permission_required(Permission.APIAbsChange)
|
||||
def abs_annule_justif(jour: datetime, matin: str, etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Retourne un html
|
||||
|
||||
jour : la date de l'absence a annulé
|
||||
matin : True ou False
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip: le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_abs.annule_justif()
|
||||
|
||||
if etudid is None:
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
# Récupération de l'etudid de l'étudiant
|
||||
etudid = etu.etudid
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
# Récupération de l'etudid de l'étudiant
|
||||
etudid = etu.etudid
|
||||
try:
|
||||
# Utilisation de la fonction annule_justif
|
||||
annule_justif(etudid, jour, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return error_response(200, message="OK")
|
||||
|
||||
|
||||
@bp.route("/absences/abs_group_etat/?group_id=<int:group_id>&date_debut=date_debut&date_fin=date_fin", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def abs_groupe_etat(group_id: int, date_debut, date_fin, with_boursier=True, format="html"):
|
||||
"""
|
||||
Retoune la liste des absences d'un ou plusieurs groupes entre deux dates
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date()
|
||||
|
||||
try:
|
||||
# Utilisation de la fonction get_group_members
|
||||
members = get_group_members(group_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
data = []
|
||||
# Filtre entre les deux dates renseignées
|
||||
for member in members:
|
||||
abs = list_abs_date(member.id, date_debut, date_fin)
|
||||
data.append(abs)
|
||||
|
||||
# return jsonify(data) # XXX TODO faire en sorte de pouvoir renvoyer sa (ex to_dict() dans absences)
|
||||
return error_response(501, message="Not implemented")
|
|
@ -0,0 +1,191 @@
|
|||
############################################### Departements ##########################################################
|
||||
from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.auth import token_auth
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.models import ApcReferentielCompetences
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_prepajury import feuille_preparation_jury
|
||||
from app.scodoc.sco_pvjury import formsemestre_pvjury
|
||||
from app.scodoc.sco_recapcomplet import formsemestre_recapcomplet
|
||||
from app.scodoc.sco_saisie_notes import notes_add
|
||||
|
||||
|
||||
|
||||
@bp.route("/departements", methods=["GET"])
|
||||
@token_auth.login_required # Commenté le temps des tests
|
||||
# @permission_required(Permission.ScoView)
|
||||
def departements():
|
||||
"""
|
||||
Retourne la liste des ids de départements visibles
|
||||
|
||||
Exemple de résultat : [2, 5, 8, 1, 4, 18]
|
||||
"""
|
||||
# Récupération de tous les départements
|
||||
depts = models.Departement.query.filter_by(visible=True).all()
|
||||
|
||||
# Mise en place de la liste avec tous les ids de départements
|
||||
depts_ids = [d.id for d in depts]
|
||||
|
||||
return jsonify(depts_ids)
|
||||
|
||||
|
||||
@bp.route("/departements/<string:dept>/etudiants/liste", methods=["GET"])
|
||||
@bp.route("/departements/<string:dept>/etudiants/liste/<int:formsemestre_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
# @permission_required(Permission.APIView)
|
||||
def liste_etudiants(dept: str, formsemestre_id=None):
|
||||
"""
|
||||
Retourne la liste des étudiants d'un département
|
||||
|
||||
dept: l'acronym d'un département
|
||||
formsemestre_id: l'id d'un formesemestre
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"civilite": "X",
|
||||
"code_ine": null,
|
||||
"code_nip": null,
|
||||
"date_naissance": null,
|
||||
"email": null,
|
||||
"emailperso": null,
|
||||
"etudid": 18,
|
||||
"nom": "MOREL",
|
||||
"prenom": "JACQUES"
|
||||
},
|
||||
{
|
||||
"civilite": "X",
|
||||
"code_ine": null,
|
||||
"code_nip": null,
|
||||
"date_naissance": null,
|
||||
"email": null,
|
||||
"emailperso": null,
|
||||
"etudid": 19,
|
||||
"nom": "FOURNIER",
|
||||
"prenom": "ANNE"
|
||||
},
|
||||
...
|
||||
"""
|
||||
# Si le formsemestre_id a été renseigné
|
||||
if formsemestre_id is not None:
|
||||
# Récupération du formsemestre
|
||||
formsemestre = models.FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||
# Récupération du département
|
||||
departement = formsemestre.departement
|
||||
# Récupération des étudiants
|
||||
etudiants = departement.etudiants.all()
|
||||
|
||||
# Mise en forme des données
|
||||
list_etu = [etu.to_dict_bul(include_urls=False) for etu in etudiants]
|
||||
|
||||
# Si le formsemestre_id n'a pas été renseigné
|
||||
else:
|
||||
# Récupération du formsemestre
|
||||
departement = models.Departement.query.filter_by(acronym=dept).first()
|
||||
# Récupération des étudiants
|
||||
etudiants = departement.etudiants.all()
|
||||
|
||||
# Mise en forme des données
|
||||
list_etu = [etu.to_dict_bul(include_urls=False) for etu in etudiants]
|
||||
|
||||
return jsonify(list_etu)
|
||||
|
||||
|
||||
@bp.route("/departements/<string:dept>/semestres_courants", methods=["GET"])
|
||||
# @token_auth.login_required # Commenté le temps des tests
|
||||
# @permission_required(Permission.APIView)
|
||||
def liste_semestres_courant(dept: str):
|
||||
"""
|
||||
Liste des semestres actifs d'un départements donné
|
||||
|
||||
dept: l'acronym d'un département
|
||||
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"titre": "master machine info",
|
||||
"gestion_semestrielle": false,
|
||||
"scodoc7_id": null,
|
||||
"date_debut": "01/09/2021",
|
||||
"bul_bgcolor": null,
|
||||
"date_fin": "15/12/2022",
|
||||
"resp_can_edit": false,
|
||||
"dept_id": 1,
|
||||
"etat": true,
|
||||
"resp_can_change_ens": false,
|
||||
"id": 1,
|
||||
"modalite": "FI",
|
||||
"ens_can_edit_eval": false,
|
||||
"formation_id": 1,
|
||||
"gestion_compensation": false,
|
||||
"elt_sem_apo": null,
|
||||
"semestre_id": 1,
|
||||
"bul_hide_xml": false,
|
||||
"elt_annee_apo": null,
|
||||
"block_moyennes": false,
|
||||
"formsemestre_id": 1,
|
||||
"titre_num": "master machine info semestre 1",
|
||||
"date_debut_iso": "2021-09-01",
|
||||
"date_fin_iso": "2022-12-15",
|
||||
"responsables": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
# Récupération des départements comportant l'acronym mit en paramètre
|
||||
depts = models.Departement.query.filter_by(acronym=dept).all()
|
||||
|
||||
# Récupération de l'id
|
||||
id_dept = depts[0].id
|
||||
|
||||
# Récupération des semestres suivant id_dept
|
||||
semestres = models.FormSemestre.query.filter_by(dept_id=id_dept, etat=True).all()
|
||||
|
||||
# Mise en forme des données
|
||||
|
||||
data = [d.to_dict() for d in semestres]
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/departements/<string:dept>/formations/<int:formation_id>/referentiel_competences", methods=["GET"])
|
||||
# @permission_required(Permission.APIView)
|
||||
def referenciel_competences(dept: str, formation_id: int):
|
||||
"""
|
||||
Retourne le référentiel de compétences
|
||||
|
||||
dept : l'acronym d'un département
|
||||
formation_id : l'id d'une formation
|
||||
"""
|
||||
depts = models.Departement.query.filter_by(acronym=dept).all()
|
||||
|
||||
id_dept = depts[0].id
|
||||
|
||||
formations = models.Formation.query.filter_by(id=formation_id, dept_id=id_dept).all()
|
||||
|
||||
ref_comp = formations[0].referentiel_competence_id
|
||||
|
||||
if ref_comp is None:
|
||||
return error_response(204, message="Pas de référenciel de compétences pour cette formation")
|
||||
else:
|
||||
return jsonify(ref_comp)
|
||||
|
||||
# ref = ApcReferentielCompetences.query.get_or_404(formation_id)
|
||||
#
|
||||
# return jsonify(ref.to_dict())
|
||||
|
||||
|
||||
@bp.route("/departements/<string:dept>/formsemestre/<string:formsemestre_id>/programme", methods=["GET"])
|
||||
# @permission_required(Permission.APIView)
|
||||
def semestre_index(dept: str, formsemestre_id: int):
|
||||
"""
|
||||
Retourne la liste des Ues, ressources et SAE d'un semestre
|
||||
"""
|
||||
|
||||
return error_response(501, message="not implemented")
|
|
@ -0,0 +1,324 @@
|
|||
#################################################### Etudiants ########################################################
|
||||
from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud
|
||||
from app.scodoc.sco_groups import get_etud_groups
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/etudiants", methods=["GET"])
|
||||
#@permission_required(Permission.APIView)
|
||||
def etudiants():
|
||||
"""
|
||||
Retourne la liste de tous les étudiants
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"civilite": "X",
|
||||
"code_ine": null,
|
||||
"code_nip": null,
|
||||
"date_naissance": null,
|
||||
"email": null,
|
||||
"emailperso": null,
|
||||
"etudid": 18,
|
||||
"nom": "MOREL",
|
||||
"prenom": "JACQUES"
|
||||
},
|
||||
{
|
||||
"civilite": "X",
|
||||
"code_ine": null,
|
||||
"code_nip": null,
|
||||
"date_naissance": null,
|
||||
"email": null,
|
||||
"emailperso": null,
|
||||
"etudid": 19,
|
||||
"nom": "FOURNIER",
|
||||
"prenom": "ANNE"
|
||||
},
|
||||
...
|
||||
"""
|
||||
# Récupération de tous les étudiants
|
||||
etu = models.Identite.query.all()
|
||||
|
||||
# Mise en forme des données
|
||||
data = [d.to_dict_bul(include_urls=False) for d in etu]
|
||||
|
||||
return jsonify(data)
|
||||
# return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/etudiants/courant", methods=["GET"])
|
||||
#@permission_required(Permission.APIView)
|
||||
def etudiants_courant():
|
||||
"""
|
||||
Retourne la liste des étudiants courant
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"civilite": "X",
|
||||
"code_ine": null,
|
||||
"code_nip": null,
|
||||
"date_naissance": null,
|
||||
"email": null,
|
||||
"emailperso": null,
|
||||
"etudid": 18,
|
||||
"nom": "MOREL",
|
||||
"prenom": "JACQUES"
|
||||
},
|
||||
{
|
||||
"civilite": "X",
|
||||
"code_ine": null,
|
||||
"code_nip": null,
|
||||
"date_naissance": null,
|
||||
"email": null,
|
||||
"emailperso": null,
|
||||
"etudid": 19,
|
||||
"nom": "FOURNIER",
|
||||
"prenom": "ANNE"
|
||||
},
|
||||
...
|
||||
"""
|
||||
# Récupération de tous les étudiants
|
||||
etus = models.Identite.query.all()
|
||||
|
||||
data = []
|
||||
# Récupère uniquement les étudiants courant
|
||||
for etu in etus:
|
||||
if etu.inscription_courante() is not None:
|
||||
data.append(etu.to_dict_bul(include_urls=False))
|
||||
|
||||
return jsonify(data)
|
||||
# return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/etudiant/etudid/<int:etudid>", methods=["GET"])
|
||||
@bp.route("/etudiant/nip/<int:nip>", methods=["GET"])
|
||||
@bp.route("/etudiant/ine/<int:ine>", methods=["GET"])
|
||||
#@permission_required(Permission.APIView)
|
||||
def etudiant(etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Retourne les informations de l'étudiant correspondant à l'id passé en paramètres.
|
||||
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip : le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"civilite": "X",
|
||||
"code_ine": null,
|
||||
"code_nip": null,
|
||||
"date_naissance": null,
|
||||
"email": null,
|
||||
"emailperso": null,
|
||||
"etudid": 18,
|
||||
"nom": "MOREL",
|
||||
"prenom": "JACQUES"
|
||||
}
|
||||
"""
|
||||
etu = []
|
||||
|
||||
if etudid is not None: # Si route etudid
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(id=etudid).first()
|
||||
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
|
||||
# Mise en forme des données
|
||||
data = etu.to_dict_bul(include_urls=False)
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
||||
@bp.route("/etudiant/nip/<int:nip>/formsemestres")
|
||||
@bp.route("/etudiant/ine/<int:ine>/formsemestres")
|
||||
#@permission_required(Permission.APIView)
|
||||
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Retourne la liste des semestres qu'un étudiant a suivis
|
||||
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip : le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"titre": "master machine info",
|
||||
"gestion_semestrielle": false,
|
||||
"scodoc7_id": null,
|
||||
"date_debut": "01/09/2021",
|
||||
"bul_bgcolor": null,
|
||||
"date_fin": "15/12/2022",
|
||||
"resp_can_edit": false,
|
||||
"dept_id": 1,
|
||||
"etat": true,
|
||||
"resp_can_change_ens": false,
|
||||
"id": 1,
|
||||
"modalite": "FI",
|
||||
"ens_can_edit_eval": false,
|
||||
"formation_id": 1,
|
||||
"gestion_compensation": false,
|
||||
"elt_sem_apo": null,
|
||||
"semestre_id": 1,
|
||||
"bul_hide_xml": false,
|
||||
"elt_annee_apo": null,
|
||||
"block_moyennes": false,
|
||||
"formsemestre_id": 1,
|
||||
"titre_num": "master machine info semestre 1",
|
||||
"date_debut_iso": "2021-09-01",
|
||||
"date_fin_iso": "2022-12-15",
|
||||
"responsables": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
|
||||
# Récupération de toutes les inscriptions
|
||||
inscriptions = models.FormSemestreInscription.query.all()
|
||||
|
||||
sems = []
|
||||
# Filtre les inscriptions contenant l'étudiant
|
||||
for sem in inscriptions:
|
||||
if etudid is not None: # Si route etudid
|
||||
if sem.etudid == etudid:
|
||||
sems.append(sem)
|
||||
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
if sem.etudid == etu.etudid:
|
||||
sems.append(sem)
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).firt()
|
||||
if sem.etudid == etu.etudid:
|
||||
sems.append(sem)
|
||||
|
||||
# Mise en forme des données
|
||||
# data_inscriptions = [d.to_dict() for d in sems]
|
||||
|
||||
formsemestres = []
|
||||
|
||||
# Filtre les formsemestre contenant les inscriptions de l'étudiant
|
||||
for sem in sems:#data_inscriptions:
|
||||
res = models.FormSemestre.query.filter_by(id=sem.formsemestre_id).first()
|
||||
formsemestres.append(res)
|
||||
|
||||
data = []
|
||||
# Mise en forme des données
|
||||
for formsem in formsemestres:
|
||||
data.append(formsem.to_dict())
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin", methods=["GET"])
|
||||
@bp.route("/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin", methods=["GET"])
|
||||
@bp.route("/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin", methods=["GET"])
|
||||
#@permission_required(Permission.APIView)
|
||||
def etudiant_bulletin_semestre(formsemestre_id, etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Retourne le bulletin d'un étudiant en fonction de son id et d'un semestre donné
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip : le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_bulletins_json.make_json_formsemestre_bulletinetud()
|
||||
|
||||
etu = None
|
||||
if etudid is not None: # Si route etudid
|
||||
return make_json_formsemestre_bulletinetud(formsemestre_id, etudid)
|
||||
else:
|
||||
if nip is not None: # Si route nip
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
etu = models.Identite.query.filter_by(code_nip=ine).first()
|
||||
|
||||
if etu is not None: # Si route nip ou ine
|
||||
return make_json_formsemestre_bulletinetud(formsemestre_id, etu.etudid)
|
||||
|
||||
# return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/etudiant/etudid/<int:etudid>/semestre/<int:formsemestre_id>/groups", methods=["GET"])
|
||||
@bp.route("/etudiant/nip/<int:nip>/semestre/<int:formsemestre_id>/groups", methods=["GET"])
|
||||
@bp.route("/etudiant/ine/<int:ine>/semestre/<int:formsemestre_id>/groups", methods=["GET"])
|
||||
#@permission_required(Permission.APIView)
|
||||
def etudiant_groups(formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Retourne la liste des groupes auxquels appartient l'étudiant dans le semestre indiqué
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip : le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"partition_id": 1,
|
||||
"id": 1,
|
||||
"formsemestre_id": 1,
|
||||
"partition_name": null,
|
||||
"numero": 0,
|
||||
"bul_show_rank": false,
|
||||
"show_in_lists": true,
|
||||
"group_id": 1,
|
||||
"group_name": null
|
||||
},
|
||||
{
|
||||
"partition_id": 2,
|
||||
"id": 2,
|
||||
"formsemestre_id": 1,
|
||||
"partition_name": "TD",
|
||||
"numero": 1,
|
||||
"bul_show_rank": false,
|
||||
"show_in_lists": true,
|
||||
"group_id": 2,
|
||||
"group_name": "A"
|
||||
}
|
||||
]
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_groups.get_etud_groups()
|
||||
|
||||
if etudid is None:
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
# Récupération de sont etudid
|
||||
etudid = etu.etudid
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
# Récupération de sont etudid
|
||||
etudid = etu.etudid
|
||||
|
||||
# Récupération du formsemestre
|
||||
sem = models.FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||
try:
|
||||
# Utilisation de la fonction get_etud_groups
|
||||
data = get_etud_groups(etudid, sem.to_dict())
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return jsonify(data)
|
|
@ -0,0 +1,69 @@
|
|||
############################################### Evaluations ###########################################################
|
||||
from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.auth import token_auth
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/evaluations/<int:moduleimpl_id>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def evaluations(moduleimpl_id: int):
|
||||
"""
|
||||
Retourne la liste des évaluations à partir de l'id d'un moduleimpl
|
||||
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
"""
|
||||
# Récupération de toutes les évaluations
|
||||
evals = models.Evaluation.query.filter_by(id=moduleimpl_id).all()
|
||||
|
||||
# Mise en forme des données
|
||||
data = [d.to_dict() for d in evals]
|
||||
|
||||
return jsonify(data)
|
||||
# return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/evaluations/eval_notes/<int:evaluation_id>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def evaluation_notes(evaluation_id: int):
|
||||
"""
|
||||
Retourne la liste des notes à partir de l'id d'une évaluation donnée
|
||||
|
||||
evaluation_id : l'id d'une évaluation
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_evaluation_db.do_evaluation_get_all_notes()
|
||||
|
||||
try:
|
||||
# Utilisation de la fonction do_evaluation_get_all_notes
|
||||
data = do_evaluation_get_all_notes(evaluation_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/evaluations/eval_set_notes?eval_id=<int:eval_id>&etudid=<int:etudid>¬e=<float:note>", methods=["POST"])
|
||||
@bp.route("/evaluations/eval_set_notes?eval_id=<int:eval_id>&nip=<int:nip>¬e=<float:note>", methods=["POST"])
|
||||
@bp.route("/evaluations/eval_set_notes?eval_id=<int:eval_id>&ine=<int:ine>¬e=<float:note>", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
@permission_required(Permission.APIEditAllNotes)
|
||||
def evaluation_set_notes(eval_id: int, note: float, etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Set les notes d'une évaluation pour un étudiant donnée
|
||||
|
||||
eval_id : l'id d'une évaluation
|
||||
note : la note à attribuer
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip : le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_saisie_notes.notes_add()
|
||||
|
||||
# Qu'est ce qu'un user ???
|
||||
# notes_add()
|
||||
return error_response(501, message="Not implemented")
|
|
@ -0,0 +1,117 @@
|
|||
##############################################" Formations ############################################################
|
||||
from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.scodoc.sco_formations import formation_export
|
||||
from app.scodoc.sco_moduleimpl import moduleimpl_list
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/formations", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def formations():
|
||||
"""
|
||||
Retourne la liste des formations
|
||||
"""
|
||||
# Récupération de toutes les formations
|
||||
list_formations = models.Formation.query.all()
|
||||
|
||||
# Mise en forme des données
|
||||
data = [d.to_dict() for d in list_formations]
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formations/<int:formation_id>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def formations_by_id(formation_id: int):
|
||||
"""
|
||||
Retourne une formation en fonction d'un id donné
|
||||
|
||||
formation_id : l'id d'une formation
|
||||
"""
|
||||
# Récupération de la formation
|
||||
forma = models.Formation.query.filter_by(id=formation_id).first()
|
||||
|
||||
# Mise en forme des données
|
||||
data = [d.to_dict() for d in forma]
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formations/formation_export/<int:formation_id>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
||||
"""
|
||||
Retourne la formation, avec UE, matières, modules
|
||||
"""
|
||||
# Fonction utilité : app.scodoc.sco_formations.formation_export()
|
||||
|
||||
try:
|
||||
# Utilisation de la fonction formation_export
|
||||
data = formation_export(formation_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formations/apo/<string:etape_apo>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def formsemestre_apo(etape_apo: int):
|
||||
"""
|
||||
Retourne les informations sur les formsemestres
|
||||
|
||||
etape_apo : l'id d'une étape apogée
|
||||
"""
|
||||
# Récupération des formsemestres
|
||||
apos = models.FormSemestreEtape.query.filter_by(etape_apo=etape_apo).all()
|
||||
|
||||
data = []
|
||||
# Filtre les formsemestres correspondant + mise en forme des données
|
||||
for apo in apos:
|
||||
formsem = models.FormSemestre.query.filter_by(id=apo["formsemestre_id"]).first()
|
||||
data.append(formsem.to_dict())
|
||||
|
||||
return jsonify(data)
|
||||
# return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/formations/moduleimpl/<int:moduleimpl_id>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def moduleimpls(moduleimpl_id: int):
|
||||
"""
|
||||
Retourne la liste des moduleimpl
|
||||
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
"""
|
||||
# Récupération des tous les moduleimpl
|
||||
list_moduleimpls = models.ModuleImpl.query.filter_by(id=moduleimpl_id).all()
|
||||
|
||||
# Mise en forme des données
|
||||
data = list_moduleimpls[0].to_dict()
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formations/moduleimpl/<int:moduleimpl_id>/formsemestre/<int:formsemestre_id>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def moduleimpls_sem(moduleimpl_id: int, formsemestre_id: int):
|
||||
"""
|
||||
Retourne la liste des moduleimpl d'un semestre
|
||||
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_moduleimpl.moduleimpl_list()
|
||||
|
||||
try:
|
||||
# Utilisation de la fonction moduleimpl_list
|
||||
data = moduleimpl_list(moduleimpl_id, formsemestre_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return jsonify(data)
|
|
@ -0,0 +1,104 @@
|
|||
########################################## Formsemestres ##############################################################
|
||||
from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.scodoc.sco_bulletins import formsemestre_bulletinetud_dict
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_pvjury import formsemestre_pvjury
|
||||
from app.scodoc.sco_recapcomplet import formsemestre_recapcomplet
|
||||
|
||||
|
||||
@bp.route("/formations/formsemestre/<int:formsemestre_id>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def formsemestre(formsemestre_id: int):
|
||||
"""
|
||||
Retourne l'information sur le formsemestre correspondant au formsemestre_id
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
|
||||
"""
|
||||
# Récupération de tous les formsemestres
|
||||
list_formsemetre = models.FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
|
||||
# Mise en forme des données
|
||||
data = list_formsemetre[0].to_dict()
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/etudid/<int:etudid>/bulletin",
|
||||
methods=["GET"],
|
||||
)
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/nip/<int:nip>/bulletin",
|
||||
methods=["GET"],
|
||||
)
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/ine/<int:ine>/bulletin",
|
||||
methods=["GET"],
|
||||
)
|
||||
@permission_required(Permission.APIView)
|
||||
def etudiant_bulletin(formsemestre_id, dept, etudid, format="json", *args, size):
|
||||
"""
|
||||
Retourne le bulletin de note d'un étudiant
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip : le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_bulletins.formsemestre_billetinetud_dict()
|
||||
|
||||
data = []
|
||||
if args[0] == "short":
|
||||
data = formsemestre_bulletinetud_dict(formsemestre_id, etudid, version=args[0])
|
||||
elif args[0] == "selectevals":
|
||||
data = formsemestre_bulletinetud_dict(formsemestre_id, etudid, version=args[0])
|
||||
elif args[0] == "long":
|
||||
data = formsemestre_bulletinetud_dict(formsemestre_id, etudid)
|
||||
else:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def bulletins(formsemestre_id: int):
|
||||
"""
|
||||
Retourne les bulletins d'un formsemestre donné
|
||||
|
||||
formsemestre_id : l'id d'un formesemestre
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_recapcomplet.formsemestre_recapcomplet()
|
||||
|
||||
try:
|
||||
# Utilisation de la fonction formsemestre_recapcomplet
|
||||
data = formsemestre_recapcomplet(formsemestre_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/jury", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def jury(formsemestre_id: int):
|
||||
"""
|
||||
Retourne le récapitulatif des décisions jury
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury()
|
||||
|
||||
try:
|
||||
# Utilisation de la fonction formsemestre_pvjury
|
||||
data = formsemestre_pvjury(formsemestre_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return jsonify(data)
|
|
@ -0,0 +1,75 @@
|
|||
#################################################### Jury #############################################################
|
||||
from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.errors import error_response
|
||||
from app.scodoc.sco_prepajury import feuille_preparation_jury
|
||||
from app.scodoc.sco_pvjury import formsemestre_pvjury
|
||||
|
||||
|
||||
@bp.route("/jury/formsemestre/<int:formsemestre_id>/preparation_jury", methods=["GET"])
|
||||
def jury_preparation(formsemestre_id: int): # XXX TODO check à quoi resemble le retour de la fonction
|
||||
"""
|
||||
Retourne la feuille de préparation du jury
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury()
|
||||
|
||||
# Utilisation de la fonction feuille_preparation_jury
|
||||
prepa_jury = feuille_preparation_jury(formsemestre_id)
|
||||
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/jury/formsemestre/<int:formsemestre_id>/decisions_jury", methods=["GET"])
|
||||
def jury_decisions(formsemestre_id: int): # XXX TODO check à quoi resemble le retour de la fonction
|
||||
"""
|
||||
Retourne les décisions du jury suivant un formsemestre donné
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury()
|
||||
|
||||
# Utilisation de la fonction formsemestre_pvjury
|
||||
decision_jury = formsemestre_pvjury(formsemestre_id)
|
||||
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/jury/set_decision/etudid?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
|
||||
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>", methods=["POST"])
|
||||
@bp.route("/jury/set_decision/nip?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
|
||||
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>", methods=["POST"])
|
||||
@bp.route("/jury/set_decision/ine?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
|
||||
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>", methods=["POST"])
|
||||
def set_decision_jury(formsemestre_id: int, decision_jury: str, devenir_jury: str, assiduite: bool,
|
||||
etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Attribuer la décision du jury et le devenir à un etudiant
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
decision_jury : la décision du jury
|
||||
devenir_jury : le devenir du jury
|
||||
assiduite : True ou False
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip: le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/jury/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/annule_decision", methods=["DELETE"])
|
||||
@bp.route("/jury/nip/<int:nip>/formsemestre/<int:formsemestre_id>/annule_decision", methods=["DELETE"])
|
||||
@bp.route("/jury/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision", methods=["DELETE"])
|
||||
def annule_decision_jury(formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Supprime la déciosion du jury pour un étudiant donné
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
etudid : l'etudid d'un étudiant
|
||||
nip: le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
|
@ -36,6 +36,7 @@ from app.api import bp
|
|||
from app.api import requested_format
|
||||
from app.api.auth import token_auth
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.models import Departement
|
||||
from app.scodoc.sco_logos import list_logos, find_logo
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
@ -43,6 +44,7 @@ from app.scodoc.sco_permissions import Permission
|
|||
|
||||
@bp.route("/logos", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@permission_required(Permission.APIView)
|
||||
def api_get_glob_logos():
|
||||
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
||||
return error_response(401, message="accès interdit")
|
||||
|
@ -55,6 +57,7 @@ def api_get_glob_logos():
|
|||
|
||||
@bp.route("/logos/<string:logoname>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@permission_required(Permission.APIView)
|
||||
def api_get_glob_logo(logoname):
|
||||
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
||||
return error_response(401, message="accès interdit")
|
||||
|
@ -71,6 +74,7 @@ def api_get_glob_logo(logoname):
|
|||
|
||||
@bp.route("/departements/<string:departement>/logos", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@permission_required(Permission.APIView)
|
||||
def api_get_local_logos(departement):
|
||||
dept_id = Departement.from_acronym(departement).id
|
||||
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement):
|
||||
|
@ -81,6 +85,7 @@ def api_get_local_logos(departement):
|
|||
|
||||
@bp.route("/departements/<string:departement>/logos/<string:logoname>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@permission_required(Permission.APIView)
|
||||
def api_get_local_logo(departement, logoname):
|
||||
# format = requested_format("jpg", ['png', 'jpg']) XXX ?
|
||||
dept_id = Departement.from_acronym(departement).id
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
############################################### Partitions ############################################################
|
||||
from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.auth import token_auth
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.scodoc.sco_groups import get_group_members, setGroups
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/partitions/<int:formsemestre_id>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def partition(formsemestre_id: int):
|
||||
"""
|
||||
Retourne la liste de toutes les partitions d'un formsemestre
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
"""
|
||||
# Récupération de toutes les partitions
|
||||
partitions = models.Partition.query.filter_by(id=formsemestre_id).all()
|
||||
|
||||
# Mise en forme des données
|
||||
data = [d.to_dict() for d in partitions]
|
||||
|
||||
return jsonify(data)
|
||||
# return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
# @bp.route(
|
||||
# "/partitions/formsemestre/<int:formsemestre_id>/groups/group_ids?with_codes=&all_groups=&etat=",
|
||||
# methods=["GET"],
|
||||
# )
|
||||
@bp.route("/partitions/groups/<int:group_id>", methods=["GET"])
|
||||
@bp.route("/partitions/groups/<int:group_id>/etat/<string:etat>", methods=["GET"])
|
||||
@permission_required(Permission.APIView)
|
||||
def etud_in_group(group_id: int, etat=None):
|
||||
"""
|
||||
Retourne la liste des étudiants dans un groupe
|
||||
|
||||
group_id : l'id d'un groupe
|
||||
etat :
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_groups.get_group_members()
|
||||
|
||||
if etat is None: # Si l'état n'est pas renseigné
|
||||
try:
|
||||
# Utilisation de la fonction get_group_members
|
||||
data = get_group_members(group_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
else: # Si l'état est renseigné
|
||||
try:
|
||||
# Utilisation de la fonction get_group_members
|
||||
data = get_group_members(group_id, etat)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/partitions/set_groups?partition_id=<int:partition_id>&groups_lists=<int:groups_lists>&"
|
||||
"groups_to_create=<int:groups_to_create>&groups_to_delete=<int:groups_to_delete>", methods=["POST"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
@permission_required(Permission.APIEtudChangeGroups)
|
||||
def set_groups(partition_id: int, groups_lists: int, groups_to_delete: int, groups_to_create: int):
|
||||
"""
|
||||
Set les groups
|
||||
|
||||
partition_id : l'id d'une partition
|
||||
groups_lists :
|
||||
groups_ti_delete : les groupes à supprimer
|
||||
groups_to_create : les groupes à créer
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_groups.setGroups()
|
||||
try:
|
||||
# Utilisation de la fonction setGroups
|
||||
setGroups(partition_id, groups_lists, groups_to_create, groups_to_delete)
|
||||
return error_response(200, message="Groups set")
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
|
@ -50,442 +50,98 @@ from app.api.errors import error_response
|
|||
from app import models
|
||||
from app.models import FormSemestre, FormSemestreInscription, Identite
|
||||
from app.models import ApcReferentielCompetences
|
||||
from app.scodoc.sco_abs import annule_absence, annule_justif, add_absence, add_justif, list_abs_date
|
||||
from app.scodoc.sco_bulletins import formsemestre_bulletinetud_dict
|
||||
from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud
|
||||
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
|
||||
from app.scodoc.sco_formations import formation_export
|
||||
from app.scodoc.sco_formsemestre_inscriptions import do_formsemestre_inscription_listinscrits
|
||||
from app.scodoc.sco_groups import setGroups, get_etud_groups, get_group_members
|
||||
from app.scodoc.sco_logos import list_logos, find_logo, _list_dept_logos
|
||||
from app.scodoc.sco_moduleimpl import moduleimpl_list
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/list_depts", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def list_depts():
|
||||
depts = models.Departement.query.filter_by(visible=True).all()
|
||||
data = [d.to_dict() for d in depts]
|
||||
return jsonify(data)
|
||||
|
||||
# ###################################################### Logos ##########################################################
|
||||
#
|
||||
# # XXX TODO voir get_logo déjà existant dans app/views/scodoc.py
|
||||
#
|
||||
# @bp.route("/logos", methods=["GET"])
|
||||
# def liste_logos(format="json"):
|
||||
# """
|
||||
# Liste des logos définis pour le site scodoc.
|
||||
# """
|
||||
# # fonction to use : list_logos()
|
||||
# # try:
|
||||
# # res = list_logos()
|
||||
# # except ValueError:
|
||||
# # return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
# #
|
||||
# # if res is None:
|
||||
# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
|
||||
# #
|
||||
# # return res
|
||||
#
|
||||
#
|
||||
#
|
||||
# @bp.route("/logos/<string:logo_name>", methods=["GET"])
|
||||
# def recup_logo_global(logo_name: str):
|
||||
# """
|
||||
# Retourne l'image au format png ou jpg
|
||||
#
|
||||
# logo_name : le nom du logo rechercher
|
||||
# """
|
||||
# # fonction to use find_logo
|
||||
# # try:
|
||||
# # res = find_logo(logo_name)
|
||||
# # except ValueError:
|
||||
# # return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
# #
|
||||
# # if res is None:
|
||||
# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
|
||||
# #
|
||||
# # return res
|
||||
#
|
||||
#
|
||||
# @bp.route("/departements/<string:dept>/logos", methods=["GET"])
|
||||
# def logo_dept(dept: str):
|
||||
# """
|
||||
# Liste des logos définis pour le département visé.
|
||||
#
|
||||
# dept : l'id d'un département
|
||||
# """
|
||||
# # fonction to use: _list_dept_logos
|
||||
# # dept_id = models.Departement.query.filter_by(acronym=dept).first()
|
||||
# # try:
|
||||
# # res = _list_dept_logos(dept_id.id)
|
||||
# # except ValueError:
|
||||
# # return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
# #
|
||||
# # if res is None:
|
||||
# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
|
||||
# #
|
||||
# # return res
|
||||
#
|
||||
#
|
||||
# @bp.route("/departement/<string:dept>/logos/<string:logo_name>", methods=["GET"])
|
||||
# def recup_logo_dept_global(dept: str, logo_name: str):
|
||||
# """
|
||||
# L'image format png ou jpg
|
||||
#
|
||||
# dept : l'id d'un département
|
||||
# logo_name : le nom du logo rechercher
|
||||
# """
|
||||
# # fonction to use find_logo
|
||||
# # dept_id = models.Departement.query.filter_by(acronym=dept).first()
|
||||
# # try:
|
||||
# # res = find_logo(logo_name, dept_id.id)
|
||||
# # except ValueError:
|
||||
# # return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
# #
|
||||
# # if res is None:
|
||||
# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
|
||||
# #
|
||||
# # return res
|
||||
|
||||
@bp.route("/etudiants/courant", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def etudiants():
|
||||
"""Liste de tous les étudiants actuellement inscrits à un semestre
|
||||
en cours.
|
||||
"""
|
||||
# Vérification de l'accès: permission Observateur sur tous les départements
|
||||
# (c'est un exemple à compléter)
|
||||
if not g.current_user.has_permission(Permission.ScoObservateur, None):
|
||||
return error_response(401, message="accès interdit")
|
||||
|
||||
query = db.session.query(Identite).filter(
|
||||
FormSemestreInscription.formsemestre_id == FormSemestre.id,
|
||||
FormSemestreInscription.etudid == Identite.id,
|
||||
FormSemestre.date_debut <= func.now(),
|
||||
FormSemestre.date_fin >= func.now(),
|
||||
)
|
||||
return jsonify([e.to_dict_bul(include_urls=False) for e in query])
|
||||
|
||||
|
||||
######################## Departements ##################################
|
||||
|
||||
|
||||
@bp.route("/departements", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def departements():
|
||||
"""
|
||||
Liste des ids de départements
|
||||
"""
|
||||
depts = models.Departement.query.filter_by(visible=True).all()
|
||||
data = [d.id for d in depts]
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/departements/<string:dept>/etudiants/liste/<int:sem_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def liste_etudiants(dept, *args, sem_id): # XXX TODO A REVOIR
|
||||
"""
|
||||
Liste des étudiants d'un département
|
||||
"""
|
||||
# Test si le sem_id à été renseigné ou non
|
||||
if sem_id is not None:
|
||||
# Récupération du/des depts
|
||||
list_depts = models.Departement.query.filter(
|
||||
models.Departement.acronym == dept,
|
||||
models.FormSemestre.semestre_id == sem_id,
|
||||
)
|
||||
list_etuds = []
|
||||
for dept in list_depts:
|
||||
# Récupération des étudiants d'un département
|
||||
x = models.Identite.query.filter(models.Identite.dept_id == dept.getId())
|
||||
for y in x:
|
||||
# Ajout des étudiants dans la liste global
|
||||
list_etuds.append(y)
|
||||
else:
|
||||
list_depts = models.Departement.query.filter(
|
||||
models.Departement.acronym == dept,
|
||||
models.FormSemestre.semestre_id == models.Departement.formsemestres,
|
||||
)
|
||||
list_etuds = []
|
||||
for dept in list_depts:
|
||||
x = models.Identite.query.filter(models.Identite.dept_id == dept.getId())
|
||||
for y in x:
|
||||
list_etuds.append(y)
|
||||
|
||||
data = [d.to_dict() for d in list_etuds]
|
||||
# return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/departements/<string:dept>/semestres_actifs", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def liste_semestres_actifs(dept): # TODO : changer nom
|
||||
"""
|
||||
Liste des semestres actifs d'un départements donné
|
||||
"""
|
||||
# Récupération de l'id du dept
|
||||
dept_id = models.Departement.query.filter(models.Departement.acronym == dept)
|
||||
# Puis ici récupération du FormSemestre correspondant
|
||||
depts_actifs = models.FormSemestre.query.filter_by(
|
||||
etat=True,
|
||||
dept_id=dept_id,
|
||||
)
|
||||
data = [da.to_dict() for da in depts_actifs]
|
||||
|
||||
# return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/referentiel_competences/<int:referentiel_competence_id>")
|
||||
@token_auth.login_required
|
||||
def referentiel_competences(referentiel_competence_id):
|
||||
"""
|
||||
Le référentiel de compétences
|
||||
"""
|
||||
ref = ApcReferentielCompetences.query.get_or_404(referentiel_competence_id)
|
||||
return jsonify(ref.to_dict())
|
||||
|
||||
|
||||
####################### Etudiants ##################################
|
||||
|
||||
|
||||
@bp.route("/etudiant/<int:etudid>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def etudiant(etudid):
|
||||
"""
|
||||
Un dictionnaire avec les informations de l'étudiant correspondant à l'id passé en paramètres.
|
||||
"""
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
return jsonify(etud.to_dict_bul())
|
||||
|
||||
|
||||
@bp.route("/etudiant/<int:etudid>/semestre/<int:sem_id>/bulletin", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def etudiant_bulletin_semestre(etudid, sem_id):
|
||||
"""
|
||||
Le bulletin d'un étudiant en fonction de son id et d'un semestre donné
|
||||
"""
|
||||
# return jsonify(models.BulAppreciations.query.filter_by(etudid=etudid, formsemestre_id=sem_id))
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/nip/<int:NIP>/releve",
|
||||
methods=["GET"],
|
||||
)
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/id/<int:etudid>/releve",
|
||||
methods=["GET"],
|
||||
)
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/ine/<int:numScodoc>/releve",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
def etudiant_bulletin(formsemestre_id, dept, etudid, format="json", *args, size):
|
||||
"""
|
||||
Un bulletin de note
|
||||
"""
|
||||
formsemestres = models.FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
depts = models.Departement.query.filter_by(acronym=dept)
|
||||
etud = ""
|
||||
|
||||
data = []
|
||||
if args[0] == "short":
|
||||
pass
|
||||
elif args[0] == "selectevals":
|
||||
pass
|
||||
elif args[0] == "long":
|
||||
pass
|
||||
else:
|
||||
return "erreur"
|
||||
|
||||
# return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/etudiant/<int:etudid>/semestre/<int:formsemestre_id>/groups", methods=["GET"]
|
||||
)
|
||||
@token_auth.login_required
|
||||
def etudiant_groups(etudid: int, formsemestre_id: int):
|
||||
"""
|
||||
Liste des groupes auxquels appartient l'étudiant dans le semestre indiqué
|
||||
"""
|
||||
semestre = models.FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
etudiant = models.Identite.query.filter_by(id=etudid)
|
||||
|
||||
groups = models.Partition.query.filter(
|
||||
models.Partition.formsemestre_id == semestre,
|
||||
models.GroupDescr.etudiants == etudiant,
|
||||
)
|
||||
data = [d.to_dict() for d in groups]
|
||||
# return jsonify(data)
|
||||
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
#######################" Programmes de formations #########################
|
||||
|
||||
|
||||
@bp.route("/formations", methods=["GET"])
|
||||
@bp.route("/formations/<int:formation_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def formations(formation_id: int):
|
||||
"""
|
||||
Liste des formations
|
||||
"""
|
||||
formations = models.Formation.query.filter_by(id=formation_id)
|
||||
data = [d.to_dict() for d in formations]
|
||||
# return jsonify(data)
|
||||
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/formations/formation_export/<int:formation_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def formation_export(formation_id: int, export_ids=False):
|
||||
"""
|
||||
La formation, avec UE, matières, modules
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
###################### UE #######################################
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/departements/<string:dept>/formations/programme/<string:sem_id>", methods=["GET"]
|
||||
)
|
||||
@token_auth.login_required
|
||||
def eus(dept: str, sem_id: int):
|
||||
"""
|
||||
Liste des UES, ressources et SAE d'un semestre
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
######## Semestres de formation ###############
|
||||
|
||||
|
||||
@bp.route("/formations/formsemestre/<int:formsemestre_id>", methods=["GET"])
|
||||
@bp.route("/formations/apo/<int:etape_apo>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def formsemestre(
|
||||
id: int,
|
||||
):
|
||||
"""
|
||||
Information sur les formsemestres
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
############ Modules de formation ##############
|
||||
|
||||
|
||||
@bp.route("/formations/moduleimpl/<int:moduleimpl_id>", methods=["GET"])
|
||||
@bp.route(
|
||||
"/formations/moduleimpl/<int:moduleimpl_id>/formsemestre/<int:formsemestre_id>",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
def moduleimpl(id: int):
|
||||
"""
|
||||
Liste de moduleimpl
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
########### Groupes et partitions ###############
|
||||
|
||||
|
||||
@bp.route("/partitions/<int:formsemestre_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def partition(formsemestre_id: int):
|
||||
"""
|
||||
La liste de toutes les partitions d'un formsemestre
|
||||
"""
|
||||
partitions = models.Partition.query.filter_by(id=formsemestre_id)
|
||||
data = [d.to_dict() for d in partitions]
|
||||
|
||||
# return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/partitions/formsemestre/<int:formsemestre_id>/groups/group_ids?with_codes=&all_groups=&etat=",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
def groups(formsemestre_id: int, group_ids: int):
|
||||
"""
|
||||
Liste des étudiants dans un groupe
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/partitions/set_groups?partition_id=<int:partition_id>&groups=<int:groups>&groups_to_delete=<int:groups_to_delete>&groups_to_create=<int:groups_to_create>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
def set_groups(
|
||||
partition_id: int, groups: int, groups_to_delete: int, groups_to_create: int
|
||||
):
|
||||
"""
|
||||
Set les groups
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
####### Bulletins de notes ###########
|
||||
|
||||
|
||||
@bp.route("/evaluations/<int:moduleimpl_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def evaluations(moduleimpl_id: int):
|
||||
"""
|
||||
Liste des évaluations à partir de l'id d'un moduleimpl
|
||||
"""
|
||||
evals = models.Evaluation.query.filter_by(id=moduleimpl_id)
|
||||
data = [d.to_dict() for d in evals]
|
||||
|
||||
# return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/evaluations/eval_notes/<int:evaluation_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def evaluation_notes(evaluation_id: int):
|
||||
"""
|
||||
Liste des notes à partir de l'id d'une évaluation donnée
|
||||
"""
|
||||
evals = models.Evaluation.query.filter_by(id=evaluation_id)
|
||||
notes = evals.get_notes()
|
||||
|
||||
data = [d.to_dict() for d in notes]
|
||||
# return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/evaluations/eval_set_notes?eval_id=<int:eval_id>&etudid=<int:etudid>¬e=<int:note>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
def evaluation_set_notes(eval_id: int, etudid: int, note: float):
|
||||
"""
|
||||
Set les notes d'une évaluation pour un étudiant donnée
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
############## Absences #############
|
||||
|
||||
|
||||
@bp.route("/absences/<int:etudid>", methods=["GET"])
|
||||
@bp.route("/absences/<int:etudid>/abs_just_only", methods=["GET"])
|
||||
def absences(etudid: int):
|
||||
"""
|
||||
Liste des absences d'un étudiant donnée
|
||||
"""
|
||||
abs = models.Absence.query.filter_by(id=etudid)
|
||||
|
||||
data = [d.to_dict() for d in abs]
|
||||
|
||||
# return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/absences/abs_signale", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
def abs_signale():
|
||||
"""
|
||||
Retourne un html
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/absences/abs_annule", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
def abs_annule():
|
||||
"""
|
||||
Retourne un html
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/absences/abs_annule_justif", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
def abs_annule_justif():
|
||||
"""
|
||||
Retourne un html
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/absences/abs_group_etat/?group_ids=<int:group_ids>&date_debut=date_debut&date_fin=date_fin",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
def abs_groupe_etat(
|
||||
group_ids: int, date_debut, date_fin, with_boursier=True, format="html"
|
||||
):
|
||||
"""
|
||||
Liste des absences d'un ou plusieurs groupes entre deux dates
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
################ Logos ################
|
||||
|
||||
|
||||
@bp.route("/logos", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def liste_logos(format="json"):
|
||||
"""
|
||||
Liste des logos définis pour le site scodoc.
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/logos/<string:nom>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def recup_logo_global(nom: str):
|
||||
"""
|
||||
Retourne l'image au format png ou jpg
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/departements/<string:dept>/logos", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def logo_dept(dept: str):
|
||||
"""
|
||||
Liste des logos définis pour le département visé.
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/departement/<string:dept>/logos/<string:nom>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def recup_logo_dept_global(dept: str, nom: str):
|
||||
"""
|
||||
L'image format png ou jpg
|
||||
"""
|
||||
return error_response(501, message="Not implemented")
|
||||
|
|
|
@ -0,0 +1,493 @@
|
|||
################################################## Tests ##############################################################
|
||||
|
||||
import requests
|
||||
import os
|
||||
|
||||
from app import models
|
||||
from app.api import bp, requested_format
|
||||
from app.api.auth import token_auth
|
||||
from app.api.errors import error_response
|
||||
|
||||
SCODOC_USER = "test"
|
||||
SCODOC_PASSWORD = "test"
|
||||
SCODOC_URL = "http://192.168.1.12:5000"
|
||||
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
||||
|
||||
HEADERS = None
|
||||
|
||||
def get_token():
|
||||
"""
|
||||
Permet de set le token dans le header
|
||||
"""
|
||||
global HEADERS
|
||||
global SCODOC_USER
|
||||
global SCODOC_PASSWORD
|
||||
|
||||
r0 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
token = r0.json()["token"]
|
||||
HEADERS = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
DEPT = None
|
||||
FORMSEMESTRE = None
|
||||
ETU = None
|
||||
|
||||
|
||||
@bp.route("/test_dept", methods=["GET"])
|
||||
def get_departement():
|
||||
"""
|
||||
Permet de tester departements() mais également de set un département dans DEPT pour la suite des tests
|
||||
"""
|
||||
|
||||
get_token()
|
||||
|
||||
global HEADERS
|
||||
global CHECK_CERTIFICATE
|
||||
global SCODOC_USER
|
||||
global SCODOC_PASSWORD
|
||||
|
||||
# print(HEADERS)
|
||||
# departements
|
||||
r = requests.get(
|
||||
SCODOC_URL + "/ScoDoc/api/departements",
|
||||
headers=HEADERS, verify=CHECK_CERTIFICATE,
|
||||
)
|
||||
|
||||
if r.status_code == 200:
|
||||
dept_id = r.json()[0]
|
||||
# print(dept_id)
|
||||
|
||||
dept = models.Departement.query.filter_by(id=dept_id).first()
|
||||
dept = dept.to_dict()
|
||||
|
||||
fields = ["id", "acronym", "description", "visible", "date_creation"]
|
||||
|
||||
for field in dept:
|
||||
if field not in fields:
|
||||
return error_response(501, field + " field missing")
|
||||
|
||||
global DEPT
|
||||
DEPT = dept
|
||||
|
||||
return error_response(200, "OK")
|
||||
|
||||
return error_response(409, "La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
|
||||
@bp.route("/test_formsemestre", methods=["GET"])
|
||||
def get_formsemestre():
|
||||
"""
|
||||
Permet de tester liste_semestres_courant() mais également de set un formsemestre dans FORMSEMESTRE
|
||||
pour la suite des tests
|
||||
"""
|
||||
get_departement()
|
||||
|
||||
global DEPT
|
||||
dept_acronym = DEPT["acronym"]
|
||||
|
||||
# liste_semestres_courant
|
||||
r = requests.get(
|
||||
SCODOC_URL + "/ScoDoc/api/departements/" + dept_acronym + "/semestres_courants",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
if r.status_code == 200:
|
||||
formsemestre = r.json()[0]
|
||||
print(r.json()[0])
|
||||
|
||||
fields = ["gestion_semestrielle", "titre", "scodoc7_id", "date_debut", "bul_bgcolor", "date_fin",
|
||||
"resp_can_edit", "dept_id", "etat", "resp_can_change_ens", "id", "modalite", "ens_can_edit_eval",
|
||||
"formation_id", "gestion_compensation", "elt_sem_apo", "semestre_id", "bul_hide_xml", "elt_annee_apo",
|
||||
"block_moyennes", "formsemestre_id", "titre_num", "date_debut_iso", "date_fin_iso", "responsables"]
|
||||
|
||||
for field in formsemestre:
|
||||
if field not in fields:
|
||||
return error_response(501, field + " field missing")
|
||||
|
||||
global FORMSEMESTRE
|
||||
FORMSEMESTRE = formsemestre
|
||||
|
||||
return error_response(200, "OK")
|
||||
|
||||
return error_response(409, "La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
|
||||
|
||||
@bp.route("/test_etu", methods=["GET"])
|
||||
def get_etudiant():
|
||||
"""
|
||||
Permet de tester etudiants() mais également de set un etudiant dans ETU pour la suite des tests
|
||||
"""
|
||||
|
||||
# etudiants
|
||||
r = requests.get(
|
||||
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
if r.status_code == 200:
|
||||
etu = r.json()[0]
|
||||
|
||||
fields = ["civilite", "code_ine", "code_nip", "date_naissance", "email", "emailperso", "etudid", "nom",
|
||||
"prenom"]
|
||||
|
||||
for field in etu:
|
||||
if field not in fields:
|
||||
return error_response(501, field + " field missing")
|
||||
|
||||
global ETU
|
||||
ETU = etu
|
||||
print(etu)
|
||||
|
||||
return error_response(200, "OK")
|
||||
|
||||
return error_response(409, "La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
|
||||
############################################### Departements ##########################################################
|
||||
|
||||
|
||||
@bp.route("/test_liste_etudiants")
|
||||
def test_departements_liste_etudiants():
|
||||
"""
|
||||
Test la route liste_etudiants
|
||||
"""
|
||||
# Set un département et un formsemestre pour les tests
|
||||
get_departement()
|
||||
get_formsemestre()
|
||||
|
||||
global DEPT
|
||||
global FORMSEMESTRE
|
||||
|
||||
# Set les fields à vérifier
|
||||
fields = ["civilite", "code_ine", "code_nip", "date_naissance", "email", "emailperso", "etudid", "nom", "prenom"]
|
||||
|
||||
# liste_etudiants (sans formsemestre)
|
||||
r1 = requests.get(
|
||||
SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/etudiants/liste",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
if r1.status_code == 200: # Si la requête est "OK"
|
||||
# On récupère la liste des étudiants
|
||||
etudiants = r1.json()
|
||||
|
||||
# Vérification que tous les étudiants ont bien tous les bons champs
|
||||
for etu in etudiants:
|
||||
for field in etu:
|
||||
if field not in fields:
|
||||
return error_response(501, field + " field missing")
|
||||
|
||||
|
||||
# liste_etudiants (avec formsemestre)
|
||||
r2 = requests.get(
|
||||
SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/etudiants/liste/" +
|
||||
str(FORMSEMESTRE["formsemestre_id"]),
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
if r2.status_code == 200: # Si la requête est "OK"
|
||||
# On récupère la liste des étudiants
|
||||
etudiants = r2.json()
|
||||
|
||||
# Vérification que tous les étudiants ont bien tous les bons champs
|
||||
for etu in etudiants:
|
||||
for field in etu:
|
||||
if field not in fields:
|
||||
return error_response(501, field + " field missing")
|
||||
|
||||
return error_response(200, "OK")
|
||||
|
||||
return error_response(409, "La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
|
||||
@bp.route("/test_referenciel_competences")
|
||||
def test_departements_referenciel_competences():
|
||||
"""
|
||||
Test la route referenciel_competences
|
||||
"""
|
||||
get_departement()
|
||||
get_formsemestre()
|
||||
|
||||
global DEPT
|
||||
global FORMSEMESTRE
|
||||
|
||||
# referenciel_competences
|
||||
r = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/formations/" +
|
||||
FORMSEMESTRE["formation_id"] + "/referentiel_competences",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/test_liste_semestre_index")
|
||||
def test_departements_semestre_index():
|
||||
"""
|
||||
Test la route semestre_index
|
||||
"""
|
||||
# semestre_index
|
||||
r5 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/formsemestre/" +
|
||||
FORMSEMESTRE["formation_id"] + "/programme",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
|
||||
#################################################### Etudiants ########################################################
|
||||
|
||||
def test_routes_etudiants():
|
||||
"""
|
||||
Test les routes de la partie Etudiants
|
||||
"""
|
||||
# etudiants
|
||||
r1 = requests.get(
|
||||
SCODOC_URL + "/ScoDoc/api/etudiants",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# etudiants_courant
|
||||
r2 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# etudiant
|
||||
r3 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# etudiant_formsemestres
|
||||
r4 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# etudiant_bulletin_semestre
|
||||
r5 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# etudiant_groups
|
||||
r6 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
|
||||
def test_routes_formation():
|
||||
"""
|
||||
Test les routes de la partie Formation
|
||||
"""
|
||||
# formations
|
||||
r1 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# formations_by_id
|
||||
r2 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# formation_export_by_formation_id
|
||||
r3 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# formsemestre_apo
|
||||
r4 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# moduleimpls
|
||||
r5 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# moduleimpls_sem
|
||||
r6 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
|
||||
def test_routes_formsemestres():
|
||||
"""
|
||||
Test les routes de la partie Formsemestres
|
||||
"""
|
||||
# formsemestre
|
||||
r1 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# etudiant_bulletin
|
||||
r2 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# bulletins
|
||||
r3 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# jury
|
||||
r4 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
|
||||
def test_routes_partitions():
|
||||
"""
|
||||
Test les routes de la partie Partitions
|
||||
"""
|
||||
# partition
|
||||
r1 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# etud_in_group
|
||||
r2 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# set_groups
|
||||
r3 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
|
||||
def test_routes_evaluations():
|
||||
"""
|
||||
Test les routes de la partie Evaluations
|
||||
"""
|
||||
# evaluations
|
||||
r1 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# evaluation_notes
|
||||
r2 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# evaluation_set_notes
|
||||
r3 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
|
||||
def test_routes_jury():
|
||||
"""
|
||||
Test les routes de la partie Jury
|
||||
"""
|
||||
# jury_preparation
|
||||
r1 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# jury_decisions
|
||||
r2 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# set_decision_jury
|
||||
r3 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# annule_decision_jury
|
||||
r4 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
|
||||
def test_routes_absences():
|
||||
"""
|
||||
Test les routes de la partie Absences
|
||||
"""
|
||||
# absences
|
||||
r1 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# absences_justify
|
||||
r2 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# abs_signale
|
||||
r3 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# abs_annule
|
||||
r4 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# abs_annule_justif
|
||||
r5 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# abs_groupe_etat
|
||||
r6 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
|
||||
def test_routes_logos():
|
||||
"""
|
||||
Test les routes de la partie Logos
|
||||
"""
|
||||
# liste_logos
|
||||
r1 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# recup_logo_global
|
||||
r2 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# logo_dept
|
||||
r3 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
||||
|
||||
# recup_logo_dept_global
|
||||
r4 = requests.post(
|
||||
SCODOC_URL + "/ScoDoc/api",
|
||||
auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||
)
|
|
@ -9,14 +9,15 @@
|
|||
|
||||
import datetime
|
||||
from flask import url_for, g
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models import FormSemestre, Identite
|
||||
from app.scodoc import sco_bulletins, sco_utils as scu
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
|
||||
|
||||
class BulletinBUT:
|
||||
|
@ -28,6 +29,7 @@ class BulletinBUT:
|
|||
def __init__(self, formsemestre: FormSemestre):
|
||||
""" """
|
||||
self.res = ResultatsSemestreBUT(formsemestre)
|
||||
self.prefs = sco_preferences.SemPreferences(formsemestre.id)
|
||||
|
||||
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
|
||||
"dict synthèse résultats dans l'UE pour les modules indiqués"
|
||||
|
@ -78,13 +80,13 @@ class BulletinBUT:
|
|||
"bonus": fmt_note(res.bonus_ues[ue.id][etud.id])
|
||||
if res.bonus_ues is not None and ue.id in res.bonus_ues
|
||||
else fmt_note(0.0),
|
||||
"malus": res.malus[ue.id][etud.id],
|
||||
"malus": fmt_note(res.malus[ue.id][etud.id]),
|
||||
"capitalise": None, # "AAAA-MM-JJ" TODO #sco92
|
||||
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
|
||||
"saes": self.etud_ue_mod_results(etud, ue, res.saes),
|
||||
}
|
||||
if ue.type != UE_SPORT:
|
||||
if sco_preferences.get_preference("bul_show_ue_rangs", res.formsemestre.id):
|
||||
if self.prefs["bul_show_ue_rangs"]:
|
||||
rangs, effectif = res.ue_rangs[ue.id]
|
||||
rang = rangs[etud.id]
|
||||
else:
|
||||
|
@ -109,9 +111,10 @@ class BulletinBUT:
|
|||
d["modules"] = self.etud_mods_results(etud, modimpls_spo)
|
||||
return d
|
||||
|
||||
def etud_mods_results(self, etud, modimpls) -> dict:
|
||||
def etud_mods_results(self, etud, modimpls, version="long") -> dict:
|
||||
"""dict synthèse résultats des modules indiqués,
|
||||
avec évaluations de chacun."""
|
||||
avec évaluations de chacun (sauf si version == "short")
|
||||
"""
|
||||
res = self.res
|
||||
d = {}
|
||||
# etud_idx = self.etud_index[etud.id]
|
||||
|
@ -152,14 +155,14 @@ class BulletinBUT:
|
|||
"evaluations": [
|
||||
self.etud_eval_results(etud, e)
|
||||
for e in modimpl.evaluations
|
||||
if e.visibulletin
|
||||
if (e.visibulletin or version == "long")
|
||||
and (
|
||||
modimpl_results.evaluations_etat[e.id].is_complete
|
||||
or sco_preferences.get_preference(
|
||||
"bul_show_all_evals", res.formsemestre.id
|
||||
)
|
||||
or self.prefs["bul_show_all_evals"]
|
||||
)
|
||||
],
|
||||
]
|
||||
if version != "short"
|
||||
else [],
|
||||
}
|
||||
return d
|
||||
|
||||
|
@ -168,14 +171,18 @@ class BulletinBUT:
|
|||
# eval_notes est une pd.Series avec toutes les notes des étudiants inscrits
|
||||
eval_notes = self.res.modimpls_results[e.moduleimpl_id].evals_notes[e.id]
|
||||
notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
|
||||
poids = {
|
||||
ue.acronyme: self.res.modimpls_evals_poids[e.moduleimpl_id][ue.id][e.id]
|
||||
for ue in self.res.ues
|
||||
}
|
||||
d = {
|
||||
"id": e.id,
|
||||
"description": e.description,
|
||||
"date": e.jour.isoformat() if e.jour else None,
|
||||
"heure_debut": e.heure_debut.strftime("%H:%M") if e.heure_debut else None,
|
||||
"heure_fin": e.heure_fin.strftime("%H:%M") if e.heure_debut else None,
|
||||
"coef": e.coefficient,
|
||||
"poids": {p.ue.acronyme: p.poids for p in e.ue_poids},
|
||||
"coef": fmt_note(e.coefficient),
|
||||
"poids": poids,
|
||||
"note": {
|
||||
"value": fmt_note(
|
||||
eval_notes[etud.id],
|
||||
|
@ -216,13 +223,23 @@ class BulletinBUT:
|
|||
else:
|
||||
return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"
|
||||
|
||||
def bulletin_etud(self, etud, formsemestre, force_publishing=False) -> dict:
|
||||
"""Le bulletin de l'étudiant dans ce semestre.
|
||||
Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
||||
def bulletin_etud(
|
||||
self,
|
||||
etud: Identite,
|
||||
formsemestre: FormSemestre,
|
||||
force_publishing=False,
|
||||
version="long",
|
||||
) -> dict:
|
||||
"""Le bulletin de l'étudiant dans ce semestre: dict pour la version JSON / HTML.
|
||||
- version:
|
||||
"long", "selectedevals": toutes les infos (notes des évaluations)
|
||||
"short" : ne descend pas plus bas que les modules.
|
||||
|
||||
- Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
||||
(bulletins non publiés).
|
||||
"""
|
||||
res = self.res
|
||||
etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||
etat_inscription = etud.inscription_etat(formsemestre.id)
|
||||
nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
|
||||
published = (not formsemestre.bul_hide_xml) or force_publishing
|
||||
d = {
|
||||
|
@ -239,7 +256,9 @@ class BulletinBUT:
|
|||
},
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etat_inscription": etat_inscription,
|
||||
"options": sco_preferences.bulletin_option_affichage(formsemestre.id),
|
||||
"options": sco_preferences.bulletin_option_affichage(
|
||||
formsemestre.id, self.prefs
|
||||
),
|
||||
}
|
||||
if not published:
|
||||
return d
|
||||
|
@ -278,8 +297,10 @@ class BulletinBUT:
|
|||
)
|
||||
d.update(
|
||||
{
|
||||
"ressources": self.etud_mods_results(etud, res.ressources),
|
||||
"saes": self.etud_mods_results(etud, res.saes),
|
||||
"ressources": self.etud_mods_results(
|
||||
etud, res.ressources, version=version
|
||||
),
|
||||
"saes": self.etud_mods_results(etud, res.saes, version=version),
|
||||
"ues": {
|
||||
ue.acronyme: self.etud_ue_results(etud, ue)
|
||||
for ue in res.ues
|
||||
|
@ -312,3 +333,56 @@ class BulletinBUT:
|
|||
)
|
||||
|
||||
return d
|
||||
|
||||
def bulletin_etud_complet(self, etud: Identite, version="long") -> dict:
|
||||
"""Bulletin dict complet avec toutes les infos pour les bulletins BUT pdf
|
||||
Résultat compatible avec celui de sco_bulletins.formsemestre_bulletinetud_dict
|
||||
"""
|
||||
d = self.bulletin_etud(
|
||||
etud, self.res.formsemestre, version=version, force_publishing=True
|
||||
)
|
||||
d["etudid"] = etud.id
|
||||
d["etud"] = d["etudiant"]
|
||||
d["etud"]["nomprenom"] = etud.nomprenom
|
||||
d.update(self.res.sem)
|
||||
etud_etat = self.res.get_etud_etat(etud.id)
|
||||
d["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
||||
etud_etat,
|
||||
self.prefs,
|
||||
decision_sem=d["semestre"].get("decision_sem"),
|
||||
)
|
||||
if etud_etat == scu.DEMISSION:
|
||||
d["demission"] = "(Démission)"
|
||||
elif etud_etat == DEF:
|
||||
d["demission"] = "(Défaillant)"
|
||||
else:
|
||||
d["demission"] = ""
|
||||
|
||||
# --- Absences
|
||||
d["nbabs"], d["nbabsjust"] = self.res.formsemestre.get_abs_count(etud.id)
|
||||
|
||||
# --- Decision Jury
|
||||
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
||||
etud.id,
|
||||
self.res.formsemestre.id,
|
||||
format="html",
|
||||
show_date_inscr=self.prefs["bul_show_date_inscr"],
|
||||
show_decisions=self.prefs["bul_show_decision"],
|
||||
show_uevalid=self.prefs["bul_show_uevalid"],
|
||||
show_mention=self.prefs["bul_show_mention"],
|
||||
)
|
||||
|
||||
d.update(infos)
|
||||
# --- Rangs
|
||||
d[
|
||||
"rang_nt"
|
||||
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||
d["rang_txt"] = "Rang " + d["rang_nt"]
|
||||
|
||||
# --- Appréciations
|
||||
d.update(
|
||||
sco_bulletins.get_appreciations_list(self.res.formsemestre.id, etud.id)
|
||||
)
|
||||
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
|
||||
|
||||
return d
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Génération bulletin BUT au format PDF standard
|
||||
"""
|
||||
from reportlab.platypus import Paragraph, Spacer
|
||||
|
||||
from app.scodoc.sco_pdf import blue, cm, mm
|
||||
|
||||
from app.scodoc import gen_tables
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
|
||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||
|
||||
|
||||
class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||
"""Génération du bulletin de BUT au format PDF.
|
||||
|
||||
self.infos est le dict issu de BulletinBUT.bulletin_etud_complet()
|
||||
"""
|
||||
|
||||
list_in_menu = False # spécialisation du BulletinGeneratorStandard, ne pas présenter à l'utilisateur
|
||||
scale_table_in_page = False
|
||||
|
||||
def bul_table(self, format="html"):
|
||||
"""Génère la table centrale du bulletin de notes
|
||||
Renvoie:
|
||||
- en HTML: une chaine
|
||||
- en PDF: une liste d'objets PLATYPUS (eg instance de Table).
|
||||
"""
|
||||
tables_infos = [
|
||||
# ---- TABLE SYNTHESE UES
|
||||
self.but_table_synthese_ues(),
|
||||
]
|
||||
if self.version != "short":
|
||||
tables_infos += [
|
||||
# ---- TABLE RESSOURCES
|
||||
self.but_table_ressources(),
|
||||
# ---- TABLE SAE
|
||||
self.but_table_saes(),
|
||||
]
|
||||
objects = []
|
||||
for i, (col_keys, rows, pdf_style, col_widths) in enumerate(tables_infos):
|
||||
table = gen_tables.GenTable(
|
||||
rows=rows,
|
||||
columns_ids=col_keys,
|
||||
pdf_table_style=pdf_style,
|
||||
pdf_col_widths=[col_widths[k] for k in col_keys],
|
||||
preferences=self.preferences,
|
||||
html_class="notes_bulletin",
|
||||
html_class_ignore_default=True,
|
||||
html_with_td_classes=True,
|
||||
)
|
||||
table_objects = table.gen(format=format)
|
||||
objects += table_objects
|
||||
# objects += [KeepInFrame(0, 0, table_objects, mode="shrink")]
|
||||
if i != 2:
|
||||
objects.append(Spacer(1, 6 * mm))
|
||||
|
||||
return objects
|
||||
|
||||
def but_table_synthese_ues(self, title_bg=(182, 235, 255)):
|
||||
"""La table de synthèse; pour chaque UE, liste des ressources et SAÉs avec leurs notes
|
||||
et leurs coefs.
|
||||
Renvoie: colkeys, P, pdf_style, colWidths
|
||||
- colkeys: nom des colonnes de la table (clés)
|
||||
- P : table (liste de dicts de chaines de caracteres)
|
||||
- pdf_style : commandes table Platypus
|
||||
- largeurs de colonnes pour PDF
|
||||
"""
|
||||
col_widths = {
|
||||
"titre": None,
|
||||
"moyenne": 2 * cm,
|
||||
"coef": 2 * cm,
|
||||
}
|
||||
title_bg = tuple(x / 255.0 for x in title_bg)
|
||||
# elems pour générer table avec gen_table (liste de dicts)
|
||||
rows = [
|
||||
# Ligne de titres
|
||||
{
|
||||
"titre": "Unités d'enseignement",
|
||||
"moyenne": "Note/20",
|
||||
"coef": "Coef.",
|
||||
"_coef_pdf": Paragraph("<para align=right><b><i>Coef.</i></b></para>"),
|
||||
"_css_row_class": "note_bold",
|
||||
"_pdf_row_markup": ["b"],
|
||||
"_pdf_style": [
|
||||
("BACKGROUND", (0, 0), (-1, 0), title_bg),
|
||||
("BOTTOMPADDING", (0, 0), (-1, 0), 7),
|
||||
(
|
||||
"LINEBELOW",
|
||||
(0, 0),
|
||||
(-1, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
blue,
|
||||
),
|
||||
],
|
||||
}
|
||||
]
|
||||
col_keys = ["titre", "coef", "moyenne"] # noms des colonnes à afficher
|
||||
for ue_acronym, ue in self.infos["ues"].items():
|
||||
# 1er ligne titre UE
|
||||
moy_ue = ue.get("moyenne")
|
||||
t = {
|
||||
"titre": f"{ue_acronym} - {ue['titre']}",
|
||||
"moyenne": moy_ue.get("value", "-") if moy_ue is not None else "-",
|
||||
"_css_row_class": "note_bold",
|
||||
"_pdf_row_markup": ["b"],
|
||||
"_pdf_style": [
|
||||
(
|
||||
"LINEABOVE",
|
||||
(0, 0),
|
||||
(-1, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
self.PDF_LINECOLOR,
|
||||
),
|
||||
("BACKGROUND", (0, 0), (-1, 0), title_bg),
|
||||
("BOTTOMPADDING", (0, 0), (-1, 0), 7),
|
||||
],
|
||||
}
|
||||
rows.append(t)
|
||||
# 2eme ligne titre UE (bonus/malus/ects)
|
||||
ects_txt = f'ECTS: {ue["ECTS"]["acquis"]:.3g} / {ue["ECTS"]["total"]:.3g}'
|
||||
t = {
|
||||
"titre": f"""Bonus: {ue['bonus']} - Malus: {
|
||||
ue["malus"]}""",
|
||||
"coef": ects_txt,
|
||||
"_coef_pdf": Paragraph(f"""<para align=right>{ects_txt}</para>"""),
|
||||
"_coef_colspan": 2,
|
||||
# "_css_row_class": "",
|
||||
# "_pdf_row_markup": [""],
|
||||
"_pdf_style": [
|
||||
("BACKGROUND", (0, 0), (-1, 0), title_bg),
|
||||
(
|
||||
"LINEBELOW",
|
||||
(0, 0),
|
||||
(-1, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
self.PDF_LINECOLOR,
|
||||
),
|
||||
# cadre autour du bonus/malus
|
||||
(
|
||||
"BOX",
|
||||
(0, 0),
|
||||
(0, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
(0.7, 0.7, 0.7), # gris clair
|
||||
),
|
||||
],
|
||||
}
|
||||
rows.append(t)
|
||||
# Liste chaque ressource puis SAE
|
||||
for mod_type in ("ressources", "saes"):
|
||||
for mod_code, mod in ue[mod_type].items():
|
||||
t = {
|
||||
"titre": f"{mod_code} {self.infos[mod_type][mod_code]['titre']}",
|
||||
"moyenne": mod["moyenne"],
|
||||
"coef": mod["coef"],
|
||||
"_coef_pdf": Paragraph(
|
||||
f"<para align=right><i>{mod['coef']}</i></para>"
|
||||
),
|
||||
"_pdf_style": [
|
||||
(
|
||||
"LINEBELOW",
|
||||
(0, 0),
|
||||
(-1, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
(0.7, 0.7, 0.7), # gris clair
|
||||
)
|
||||
],
|
||||
}
|
||||
rows.append(t)
|
||||
# Global pdf style commands:
|
||||
pdf_style = [
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
|
||||
]
|
||||
return col_keys, rows, pdf_style, col_widths
|
||||
|
||||
def but_table_ressources(self):
|
||||
"""La table de synthèse; pour chaque ressources, note et liste d'évaluations
|
||||
Renvoie: colkeys, P, pdf_style, colWidths
|
||||
"""
|
||||
return self.bul_table_modules(
|
||||
mod_type="ressources", title="Ressources", title_bg=(248, 200, 68)
|
||||
)
|
||||
|
||||
def but_table_saes(self):
|
||||
"table des SAEs"
|
||||
return self.bul_table_modules(
|
||||
mod_type="saes",
|
||||
title="Situations d'apprentissage et d'évaluation",
|
||||
title_bg=(198, 255, 171),
|
||||
)
|
||||
|
||||
def bul_table_modules(self, mod_type=None, title="", title_bg=(248, 200, 68)):
|
||||
"""Table ressources ou SAEs
|
||||
- colkeys: nom des colonnes de la table (clés)
|
||||
- P : table (liste de dicts de chaines de caracteres)
|
||||
- pdf_style : commandes table Platypus
|
||||
- largeurs de colonnes pour PDF
|
||||
"""
|
||||
poids_fontsize = "8"
|
||||
# UE à utiliser pour les poids (# colonne/UE)
|
||||
ue_acros = list(self.infos["ues"].keys()) # ['RT1.1', 'RT2.1', 'RT3.1']
|
||||
# Colonnes à afficher:
|
||||
col_keys = ["titre"] + ue_acros + ["coef", "moyenne"]
|
||||
# Largeurs des colonnes:
|
||||
col_widths = {
|
||||
"titre": None,
|
||||
# "poids": None,
|
||||
"moyenne": 2 * cm,
|
||||
"coef": 2 * cm,
|
||||
}
|
||||
for ue_acro in ue_acros:
|
||||
col_widths[ue_acro] = 12 * mm # largeur col. poids
|
||||
|
||||
title_bg = tuple(x / 255.0 for x in title_bg)
|
||||
# elems pour générer table avec gen_table (liste de dicts)
|
||||
# Ligne de titres
|
||||
t = {
|
||||
"titre": title,
|
||||
# "_titre_colspan": 1 + len(ue_acros),
|
||||
"moyenne": "Note/20",
|
||||
"coef": "Coef.",
|
||||
"_coef_pdf": Paragraph("<para align=right><i>Coef.</i></para>"),
|
||||
"_css_row_class": "note_bold",
|
||||
"_pdf_row_markup": ["b"],
|
||||
"_pdf_style": [
|
||||
("BACKGROUND", (0, 0), (-1, 0), title_bg),
|
||||
("BOTTOMPADDING", (0, 0), (-1, 0), 7),
|
||||
(
|
||||
"LINEBELOW",
|
||||
(0, 0),
|
||||
(-1, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
blue,
|
||||
),
|
||||
],
|
||||
}
|
||||
for ue_acro in ue_acros:
|
||||
t[ue_acro] = Paragraph(
|
||||
f"<para align=right fontSize={poids_fontsize}><i>{ue_acro}</i></para>"
|
||||
)
|
||||
rows = [t]
|
||||
for mod_code, mod in self.infos[mod_type].items():
|
||||
# 1er ligne titre module
|
||||
t = {
|
||||
"titre": f"{mod_code} - {mod['titre']}",
|
||||
"_titre_colspan": 2 + len(ue_acros),
|
||||
"_css_row_class": "note_bold",
|
||||
"_pdf_row_markup": ["b"],
|
||||
"_pdf_style": [
|
||||
(
|
||||
"LINEABOVE",
|
||||
(0, 0),
|
||||
(-1, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
self.PDF_LINECOLOR,
|
||||
),
|
||||
("BACKGROUND", (0, 0), (-1, 0), title_bg),
|
||||
("BOTTOMPADDING", (0, 0), (-1, 0), 7),
|
||||
],
|
||||
}
|
||||
rows.append(t)
|
||||
# Evaluations:
|
||||
for e in mod["evaluations"]:
|
||||
t = {
|
||||
"titre": f"{e['description']}",
|
||||
"moyenne": e["note"]["value"],
|
||||
"coef": e["coef"],
|
||||
"_coef_pdf": Paragraph(
|
||||
f"<para align=right fontSize={poids_fontsize}><i>{e['coef']}</i></para>"
|
||||
),
|
||||
"_pdf_style": [
|
||||
(
|
||||
"LINEBELOW",
|
||||
(0, 0),
|
||||
(-1, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
(0.7, 0.7, 0.7), # gris clair
|
||||
)
|
||||
],
|
||||
}
|
||||
col_idx = 1 # 1ere col. poids
|
||||
for ue_acro in ue_acros:
|
||||
t[ue_acro] = Paragraph(
|
||||
f"""<para align=right fontSize={poids_fontsize}><i>{e["poids"].get(ue_acro, "")}</i></para>"""
|
||||
)
|
||||
t["_pdf_style"].append(
|
||||
(
|
||||
"BOX",
|
||||
(col_idx, 0),
|
||||
(col_idx, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
(0.7, 0.7, 0.7), # gris clair
|
||||
),
|
||||
)
|
||||
col_idx += 1
|
||||
rows.append(t)
|
||||
# Global pdf style commands:
|
||||
pdf_style = [
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
|
||||
]
|
||||
return col_keys, rows, pdf_style, col_widths
|
|
@ -72,7 +72,7 @@ def bulletin_but_xml_compat(
|
|||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
results = bulletin_but.ResultatsSemestreBUT(formsemestre)
|
||||
nb_inscrits = results.get_inscriptions_counts()[scu.INSCRIT]
|
||||
# etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||
# etat_inscription = etud.inscription_etat(formsemestre.id)
|
||||
etat_inscription = results.formsemestre.etuds_inscriptions[etudid].etat
|
||||
if (not formsemestre.bul_hide_xml) or force_publishing:
|
||||
published = 1
|
||||
|
|
|
@ -21,6 +21,7 @@ from flask import g
|
|||
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_codes_parcours import ParcoursDUT, ParcoursDUTMono
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
|
@ -53,7 +54,7 @@ class BonusSport:
|
|||
etud_moy_gen et etud_moy_ue ne sont PAS modifiés (mais utilisés par certains bonus non additifs).
|
||||
"""
|
||||
|
||||
# En classique, active un bonus sur les UEs: (dans ce cas bonus_moy_gen reste None)
|
||||
# En classique, active un bonus sur les UEs: (dans ce cas bonus_moy_gen est ajusté pour le prendre en compte)
|
||||
classic_use_bonus_ues = False
|
||||
|
||||
# Attributs virtuels:
|
||||
|
@ -198,23 +199,29 @@ class BonusSportAdditif(BonusSport):
|
|||
à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||
"""
|
||||
|
||||
seuil_moy_gen = 10.0 # seuls les points au dessus du seuil sont comptés
|
||||
seuil_moy_gen = 10.0 # seuls les bonus au dessus du seuil sont pris en compte
|
||||
seuil_comptage = (
|
||||
None # les points au dessus du seuil sont comptés (defaut: seuil_moy_gen)
|
||||
)
|
||||
proportion_point = 0.05 # multiplie les points au dessus du seuil
|
||||
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
"""calcul du bonus
|
||||
sem_modimpl_moys_inscrits: les notes de sport
|
||||
En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
|
||||
modimpl_coefs_etuds_no_nan:
|
||||
En classic: ndarray (nb_etuds, nb_mod_sport)
|
||||
modimpl_coefs_etuds_no_nan: même shape, les coefs.
|
||||
"""
|
||||
if 0 in sem_modimpl_moys_inscrits.shape:
|
||||
# pas d'étudiants ou pas d'UE ou pas de module...
|
||||
return
|
||||
seuil_comptage = (
|
||||
self.seuil_moy_gen if self.seuil_comptage is None else self.seuil_comptage
|
||||
)
|
||||
bonus_moy_arr = np.sum(
|
||||
np.where(
|
||||
sem_modimpl_moys_inscrits > self.seuil_moy_gen,
|
||||
(sem_modimpl_moys_inscrits - self.seuil_moy_gen)
|
||||
* self.proportion_point,
|
||||
(sem_modimpl_moys_inscrits - seuil_comptage) * self.proportion_point,
|
||||
0.0,
|
||||
),
|
||||
axis=1,
|
||||
|
@ -227,13 +234,27 @@ class BonusSportAdditif(BonusSport):
|
|||
else: # necessaire pour éviter bonus négatifs !
|
||||
bonus_moy_arr = np.clip(bonus_moy_arr, 0.0, 20.0, out=bonus_moy_arr)
|
||||
|
||||
self.bonus_additif(bonus_moy_arr)
|
||||
|
||||
def bonus_additif(self, bonus_moy_arr: np.array):
|
||||
"Set bonus_ues et bonus_moy_gen"
|
||||
# en APC, bonus_moy_arr est (nb_etuds, nb_ues_non_bonus)
|
||||
if self.formsemestre.formation.is_apc() or self.classic_use_bonus_ues:
|
||||
if self.formsemestre.formation.is_apc():
|
||||
# Bonus sur les UE et None sur moyenne générale
|
||||
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
||||
self.bonus_ues = pd.DataFrame(
|
||||
bonus_moy_arr, index=self.etuds_idx, columns=ues_idx, dtype=float
|
||||
)
|
||||
elif self.classic_use_bonus_ues:
|
||||
# Formations classiques apppliquant le bonus sur les UEs
|
||||
# ici bonus_moy_arr = ndarray 1d nb_etuds
|
||||
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
||||
self.bonus_ues = pd.DataFrame(
|
||||
np.stack([bonus_moy_arr] * len(ues_idx)).T,
|
||||
index=self.etuds_idx,
|
||||
columns=ues_idx,
|
||||
dtype=float,
|
||||
)
|
||||
else:
|
||||
# Bonus sur la moyenne générale seulement
|
||||
self.bonus_moy_gen = pd.Series(
|
||||
|
@ -284,6 +305,7 @@ class BonusSportMultiplicatif(BonusSport):
|
|||
|
||||
class BonusDirect(BonusSportAdditif):
|
||||
"""Bonus direct: les points sont directement ajoutés à la moyenne générale.
|
||||
|
||||
Les coefficients sont ignorés: tous les points de bonus sont sommés.
|
||||
(rappel: la note est ramenée sur 20 avant application).
|
||||
"""
|
||||
|
@ -294,47 +316,108 @@ class BonusDirect(BonusSportAdditif):
|
|||
proportion_point = 1.0
|
||||
|
||||
|
||||
class BonusAnnecy(BonusSport):
|
||||
"""Calcul bonus modules optionnels (sport), règle IUT d'Annecy.
|
||||
Il peut y avoir plusieurs modules de bonus.
|
||||
Prend pour chaque étudiant la meilleure de ses notes bonus et
|
||||
ajoute à chaque UE :
|
||||
0.05 point si >=10,
|
||||
0.1 point si >=12,
|
||||
0.15 point si >=14,
|
||||
0.2 point si >=16,
|
||||
0.25 point si >=18.
|
||||
class BonusAisneStQuentin(BonusSportAdditif):
|
||||
"""Calcul bonus modules optionels (sport, culture), règle IUT Aisne St Quentin
|
||||
|
||||
<p>Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||
de l'Université de St Quentin non rattachés à une unité d'enseignement.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Si la note est >= 10 et < 12.1, bonus de 0.1 point</li>
|
||||
<li>Si la note est >= 12.1 et < 14.1, bonus de 0.2 point</li>
|
||||
<li>Si la note est >= 14.1 et < 16.1, bonus de 0.3 point</li>
|
||||
<li>Si la note est >= 16.1 et < 18.1, bonus de 0.4 point</li>
|
||||
<li>Si la note est >= 18.1, bonus de 0.5 point</li>
|
||||
</ul>
|
||||
<p>
|
||||
Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
|
||||
l'étudiant (en BUT, s'ajoute à la moyenne de chaque UE).
|
||||
</p>
|
||||
"""
|
||||
|
||||
name = "bonus_iut_annecy"
|
||||
displayed_name = "IUT d'Annecy"
|
||||
name = "bonus_iutstq"
|
||||
displayed_name = "IUT de Saint-Quentin"
|
||||
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
"""calcul du bonus"""
|
||||
# if math.prod(sem_modimpl_moys_inscrits.shape) == 0:
|
||||
# return # no etuds or no mod sport
|
||||
# Prend la note de chaque modimpl, sans considération d'UE
|
||||
if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
|
||||
sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
|
||||
# ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
|
||||
note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
|
||||
bonus = np.zeros(note_bonus_max.shape)
|
||||
bonus[note_bonus_max >= 18.0] = 0.25
|
||||
bonus[note_bonus_max >= 16.0] = 0.20
|
||||
bonus[note_bonus_max >= 14.0] = 0.15
|
||||
bonus[note_bonus_max >= 12.0] = 0.10
|
||||
bonus[note_bonus_max >= 10.0] = 0.05
|
||||
if 0 in sem_modimpl_moys_inscrits.shape:
|
||||
# pas d'étudiants ou pas d'UE ou pas de module...
|
||||
return
|
||||
# Calcule moyenne pondérée des notes de sport:
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
bonus_moy_arr = np.sum(
|
||||
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||
np.nan_to_num(bonus_moy_arr, nan=0.0, copy=False)
|
||||
|
||||
# Bonus moyenne générale et sur les UE
|
||||
self.bonus_moy_gen = pd.Series(bonus, index=self.etuds_idx, dtype=float)
|
||||
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
||||
nb_ues_no_bonus = len(ues_idx)
|
||||
self.bonus_ues = pd.DataFrame(
|
||||
np.stack([bonus] * nb_ues_no_bonus, axis=1),
|
||||
columns=ues_idx,
|
||||
index=self.etuds_idx,
|
||||
dtype=float,
|
||||
)
|
||||
bonus_moy_arr[bonus_moy_arr < 10.0] = 0.0
|
||||
bonus_moy_arr[bonus_moy_arr >= 18.1] = 0.5
|
||||
bonus_moy_arr[bonus_moy_arr >= 16.1] = 0.4
|
||||
bonus_moy_arr[bonus_moy_arr >= 14.1] = 0.3
|
||||
bonus_moy_arr[bonus_moy_arr >= 12.1] = 0.2
|
||||
bonus_moy_arr[bonus_moy_arr >= 10] = 0.1
|
||||
|
||||
self.bonus_additif(bonus_moy_arr)
|
||||
|
||||
|
||||
class BonusAmiens(BonusSportAdditif):
|
||||
"""Bonus IUT Amiens pour les modules optionnels (sport, culture, ...).
|
||||
|
||||
Toute note non nulle, peu importe sa valeur, entraine un bonus de 0,1 point
|
||||
sur toutes les moyennes d'UE.
|
||||
"""
|
||||
|
||||
name = "bonus_amiens"
|
||||
displayed_name = "IUT d'Amiens"
|
||||
seuil_moy_gen = 0.0 # tous les points sont comptés
|
||||
proportion_point = 1e10
|
||||
bonus_max = 0.1
|
||||
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
||||
|
||||
|
||||
# Finalement ils n'en veulent pas.
|
||||
# class BonusAnnecy(BonusSport):
|
||||
# """Calcul bonus modules optionnels (sport), règle IUT d'Annecy.
|
||||
|
||||
# Il peut y avoir plusieurs modules de bonus.
|
||||
# Prend pour chaque étudiant la meilleure de ses notes bonus et
|
||||
# ajoute à chaque UE :<br>
|
||||
# 0.05 point si >=10,<br>
|
||||
# 0.1 point si >=12,<br>
|
||||
# 0.15 point si >=14,<br>
|
||||
# 0.2 point si >=16,<br>
|
||||
# 0.25 point si >=18.
|
||||
# """
|
||||
|
||||
# name = "bonus_iut_annecy"
|
||||
# displayed_name = "IUT d'Annecy"
|
||||
|
||||
# def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
# """calcul du bonus"""
|
||||
# # if math.prod(sem_modimpl_moys_inscrits.shape) == 0:
|
||||
# # return # no etuds or no mod sport
|
||||
# # Prend la note de chaque modimpl, sans considération d'UE
|
||||
# if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
|
||||
# sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
|
||||
# # ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
|
||||
# note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
|
||||
# bonus = np.zeros(note_bonus_max.shape)
|
||||
# bonus[note_bonus_max >= 10.0] = 0.05
|
||||
# bonus[note_bonus_max >= 12.0] = 0.10
|
||||
# bonus[note_bonus_max >= 14.0] = 0.15
|
||||
# bonus[note_bonus_max >= 16.0] = 0.20
|
||||
# bonus[note_bonus_max >= 18.0] = 0.25
|
||||
|
||||
# # Bonus moyenne générale et sur les UE
|
||||
# self.bonus_moy_gen = pd.Series(bonus, index=self.etuds_idx, dtype=float)
|
||||
# ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
||||
# nb_ues_no_bonus = len(ues_idx)
|
||||
# self.bonus_ues = pd.DataFrame(
|
||||
# np.stack([bonus] * nb_ues_no_bonus, axis=1),
|
||||
# columns=ues_idx,
|
||||
# index=self.etuds_idx,
|
||||
# dtype=float,
|
||||
# )
|
||||
|
||||
|
||||
class BonusBethune(BonusSportMultiplicatif):
|
||||
|
@ -373,26 +456,150 @@ class BonusBezier(BonusSportAdditif):
|
|||
|
||||
|
||||
class BonusBordeaux1(BonusSportMultiplicatif):
|
||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT Bordeaux 1, sur moyenne générale
|
||||
et UE.
|
||||
|
||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT Bordeaux 1,
|
||||
sur moyenne générale et UEs.
|
||||
<p>
|
||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||
de l'Université Bordeaux 1 (sport, théâtre) non rattachés à une unité d'enseignement.
|
||||
|
||||
</p><p>
|
||||
Chaque point au-dessus de 10 sur 20 obtenus dans cet enseignement correspond à un %
|
||||
qui augmente la moyenne de chaque UE et la moyenne générale.
|
||||
Formule : le % = points>moyenne / 2
|
||||
qui augmente la moyenne de chaque UE et la moyenne générale.<br>
|
||||
Formule : pourcentage = (points au dessus de 10) / 2
|
||||
</p><p>
|
||||
Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale.
|
||||
|
||||
</p>
|
||||
"""
|
||||
|
||||
name = "bonus_iutBordeaux1"
|
||||
displayed_name = "IUT de Bordeaux 1"
|
||||
displayed_name = "IUT de Bordeaux"
|
||||
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
||||
seuil_moy_gen = 10.0
|
||||
amplitude = 0.005
|
||||
|
||||
|
||||
# Exactement le même que Bordeaux:
|
||||
class BonusBrest(BonusSportMultiplicatif):
|
||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT de Brest,
|
||||
sur moyenne générale et UEs.
|
||||
<p>
|
||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||
de l'Université (sport, théâtre) non rattachés à une unité d'enseignement.
|
||||
</p><p>
|
||||
Chaque point au-dessus de 10 sur 20 obtenus dans cet enseignement correspond à un %
|
||||
qui augmente la moyenne de chaque UE et la moyenne générale.<br>
|
||||
Formule : pourcentage = (points au dessus de 10) / 2
|
||||
</p><p>
|
||||
Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale.
|
||||
</p>
|
||||
"""
|
||||
|
||||
name = "bonus_iut_brest"
|
||||
displayed_name = "IUT de Brest"
|
||||
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
||||
seuil_moy_gen = 10.0
|
||||
amplitude = 0.005
|
||||
|
||||
|
||||
class BonusCachan1(BonusSportAdditif):
|
||||
"""Calcul bonus optionnels (sport, culture), règle IUT de Cachan 1.
|
||||
|
||||
<ul>
|
||||
<li> DUT/LP : la meilleure note d'option, si elle est supérieure à 10,
|
||||
bonifie les moyennes d'UE (<b>sauf l'UE41 dont le code est UE41_E</b>) à raison
|
||||
de <em>bonus = (option - 10)/10</em>.
|
||||
</li>
|
||||
|
||||
<li> BUT : la meilleure note d'option, si elle est supérieure à 10, bonifie
|
||||
les moyennes d'UE à raison de <em>bonus = (option - 10)*5%</em>.</li>
|
||||
</ul>
|
||||
"""
|
||||
|
||||
name = "bonus_cachan1"
|
||||
displayed_name = "IUT de Cachan 1"
|
||||
seuil_moy_gen = 10.0 # tous les points sont comptés
|
||||
proportion_point = 0.05
|
||||
classic_use_bonus_ues = True
|
||||
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
"""calcul du bonus, avec réglage différent suivant le type de formation"""
|
||||
# Prend la note de chaque modimpl, sans considération d'UE
|
||||
if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
|
||||
sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
|
||||
# ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
|
||||
note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
|
||||
ues = self.formsemestre.query_ues(with_sport=False).all()
|
||||
ues_idx = [ue.id for ue in ues]
|
||||
|
||||
if self.formsemestre.formation.is_apc(): # --- BUT
|
||||
bonus_moy_arr = np.where(
|
||||
note_bonus_max > self.seuil_moy_gen,
|
||||
(note_bonus_max - self.seuil_moy_gen) * self.proportion_point,
|
||||
0.0,
|
||||
)
|
||||
self.bonus_ues = pd.DataFrame(
|
||||
np.stack([bonus_moy_arr] * len(ues)).T,
|
||||
index=self.etuds_idx,
|
||||
columns=ues_idx,
|
||||
dtype=float,
|
||||
)
|
||||
else: # --- DUT
|
||||
# pareil mais proportion différente et exclusion d'une UE
|
||||
proportion_point = 0.1
|
||||
bonus_moy_arr = np.where(
|
||||
note_bonus_max > self.seuil_moy_gen,
|
||||
(note_bonus_max - self.seuil_moy_gen) * proportion_point,
|
||||
0.0,
|
||||
)
|
||||
self.bonus_ues = pd.DataFrame(
|
||||
np.stack([bonus_moy_arr] * len(ues)).T,
|
||||
index=self.etuds_idx,
|
||||
columns=ues_idx,
|
||||
dtype=float,
|
||||
)
|
||||
# Pas de bonus sur la ou les ue de code "UE41_E"
|
||||
ue_exclues = [ue for ue in ues if ue.ue_code == "UE41_E"]
|
||||
for ue in ue_exclues:
|
||||
self.bonus_ues[ue.id] = 0.0
|
||||
|
||||
|
||||
class BonusCalais(BonusSportAdditif):
|
||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT LCO.
|
||||
|
||||
Les étudiants de l'IUT LCO peuvent suivre des enseignements optionnels non
|
||||
rattachés à une unité d'enseignement. Les points au-dessus de 10
|
||||
sur 20 obtenus dans chacune des matières optionnelles sont cumulés
|
||||
dans la limite de 10 points. 6% de ces points cumulés s'ajoutent :
|
||||
<ul>
|
||||
<li><b>en DUT</b> à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||
<li><b>en BUT et LP</b> à la moyenne des UE dont l'acronyme fini par <b>BS</b> (ex : UE2.1BS, UE32BS)
|
||||
</ul>
|
||||
"""
|
||||
|
||||
name = "bonus_calais"
|
||||
displayed_name = "IUT du Littoral"
|
||||
bonus_max = 0.6
|
||||
seuil_moy_gen = 10.0 # au dessus de 10
|
||||
proportion_point = 0.06 # 6%
|
||||
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
parcours = self.formsemestre.formation.get_parcours()
|
||||
# Variantes de DUT ?
|
||||
if (
|
||||
isinstance(parcours, ParcoursDUT)
|
||||
or parcours.TYPE_PARCOURS == ParcoursDUTMono.TYPE_PARCOURS
|
||||
): # DUT
|
||||
super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
|
||||
else:
|
||||
self.classic_use_bonus_ues = True # pour les LP
|
||||
super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
|
||||
ues = self.formsemestre.query_ues(with_sport=False).all()
|
||||
ues_sans_bs = [
|
||||
ue for ue in ues if ue.acronyme[-2:].upper() != "BS"
|
||||
] # les 2 derniers cars forcés en majus
|
||||
for ue in ues_sans_bs:
|
||||
self.bonus_ues[ue.id] = 0.0
|
||||
|
||||
|
||||
class BonusColmar(BonusSportAdditif):
|
||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT Colmar.
|
||||
|
||||
|
@ -417,19 +624,21 @@ class BonusColmar(BonusSportAdditif):
|
|||
class BonusGrenobleIUT1(BonusSportMultiplicatif):
|
||||
"""Bonus IUT1 de Grenoble
|
||||
|
||||
<p>
|
||||
À compter de sept. 2021:
|
||||
La note de sport est sur 20, et on calcule une bonification (en %)
|
||||
qui va s'appliquer à la moyenne de chaque UE du semestre en appliquant
|
||||
la formule : bonification (en %) = (note-10)*0,5.
|
||||
|
||||
Bonification qui ne s'applique que si la note est >10.
|
||||
|
||||
(Une note de 10 donne donc 0% de bonif ; note de 20 : 5% de bonif)
|
||||
|
||||
</p><p>
|
||||
<em>La bonification ne s'applique que si la note est supérieure à 10.</em>
|
||||
</p><p>
|
||||
(Une note de 10 donne donc 0% de bonif, et une note de 20 : 5% de bonif)
|
||||
</p><p>
|
||||
Avant sept. 2021, la note de sport allait de 0 à 5 points (sur 20).
|
||||
Chaque point correspondait à 0.25% d'augmentation de la moyenne
|
||||
générale.
|
||||
Par exemple : note de sport 2/5 : la moyenne générale était augmentée de 0.5%.
|
||||
</p>
|
||||
"""
|
||||
|
||||
name = "bonus_iut1grenoble_2017"
|
||||
|
@ -456,15 +665,18 @@ class BonusGrenobleIUT1(BonusSportMultiplicatif):
|
|||
class BonusLaRochelle(BonusSportAdditif):
|
||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle.
|
||||
|
||||
Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.
|
||||
Si la note de sport est comprise entre 10 et 20 : ajout de 1% de cette
|
||||
note sur la moyenne générale du semestre (ou sur les UE en BUT).
|
||||
<ul>
|
||||
<li>Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.</li>
|
||||
<li>Si la note de sport est comprise entre 10 et 20 : ajout de 1% de cette
|
||||
note sur la moyenne générale du semestre (ou sur les UE en BUT).</li>
|
||||
</ul>
|
||||
"""
|
||||
|
||||
name = "bonus_iutlr"
|
||||
displayed_name = "IUT de La Rochelle"
|
||||
seuil_moy_gen = 10.0 # tous les points sont comptés
|
||||
proportion_point = 0.01
|
||||
seuil_moy_gen = 10.0 # si bonus > 10,
|
||||
seuil_comptage = 0.0 # tous les points sont comptés
|
||||
proportion_point = 0.01 # 1%
|
||||
|
||||
|
||||
class BonusLeHavre(BonusSportMultiplicatif):
|
||||
|
@ -483,16 +695,17 @@ class BonusLeHavre(BonusSportMultiplicatif):
|
|||
class BonusLeMans(BonusSportAdditif):
|
||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT Le Mans.
|
||||
|
||||
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||
<p>Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||
optionnelles sont cumulés.
|
||||
</p>
|
||||
<ul>
|
||||
<li>En BUT: la moyenne de chacune des UE du semestre est augmentée de
|
||||
2% du cumul des points de bonus;</li>
|
||||
|
||||
|
||||
En BUT: la moyenne de chacune des UE du semestre est augmentée de
|
||||
2% du cumul des points de bonus,
|
||||
|
||||
En DUT/LP: la moyenne générale est augmentée de 5% du cumul des points bonus.
|
||||
|
||||
Dans tous les cas, le bonus est dans la limite de 0,5 point.
|
||||
<li>En DUT/LP: la moyenne générale est augmentée de 5% du cumul des points bonus.
|
||||
</li>
|
||||
</ul>
|
||||
<p>Dans tous les cas, le bonus est dans la limite de 0,5 point.</p>
|
||||
"""
|
||||
|
||||
name = "bonus_iutlemans"
|
||||
|
@ -516,12 +729,13 @@ class BonusLeMans(BonusSportAdditif):
|
|||
class BonusLille(BonusSportAdditif):
|
||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT Villeneuve d'Ascq
|
||||
|
||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||
<p>Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||
de l'Université Lille (sports, etc) non rattachés à une unité d'enseignement.
|
||||
|
||||
</p><p>
|
||||
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||
optionnelles sont cumulés et 4% (2% avant août 2010) de ces points cumulés
|
||||
s'ajoutent à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||
</p>
|
||||
"""
|
||||
|
||||
name = "bonus_lille"
|
||||
|
@ -573,17 +787,19 @@ class BonusMulhouse(BonusSportAdditif):
|
|||
class BonusNantes(BonusSportAdditif):
|
||||
"""IUT de Nantes (Septembre 2018)
|
||||
|
||||
Nous avons différents types de bonification
|
||||
<p>Nous avons différents types de bonification
|
||||
(sport, culture, engagement citoyen).
|
||||
|
||||
</p><p>
|
||||
Nous ajoutons aux moyennes une bonification de 0,2 pour chaque item
|
||||
la bonification totale ne doit pas excéder les 0,5 point.
|
||||
Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications.
|
||||
|
||||
Dans ScoDoc: on a déclarera une UE "sport&culture" dans laquelle on aura des modules
|
||||
pour chaque activité (Sport, Associations, ...)
|
||||
avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20, mais en fait ce sera la
|
||||
valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale)
|
||||
</p><p>
|
||||
Dans ScoDoc: on a déclarera une UE "sport&culture" dans laquelle on aura
|
||||
des modules pour chaque activité (Sport, Associations, ...)
|
||||
avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20,
|
||||
mais en fait ce sera la valeur de la bonification: entrer 0,1/20 signifiera
|
||||
un bonus de 0,1 point la moyenne générale).
|
||||
</p>
|
||||
"""
|
||||
|
||||
name = "bonus_nantes"
|
||||
|
@ -604,7 +820,29 @@ class BonusRoanne(BonusSportAdditif):
|
|||
displayed_name = "IUT de Roanne"
|
||||
seuil_moy_gen = 0.0
|
||||
bonus_max = 0.6 # plafonnement à 0.6 points
|
||||
apply_bonus_mg_to_ues = True # sur les UE, même en DUT et LP
|
||||
classic_use_bonus_ues = True # sur les UE, même en DUT et LP
|
||||
proportion_point = 1
|
||||
|
||||
|
||||
class BonusStBrieuc(BonusSportAdditif):
|
||||
"""IUT de Saint Brieuc
|
||||
|
||||
Ne s'applique qu'aux semestres pairs (S2, S4, S6), et bonifie les moyennes d'UE:
|
||||
<ul>
|
||||
<li>Bonus = (S - 10)/20</li>
|
||||
</ul>
|
||||
<div class="warning">(XXX vérifier si S6 est éligible au bonus, et le S2 du DUT XXX)</div>
|
||||
"""
|
||||
|
||||
name = "bonus_iut_stbrieuc"
|
||||
displayed_name = "IUT de Saint-Brieuc"
|
||||
proportion_point = 1 / 20.0
|
||||
classic_use_bonus_ues = True
|
||||
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
"""calcul du bonus"""
|
||||
if self.formsemestre.semestre_id % 2 == 0:
|
||||
super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
|
||||
|
||||
|
||||
class BonusStDenis(BonusSportAdditif):
|
||||
|
@ -624,16 +862,62 @@ class BonusStDenis(BonusSportAdditif):
|
|||
bonus_max = 0.5
|
||||
|
||||
|
||||
class BonusTarbes(BonusSportAdditif):
|
||||
"""Calcul bonus optionnels (sport, culture), règle IUT de Tarbes.
|
||||
|
||||
<ul>
|
||||
<li>Les étudiants opeuvent suivre un ou plusieurs activités optionnelles notées.
|
||||
La meilleure des notes obtenue est prise en compte, si elle est supérieure à 10/20.
|
||||
</li>
|
||||
<li>Le trentième des points au dessus de 10 est ajouté à la moyenne des UE.
|
||||
</li>
|
||||
<li> Exemple: un étudiant ayant 16/20 bénéficiera d'un bonus de (16-10)/30 = 0,2 points
|
||||
sur chaque UE.
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
|
||||
name = "bonus_tarbes"
|
||||
displayed_name = "IUT de Tazrbes"
|
||||
seuil_moy_gen = 10.0
|
||||
proportion_point = 1 / 30.0
|
||||
classic_use_bonus_ues = True
|
||||
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
"""calcul du bonus"""
|
||||
# Prend la note de chaque modimpl, sans considération d'UE
|
||||
if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
|
||||
sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
|
||||
# ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
|
||||
note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
|
||||
ues = self.formsemestre.query_ues(with_sport=False).all()
|
||||
ues_idx = [ue.id for ue in ues]
|
||||
|
||||
if self.formsemestre.formation.is_apc(): # --- BUT
|
||||
bonus_moy_arr = np.where(
|
||||
note_bonus_max > self.seuil_moy_gen,
|
||||
(note_bonus_max - self.seuil_moy_gen) * self.proportion_point,
|
||||
0.0,
|
||||
)
|
||||
self.bonus_ues = pd.DataFrame(
|
||||
np.stack([bonus_moy_arr] * len(ues)).T,
|
||||
index=self.etuds_idx,
|
||||
columns=ues_idx,
|
||||
dtype=float,
|
||||
)
|
||||
|
||||
|
||||
class BonusTours(BonusDirect):
|
||||
"""Calcul bonus sport & culture IUT Tours.
|
||||
|
||||
Les notes des UE bonus (ramenées sur 20) sont sommées
|
||||
<p>Les notes des UE bonus (ramenées sur 20) sont sommées
|
||||
et 1/40 (2,5%) est ajouté aux moyennes: soit à la moyenne générale,
|
||||
soit pour le BUT à chaque moyenne d'UE.
|
||||
|
||||
Attention: en GEII, facteur 1/40, ailleurs facteur 1.
|
||||
|
||||
</p><p>
|
||||
<em>Attention: en GEII, facteur 1/40, ailleurs facteur 1.</em>
|
||||
</p><p>
|
||||
Le bonus total est limité à 1 point.
|
||||
</p>
|
||||
"""
|
||||
|
||||
name = "bonus_tours"
|
||||
|
@ -658,11 +942,13 @@ class BonusVilleAvray(BonusSport):
|
|||
|
||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||
de l'Université Paris 10 (C2I) non rattachés à une unité d'enseignement.
|
||||
Si la note est >= 10 et < 12, bonus de 0.1 point
|
||||
Si la note est >= 12 et < 16, bonus de 0.2 point
|
||||
Si la note est >= 16, bonus de 0.3 point
|
||||
Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
|
||||
l'étudiant.
|
||||
<ul>
|
||||
<li>Si la note est >= 10 et < 12, bonus de 0.1 point</li>
|
||||
<li>Si la note est >= 12 et < 16, bonus de 0.2 point</li>
|
||||
<li>Si la note est >= 16, bonus de 0.3 point</li>
|
||||
</ul>
|
||||
<p>Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
|
||||
l'étudiant.</p>
|
||||
"""
|
||||
|
||||
name = "bonus_iutva"
|
||||
|
@ -670,21 +956,21 @@ class BonusVilleAvray(BonusSport):
|
|||
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
"""calcul du bonus"""
|
||||
if 0 in sem_modimpl_moys_inscrits.shape:
|
||||
# pas d'étudiants ou pas d'UE ou pas de module...
|
||||
return
|
||||
# Calcule moyenne pondérée des notes de sport:
|
||||
bonus_moy_arr = np.sum(
|
||||
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||
bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.1
|
||||
bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
bonus_moy_arr = np.sum(
|
||||
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||
np.nan_to_num(bonus_moy_arr, nan=0.0, copy=False)
|
||||
bonus_moy_arr[bonus_moy_arr < 10.0] = 0.0
|
||||
bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.3
|
||||
bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2
|
||||
bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.1
|
||||
|
||||
# Bonus moyenne générale, et 0 sur les UE
|
||||
self.bonus_moy_gen = pd.Series(bonus_moy_arr, index=self.etuds_idx, dtype=float)
|
||||
if self.bonus_max is not None:
|
||||
# Seuil: bonus (sur moy. gen.) limité à bonus_max points
|
||||
self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_max)
|
||||
|
||||
# Laisse bonus_ues à None, en APC le bonus moy. gen. sera réparti sur les UEs.
|
||||
self.bonus_additif(bonus_moy_arr)
|
||||
|
||||
|
||||
class BonusIUTV(BonusSportAdditif):
|
||||
|
@ -700,7 +986,7 @@ class BonusIUTV(BonusSportAdditif):
|
|||
|
||||
name = "bonus_iutv"
|
||||
displayed_name = "IUT de Villetaneuse"
|
||||
pass # oui, c'ets le bonus par défaut
|
||||
pass # oui, c'est le bonus par défaut
|
||||
|
||||
|
||||
def get_bonus_class_dict(start=BonusSport, d=None):
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Calcul des moyennes de matières
|
||||
"""
|
||||
|
||||
# C'est un recalcul (optionnel) effectué _après_ le calcul standard.
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from app.comp import moy_ue
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
def compute_mat_moys_classic(
|
||||
formsemestre: FormSemestre,
|
||||
sem_matrix: np.array,
|
||||
ues: list,
|
||||
modimpl_inscr_df: pd.DataFrame,
|
||||
modimpl_coefs: np.array,
|
||||
) -> dict:
|
||||
"""Calcul des moyennes par matières.
|
||||
Result: dict, { matiere_id : Series, index etudid }
|
||||
"""
|
||||
modimpls_std = [
|
||||
m
|
||||
for m in formsemestre.modimpls_sorted
|
||||
if (m.module.module_type == ModuleType.STANDARD)
|
||||
and (m.module.ue.type != UE_SPORT)
|
||||
]
|
||||
matiere_ids = {m.module.matiere.id for m in modimpls_std}
|
||||
matiere_moy = {} # { matiere_id : moy pd.Series, index etudid }
|
||||
for matiere_id in matiere_ids:
|
||||
modimpl_mask = np.array(
|
||||
[m.module.matiere.id == matiere_id for m in formsemestre.modimpls_sorted]
|
||||
)
|
||||
etud_moy_mat = moy_ue.compute_mat_moys_classic(
|
||||
sem_matrix=sem_matrix,
|
||||
modimpl_inscr_df=modimpl_inscr_df,
|
||||
modimpl_coefs=modimpl_coefs,
|
||||
modimpl_mask=modimpl_mask,
|
||||
)
|
||||
matiere_moy[matiere_id] = etud_moy_mat
|
||||
return matiere_moy
|
|
@ -335,15 +335,17 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||
notes_rat / (eval_rat.note_max / 20.0),
|
||||
np.nan,
|
||||
)
|
||||
# "Étend" le rattrapage sur les UE: la note de rattrapage est la même
|
||||
# pour toutes les UE mais ne remplace que là où elle est supérieure
|
||||
notes_rat_ues = np.stack([notes_rat] * nb_ues, axis=1)
|
||||
# prend le max
|
||||
etuds_use_rattrapage = notes_rat > etuds_moy_module
|
||||
etuds_use_rattrapage = notes_rat_ues > etuds_moy_module
|
||||
etuds_moy_module = np.where(
|
||||
etuds_use_rattrapage[:, np.newaxis],
|
||||
np.tile(notes_rat[:, np.newaxis], nb_ues),
|
||||
etuds_moy_module,
|
||||
etuds_use_rattrapage, notes_rat_ues, etuds_moy_module
|
||||
)
|
||||
# Serie indiquant que l'étudiant utilise une note de rattarage sur l'une des UE:
|
||||
self.etuds_use_rattrapage = pd.Series(
|
||||
etuds_use_rattrapage, index=self.evals_notes.index
|
||||
etuds_use_rattrapage.any(axis=1), index=self.evals_notes.index
|
||||
)
|
||||
self.etuds_moy_module = pd.DataFrame(
|
||||
etuds_moy_module,
|
||||
|
@ -359,6 +361,10 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
|||
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
||||
remplies: 1 si le coef de ce module dans l'UE est non nul, zéro sinon
|
||||
(sauf pour module bonus, defaut à 1)
|
||||
|
||||
Si le module n'est pas une ressource ou une SAE, ne charge pas de poids
|
||||
et renvoie toujours les poids par défaut.
|
||||
|
||||
Résultat: (evals_poids, liste de UEs du semestre sauf le sport)
|
||||
"""
|
||||
modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
|
@ -367,13 +373,17 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
|||
ue_ids = [ue.id for ue in ues]
|
||||
evaluation_ids = [evaluation.id for evaluation in evaluations]
|
||||
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
||||
for ue_poids in EvaluationUEPoids.query.join(
|
||||
EvaluationUEPoids.evaluation
|
||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||
try:
|
||||
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
||||
except KeyError as exc:
|
||||
pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
|
||||
if (
|
||||
modimpl.module.module_type == ModuleType.RESSOURCE
|
||||
or modimpl.module.module_type == ModuleType.SAE
|
||||
):
|
||||
for ue_poids in EvaluationUEPoids.query.join(
|
||||
EvaluationUEPoids.evaluation
|
||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||
try:
|
||||
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
||||
except KeyError as exc:
|
||||
pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
|
||||
|
||||
# Initialise poids non enregistrés:
|
||||
default_poids = (
|
||||
|
|
|
@ -30,8 +30,10 @@
|
|||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from flask import flash
|
||||
|
||||
def compute_sem_moys_apc(
|
||||
|
||||
def compute_sem_moys_apc_using_coefs(
|
||||
etud_moy_ue_df: pd.DataFrame, modimpl_coefs_df: pd.DataFrame
|
||||
) -> pd.Series:
|
||||
"""Calcule les moyennes générales indicatives de tous les étudiants
|
||||
|
@ -48,6 +50,28 @@ def compute_sem_moys_apc(
|
|||
return moy_gen
|
||||
|
||||
|
||||
def compute_sem_moys_apc_using_ects(
|
||||
etud_moy_ue_df: pd.DataFrame, ects: list, formation_id=None
|
||||
) -> pd.Series:
|
||||
"""Calcule les moyennes générales indicatives de tous les étudiants
|
||||
= moyenne des moyennes d'UE, pondérée par leurs ECTS.
|
||||
|
||||
etud_moy_ue_df: DataFrame, colonnes ue_id, lignes etudid
|
||||
ects: liste de floats ou None, 1 par UE
|
||||
|
||||
Result: panda Series, index etudid, valeur float (moyenne générale)
|
||||
"""
|
||||
try:
|
||||
moy_gen = (etud_moy_ue_df * ects).sum(axis=1) / sum(ects)
|
||||
except TypeError:
|
||||
if None in ects:
|
||||
flash("""Calcul moyenne générale impossible: ECTS des UE manquants !""")
|
||||
moy_gen = pd.Series(np.NaN, index=etud_moy_ue_df.index)
|
||||
else:
|
||||
raise
|
||||
return moy_gen
|
||||
|
||||
|
||||
def comp_ranks_series(notes: pd.Series) -> (pd.Series, pd.Series):
|
||||
"""Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur
|
||||
numérique) en tenant compte des ex-aequos.
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
"""Fonctions de calcul des moyennes d'UE (classiques ou BUT)
|
||||
"""
|
||||
from re import X
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
@ -198,6 +197,7 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
||||
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||
modimpls_results[modimpl.id] = mod_results
|
||||
modimpls_evals_poids[modimpl.id] = evals_poids
|
||||
modimpls_notes.append(etuds_moy_module)
|
||||
if len(modimpls_notes):
|
||||
cube = notes_sem_assemble_cube(modimpls_notes)
|
||||
|
@ -218,21 +218,25 @@ def compute_ue_moys_apc(
|
|||
ues: list,
|
||||
modimpl_inscr_df: pd.DataFrame,
|
||||
modimpl_coefs_df: pd.DataFrame,
|
||||
modimpl_mask: np.array,
|
||||
) -> pd.DataFrame:
|
||||
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||
NI non inscrit à (au moins un) module de cette UE
|
||||
NA pas de notes disponibles
|
||||
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
||||
ERR erreur dans une formule utilisateurs (pas gérées ici).
|
||||
|
||||
sem_cube: notes moyennes aux modules
|
||||
ndarray (etuds x modimpls x UEs)
|
||||
(floats avec des NaN)
|
||||
etuds : liste des étudiants (dim. 0 du cube)
|
||||
modimpls : liste des modules à considérer (dim. 1 du cube)
|
||||
modimpls : liste des module_impl (dim. 1 du cube)
|
||||
ues : liste des UE (dim. 2 du cube)
|
||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport
|
||||
modimpl_mask: liste de booléens, indiquants le module doit être pris ou pas.
|
||||
(utilisé pour éliminer les bonus, et pourra servir à cacluler
|
||||
sur des sous-ensembles de modules)
|
||||
|
||||
Résultat: DataFrame columns UE (sans bonus), rows etudid
|
||||
"""
|
||||
|
@ -249,7 +253,8 @@ def compute_ue_moys_apc(
|
|||
assert modimpl_coefs_df.shape[0] == nb_ues_no_bonus
|
||||
assert modimpl_coefs_df.shape[1] == nb_modules
|
||||
modimpl_inscr = modimpl_inscr_df.values
|
||||
modimpl_coefs = modimpl_coefs_df.values
|
||||
# Met à zéro tous les coefs des modules non sélectionnés dans le masque:
|
||||
modimpl_coefs = np.where(modimpl_mask, modimpl_coefs_df.values, 0.0)
|
||||
|
||||
# Duplique les inscriptions sur les UEs non bonus:
|
||||
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues_no_bonus, axis=2)
|
||||
|
@ -290,7 +295,8 @@ def compute_ue_moys_classic(
|
|||
modimpl_coefs: np.array,
|
||||
modimpl_mask: np.array,
|
||||
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
||||
"""Calcul de la moyenne d'UE en mode classique.
|
||||
"""Calcul de la moyenne d'UE et de la moy. générale en mode classique (DUT, LMD, ...).
|
||||
|
||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||
NI non inscrit à (au moins un) module de cette UE
|
||||
NA pas de notes disponibles
|
||||
|
@ -359,7 +365,7 @@ def compute_ue_moys_classic(
|
|||
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
||||
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
||||
)
|
||||
# nb_ue x nb_etuds x nb_mods : coefs prenant en compte NaN et inscriptions
|
||||
# nb_ue x nb_etuds x nb_mods : coefs prenant en compte NaN et inscriptions:
|
||||
coefs = (modimpl_coefs_etuds_no_nan_stacked * ue_modules).swapaxes(1, 2)
|
||||
if coefs.dtype == np.object: # arrive sur des tableaux vides
|
||||
coefs = coefs.astype(np.float)
|
||||
|
@ -404,6 +410,68 @@ def compute_ue_moys_classic(
|
|||
return etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||
|
||||
|
||||
def compute_mat_moys_classic(
|
||||
sem_matrix: np.array,
|
||||
modimpl_inscr_df: pd.DataFrame,
|
||||
modimpl_coefs: np.array,
|
||||
modimpl_mask: np.array,
|
||||
) -> pd.Series:
|
||||
"""Calcul de la moyenne sur un sous-enemble de modules en formation CLASSIQUE
|
||||
|
||||
La moyenne est un nombre (note/20 ou NaN.
|
||||
|
||||
Le masque modimpl_mask est un tableau de booléens (un par modimpl) qui
|
||||
permet de sélectionner un sous-ensemble de modules (ceux de la matière d'intérêt).
|
||||
|
||||
sem_matrix: notes moyennes aux modules (tous les étuds x tous les modimpls)
|
||||
ndarray (etuds x modimpls)
|
||||
(floats avec des NaN)
|
||||
etuds : listes des étudiants (dim. 0 de la matrice)
|
||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||
modimpl_coefs: vecteur des coefficients de modules
|
||||
modimpl_mask: masque des modimpls à prendre en compte
|
||||
|
||||
Résultat:
|
||||
- moyennes: pd.Series, index etudid
|
||||
"""
|
||||
if (not len(modimpl_mask)) or (
|
||||
sem_matrix.shape[0] == 0
|
||||
): # aucun module ou aucun étudiant
|
||||
# etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||
return pd.Series(
|
||||
[0.0] * len(modimpl_inscr_df.index), index=modimpl_inscr_df.index
|
||||
)
|
||||
# Restreint aux modules sélectionnés:
|
||||
sem_matrix = sem_matrix[:, modimpl_mask]
|
||||
modimpl_inscr = modimpl_inscr_df.values[:, modimpl_mask]
|
||||
modimpl_coefs = modimpl_coefs[modimpl_mask]
|
||||
|
||||
nb_etuds, nb_modules = sem_matrix.shape
|
||||
assert len(modimpl_coefs) == nb_modules
|
||||
|
||||
# Enlève les NaN du numérateur:
|
||||
sem_matrix_no_nan = np.nan_to_num(sem_matrix, nan=0.0)
|
||||
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||
# Annule les notes:
|
||||
sem_matrix_inscrits = np.where(modimpl_inscr, sem_matrix_no_nan, 0.0)
|
||||
# Annule les coefs des modules où l'étudiant n'est pas inscrit:
|
||||
modimpl_coefs_etuds = np.where(
|
||||
modimpl_inscr, np.stack([modimpl_coefs.T] * nb_etuds), 0.0
|
||||
)
|
||||
# Annule les coefs des modules NaN (nb_etuds x nb_mods)
|
||||
modimpl_coefs_etuds_no_nan = np.where(
|
||||
np.isnan(sem_matrix), 0.0, modimpl_coefs_etuds
|
||||
)
|
||||
if modimpl_coefs_etuds_no_nan.dtype == np.object: # arrive sur des tableaux vides
|
||||
modimpl_coefs_etuds_no_nan = modimpl_coefs_etuds_no_nan.astype(np.float)
|
||||
|
||||
etud_moy_mat = (modimpl_coefs_etuds_no_nan * sem_matrix_inscrits).sum(
|
||||
axis=1
|
||||
) / modimpl_coefs_etuds_no_nan.sum(axis=1)
|
||||
|
||||
return pd.Series(etud_moy_mat, index=modimpl_inscr_df.index)
|
||||
|
||||
|
||||
def compute_malus(
|
||||
formsemestre: FormSemestre,
|
||||
sem_modimpl_moys: np.array,
|
||||
|
|
|
@ -14,7 +14,7 @@ from app import log
|
|||
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.comp.bonus_spo import BonusSport
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models import ScoDocSiteConfig, formsemestre
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
|
||||
|
@ -56,14 +56,11 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
||||
|
||||
# Elimine les coefs des modimpl bonus sports:
|
||||
modimpls_sport = [
|
||||
modimpl
|
||||
# Masque de tous les modules _sauf_ les bonus (sport)
|
||||
modimpls_mask = [
|
||||
modimpl.module.ue.type != UE_SPORT
|
||||
for modimpl in self.formsemestre.modimpls_sorted
|
||||
if modimpl.module.ue.type == UE_SPORT
|
||||
]
|
||||
for modimpl in modimpls_sport:
|
||||
self.modimpl_coefs_df[modimpl.id] = 0
|
||||
|
||||
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||
self.sem_cube,
|
||||
|
@ -72,10 +69,11 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||
self.ues,
|
||||
self.modimpl_inscr_df,
|
||||
self.modimpl_coefs_df,
|
||||
modimpls_mask,
|
||||
)
|
||||
# Les coefficients d'UE ne sont pas utilisés en APC
|
||||
self.etud_coef_ue_df = pd.DataFrame(
|
||||
1.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
|
||||
0.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
|
||||
)
|
||||
|
||||
# --- Modules de MALUS sur les UEs
|
||||
|
@ -85,7 +83,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||
self.etud_moy_ue -= self.malus
|
||||
|
||||
# --- Bonus Sport & Culture
|
||||
if len(modimpls_sport) > 0:
|
||||
if not all(modimpls_mask): # au moins un module bonus
|
||||
bonus_class = ScoDocSiteConfig.get_bonus_sport_class()
|
||||
if bonus_class is not None:
|
||||
bonus: BonusSport = bonus_class(
|
||||
|
@ -100,13 +98,20 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||
self.bonus_ues = bonus.get_bonus_ues()
|
||||
if self.bonus_ues is not None:
|
||||
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
||||
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||
|
||||
# Clippe toutes les moyennes d'UE dans [0,20]
|
||||
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||
|
||||
# Moyenne générale indicative:
|
||||
# (note: le bonus sport a déjà été appliqué aux moyennes d'UE, et impacte
|
||||
# donc la moyenne indicative)
|
||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||
self.etud_moy_ue, self.modimpl_coefs_df
|
||||
# self.etud_moy_gen = moy_sem.compute_sem_moys_apc_using_coefs(
|
||||
# self.etud_moy_ue, self.modimpl_coefs_df
|
||||
# )
|
||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc_using_ects(
|
||||
self.etud_moy_ue,
|
||||
[ue.ects for ue in self.ues if ue.type != UE_SPORT],
|
||||
formation_id=self.formsemestre.formation_id,
|
||||
)
|
||||
# --- UE capitalisées
|
||||
self.apply_capitalisation()
|
||||
|
|
|
@ -15,7 +15,7 @@ from flask import g, url_for
|
|||
|
||||
from app import db
|
||||
from app import log
|
||||
from app.comp import moy_mod, moy_ue, inscr_mod
|
||||
from app.comp import moy_mat, moy_mod, moy_ue, inscr_mod
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.comp.bonus_spo import BonusSport
|
||||
from app.models import ScoDocSiteConfig
|
||||
|
@ -24,6 +24,7 @@ from app.models.formsemestre import FormSemestre
|
|||
from app.models.ues import UniteEns
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
|
@ -60,7 +61,7 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||
)
|
||||
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
||||
self.modimpl_coefs = np.array(
|
||||
[m.module.coefficient for m in self.formsemestre.modimpls_sorted]
|
||||
[m.module.coefficient or 0.0 for m in self.formsemestre.modimpls_sorted]
|
||||
)
|
||||
self.modimpl_idx = {
|
||||
m.id: i for i, m in enumerate(self.formsemestre.modimpls_sorted)
|
||||
|
@ -113,22 +114,44 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
||||
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||
bonus_mg = bonus.get_bonus_moy_gen()
|
||||
if bonus_mg is not None:
|
||||
if bonus_mg is None and self.bonus_ues is not None:
|
||||
# pas de bonus explicite sur la moyenne générale
|
||||
# on l'ajuste pour refléter les modifs d'UE, à l'aide des coefs d'UE.
|
||||
bonus_mg = (self.etud_coef_ue_df * self.bonus_ues).sum(
|
||||
axis=1
|
||||
) / self.etud_coef_ue_df.sum(axis=1)
|
||||
self.etud_moy_gen += bonus_mg
|
||||
self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
|
||||
# compat nt, utilisé pour l'afficher sur les bulletins:
|
||||
self.bonus = bonus_mg
|
||||
elif bonus_mg is not None:
|
||||
# Applique le bonus moyenne générale renvoyé
|
||||
self.etud_moy_gen += bonus_mg
|
||||
|
||||
# compat nt, utilisé pour l'afficher sur les bulletins:
|
||||
self.bonus = bonus_mg
|
||||
|
||||
# --- UE capitalisées
|
||||
self.apply_capitalisation()
|
||||
|
||||
# Clippe toutes les moyennes dans [0,20]
|
||||
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||
self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
|
||||
|
||||
# --- Classements:
|
||||
self.compute_rangs()
|
||||
|
||||
# --- En option, moyennes par matières
|
||||
if sco_preferences.get_preference("bul_show_matieres", self.formsemestre.id):
|
||||
self.compute_moyennes_matieres()
|
||||
|
||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||
"""La moyenne de l'étudiant dans le moduleimpl
|
||||
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM)
|
||||
"""
|
||||
return self.modimpls_results[moduleimpl_id].etuds_moy_module.get(etudid, "NI")
|
||||
try:
|
||||
if self.modimpl_inscr_df[moduleimpl_id][etudid]:
|
||||
return self.modimpls_results[moduleimpl_id].etuds_moy_module[etudid]
|
||||
except KeyError:
|
||||
pass
|
||||
return "NI"
|
||||
|
||||
def get_mod_stats(self, moduleimpl_id: int) -> dict:
|
||||
"""Stats sur les notes obtenues dans un modimpl"""
|
||||
|
@ -149,6 +172,16 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||
),
|
||||
}
|
||||
|
||||
def compute_moyennes_matieres(self):
|
||||
"""Calcul les moyennes par matière. Doit être appelée au besoin, en fin de compute."""
|
||||
self.moyennes_matieres = moy_mat.compute_mat_moys_classic(
|
||||
self.formsemestre,
|
||||
self.sem_matrix,
|
||||
self.ues,
|
||||
self.modimpl_inscr_df,
|
||||
self.modimpl_coefs,
|
||||
)
|
||||
|
||||
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
|
||||
"""Détermine le coefficient de l'UE pour cet étudiant.
|
||||
N'est utilisé que pour l'injection des UE capitalisées dans la
|
||||
|
|
|
@ -9,18 +9,22 @@ from functools import cached_property
|
|||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from flask import g, flash, url_for
|
||||
|
||||
from app import log
|
||||
from app.comp.aux_stats import StatsMoyenne
|
||||
from app.comp import moy_sem
|
||||
from app.comp.res_cache import ResultatsCache
|
||||
from app.comp import res_sem
|
||||
from app.comp.moy_mod import ModuleImplResults
|
||||
from app.models import FormSemestre, Identite, ModuleImpl
|
||||
from app.models import FormSemestreUECoef
|
||||
from app.models import FormSemestre, FormSemestreUECoef
|
||||
from app.models import Identite
|
||||
from app.models import ModuleImpl, ModuleImplInscription
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
# Il faut bien distinguer
|
||||
# - ce qui est caché de façon persistente (via redis):
|
||||
|
@ -39,6 +43,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||
"modimpl_inscr_df",
|
||||
"modimpls_results",
|
||||
"etud_coef_ue_df",
|
||||
"moyennes_matieres",
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
|
@ -57,6 +62,8 @@ class ResultatsSemestre(ResultatsCache):
|
|||
self.etud_coef_ue_df = None
|
||||
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
|
||||
self.validations = None
|
||||
self.moyennes_matieres = {}
|
||||
"""Moyennes de matières, si calculées. { matiere_id : Series, index etudid }"""
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||
|
@ -165,7 +172,6 @@ class ResultatsSemestre(ResultatsCache):
|
|||
"""
|
||||
# Supposant qu'il y a peu d'UE capitalisées,
|
||||
# on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée.
|
||||
# return # XXX XXX XXX
|
||||
if not self.validations:
|
||||
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||
ue_capitalisees = self.validations.ue_capitalisees
|
||||
|
@ -184,10 +190,12 @@ class ResultatsSemestre(ResultatsCache):
|
|||
sum_coefs_ue = 0.0
|
||||
for ue in self.formsemestre.query_ues():
|
||||
ue_cap = self.get_etud_ue_status(etudid, ue.id)
|
||||
if ue_cap and ue_cap["is_capitalized"]:
|
||||
if ue_cap is None:
|
||||
continue
|
||||
if ue_cap["is_capitalized"]:
|
||||
recompute_mg = True
|
||||
coef = ue_cap["coef_ue"]
|
||||
if not np.isnan(ue_cap["moy"]):
|
||||
if not np.isnan(ue_cap["moy"]) and coef:
|
||||
sum_notes_ue += ue_cap["moy"] * coef
|
||||
sum_coefs_ue += coef
|
||||
|
||||
|
@ -195,13 +203,25 @@ class ResultatsSemestre(ResultatsCache):
|
|||
# On doit prendre en compte une ou plusieurs UE capitalisées
|
||||
# et donc recalculer la moyenne générale
|
||||
self.etud_moy_gen[etudid] = sum_notes_ue / sum_coefs_ue
|
||||
# Ajoute le bonus sport
|
||||
if self.bonus is not None and self.bonus[etudid]:
|
||||
self.etud_moy_gen[etudid] += self.bonus[etudid]
|
||||
self.etud_moy_gen[etudid] = max(
|
||||
0.0, min(self.etud_moy_gen[etudid], 20.0)
|
||||
)
|
||||
|
||||
def _get_etud_ue_cap(self, etudid, ue):
|
||||
""""""
|
||||
def _get_etud_ue_cap(self, etudid: int, ue: UniteEns) -> dict:
|
||||
"""Donne les informations sur la capitalisation de l'UE ue pour cet étudiant.
|
||||
Résultat:
|
||||
Si pas capitalisée: None
|
||||
Si capitalisée: un dict, avec les colonnes de validation.
|
||||
"""
|
||||
capitalisations = self.validations.ue_capitalisees.loc[etudid]
|
||||
if isinstance(capitalisations, pd.DataFrame):
|
||||
ue_cap = capitalisations[capitalisations["ue_code"] == ue.ue_code]
|
||||
if isinstance(ue_cap, pd.DataFrame) and not ue_cap.empty:
|
||||
if ue_cap.empty:
|
||||
return None
|
||||
if isinstance(ue_cap, pd.DataFrame):
|
||||
# si plusieurs fois capitalisée, prend le max
|
||||
cap_idx = ue_cap["moy_ue"].values.argmax()
|
||||
ue_cap = ue_cap.iloc[cap_idx]
|
||||
|
@ -209,8 +229,9 @@ class ResultatsSemestre(ResultatsCache):
|
|||
if capitalisations["ue_code"] == ue.ue_code:
|
||||
ue_cap = capitalisations
|
||||
else:
|
||||
ue_cap = None
|
||||
return ue_cap
|
||||
return None
|
||||
# converti la Series en dict, afin que les np.int64 reviennent en int
|
||||
return ue_cap.to_dict()
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
|
||||
"""L'état de l'UE pour cet étudiant.
|
||||
|
@ -238,22 +259,45 @@ class ResultatsSemestre(ResultatsCache):
|
|||
cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
|
||||
moy_ue = cur_moy_ue
|
||||
is_capitalized = False # si l'UE prise en compte est une UE capitalisée
|
||||
was_capitalized = (
|
||||
False # s'il y a precedemment une UE capitalisée (pas forcement meilleure)
|
||||
)
|
||||
# s'il y a precedemment une UE capitalisée (pas forcement meilleure):
|
||||
was_capitalized = False
|
||||
if etudid in self.validations.ue_capitalisees.index:
|
||||
ue_cap = self._get_etud_ue_cap(etudid, ue)
|
||||
if (
|
||||
ue_cap is not None
|
||||
and not ue_cap.empty
|
||||
and not np.isnan(ue_cap["moy_ue"])
|
||||
):
|
||||
if ue_cap and not np.isnan(ue_cap["moy_ue"]):
|
||||
was_capitalized = True
|
||||
if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue):
|
||||
moy_ue = ue_cap["moy_ue"]
|
||||
is_capitalized = True
|
||||
|
||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||
# Coef l'UE dans le semestre courant:
|
||||
if self.is_apc:
|
||||
# utilise les ECTS comme coef.
|
||||
coef_ue = ue.ects
|
||||
else:
|
||||
# formations classiques
|
||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||
if (not coef_ue) and is_capitalized: # étudiant non inscrit dans l'UE courante
|
||||
if self.is_apc:
|
||||
# Coefs de l'UE capitalisée en formation APC: donné par ses ECTS
|
||||
ue_capitalized = UniteEns.query.get(ue_cap["ue_id"])
|
||||
coef_ue = ue_capitalized.ects
|
||||
if coef_ue is None:
|
||||
orig_sem = FormSemestre.query.get(ue_cap["formsemestre_id"])
|
||||
raise ScoValueError(
|
||||
f"""L'UE capitalisée {ue_capitalized.acronyme}
|
||||
du semestre {orig_sem.titre_annee()}
|
||||
n'a pas d'indication d'ECTS.
|
||||
Corrigez ou faite corriger le programme
|
||||
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=ue_capitalized.formation_id)}">via cette page</a>.
|
||||
"""
|
||||
)
|
||||
else:
|
||||
# Coefs de l'UE capitalisée en formation classique:
|
||||
# va chercher le coef dans le semestre d'origine
|
||||
coef_ue = ModuleImplInscription.sum_coefs_modimpl_ue(
|
||||
ue_cap["formsemestre_id"], etudid, ue_cap["ue_id"]
|
||||
)
|
||||
|
||||
return {
|
||||
"is_capitalized": is_capitalized,
|
||||
|
@ -375,21 +419,31 @@ class NotesTableCompat(ResultatsSemestre):
|
|||
"""Stats (moy/min/max) sur la moyenne générale"""
|
||||
return StatsMoyenne(self.etud_moy_gen)
|
||||
|
||||
def get_ues_stat_dict(self, filter_sport=False): # was get_ues()
|
||||
def get_ues_stat_dict(
|
||||
self, filter_sport=False, check_apc_ects=True
|
||||
) -> list[dict]: # was get_ues()
|
||||
"""Liste des UEs, ordonnée par numero.
|
||||
Si filter_sport, retire les UE de type SPORT.
|
||||
Résultat: liste de dicts { champs UE U stats moyenne UE }
|
||||
"""
|
||||
ues = []
|
||||
for ue in self.formsemestre.query_ues(with_sport=not filter_sport):
|
||||
ues = self.formsemestre.query_ues(with_sport=not filter_sport)
|
||||
ues_dict = []
|
||||
for ue in ues:
|
||||
d = ue.to_dict()
|
||||
if ue.type != UE_SPORT:
|
||||
moys = self.etud_moy_ue[ue.id]
|
||||
else:
|
||||
moys = None
|
||||
d.update(StatsMoyenne(moys).to_dict())
|
||||
ues.append(d)
|
||||
return ues
|
||||
ues_dict.append(d)
|
||||
if check_apc_ects and self.is_apc and not hasattr(g, "checked_apc_ects"):
|
||||
g.checked_apc_ects = True
|
||||
if None in [ue.ects for ue in ues if ue.type != UE_SPORT]:
|
||||
flash(
|
||||
"""Calcul moyenne générale impossible: ECTS des UE manquants !""",
|
||||
category="danger",
|
||||
)
|
||||
return ues_dict
|
||||
|
||||
def get_modimpls_dict(self, ue_id=None) -> list[dict]:
|
||||
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
||||
|
@ -508,10 +562,15 @@ class NotesTableCompat(ResultatsSemestre):
|
|||
return ""
|
||||
return ins.etat
|
||||
|
||||
def get_etud_mat_moy(self, matiere_id, etudid):
|
||||
def get_etud_mat_moy(self, matiere_id: int, etudid: int) -> str:
|
||||
"""moyenne d'un étudiant dans une matière (ou NA si pas de notes)"""
|
||||
# non supporté en 9.2
|
||||
return "na"
|
||||
if not self.moyennes_matieres:
|
||||
return "nd"
|
||||
return (
|
||||
self.moyennes_matieres[matiere_id].get(etudid, "-")
|
||||
if matiere_id in self.moyennes_matieres
|
||||
else "-"
|
||||
)
|
||||
|
||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||
"""La moyenne de l'étudiant dans le moduleimpl
|
||||
|
|
|
@ -8,11 +8,13 @@
|
|||
"""
|
||||
from flask import g
|
||||
|
||||
from app import db
|
||||
from app.comp.jury import ValidationsSemestre
|
||||
from app.comp.res_common import ResultatsSemestre
|
||||
from app.comp.res_classic import ResultatsSemestreClassic
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc import sco_cache
|
||||
|
||||
|
||||
def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
|
@ -23,6 +25,13 @@ def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
|||
Search in local cache (g.formsemestre_result_cache)
|
||||
If not in cache, build it and cache it.
|
||||
"""
|
||||
is_apc = formsemestre.formation.is_apc()
|
||||
if is_apc and formsemestre.semestre_id == -1:
|
||||
formsemestre.semestre_id = 1
|
||||
db.session.add(formsemestre)
|
||||
db.session.commit()
|
||||
sco_cache.invalidate_formsemestre(formsemestre.id)
|
||||
|
||||
# --- Try local cache (within the same request context)
|
||||
if not hasattr(g, "formsemestre_results_cache"):
|
||||
g.formsemestre_results_cache = {}
|
||||
|
@ -30,11 +39,7 @@ def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
|||
if formsemestre.id in g.formsemestre_results_cache:
|
||||
return g.formsemestre_results_cache[formsemestre.id]
|
||||
|
||||
klass = (
|
||||
ResultatsSemestreBUT
|
||||
if formsemestre.formation.is_apc()
|
||||
else ResultatsSemestreClassic
|
||||
)
|
||||
klass = ResultatsSemestreBUT if is_apc else ResultatsSemestreClassic
|
||||
g.formsemestre_results_cache[formsemestre.id] = klass(formsemestre)
|
||||
return g.formsemestre_results_cache[formsemestre.id]
|
||||
|
||||
|
|
|
@ -193,7 +193,7 @@ def scodoc7func(func):
|
|||
# necessary for db ids and boolean values
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
pos_arg_values.append(v)
|
||||
# current_app.logger.info("pos_arg_values=%s" % pos_arg_values)
|
||||
|
|
65
app/email.py
65
app/email.py
|
@ -1,8 +1,17 @@
|
|||
# -*- coding: UTF-8 -*
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
from threading import Thread
|
||||
from flask import current_app
|
||||
|
||||
from flask import current_app, g
|
||||
from flask_mail import Message
|
||||
|
||||
from app import mail
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
|
||||
def send_async_email(app, msg):
|
||||
|
@ -11,20 +20,66 @@ def send_async_email(app, msg):
|
|||
|
||||
|
||||
def send_email(
|
||||
subject: str, sender: str, recipients: list, text_body: str, html_body=""
|
||||
subject: str,
|
||||
sender: str,
|
||||
recipients: list,
|
||||
text_body: str,
|
||||
html_body="",
|
||||
bcc=(),
|
||||
attachments=(),
|
||||
):
|
||||
"""
|
||||
Send an email
|
||||
Send an email. _All_ ScoDoc mails SHOULD be sent using this function.
|
||||
|
||||
If html_body is specified, build a multipart message with HTML content,
|
||||
else send a plain text email.
|
||||
|
||||
attachements: list of dict { 'filename', 'mimetype', 'data' }
|
||||
"""
|
||||
msg = Message(subject, sender=sender, recipients=recipients)
|
||||
msg = Message(subject, sender=sender, recipients=recipients, bcc=bcc)
|
||||
msg.body = text_body
|
||||
msg.html = html_body
|
||||
if attachments:
|
||||
for attachment in attachments:
|
||||
msg.attach(
|
||||
attachment["filename"], attachment["mimetype"], attachment["data"]
|
||||
)
|
||||
|
||||
send_message(msg)
|
||||
|
||||
|
||||
def send_message(msg):
|
||||
def send_message(msg: Message):
|
||||
"""Send a message.
|
||||
All ScoDoc emails MUST be sent by this function.
|
||||
|
||||
In mail debug mode, addresses are discarded and all mails are sent to the
|
||||
specified debugging address.
|
||||
"""
|
||||
if hasattr(g, "scodoc_dept"):
|
||||
# on est dans un département, on peut accéder aux préférences
|
||||
email_test_mode_address = sco_preferences.get_preference(
|
||||
"email_test_mode_address"
|
||||
)
|
||||
if email_test_mode_address:
|
||||
# Mode spécial test: remplace les adresses de destination
|
||||
orig_to = msg.recipients
|
||||
orig_cc = msg.cc
|
||||
orig_bcc = msg.bcc
|
||||
msg.recipients = [email_test_mode_address]
|
||||
msg.cc = None
|
||||
msg.bcc = None
|
||||
msg.subject = "[TEST SCODOC] " + msg.subject
|
||||
msg.body = (
|
||||
f"""--- Message ScoDoc dérouté pour tests ---
|
||||
Adresses d'origine:
|
||||
to : {orig_to}
|
||||
cc : {orig_cc}
|
||||
bcc: {orig_bcc}
|
||||
---
|
||||
\n\n"""
|
||||
+ msg.body
|
||||
)
|
||||
|
||||
Thread(
|
||||
target=send_async_email, args=(current_app._get_current_object(), msg)
|
||||
).start()
|
||||
|
|
|
@ -30,17 +30,15 @@ Formulaires configuration logos
|
|||
|
||||
Contrib @jmp, dec 21
|
||||
"""
|
||||
import re
|
||||
|
||||
from flask import flash, url_for, redirect, render_template
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileField, FileAllowed
|
||||
from wtforms import SelectField, SubmitField, FormField, validators, FieldList
|
||||
from wtforms import SubmitField, FormField, validators, FieldList
|
||||
from wtforms import ValidationError
|
||||
from wtforms.fields.simple import StringField, HiddenField
|
||||
|
||||
from app import AccessDenied
|
||||
from app.models import Departement
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.scodoc import sco_logos, html_sco_header
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_config_actions import (
|
||||
|
@ -49,10 +47,11 @@ from app.scodoc.sco_config_actions import (
|
|||
LogoInsert,
|
||||
)
|
||||
|
||||
from flask_login import current_user
|
||||
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
|
||||
|
||||
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + []
|
||||
|
||||
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||
|
@ -111,6 +110,15 @@ def dept_key_to_id(dept_key):
|
|||
return dept_key
|
||||
|
||||
|
||||
def logo_name_validator(message=None):
|
||||
def validate_logo_name(form, field):
|
||||
name = field.data if field.data else ""
|
||||
if not scu.is_valid_filename(name):
|
||||
raise ValidationError(message)
|
||||
|
||||
return validate_logo_name
|
||||
|
||||
|
||||
class AddLogoForm(FlaskForm):
|
||||
"""Formulaire permettant l'ajout d'un logo (dans un département)"""
|
||||
|
||||
|
@ -118,11 +126,7 @@ class AddLogoForm(FlaskForm):
|
|||
name = StringField(
|
||||
label="Nom",
|
||||
validators=[
|
||||
validators.regexp(
|
||||
r"^[a-zA-Z0-9-_]*$",
|
||||
re.IGNORECASE,
|
||||
"Ne doit comporter que lettres, chiffres, _ ou -",
|
||||
),
|
||||
logo_name_validator("Nom de logo invalide (alphanumérique, _)"),
|
||||
validators.Length(
|
||||
max=20, message="Un nom ne doit pas dépasser 20 caractères"
|
||||
),
|
||||
|
@ -373,11 +377,11 @@ def config_logos():
|
|||
if action:
|
||||
action.execute()
|
||||
flash(action.message)
|
||||
return redirect(
|
||||
url_for(
|
||||
"scodoc.configure_logos",
|
||||
)
|
||||
)
|
||||
return redirect(url_for("scodoc.configure_logos"))
|
||||
else:
|
||||
if not form.validate():
|
||||
scu.flash_errors(form)
|
||||
|
||||
return render_template(
|
||||
"config_logos.html",
|
||||
scodoc_dept=None,
|
||||
|
|
|
@ -55,6 +55,9 @@ def create_dept(acronym: str, visible=True) -> Departement:
|
|||
"Create new departement"
|
||||
from app.models import ScoPreference
|
||||
|
||||
existing = Departement.query.filter_by(acronym=acronym).count()
|
||||
if existing:
|
||||
raise ValueError(f"acronyme {acronym} déjà existant")
|
||||
departement = Departement(acronym=acronym, visible=visible)
|
||||
p1 = ScoPreference(name="DeptName", value=acronym, departement=departement)
|
||||
db.session.add(p1)
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
et données rattachées (adresses, annotations, ...)
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from functools import cached_property
|
||||
from flask import abort, url_for
|
||||
from flask import g, request
|
||||
import sqlalchemy
|
||||
from sqlalchemy import desc, text
|
||||
|
||||
from app import db
|
||||
from app import db, log
|
||||
from app import models
|
||||
|
||||
from app.scodoc import notesdb as ndb
|
||||
|
@ -82,6 +84,11 @@ class Identite(db.Model):
|
|||
return scu.suppress_accents(s)
|
||||
return s
|
||||
|
||||
@property
|
||||
def e(self):
|
||||
"terminaison en français: 'ne', '', 'ou '(e)'"
|
||||
return {"M": "", "F": "e"}.get(self.civilite, "(e)")
|
||||
|
||||
def nom_disp(self) -> str:
|
||||
"Nom à afficher"
|
||||
if self.nom_usuel:
|
||||
|
@ -123,7 +130,7 @@ class Identite(db.Model):
|
|||
|
||||
def get_first_email(self, field="email") -> str:
|
||||
"Le mail associé à la première adrese de l'étudiant, ou None"
|
||||
return self.adresses[0].email or None if self.adresses.count() > 0 else None
|
||||
return getattr(self.adresses[0], field) if self.adresses.count() > 0 else None
|
||||
|
||||
def to_dict_scodoc7(self):
|
||||
"""Représentation dictionnaire,
|
||||
|
@ -134,7 +141,7 @@ class Identite(db.Model):
|
|||
# ScoDoc7 output_formators: (backward compat)
|
||||
e["etudid"] = self.id
|
||||
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
|
||||
e["ne"] = {"M": "", "F": "ne"}.get(self.civilite, "(e)")
|
||||
e["ne"] = self.e
|
||||
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
|
||||
|
||||
def to_dict_bul(self, include_urls=True):
|
||||
|
@ -153,6 +160,7 @@ class Identite(db.Model):
|
|||
"etudid": self.id,
|
||||
"nom": self.nom_disp(),
|
||||
"prenom": self.prenom,
|
||||
"nomprenom": self.nomprenom,
|
||||
}
|
||||
if include_urls:
|
||||
d["fiche_url"] = url_for(
|
||||
|
@ -172,6 +180,23 @@ class Identite(db.Model):
|
|||
]
|
||||
return r[0] if r else None
|
||||
|
||||
def inscriptions_courantes(self) -> list: # -> list[FormSemestreInscription]:
|
||||
"""Liste des inscriptions à des semestres _courants_
|
||||
(il est rare qu'il y en ai plus d'une, mais c'est possible).
|
||||
Triées par date de début de semestre décroissante (le plus récent en premier).
|
||||
"""
|
||||
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||
|
||||
return (
|
||||
FormSemestreInscription.query.join(FormSemestreInscription.formsemestre)
|
||||
.filter(
|
||||
FormSemestreInscription.etudid == self.id,
|
||||
text("date_debut < now() and date_fin > now()"),
|
||||
)
|
||||
.order_by(desc(FormSemestre.date_debut))
|
||||
.all()
|
||||
)
|
||||
|
||||
def inscription_courante_date(self, date_debut, date_fin):
|
||||
"""La première inscription à un formsemestre incluant la
|
||||
période [date_debut, date_fin]
|
||||
|
@ -183,8 +208,8 @@ class Identite(db.Model):
|
|||
]
|
||||
return r[0] if r else None
|
||||
|
||||
def etat_inscription(self, formsemestre_id):
|
||||
"""etat de l'inscription de cet étudiant au semestre:
|
||||
def inscription_etat(self, formsemestre_id):
|
||||
"""État de l'inscription de cet étudiant au semestre:
|
||||
False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
|
||||
"""
|
||||
# voir si ce n'est pas trop lent:
|
||||
|
@ -195,6 +220,110 @@ class Identite(db.Model):
|
|||
return ins.etat
|
||||
return False
|
||||
|
||||
def inscription_descr(self) -> dict:
|
||||
"""Description de l'état d'inscription"""
|
||||
inscription_courante = self.inscription_courante()
|
||||
if inscription_courante:
|
||||
titre_sem = inscription_courante.formsemestre.titre_mois()
|
||||
return {
|
||||
"etat_in_cursem": inscription_courante.etat,
|
||||
"inscription_courante": inscription_courante,
|
||||
"inscription": titre_sem,
|
||||
"inscription_str": "Inscrit en " + titre_sem,
|
||||
"situation": self.descr_situation_etud(),
|
||||
}
|
||||
else:
|
||||
if self.formsemestre_inscriptions:
|
||||
# cherche l'inscription la plus récente:
|
||||
fin_dernier_sem = max(
|
||||
[
|
||||
inscr.formsemestre.date_debut
|
||||
for inscr in self.formsemestre_inscriptions
|
||||
]
|
||||
)
|
||||
if fin_dernier_sem > datetime.date.today():
|
||||
inscription = "futur"
|
||||
situation = "futur élève"
|
||||
else:
|
||||
inscription = "ancien"
|
||||
situation = "ancien élève"
|
||||
else:
|
||||
inscription = ("non inscrit",)
|
||||
situation = inscription
|
||||
return {
|
||||
"etat_in_cursem": "?",
|
||||
"inscription_courante": None,
|
||||
"inscription": inscription,
|
||||
"inscription_str": inscription,
|
||||
"situation": situation,
|
||||
}
|
||||
|
||||
def descr_situation_etud(self) -> str:
|
||||
"""Chaîne décrivant la situation _actuelle_ de l'étudiant.
|
||||
Exemple:
|
||||
"inscrit en BUT R&T semestre 2 FI (Jan 2022 - Jul 2022) le 16/01/2022"
|
||||
ou
|
||||
"non inscrit"
|
||||
"""
|
||||
inscriptions_courantes = self.inscriptions_courantes()
|
||||
if inscriptions_courantes:
|
||||
inscr = inscriptions_courantes[0]
|
||||
if inscr.etat == scu.INSCRIT:
|
||||
situation = f"inscrit{self.e} en {inscr.formsemestre.titre_mois()}"
|
||||
# Cherche la date d'inscription dans scolar_events:
|
||||
events = models.ScolarEvent.query.filter_by(
|
||||
etudid=self.id,
|
||||
formsemestre_id=inscr.formsemestre.id,
|
||||
event_type="INSCRIPTION",
|
||||
).all()
|
||||
if not events:
|
||||
log(
|
||||
f"*** situation inconsistante pour {self} (inscrit mais pas d'event)"
|
||||
)
|
||||
date_ins = "???" # ???
|
||||
else:
|
||||
date_ins = events[0].event_date
|
||||
situation += date_ins.strftime(" le %d/%m/%Y")
|
||||
else:
|
||||
situation = f"démission de {inscr.formsemestre.titre_mois()}"
|
||||
# Cherche la date de demission dans scolar_events:
|
||||
events = models.ScolarEvent.query.filter_by(
|
||||
etudid=self.id,
|
||||
formsemestre_id=inscr.formsemestre.id,
|
||||
event_type="DEMISSION",
|
||||
).all()
|
||||
if not events:
|
||||
log(
|
||||
f"*** situation inconsistante pour {self} (demission mais pas d'event)"
|
||||
)
|
||||
date_dem = "???" # ???
|
||||
else:
|
||||
date_dem = events[0].event_date
|
||||
situation += date_dem.strftime(" le %d/%m/%Y")
|
||||
else:
|
||||
situation = "non inscrit" + self.e
|
||||
|
||||
return situation
|
||||
|
||||
def photo_html(self, title=None, size="small") -> str:
|
||||
"""HTML img tag for the photo, either in small size (h90)
|
||||
or original size (size=="orig")
|
||||
"""
|
||||
from app.scodoc import sco_photos
|
||||
|
||||
# sco_photo traite des dicts:
|
||||
return sco_photos.etud_photo_html(
|
||||
etud=dict(
|
||||
etudid=self.id,
|
||||
code_nip=self.code_nip,
|
||||
nomprenom=self.nomprenom,
|
||||
nom_disp=self.nom_disp(),
|
||||
photo_filename=self.photo_filename,
|
||||
),
|
||||
title=title,
|
||||
size=size,
|
||||
)
|
||||
|
||||
|
||||
def make_etud_args(
|
||||
etudid=None, code_nip=None, use_request=True, raise_exc=False, abort_404=True
|
||||
|
|
|
@ -12,7 +12,6 @@ from app import log
|
|||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
from app.models import UniteEns
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.models.ues import UniteEns
|
||||
|
@ -23,6 +22,7 @@ from app.scodoc import sco_codes_parcours
|
|||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_vdi import ApoEtapeVDI
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_utils import MONTH_NAMES_ABBREV
|
||||
|
||||
|
||||
class FormSemestre(db.Model):
|
||||
|
@ -104,6 +104,11 @@ class FormSemestre(db.Model):
|
|||
lazy=True,
|
||||
backref=db.backref("formsemestres", lazy=True),
|
||||
)
|
||||
partitions = db.relationship(
|
||||
"Partition",
|
||||
backref=db.backref("formsemestre", lazy=True),
|
||||
lazy="dynamic",
|
||||
)
|
||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||
# ne pas utiliser après migrate_scodoc7_dept_archives
|
||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||
|
@ -117,6 +122,7 @@ class FormSemestre(db.Model):
|
|||
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
|
||||
|
||||
def to_dict(self):
|
||||
"dict (compatible ScoDoc7)"
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
|
@ -157,8 +163,8 @@ class FormSemestre(db.Model):
|
|||
d["periode"] = 2 # typiquement, début en février: S2, S4...
|
||||
d["titre_num"] = self.titre_num()
|
||||
d["titreannee"] = self.titre_annee()
|
||||
d["mois_debut"] = f"{self.date_debut.month} {self.date_debut.year}"
|
||||
d["mois_fin"] = f"{self.date_fin.month} {self.date_fin.year}"
|
||||
d["mois_debut"] = self.mois_debut()
|
||||
d["mois_fin"] = self.mois_fin()
|
||||
d["titremois"] = "%s %s (%s - %s)" % (
|
||||
d["titre_num"],
|
||||
self.modalite or "",
|
||||
|
@ -201,7 +207,11 @@ class FormSemestre(db.Model):
|
|||
modimpls = self.modimpls.all()
|
||||
if self.formation.is_apc():
|
||||
modimpls.sort(
|
||||
key=lambda m: (m.module.module_type, m.module.numero, m.module.code)
|
||||
key=lambda m: (
|
||||
m.module.module_type or 0,
|
||||
m.module.numero or 0,
|
||||
m.module.code or 0,
|
||||
)
|
||||
)
|
||||
else:
|
||||
modimpls.sort(
|
||||
|
@ -284,6 +294,7 @@ class FormSemestre(db.Model):
|
|||
"""chaîne "J. Dupond, X. Martin"
|
||||
ou "Jacques Dupond, Xavier Martin"
|
||||
"""
|
||||
# was "nomcomplet"
|
||||
if not self.responsables:
|
||||
return ""
|
||||
if abbrev_prenom:
|
||||
|
@ -295,6 +306,14 @@ class FormSemestre(db.Model):
|
|||
"2021 - 2022"
|
||||
return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month)
|
||||
|
||||
def mois_debut(self) -> str:
|
||||
"Oct 2021"
|
||||
return f"{MONTH_NAMES_ABBREV[self.date_debut.month - 1]} {self.date_debut.year}"
|
||||
|
||||
def mois_fin(self) -> str:
|
||||
"Jul 2022"
|
||||
return f"{MONTH_NAMES_ABBREV[self.date_fin.month - 1]} {self.date_debut.year}"
|
||||
|
||||
def session_id(self) -> str:
|
||||
"""identifiant externe de semestre de formation
|
||||
Exemple: RT-DUT-FI-S1-ANNEE
|
||||
|
@ -355,7 +374,7 @@ class FormSemestre(db.Model):
|
|||
|
||||
def get_abs_count(self, etudid):
|
||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||
tuple (nb abs non justifiées, nb abs justifiées)
|
||||
tuple (nb abs, nb abs justifiées)
|
||||
Utilise un cache.
|
||||
"""
|
||||
from app.scodoc import sco_abs
|
||||
|
|
|
@ -31,6 +31,11 @@ class Partition(db.Model):
|
|||
show_in_lists = db.Column(
|
||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
||||
)
|
||||
groups = db.relationship(
|
||||
"GroupDescr",
|
||||
backref=db.backref("partition", lazy=True),
|
||||
lazy="dynamic",
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Partition, self).__init__(**kwargs)
|
||||
|
@ -42,6 +47,9 @@ class Partition(db.Model):
|
|||
else:
|
||||
self.numero = 1
|
||||
|
||||
def __repr__(self):
|
||||
return f"""<{self.__class__.__name__} {self.id} "{self.partition_name or '(default)'}">"""
|
||||
|
||||
|
||||
class GroupDescr(db.Model):
|
||||
"""Description d'un groupe d'une partition"""
|
||||
|
@ -55,6 +63,17 @@ class GroupDescr(db.Model):
|
|||
# "A", "C2", ... (NULL for 'all'):
|
||||
group_name = db.Column(db.String(GROUPNAME_STR_LEN))
|
||||
|
||||
etuds = db.relationship(
|
||||
"Identite",
|
||||
secondary="group_membership",
|
||||
lazy="dynamic",
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"""<{self.__class__.__name__} {self.id} "{self.group_name or '(tous)'}">"""
|
||||
)
|
||||
|
||||
|
||||
group_membership = db.Table(
|
||||
"group_membership",
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
"""ScoDoc models: moduleimpls
|
||||
"""
|
||||
import pandas as pd
|
||||
import flask_sqlalchemy
|
||||
|
||||
from app import db
|
||||
from app.comp import df_cache
|
||||
from app.models import Identite, Module
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.modules import Module
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
@ -129,14 +131,36 @@ class ModuleImplInscription(db.Model):
|
|||
)
|
||||
|
||||
@classmethod
|
||||
def nb_inscriptions_dans_ue(
|
||||
def etud_modimpls_in_ue(
|
||||
cls, formsemestre_id: int, etudid: int, ue_id: int
|
||||
) -> int:
|
||||
"""Nombre de moduleimpls de l'UE auxquels l'étudiant est inscrit"""
|
||||
) -> flask_sqlalchemy.BaseQuery:
|
||||
"""moduleimpls de l'UE auxquels l'étudiant est inscrit"""
|
||||
return ModuleImplInscription.query.filter(
|
||||
ModuleImplInscription.etudid == etudid,
|
||||
ModuleImplInscription.moduleimpl_id == ModuleImpl.id,
|
||||
ModuleImpl.formsemestre_id == formsemestre_id,
|
||||
ModuleImpl.module_id == Module.id,
|
||||
Module.ue_id == ue_id,
|
||||
).count()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def nb_inscriptions_dans_ue(
|
||||
cls, formsemestre_id: int, etudid: int, ue_id: int
|
||||
) -> int:
|
||||
"""Nombre de moduleimpls de l'UE auxquels l'étudiant est inscrit"""
|
||||
return cls.etud_modimpls_in_ue(formsemestre_id, etudid, ue_id).count()
|
||||
|
||||
@classmethod
|
||||
def sum_coefs_modimpl_ue(
|
||||
cls, formsemestre_id: int, etudid: int, ue_id: int
|
||||
) -> float:
|
||||
"""Somme des coefficients des modules auxquels l'étudiant est inscrit
|
||||
dans l'UE du semestre indiqué.
|
||||
N'utilise que les coefficients, donc inadapté aux formations APC.
|
||||
"""
|
||||
return sum(
|
||||
[
|
||||
inscr.modimpl.module.coefficient
|
||||
for inscr in cls.etud_modimpls_in_ue(formsemestre_id, etudid, ue_id)
|
||||
]
|
||||
)
|
||||
|
|
|
@ -54,13 +54,15 @@ class UniteEns(db.Model):
|
|||
'EXTERNE' if self.is_external else ''})>"""
|
||||
|
||||
def to_dict(self):
|
||||
"""as a dict, with the same conversions as in ScoDoc7"""
|
||||
"""as a dict, with the same conversions as in ScoDoc7
|
||||
(except ECTS: keep None)
|
||||
"""
|
||||
e = dict(self.__dict__)
|
||||
e.pop("_sa_instance_state", None)
|
||||
# ScoDoc7 output_formators
|
||||
e["ue_id"] = self.id
|
||||
e["numero"] = e["numero"] if e["numero"] else 0
|
||||
e["ects"] = e["ects"] if e["ects"] else 0.0
|
||||
e["ects"] = e["ects"]
|
||||
e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0
|
||||
e["code_apogee"] = e["code_apogee"] or "" # pas de None
|
||||
return e
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
"""
|
||||
|
||||
from flask import send_file, request
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
@ -97,8 +98,12 @@ def pe_view_sem_recap(
|
|||
template_latex = ""
|
||||
# template fourni via le formulaire Web
|
||||
if avis_tmpl_file:
|
||||
template_latex = avis_tmpl_file.read().decode('utf-8')
|
||||
template_latex = template_latex
|
||||
try:
|
||||
template_latex = avis_tmpl_file.read().decode("utf-8")
|
||||
except UnicodeDecodeError as e:
|
||||
raise ScoValueError(
|
||||
"Données (template) invalides (caractères non UTF8 ?)"
|
||||
) from e
|
||||
else:
|
||||
# template indiqué dans préférences ScoDoc ?
|
||||
template_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||
|
@ -114,7 +119,7 @@ def pe_view_sem_recap(
|
|||
footer_latex = ""
|
||||
# template fourni via le formulaire Web
|
||||
if footer_tmpl_file:
|
||||
footer_latex = footer_tmpl_file.read().decode('utf-8')
|
||||
footer_latex = footer_tmpl_file.read().decode("utf-8")
|
||||
footer_latex = footer_latex
|
||||
else:
|
||||
footer_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||
|
|
|
@ -63,12 +63,15 @@ from app.scodoc.sco_pdf import SU
|
|||
from app import log
|
||||
|
||||
|
||||
def mark_paras(L, tags):
|
||||
"""Put each (string) element of L between <b>"""
|
||||
def mark_paras(L, tags) -> list[str]:
|
||||
"""Put each (string) element of L between <tag>...</tag>,
|
||||
for each supplied tag.
|
||||
Leave non string elements untouched.
|
||||
"""
|
||||
for tag in tags:
|
||||
b = "<" + tag + ">"
|
||||
c = "</" + tag.split()[0] + ">"
|
||||
L = [b + (x or "") + c for x in L]
|
||||
start = "<" + tag + ">"
|
||||
end = "</" + tag.split()[0] + ">"
|
||||
L = [(start + (x or "") + end) if isinstance(x, str) else x for x in L]
|
||||
return L
|
||||
|
||||
|
||||
|
@ -233,7 +236,10 @@ class GenTable(object):
|
|||
colspan_count -= 1
|
||||
# if colspan_count > 0:
|
||||
# continue # skip cells after a span
|
||||
content = row.get(cid, "") or "" # nota: None converted to ''
|
||||
if pdf_mode:
|
||||
content = row.get(f"_{cid}_pdf", "") or row.get(cid, "") or ""
|
||||
else:
|
||||
content = row.get(cid, "") or "" # nota: None converted to ''
|
||||
colspan = row.get("_%s_colspan" % cid, 0)
|
||||
if colspan > 1:
|
||||
pdf_style_list.append(
|
||||
|
@ -547,9 +553,16 @@ class GenTable(object):
|
|||
omit_hidden_lines=True,
|
||||
)
|
||||
try:
|
||||
Pt = [
|
||||
[Paragraph(SU(str(x)), CellStyle) for x in line] for line in data_list
|
||||
]
|
||||
Pt = []
|
||||
for line in data_list:
|
||||
Pt.append(
|
||||
[
|
||||
Paragraph(SU(str(x)), CellStyle)
|
||||
if (not isinstance(x, Paragraph))
|
||||
else x
|
||||
for x in line
|
||||
]
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ScoPDFFormatError(str(exc)) from exc
|
||||
pdf_style_list += self.pdf_table_style
|
||||
|
|
|
@ -30,12 +30,12 @@
|
|||
|
||||
import html
|
||||
|
||||
from flask import g
|
||||
from flask import render_template
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app import scodoc_flash_status_messages
|
||||
from app.scodoc import html_sidebar
|
||||
import sco_version
|
||||
|
||||
|
@ -153,13 +153,14 @@ def sco_header(
|
|||
"Main HTML page header for ScoDoc"
|
||||
from app.scodoc.sco_formsemestre_status import formsemestre_page_title
|
||||
|
||||
scodoc_flash_status_messages()
|
||||
|
||||
# Get head message from http request:
|
||||
if not head_message:
|
||||
if request.method == "POST":
|
||||
head_message = request.form.get("head_message", "")
|
||||
elif request.method == "GET":
|
||||
head_message = request.args.get("head_message", "")
|
||||
|
||||
params = {
|
||||
"page_title": page_title or sco_version.SCONAME,
|
||||
"no_side_bar": no_side_bar,
|
||||
|
@ -280,6 +281,9 @@ def sco_header(
|
|||
if not no_side_bar:
|
||||
H.append(html_sidebar.sidebar())
|
||||
H.append("""<div id="gtrcontent">""")
|
||||
# En attendant le replacement complet de cette fonction,
|
||||
# inclusion ici des messages flask
|
||||
H.append(render_template("flashed_messages.html"))
|
||||
#
|
||||
# Barre menu semestre:
|
||||
H.append(formsemestre_page_title())
|
||||
|
|
|
@ -1037,7 +1037,7 @@ def get_abs_count(etudid, sem):
|
|||
|
||||
def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
|
||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||
tuple (nb abs non justifiées, nb abs justifiées)
|
||||
tuple (nb abs, nb abs justifiées)
|
||||
Utilise un cache.
|
||||
"""
|
||||
key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso
|
||||
|
|
|
@ -35,6 +35,7 @@ import datetime
|
|||
|
||||
from flask import g, url_for
|
||||
from flask_mail import Message
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
@ -55,27 +56,30 @@ def abs_notify(etudid, date):
|
|||
"""
|
||||
from app.scodoc import sco_abs
|
||||
|
||||
sem = retreive_current_formsemestre(etudid, date)
|
||||
if not sem:
|
||||
formsemestre = retreive_current_formsemestre(etudid, date)
|
||||
if not formsemestre:
|
||||
return # non inscrit a la date, pas de notification
|
||||
|
||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
||||
do_abs_notify(sem, etudid, date, nbabs, nbabsjust)
|
||||
nbabs, nbabsjust = sco_abs.get_abs_count_in_interval(
|
||||
etudid, formsemestre.date_debut.isoformat(), formsemestre.date_fin.isoformat()
|
||||
)
|
||||
do_abs_notify(formsemestre, etudid, date, nbabs, nbabsjust)
|
||||
|
||||
|
||||
def do_abs_notify(sem, etudid, date, nbabs, nbabsjust):
|
||||
def do_abs_notify(formsemestre: FormSemestre, etudid, date, nbabs, nbabsjust):
|
||||
"""Given new counts of absences, check if notifications are requested and send them."""
|
||||
# prefs fallback to global pref if sem is None:
|
||||
if sem:
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
if formsemestre:
|
||||
formsemestre_id = formsemestre.id
|
||||
else:
|
||||
formsemestre_id = None
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id=sem["formsemestre_id"])
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id)
|
||||
|
||||
destinations = abs_notify_get_destinations(
|
||||
sem, prefs, etudid, date, nbabs, nbabsjust
|
||||
formsemestre, prefs, etudid, date, nbabs, nbabsjust
|
||||
)
|
||||
msg = abs_notification_message(sem, prefs, etudid, nbabs, nbabsjust)
|
||||
|
||||
msg = abs_notification_message(formsemestre, prefs, etudid, nbabs, nbabsjust)
|
||||
if not msg:
|
||||
return # abort
|
||||
|
||||
|
@ -131,19 +135,19 @@ def abs_notify_send(destinations, etudid, msg, nbabs, nbabsjust, formsemestre_id
|
|||
)
|
||||
|
||||
|
||||
def abs_notify_get_destinations(sem, prefs, etudid, date, nbabs, nbabsjust):
|
||||
def abs_notify_get_destinations(
|
||||
formsemestre: FormSemestre, prefs, etudid, date, nbabs, nbabsjust
|
||||
) -> set:
|
||||
"""Returns set of destination emails to be notified"""
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
|
||||
destinations = [] # list of email address to notify
|
||||
|
||||
if abs_notify_is_above_threshold(etudid, nbabs, nbabsjust, formsemestre_id):
|
||||
if sem and prefs["abs_notify_respsem"]:
|
||||
if abs_notify_is_above_threshold(etudid, nbabs, nbabsjust, formsemestre.id):
|
||||
if prefs["abs_notify_respsem"]:
|
||||
# notifie chaque responsable du semestre
|
||||
for responsable_id in sem["responsables"]:
|
||||
u = sco_users.user_info(responsable_id)
|
||||
if u["email"]:
|
||||
destinations.append(u["email"])
|
||||
for responsable in formsemestre.responsables:
|
||||
if responsable.email:
|
||||
destinations.append(responsable.email)
|
||||
if prefs["abs_notify_chief"] and prefs["email_chefdpt"]:
|
||||
destinations.append(prefs["email_chefdpt"])
|
||||
if prefs["abs_notify_email"]:
|
||||
|
@ -156,7 +160,7 @@ def abs_notify_get_destinations(sem, prefs, etudid, date, nbabs, nbabsjust):
|
|||
# Notification (à chaque fois) des resp. de modules ayant des évaluations
|
||||
# à cette date
|
||||
# nb: on pourrait prevoir d'utiliser un autre format de message pour ce cas
|
||||
if sem and prefs["abs_notify_respeval"]:
|
||||
if prefs["abs_notify_respeval"]:
|
||||
mods = mod_with_evals_at_date(date, etudid)
|
||||
for mod in mods:
|
||||
u = sco_users.user_info(mod["responsable_id"])
|
||||
|
@ -232,7 +236,9 @@ def user_nbdays_since_last_notif(email_addr, etudid):
|
|||
return None
|
||||
|
||||
|
||||
def abs_notification_message(sem, prefs, etudid, nbabs, nbabsjust):
|
||||
def abs_notification_message(
|
||||
formsemestre: FormSemestre, prefs, etudid, nbabs, nbabsjust
|
||||
):
|
||||
"""Mime notification message based on template.
|
||||
returns a Message instance
|
||||
or None if sending should be canceled (empty template).
|
||||
|
@ -242,7 +248,7 @@ def abs_notification_message(sem, prefs, etudid, nbabs, nbabsjust):
|
|||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
|
||||
# Variables accessibles dans les balises du template: %(nom_variable)s :
|
||||
values = sco_bulletins.make_context_dict(sem, etud)
|
||||
values = sco_bulletins.make_context_dict(formsemestre, etud)
|
||||
|
||||
values["nbabs"] = nbabs
|
||||
values["nbabsjust"] = nbabsjust
|
||||
|
@ -264,9 +270,11 @@ def abs_notification_message(sem, prefs, etudid, nbabs, nbabsjust):
|
|||
return msg
|
||||
|
||||
|
||||
def retreive_current_formsemestre(etudid, cur_date):
|
||||
def retreive_current_formsemestre(etudid: int, cur_date) -> FormSemestre:
|
||||
"""Get formsemestre dans lequel etudid est (ou était) inscrit a la date indiquée
|
||||
date est une chaine au format ISO (yyyy-mm-dd)
|
||||
|
||||
Result: FormSemestre ou None si pas inscrit à la date indiquée
|
||||
"""
|
||||
req = """SELECT i.formsemestre_id
|
||||
FROM notes_formsemestre_inscription i, notes_formsemestre sem
|
||||
|
@ -278,8 +286,8 @@ def retreive_current_formsemestre(etudid, cur_date):
|
|||
if not r:
|
||||
return None
|
||||
# s'il y a plusieurs semestres, prend le premier (rarissime et non significatif):
|
||||
sem = sco_formsemestre.get_formsemestre(r[0]["formsemestre_id"])
|
||||
return sem
|
||||
formsemestre = FormSemestre.query.get(r[0]["formsemestre_id"])
|
||||
return formsemestre
|
||||
|
||||
|
||||
def mod_with_evals_at_date(date_abs, etudid):
|
||||
|
|
|
@ -98,7 +98,7 @@ from chardet import detect as chardet_detect
|
|||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models import FormSemestre, Identite
|
||||
from app.models.config import ScoDocSiteConfig
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
|
||||
|
@ -111,7 +111,6 @@ from app.scodoc.sco_codes_parcours import (
|
|||
NAR,
|
||||
RAT,
|
||||
)
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_parcours_dut
|
||||
from app.scodoc import sco_etud
|
||||
|
@ -454,6 +453,12 @@ class ApoEtud(dict):
|
|||
|
||||
def comp_elt_semestre(self, nt, decision, etudid):
|
||||
"""Calcul résultat apo semestre"""
|
||||
if decision is None:
|
||||
etud = Identite.query.get(etudid)
|
||||
nomprenom = etud.nomprenom if etud else "(inconnu)"
|
||||
raise ScoValueError(
|
||||
f"decision absente pour l'étudiant {nomprenom} ({etudid})"
|
||||
)
|
||||
# resultat du semestre
|
||||
decision_apo = ScoDocSiteConfig.get_code_apo(decision["code"])
|
||||
note = nt.get_etud_moy_gen(etudid)
|
||||
|
|
|
@ -70,13 +70,13 @@ from app.scodoc.sco_exceptions import (
|
|||
)
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_pvjury
|
||||
from app.scodoc import sco_pvpdf
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
class BaseArchiver(object):
|
||||
|
@ -254,7 +254,7 @@ class BaseArchiver(object):
|
|||
self.initialize()
|
||||
if not scu.is_valid_filename(filename):
|
||||
log('Archiver.get: invalid filename "%s"' % filename)
|
||||
raise ValueError("invalid filename")
|
||||
raise ScoValueError("archive introuvable (déjà supprimée ?)")
|
||||
fname = os.path.join(archive_id, filename)
|
||||
log("reading archive file %s" % fname)
|
||||
with open(fname, "rb") as f:
|
||||
|
|
|
@ -28,30 +28,19 @@
|
|||
"""Génération des bulletins de notes
|
||||
|
||||
"""
|
||||
from app.models import formsemestre
|
||||
import time
|
||||
import pprint
|
||||
import email
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.base import MIMEBase
|
||||
from email.header import Header
|
||||
from reportlab.lib.colors import Color
|
||||
import urllib
|
||||
import time
|
||||
|
||||
from flask import g, request
|
||||
from flask import url_for
|
||||
from flask import render_template, url_for
|
||||
from flask_login import current_user
|
||||
from flask_mail import Message
|
||||
from app.models.moduleimpls import ModuleImplInscription
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import email
|
||||
from app import log
|
||||
from app.but import bulletin_but
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models import FormSemestre, Identite, ModuleImplInscription
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc import html_sco_header
|
||||
|
@ -60,9 +49,9 @@ from app.scodoc import sco_abs
|
|||
from app.scodoc import sco_abs_views
|
||||
from app.scodoc import sco_bulletins_generator
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
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_evaluation_db
|
||||
from app.scodoc import sco_formations
|
||||
|
@ -73,7 +62,9 @@ from app.scodoc import sco_photos
|
|||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_pvjury
|
||||
from app.scodoc import sco_users
|
||||
from app import email
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType, fmt_note
|
||||
import app.scodoc.notesdb as ndb
|
||||
|
||||
# ----- CLASSES DE BULLETINS DE NOTES
|
||||
from app.scodoc import sco_bulletins_standard
|
||||
|
@ -85,33 +76,20 @@ from app.scodoc import sco_bulletins_legacy
|
|||
from app.scodoc import sco_bulletins_ucac # format expérimental UCAC Cameroun
|
||||
|
||||
|
||||
def make_context_dict(sem, etud):
|
||||
def make_context_dict(formsemestre: FormSemestre, etud: dict) -> dict:
|
||||
"""Construit dictionnaire avec valeurs pour substitution des textes
|
||||
(preferences bul_pdf_*)
|
||||
"""
|
||||
C = sem.copy()
|
||||
C["responsable"] = " ,".join(
|
||||
[
|
||||
sco_users.user_info(responsable_id)["prenomnom"]
|
||||
for responsable_id in sem["responsables"]
|
||||
]
|
||||
)
|
||||
|
||||
annee_debut = sem["date_debut"].split("/")[2]
|
||||
annee_fin = sem["date_fin"].split("/")[2]
|
||||
if annee_debut != annee_fin:
|
||||
annee = "%s - %s" % (annee_debut, annee_fin)
|
||||
else:
|
||||
annee = annee_debut
|
||||
C["anneesem"] = annee
|
||||
C = formsemestre.get_infos_dict()
|
||||
C["responsable"] = formsemestre.responsables_str()
|
||||
C["anneesem"] = C["annee"] # backward compat
|
||||
C.update(etud)
|
||||
# copie preferences
|
||||
# XXX devrait acceder directement à un dict de preferences, à revoir
|
||||
for name in sco_preferences.get_base_preferences().prefs_name:
|
||||
C[name] = sco_preferences.get_preference(name, sem["formsemestre_id"])
|
||||
C[name] = sco_preferences.get_preference(name, formsemestre.id)
|
||||
|
||||
# ajoute groupes et group_0, group_1, ...
|
||||
sco_groups.etud_add_group_infos(etud, sem)
|
||||
sco_groups.etud_add_group_infos(etud, formsemestre.id)
|
||||
C["groupes"] = etud["groupes"]
|
||||
n = 0
|
||||
for partition_id in etud["partitions"]:
|
||||
|
@ -132,7 +110,8 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
Le contenu du dictionnaire dépend des options (rangs, ...)
|
||||
et de la version choisie (short, long, selectedevals).
|
||||
|
||||
Cette fonction est utilisée pour les bulletins HTML et PDF, mais pas ceux en XML.
|
||||
Cette fonction est utilisée pour les bulletins CLASSIQUES (DUT, ...)
|
||||
en HTML et PDF, mais pas ceux en XML.
|
||||
"""
|
||||
from app.scodoc import sco_abs
|
||||
|
||||
|
@ -190,39 +169,23 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
show_mention=prefs["bul_show_mention"],
|
||||
)
|
||||
|
||||
if dpv:
|
||||
I["decision_sem"] = dpv["decisions"][0]["decision_sem"]
|
||||
else:
|
||||
I["decision_sem"] = ""
|
||||
I.update(infos)
|
||||
|
||||
I["etud_etat_html"] = _get_etud_etat_html(
|
||||
formsemestre.etuds_inscriptions[etudid].etat
|
||||
)
|
||||
I["etud_etat"] = nt.get_etud_etat(etudid)
|
||||
I["filigranne"] = ""
|
||||
I["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
||||
I["etud_etat"], prefs, decision_sem=I["decision_sem"]
|
||||
)
|
||||
I["demission"] = ""
|
||||
if I["etud_etat"] == "D":
|
||||
if I["etud_etat"] == scu.DEMISSION:
|
||||
I["demission"] = "(Démission)"
|
||||
I["filigranne"] = "Démission"
|
||||
elif I["etud_etat"] == sco_codes_parcours.DEF:
|
||||
I["demission"] = "(Défaillant)"
|
||||
I["filigranne"] = "Défaillant"
|
||||
elif (prefs["bul_show_temporary"] and not I["decision_sem"]) or prefs[
|
||||
"bul_show_temporary_forced"
|
||||
]:
|
||||
I["filigranne"] = prefs["bul_temporary_txt"]
|
||||
|
||||
# --- Appreciations
|
||||
cnx = ndb.GetDBConnexion()
|
||||
apprecs = sco_etud.appreciations_list(
|
||||
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||
)
|
||||
I["appreciations_list"] = apprecs
|
||||
I["appreciations_txt"] = [x["date"] + ": " + x["comment"] for x in apprecs]
|
||||
I["appreciations"] = I[
|
||||
"appreciations_txt"
|
||||
] # deprecated / keep it for backward compat in templates
|
||||
I.update(get_appreciations_list(formsemestre_id, etudid))
|
||||
|
||||
# --- Notes
|
||||
ues = nt.get_ues_stat_dict()
|
||||
|
@ -291,15 +254,17 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
I["matieres_modules"] = {}
|
||||
I["matieres_modules_capitalized"] = {}
|
||||
for ue in ues:
|
||||
u = ue.copy()
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
if (
|
||||
ModuleImplInscription.nb_inscriptions_dans_ue(
|
||||
formsemestre_id, etudid, ue["ue_id"]
|
||||
)
|
||||
== 0
|
||||
):
|
||||
) and not ue_status["is_capitalized"]:
|
||||
# saute les UE où l'on est pas inscrit et n'avons pas de capitalisation
|
||||
continue
|
||||
u = ue.copy()
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
|
||||
u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...}
|
||||
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
||||
u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"])
|
||||
|
@ -314,14 +279,14 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
else:
|
||||
u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs"
|
||||
else:
|
||||
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x
|
||||
u["cur_moy_ue_txt"] = f"bonus de {fmt_note(x)} points"
|
||||
if nt.bonus_ues is not None:
|
||||
u["cur_moy_ue_txt"] += " (+ues)"
|
||||
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
|
||||
if ue_status["coef_ue"] != None:
|
||||
u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"])
|
||||
else:
|
||||
# C'est un bug:
|
||||
log("u=" + pprint.pformat(u))
|
||||
raise Exception("invalid None coef for ue")
|
||||
u["coef_ue_txt"] = "-"
|
||||
|
||||
if (
|
||||
dpv
|
||||
|
@ -405,13 +370,28 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
|
||||
|
||||
#
|
||||
C = make_context_dict(I["sem"], I["etud"])
|
||||
C = make_context_dict(formsemestre, I["etud"])
|
||||
C.update(I)
|
||||
#
|
||||
# log( 'C = \n%s\n' % pprint.pformat(C) ) # tres pratique pour voir toutes les infos dispo
|
||||
return C
|
||||
|
||||
|
||||
def get_appreciations_list(formsemestre_id: int, etudid: int) -> dict:
|
||||
"""Appréciations pour cet étudiant dans ce semestre"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
apprecs = sco_etud.appreciations_list(
|
||||
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||
)
|
||||
d = {
|
||||
"appreciations_list": apprecs,
|
||||
"appreciations_txt": [x["date"] + ": " + x["comment"] for x in apprecs],
|
||||
}
|
||||
# deprecated / keep it for backward compat in templates:
|
||||
d["appreciations"] = d["appreciations_txt"]
|
||||
return d
|
||||
|
||||
|
||||
def _get_etud_etat_html(etat: str) -> str:
|
||||
"""chaine html représentant l'état (backward compat sco7)"""
|
||||
if etat == scu.INSCRIT: # "I"
|
||||
|
@ -439,7 +419,9 @@ def _sort_mod_by_matiere(modlist, nt, etudid):
|
|||
return matmod
|
||||
|
||||
|
||||
def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
|
||||
def _ue_mod_bulletin(
|
||||
etudid, formsemestre_id, ue_id, modimpls, nt: NotesTableCompat, version
|
||||
):
|
||||
"""Infos sur les modules (et évaluations) dans une UE
|
||||
(ajoute les informations aux modimpls)
|
||||
Result: liste de modules de l'UE avec les infos dans chacun (seulement ceux où l'étudiant est inscrit).
|
||||
|
@ -687,6 +669,7 @@ def etud_descr_situation_semestre(
|
|||
descr_defaillance : "Défaillant" ou vide si non défaillant.
|
||||
decision_jury : "Validé", "Ajourné", ... (code semestre)
|
||||
descr_decision_jury : "Décision jury: Validé" (une phrase)
|
||||
decision_sem :
|
||||
decisions_ue : noms (acronymes) des UE validées, séparées par des virgules.
|
||||
descr_decisions_ue : ' UE acquises: UE1, UE2', ou vide si pas de dec. ou si pas show_uevalid
|
||||
descr_mention : 'Mention Bien', ou vide si pas de mention ou si pas show_mention
|
||||
|
@ -696,7 +679,7 @@ def etud_descr_situation_semestre(
|
|||
|
||||
# --- Situation et décisions jury
|
||||
|
||||
# demission/inscription ?
|
||||
# démission/inscription ?
|
||||
events = sco_etud.scolar_events_list(
|
||||
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||
)
|
||||
|
@ -763,11 +746,15 @@ def etud_descr_situation_semestre(
|
|||
infos["situation"] += " " + infos["descr_defaillance"]
|
||||
|
||||
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid])
|
||||
if dpv:
|
||||
infos["decision_sem"] = dpv["decisions"][0]["decision_sem"]
|
||||
else:
|
||||
infos["decision_sem"] = ""
|
||||
|
||||
if not show_decisions:
|
||||
return infos, dpv
|
||||
|
||||
# Decisions de jury:
|
||||
# Décisions de jury:
|
||||
pv = dpv["decisions"][0]
|
||||
dec = ""
|
||||
if pv["decision_sem_descr"]:
|
||||
|
@ -806,24 +793,21 @@ def etud_descr_situation_semestre(
|
|||
def formsemestre_bulletinetud(
|
||||
etudid=None,
|
||||
formsemestre_id=None,
|
||||
format="html",
|
||||
format=None,
|
||||
version="long",
|
||||
xml_with_decisions=False,
|
||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||
prefer_mail_perso=False,
|
||||
):
|
||||
"page bulletin de notes"
|
||||
try:
|
||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
||||
etudid = etud["etudid"]
|
||||
except:
|
||||
sco_etud.log_unknown_etud()
|
||||
raise ScoValueError("étudiant inconnu")
|
||||
# API, donc erreurs admises en ScoValueError
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||
format = format or "html"
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
if not formsemestre:
|
||||
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
|
||||
|
||||
bulletin = do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre,
|
||||
etudid,
|
||||
format=format,
|
||||
version=version,
|
||||
|
@ -832,52 +816,22 @@ def formsemestre_bulletinetud(
|
|||
prefer_mail_perso=prefer_mail_perso,
|
||||
)[0]
|
||||
if format not in {"html", "pdfmail"}:
|
||||
filename = scu.bul_filename(sem, etud, format)
|
||||
filename = scu.bul_filename(formsemestre, etud, format)
|
||||
return scu.send_file(bulletin, filename, mime=scu.get_mime_suffix(format)[0])
|
||||
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
_formsemestre_bulletinetud_header_html(
|
||||
etud, etudid, sem, formsemestre_id, format, version
|
||||
),
|
||||
_formsemestre_bulletinetud_header_html(etud, formsemestre, format, version),
|
||||
bulletin,
|
||||
render_template(
|
||||
"bul_foot.html",
|
||||
etud=etud,
|
||||
formsemestre=formsemestre,
|
||||
inscription_courante=etud.inscription_courante(),
|
||||
inscription_str=etud.inscription_descr()["inscription_str"],
|
||||
),
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
|
||||
H.append("""<p>Situation actuelle: """)
|
||||
if etud["inscription_formsemestre_id"]:
|
||||
H.append(
|
||||
f"""<a class="stdlink" href="{url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=etud["inscription_formsemestre_id"])
|
||||
}">"""
|
||||
)
|
||||
H.append(etud["inscriptionstr"])
|
||||
if etud["inscription_formsemestre_id"]:
|
||||
H.append("""</a>""")
|
||||
H.append("""</p>""")
|
||||
if sem["modalite"] == "EXT":
|
||||
H.append(
|
||||
"""<p><a
|
||||
href="formsemestre_ext_edit_ue_validations?formsemestre_id=%s&etudid=%s"
|
||||
class="stdlink">
|
||||
Editer les validations d'UE dans ce semestre extérieur
|
||||
</a></p>"""
|
||||
% (formsemestre_id, etudid)
|
||||
)
|
||||
# Place du diagramme radar
|
||||
H.append(
|
||||
"""<form id="params">
|
||||
<input type="hidden" name="etudid" id="etudid" value="%s"/>
|
||||
<input type="hidden" name="formsemestre_id" id="formsemestre_id" value="%s"/>
|
||||
</form>"""
|
||||
% (etudid, formsemestre_id)
|
||||
)
|
||||
H.append('<div id="radar_bulletin"></div>')
|
||||
|
||||
# --- Pied de page
|
||||
H.append(html_sco_header.sco_footer())
|
||||
|
||||
return "".join(H)
|
||||
|
||||
|
||||
|
@ -892,23 +846,24 @@ def can_send_bulletin_by_mail(formsemestre_id):
|
|||
|
||||
|
||||
def do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
formsemestre: FormSemestre,
|
||||
etudid: int,
|
||||
version="long", # short, long, selectedevals
|
||||
format="html",
|
||||
format=None,
|
||||
nohtml=False,
|
||||
xml_with_decisions=False, # force decisions dans XML
|
||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||
prefer_mail_perso=False, # mails envoyes sur adresse perso si non vide
|
||||
xml_with_decisions=False, # force décisions dans XML
|
||||
force_publishing=False, # force publication meme si semestre non publié sur "portail"
|
||||
prefer_mail_perso=False, # mails envoyés sur adresse perso si non vide
|
||||
):
|
||||
"""Génère le bulletin au format demandé.
|
||||
Retourne: (bul, filigranne)
|
||||
où bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
|
||||
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
|
||||
"""
|
||||
format = format or "html"
|
||||
if format == "xml":
|
||||
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre.id,
|
||||
etudid,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
|
@ -919,7 +874,7 @@ def do_formsemestre_bulletinetud(
|
|||
|
||||
elif format == "json":
|
||||
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre.id,
|
||||
etudid,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
|
@ -927,8 +882,13 @@ def do_formsemestre_bulletinetud(
|
|||
)
|
||||
return bul, ""
|
||||
|
||||
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid)
|
||||
etud = I["etud"]
|
||||
if formsemestre.formation.is_apc():
|
||||
etud = Identite.query.get(etudid)
|
||||
r = bulletin_but.BulletinBUT(formsemestre)
|
||||
I = r.bulletin_etud_complet(etud, version=version)
|
||||
else:
|
||||
I = formsemestre_bulletinetud_dict(formsemestre.id, etudid)
|
||||
etud = I["etud"]
|
||||
|
||||
if format == "html":
|
||||
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
|
||||
|
@ -954,7 +914,7 @@ def do_formsemestre_bulletinetud(
|
|||
elif format == "pdfmail":
|
||||
# format pdfmail: envoie le pdf par mail a l'etud, et affiche le html
|
||||
# check permission
|
||||
if not can_send_bulletin_by_mail(formsemestre_id):
|
||||
if not can_send_bulletin_by_mail(formsemestre.id):
|
||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||
|
||||
if nohtml:
|
||||
|
@ -983,7 +943,7 @@ def do_formsemestre_bulletinetud(
|
|||
) + htm
|
||||
return h, I["filigranne"]
|
||||
#
|
||||
mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr)
|
||||
mail_bulletin(formsemestre.id, I, pdfdata, filename, recipient_addr)
|
||||
emaillink = '<a class="stdlink" href="mailto:%s">%s</a>' % (
|
||||
recipient_addr,
|
||||
recipient_addr,
|
||||
|
@ -1011,11 +971,16 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
|
|||
intro_mail = sco_preferences.get_preference("bul_intro_mail", formsemestre_id)
|
||||
|
||||
if intro_mail:
|
||||
hea = intro_mail % {
|
||||
"nomprenom": etud["nomprenom"],
|
||||
"dept": dept,
|
||||
"webmaster": webmaster,
|
||||
}
|
||||
try:
|
||||
hea = intro_mail % {
|
||||
"nomprenom": etud["nomprenom"],
|
||||
"dept": dept,
|
||||
"webmaster": webmaster,
|
||||
}
|
||||
except KeyError as e:
|
||||
raise ScoValueError(
|
||||
"format 'Message d'accompagnement' (bul_intro_mail) invalide, revoir les réglages dans les préférences"
|
||||
) from e
|
||||
else:
|
||||
hea = ""
|
||||
|
||||
|
@ -1031,81 +996,32 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
|
|||
bcc = copy_addr.strip()
|
||||
else:
|
||||
bcc = ""
|
||||
msg = Message(subject, sender=sender, recipients=recipients, bcc=[bcc])
|
||||
msg.body = hea
|
||||
|
||||
# Attach pdf
|
||||
msg.attach(filename, scu.PDF_MIMETYPE, pdfdata)
|
||||
log("mail bulletin a %s" % recipient_addr)
|
||||
email.send_message(msg)
|
||||
email.send_email(
|
||||
subject,
|
||||
sender,
|
||||
recipients,
|
||||
bcc=[bcc],
|
||||
text_body=hea,
|
||||
attachments=[
|
||||
{"filename": filename, "mimetype": scu.PDF_MIMETYPE, "data": pdfdata}
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _formsemestre_bulletinetud_header_html(
|
||||
etud,
|
||||
etudid,
|
||||
sem,
|
||||
formsemestre_id=None,
|
||||
format=None,
|
||||
version=None,
|
||||
):
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Bulletin de %(nomprenom)s" % etud,
|
||||
javascripts=[
|
||||
"js/bulletin.js",
|
||||
"libjs/d3.v3.min.js",
|
||||
"js/radar_bulletin.js",
|
||||
],
|
||||
cssstyles=["css/radar_bulletin.css"],
|
||||
),
|
||||
"""<table class="bull_head"><tr><td>
|
||||
<h2><a class="discretelink" href="%s">%s</a></h2>
|
||||
"""
|
||||
% (
|
||||
url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||
),
|
||||
etud["nomprenom"],
|
||||
),
|
||||
"""
|
||||
<form name="f" method="GET" action="%s">"""
|
||||
% request.base_url,
|
||||
f"""Bulletin <span class="bull_liensemestre"><a href="{
|
||||
url_for("notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=sem["formsemestre_id"])}
|
||||
">{sem["titremois"]}</a></span>
|
||||
<br/>"""
|
||||
% sem,
|
||||
"""<table><tr>""",
|
||||
"""<td>établi le %s (notes sur 20)</td>""" % time.strftime("%d/%m/%Y à %Hh%M"),
|
||||
"""<td><span class="rightjust">
|
||||
<input type="hidden" name="formsemestre_id" value="%s"></input>"""
|
||||
% formsemestre_id,
|
||||
"""<input type="hidden" name="etudid" value="%s"></input>""" % etudid,
|
||||
"""<input type="hidden" name="format" value="%s"></input>""" % format,
|
||||
"""<select name="version" onchange="document.f.submit()" class="noprint">""",
|
||||
]
|
||||
for (v, e) in (
|
||||
("short", "Version courte"),
|
||||
("selectedevals", "Version intermédiaire"),
|
||||
("long", "Version complète"),
|
||||
):
|
||||
if v == version:
|
||||
selected = " selected"
|
||||
else:
|
||||
selected = ""
|
||||
H.append('<option value="%s"%s>%s</option>' % (v, selected, e))
|
||||
H.append("""</select></td>""")
|
||||
# Menu
|
||||
endpoint = "notes.formsemestre_bulletinetud"
|
||||
|
||||
menuBul = [
|
||||
def make_menu_autres_operations(
|
||||
formsemestre: FormSemestre, etud: Identite, endpoint: str, version: str
|
||||
) -> str:
|
||||
etud_email = etud.get_first_email() or ""
|
||||
etud_perso = etud.get_first_email("emailperso") or ""
|
||||
menu_items = [
|
||||
{
|
||||
"title": "Réglages bulletins",
|
||||
"endpoint": "notes.formsemestre_edit_options",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
# "target_url": url_for(
|
||||
# "notes.formsemestre_bulletinetud",
|
||||
# scodoc_dept=g.scodoc_dept,
|
||||
|
@ -1113,54 +1029,52 @@ def _formsemestre_bulletinetud_header_html(
|
|||
# etudid=etudid,
|
||||
# ),
|
||||
},
|
||||
"enabled": (current_user.id in sem["responsables"])
|
||||
or current_user.has_permission(Permission.ScoImplement),
|
||||
"enabled": formsemestre.can_be_edited_by(current_user),
|
||||
},
|
||||
{
|
||||
"title": 'Version papier (pdf, format "%s")'
|
||||
% sco_bulletins_generator.bulletin_get_class_name_displayed(
|
||||
formsemestre_id
|
||||
formsemestre.id
|
||||
),
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
"title": "Envoi par mail à %s" % etud["email"],
|
||||
"title": f"Envoi par mail à {etud_email}",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "pdfmail",
|
||||
},
|
||||
# possible slt si on a un mail...
|
||||
"enabled": etud["email"] and can_send_bulletin_by_mail(formsemestre_id),
|
||||
"enabled": etud_email and can_send_bulletin_by_mail(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": "Envoi par mail à %s (adr. personnelle)" % etud["emailperso"],
|
||||
"title": f"Envoi par mail à {etud_perso} (adr. personnelle)",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "pdfmail",
|
||||
"prefer_mail_perso": 1,
|
||||
},
|
||||
# possible slt si on a un mail...
|
||||
"enabled": etud["emailperso"]
|
||||
and can_send_bulletin_by_mail(formsemestre_id),
|
||||
"enabled": etud_perso and can_send_bulletin_by_mail(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": "Version json",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "json",
|
||||
},
|
||||
|
@ -1169,8 +1083,8 @@ def _formsemestre_bulletinetud_header_html(
|
|||
"title": "Version XML",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "xml",
|
||||
},
|
||||
|
@ -1179,20 +1093,20 @@ def _formsemestre_bulletinetud_header_html(
|
|||
"title": "Ajouter une appréciation",
|
||||
"endpoint": "notes.appreciation_add_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": (
|
||||
(current_user.id in sem["responsables"])
|
||||
or (current_user.has_permission(Permission.ScoEtudInscrit))
|
||||
formsemestre.can_be_edited_by(current_user)
|
||||
or current_user.has_permission(Permission.ScoEtudInscrit)
|
||||
),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer un semestre effectué ailleurs",
|
||||
"endpoint": "notes.formsemestre_ext_create_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": current_user.has_permission(Permission.ScoImplement),
|
||||
},
|
||||
|
@ -1200,71 +1114,72 @@ def _formsemestre_bulletinetud_header_html(
|
|||
"title": "Enregistrer une validation d'UE antérieure",
|
||||
"endpoint": "notes.formsemestre_validate_previous_ue",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre_id),
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer note d'une UE externe",
|
||||
"endpoint": "notes.external_ue_create_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre_id),
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": "Entrer décisions jury",
|
||||
"endpoint": "notes.formsemestre_validation_etud_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre_id),
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": "Editer PV jury",
|
||||
"title": "Éditer PV jury",
|
||||
"endpoint": "notes.formsemestre_pvjury_pdf",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": True,
|
||||
},
|
||||
]
|
||||
return htmlutils.make_menu("Autres opérations", menu_items, alone=True)
|
||||
|
||||
H.append("""<td class="bulletin_menubar"><div class="bulletin_menubar">""")
|
||||
H.append(htmlutils.make_menu("Autres opérations", menuBul, alone=True))
|
||||
H.append("""</div></td>""")
|
||||
H.append(
|
||||
'<td> <a href="%s">%s</a></td>'
|
||||
% (
|
||||
url_for(
|
||||
"notes.formsemestre_bulletinetud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
etudid=etudid,
|
||||
format="pdf",
|
||||
|
||||
def _formsemestre_bulletinetud_header_html(
|
||||
etud,
|
||||
formsemestre: FormSemestre,
|
||||
format=None,
|
||||
version=None,
|
||||
):
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"Bulletin de {etud.nomprenom}",
|
||||
javascripts=[
|
||||
"js/bulletin.js",
|
||||
"libjs/d3.v3.min.js",
|
||||
"js/radar_bulletin.js",
|
||||
],
|
||||
cssstyles=["css/radar_bulletin.css"],
|
||||
),
|
||||
render_template(
|
||||
"bul_head.html",
|
||||
etud=etud,
|
||||
format=format,
|
||||
formsemestre=formsemestre,
|
||||
menu_autres_operations=make_menu_autres_operations(
|
||||
etud=etud,
|
||||
formsemestre=formsemestre,
|
||||
endpoint="notes.formsemestre_bulletinetud",
|
||||
version=version,
|
||||
),
|
||||
scu.ICON_PDF,
|
||||
)
|
||||
)
|
||||
H.append("""</tr></table>""")
|
||||
#
|
||||
H.append(
|
||||
"""</form></span></td><td class="bull_photo"><a href="%s">%s</a>
|
||||
"""
|
||||
% (
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||
sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"]),
|
||||
)
|
||||
)
|
||||
H.append(
|
||||
"""</td></tr>
|
||||
</table>
|
||||
"""
|
||||
)
|
||||
|
||||
return "".join(H)
|
||||
scu=scu,
|
||||
time=time,
|
||||
version=version,
|
||||
),
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
|
|
@ -63,48 +63,15 @@ from app.scodoc import sco_pdf
|
|||
from app.scodoc.sco_pdf import PDFLOCK
|
||||
import sco_version
|
||||
|
||||
# Liste des types des classes de générateurs de bulletins PDF:
|
||||
BULLETIN_CLASSES = collections.OrderedDict()
|
||||
|
||||
|
||||
def register_bulletin_class(klass):
|
||||
BULLETIN_CLASSES[klass.__name__] = klass
|
||||
|
||||
|
||||
def bulletin_class_descriptions():
|
||||
return [x.description for x in BULLETIN_CLASSES.values()]
|
||||
|
||||
|
||||
def bulletin_class_names():
|
||||
return list(BULLETIN_CLASSES.keys())
|
||||
|
||||
|
||||
def bulletin_default_class_name():
|
||||
return bulletin_class_names()[0]
|
||||
|
||||
|
||||
def bulletin_get_class(class_name):
|
||||
return BULLETIN_CLASSES[class_name]
|
||||
|
||||
|
||||
def bulletin_get_class_name_displayed(formsemestre_id):
|
||||
"""Le nom du générateur utilisé, en clair"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
try:
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
return gen_class.description
|
||||
except:
|
||||
return "invalide ! (voir paramètres)"
|
||||
|
||||
|
||||
class BulletinGenerator(object):
|
||||
class BulletinGenerator:
|
||||
"Virtual superclass for PDF bulletin generators" ""
|
||||
# Here some helper methods
|
||||
# see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods
|
||||
supported_formats = [] # should list supported formats, eg [ 'html', 'pdf' ]
|
||||
description = "superclass for bulletins" # description for user interface
|
||||
list_in_menu = True # la classe doit-elle est montrée dans le menu de config ?
|
||||
scale_table_in_page = True # rescale la table sur 1 page
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -151,7 +118,7 @@ class BulletinGenerator(object):
|
|||
def get_filename(self):
|
||||
"""Build a filename to be proposed to the web client"""
|
||||
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
|
||||
return scu.bul_filename(sem, self.infos["etud"], "pdf")
|
||||
return scu.bul_filename_old(sem, self.infos["etud"], "pdf")
|
||||
|
||||
def generate(self, format="", stand_alone=True):
|
||||
"""Return bulletin in specified format"""
|
||||
|
@ -197,8 +164,9 @@ class BulletinGenerator(object):
|
|||
# signatures
|
||||
objects += self.bul_signatures_pdf() # pylint: disable=no-member
|
||||
|
||||
# Réduit sur une page
|
||||
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
|
||||
if self.scale_table_in_page:
|
||||
# Réduit sur une page
|
||||
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
|
||||
#
|
||||
if not stand_alone:
|
||||
objects.append(PageBreak()) # insert page break at end
|
||||
|
@ -253,7 +221,7 @@ class BulletinGenerator(object):
|
|||
# ---------------------------------------------------------------------------
|
||||
def make_formsemestre_bulletinetud(
|
||||
infos,
|
||||
version="long", # short, long, selectedevals
|
||||
version=None, # short, long, selectedevals
|
||||
format="pdf", # html, pdf
|
||||
stand_alone=True,
|
||||
):
|
||||
|
@ -265,14 +233,20 @@ def make_formsemestre_bulletinetud(
|
|||
"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
version = version or "long"
|
||||
if not version in scu.BULLETINS_VERSIONS:
|
||||
raise ValueError("invalid version code !")
|
||||
|
||||
formsemestre_id = infos["formsemestre_id"]
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
try:
|
||||
|
||||
gen_class = None
|
||||
if infos.get("type") == "BUT" and format.startswith("pdf"):
|
||||
gen_class = bulletin_get_class(bul_class_name + "BUT")
|
||||
if gen_class is None:
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
except:
|
||||
|
||||
if gen_class is None:
|
||||
raise ValueError(
|
||||
"Type de bulletin PDF invalide (paramètre: %s)" % bul_class_name
|
||||
)
|
||||
|
@ -313,3 +287,52 @@ def make_formsemestre_bulletinetud(
|
|||
filename = bul_generator.get_filename()
|
||||
|
||||
return data, filename
|
||||
|
||||
|
||||
####
|
||||
|
||||
# Liste des types des classes de générateurs de bulletins PDF:
|
||||
BULLETIN_CLASSES = collections.OrderedDict()
|
||||
|
||||
|
||||
def register_bulletin_class(klass):
|
||||
BULLETIN_CLASSES[klass.__name__] = klass
|
||||
|
||||
|
||||
def bulletin_class_descriptions():
|
||||
return [
|
||||
BULLETIN_CLASSES[class_name].description
|
||||
for class_name in BULLETIN_CLASSES
|
||||
if BULLETIN_CLASSES[class_name].list_in_menu
|
||||
]
|
||||
|
||||
|
||||
def bulletin_class_names() -> list[str]:
|
||||
"Liste les noms des classes de bulletins à présenter à l'utilisateur"
|
||||
return [
|
||||
class_name
|
||||
for class_name in BULLETIN_CLASSES
|
||||
if BULLETIN_CLASSES[class_name].list_in_menu
|
||||
]
|
||||
|
||||
|
||||
def bulletin_default_class_name():
|
||||
return bulletin_class_names()[0]
|
||||
|
||||
|
||||
def bulletin_get_class(class_name: str) -> BulletinGenerator:
|
||||
"""La class de génération de bulletin de ce nom,
|
||||
ou None si pas trouvée
|
||||
"""
|
||||
return BULLETIN_CLASSES.get(class_name)
|
||||
|
||||
|
||||
def bulletin_get_class_name_displayed(formsemestre_id):
|
||||
"""Le nom du générateur utilisé, en clair"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
if gen_class is None:
|
||||
return "invalide ! (voir paramètres)"
|
||||
return gen_class.description
|
||||
|
|
|
@ -138,7 +138,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||
if not published:
|
||||
return d # stop !
|
||||
|
||||
etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||
etat_inscription = etud.inscription_etat(formsemestre.id)
|
||||
if etat_inscription != scu.INSCRIT:
|
||||
d.update(dict_decision_jury(etudid, formsemestre_id, with_decisions=True))
|
||||
return d
|
||||
|
|
|
@ -61,12 +61,10 @@ from reportlab.platypus.doctemplate import BaseDocTemplate
|
|||
from flask import g, request
|
||||
|
||||
from app import log, ScoValueError
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_etud
|
||||
|
@ -190,7 +188,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
|||
i = 1
|
||||
for etud in formsemestre.get_inscrits(include_demdef=True, order=True):
|
||||
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre,
|
||||
etud.id,
|
||||
format="pdfpart",
|
||||
version=version,
|
||||
|
@ -239,8 +237,9 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
|||
filigrannes = {}
|
||||
i = 1
|
||||
for sem in etud["sems"]:
|
||||
formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
|
||||
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
sem["formsemestre_id"],
|
||||
formsemestre,
|
||||
etudid,
|
||||
format="pdfpart",
|
||||
version=version,
|
||||
|
@ -275,3 +274,16 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
|||
)
|
||||
|
||||
return pdfdoc, filename
|
||||
|
||||
|
||||
def get_filigranne(etud_etat: str, prefs, decision_sem=None) -> str:
|
||||
"""Texte à placer en "filigranne" sur le bulletin pdf"""
|
||||
if etud_etat == scu.DEMISSION:
|
||||
return "Démission"
|
||||
elif etud_etat == sco_codes_parcours.DEF:
|
||||
return "Défaillant"
|
||||
elif (prefs["bul_show_temporary"] and not decision_sem) or prefs[
|
||||
"bul_show_temporary_forced"
|
||||
]:
|
||||
return prefs["bul_temporary_txt"]
|
||||
return ""
|
||||
|
|
|
@ -46,10 +46,11 @@ de la forme %(XXX)s sont remplacées par la valeur de XXX, pour XXX dans:
|
|||
Balises img: actuellement interdites.
|
||||
|
||||
"""
|
||||
from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.lib.colors import Color, blue
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_pdf import Color, Paragraph, Spacer, Table
|
||||
from app.scodoc.sco_pdf import blue, cm, mm
|
||||
from app.scodoc.sco_pdf import SU
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
@ -66,7 +67,8 @@ from app.scodoc import sco_groups
|
|||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import gen_tables
|
||||
|
||||
# Important: Le nom de la classe ne doit pas changer (bien le choisir), car il sera stocké en base de données (dans les préférences)
|
||||
# Important: Le nom de la classe ne doit pas changer (bien le choisir),
|
||||
# car il sera stocké en base de données (dans les préférences)
|
||||
class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
description = "standard ScoDoc (version 2011)" # la description doit être courte: elle apparait dans le menu de paramètrage ScoDoc
|
||||
supported_formats = ["html", "pdf"]
|
||||
|
@ -194,7 +196,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||
|
||||
# -----
|
||||
if format == "pdf":
|
||||
return Op
|
||||
return [KeepTogether(Op)]
|
||||
elif format == "html":
|
||||
return "\n".join(H)
|
||||
|
||||
|
@ -264,11 +266,11 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||
|
||||
def build_bulletin_table(self):
|
||||
"""Génère la table centrale du bulletin de notes
|
||||
Renvoie: colkeys, P, pdf_style, colWidths
|
||||
- colkeys: nom des colonnes de la table (clés)
|
||||
- table (liste de dicts de chaines de caracteres)
|
||||
- style (commandes table Platypus)
|
||||
- largeurs de colonnes pour PDF
|
||||
Renvoie: col_keys, P, pdf_style, col_widths
|
||||
- col_keys: nom des colonnes de la table (clés)
|
||||
- table: liste de dicts de chaines de caractères
|
||||
- pdf_style: commandes table Platypus
|
||||
- col_widths: largeurs de colonnes pour PDF
|
||||
"""
|
||||
I = self.infos
|
||||
P = [] # elems pour générer table avec gen_table (liste de dicts)
|
||||
|
@ -284,28 +286,28 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||
)
|
||||
with_col_moypromo = prefs["bul_show_moypromo"]
|
||||
with_col_rang = prefs["bul_show_rangs"]
|
||||
with_col_coef = prefs["bul_show_coef"]
|
||||
with_col_coef = prefs["bul_show_coef"] or prefs["bul_show_ue_coef"]
|
||||
with_col_ects = prefs["bul_show_ects"]
|
||||
|
||||
colkeys = ["titre", "module"] # noms des colonnes à afficher
|
||||
col_keys = ["titre", "module"] # noms des colonnes à afficher
|
||||
if with_col_rang:
|
||||
colkeys += ["rang"]
|
||||
col_keys += ["rang"]
|
||||
if with_col_minmax:
|
||||
colkeys += ["min"]
|
||||
col_keys += ["min"]
|
||||
if with_col_moypromo:
|
||||
colkeys += ["moy"]
|
||||
col_keys += ["moy"]
|
||||
if with_col_minmax:
|
||||
colkeys += ["max"]
|
||||
colkeys += ["note"]
|
||||
col_keys += ["max"]
|
||||
col_keys += ["note"]
|
||||
if with_col_coef:
|
||||
colkeys += ["coef"]
|
||||
col_keys += ["coef"]
|
||||
if with_col_ects:
|
||||
colkeys += ["ects"]
|
||||
col_keys += ["ects"]
|
||||
if with_col_abs:
|
||||
colkeys += ["abs"]
|
||||
col_keys += ["abs"]
|
||||
colidx = {} # { nom_colonne : indice à partir de 0 } (pour styles platypus)
|
||||
i = 0
|
||||
for k in colkeys:
|
||||
for k in col_keys:
|
||||
colidx[k] = i
|
||||
i += 1
|
||||
|
||||
|
@ -313,7 +315,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||
bul_pdf_mod_colwidth = float(prefs["bul_pdf_mod_colwidth"]) * cm
|
||||
else:
|
||||
bul_pdf_mod_colwidth = None
|
||||
colWidths = {
|
||||
col_widths = {
|
||||
"titre": None,
|
||||
"module": bul_pdf_mod_colwidth,
|
||||
"min": 1.5 * cm,
|
||||
|
@ -409,7 +411,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||
# Chaque UE:
|
||||
for ue in I["ues"]:
|
||||
ue_type = None
|
||||
coef_ue = ue["coef_ue_txt"]
|
||||
coef_ue = ue["coef_ue_txt"] if prefs["bul_show_ue_coef"] else ""
|
||||
ue_descr = ue["ue_descr_txt"]
|
||||
rowstyle = ""
|
||||
plusminus = minuslink #
|
||||
|
@ -541,7 +543,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
|
||||
]
|
||||
#
|
||||
return colkeys, P, pdf_style, colWidths
|
||||
return col_keys, P, pdf_style, col_widths
|
||||
|
||||
def _list_modules(
|
||||
self,
|
||||
|
@ -592,7 +594,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||
"_titre_colspan": 2,
|
||||
"rang": mod["mod_rang_txt"], # vide si pas option rang
|
||||
"note": mod["mod_moy_txt"],
|
||||
"coef": mod["mod_coef_txt"],
|
||||
"coef": mod["mod_coef_txt"] if prefs["bul_show_coef"] else "",
|
||||
"abs": mod.get(
|
||||
"mod_abs_txt", ""
|
||||
), # absent si pas option show abs module
|
||||
|
@ -656,7 +658,9 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||
eval_style = ""
|
||||
t = {
|
||||
"module": '<bullet indent="2mm">•</bullet> ' + e["name"],
|
||||
"coef": "<i>" + e["coef_txt"] + "</i>",
|
||||
"coef": ("<i>" + e["coef_txt"] + "</i>")
|
||||
if prefs["bul_show_coef"]
|
||||
else "",
|
||||
"_hidden": hidden,
|
||||
"_module_target": e["target_html"],
|
||||
# '_module_help' : ,
|
||||
|
|
|
@ -33,17 +33,12 @@
|
|||
"""
|
||||
|
||||
|
||||
# API ScoDoc8 pour les caches:
|
||||
# sco_cache.NotesTableCache.get( formsemestre_id)
|
||||
# => sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
# API pour les caches:
|
||||
# sco_cache.MyCache.get( formsemestre_id)
|
||||
# => sco_cache.MyCache.get(formsemestre_id)
|
||||
#
|
||||
# sco_core.inval_cache(formsemestre_id=None, pdfonly=False, formsemestre_id_list=None)
|
||||
# => deprecated, NotesTableCache.invalidate_formsemestre(formsemestre_id=None, pdfonly=False)
|
||||
#
|
||||
#
|
||||
# Nouvelles fonctions:
|
||||
# sco_cache.NotesTableCache.delete(formsemestre_id)
|
||||
# sco_cache.NotesTableCache.delete_many(formsemestre_id_list)
|
||||
# sco_cache.MyCache.delete(formsemestre_id)
|
||||
# sco_cache.MyCache.delete_many(formsemestre_id_list)
|
||||
#
|
||||
# Bulletins PDF:
|
||||
# sco_cache.SemBulletinsPDFCache.get(formsemestre_id, version)
|
||||
|
@ -203,49 +198,6 @@ class SemInscriptionsCache(ScoDocCache):
|
|||
duration = 12 * 60 * 60 # ttl 12h
|
||||
|
||||
|
||||
class NotesTableCache(ScoDocCache):
|
||||
"""Cache pour les NotesTable
|
||||
Clé: formsemestre_id
|
||||
Valeur: NotesTable instance
|
||||
"""
|
||||
|
||||
prefix = "NT"
|
||||
|
||||
@classmethod
|
||||
def get(cls, formsemestre_id, compute=True):
|
||||
"""Returns NotesTable for this formsemestre
|
||||
Search in local cache (g.nt_cache) or global app cache (eg REDIS)
|
||||
If not in cache:
|
||||
If compute is True, build it and cache it
|
||||
Else return None
|
||||
"""
|
||||
# try local cache (same request)
|
||||
if not hasattr(g, "nt_cache"):
|
||||
g.nt_cache = {}
|
||||
else:
|
||||
if formsemestre_id in g.nt_cache:
|
||||
return g.nt_cache[formsemestre_id]
|
||||
# try REDIS
|
||||
key = cls._get_key(formsemestre_id)
|
||||
nt = CACHE.get(key)
|
||||
if nt:
|
||||
g.nt_cache[formsemestre_id] = nt # cache locally (same request)
|
||||
return nt
|
||||
if not compute:
|
||||
return None
|
||||
# Recompute requested table:
|
||||
from app.scodoc import notes_table
|
||||
|
||||
t0 = time.time()
|
||||
nt = notes_table.NotesTable(formsemestre_id)
|
||||
t1 = time.time()
|
||||
_ = cls.set(formsemestre_id, nt) # cache in REDIS
|
||||
t2 = time.time()
|
||||
log(f"cached formsemestre_id={formsemestre_id} ({(t1-t0):g}s +{(t2-t1):g}s)")
|
||||
g.nt_cache[formsemestre_id] = nt
|
||||
return nt
|
||||
|
||||
|
||||
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
|
||||
formsemestre_id=None, pdfonly=False
|
||||
):
|
||||
|
@ -278,22 +230,24 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
|||
|
||||
if not pdfonly:
|
||||
# Delete cached notes and evaluations
|
||||
NotesTableCache.delete_many(formsemestre_ids)
|
||||
if formsemestre_id:
|
||||
for fid in formsemestre_ids:
|
||||
EvaluationCache.invalidate_sem(fid)
|
||||
if hasattr(g, "nt_cache") and fid in g.nt_cache:
|
||||
del g.nt_cache[fid]
|
||||
if (
|
||||
hasattr(g, "formsemestre_results_cache")
|
||||
and fid in g.formsemestre_results_cache
|
||||
):
|
||||
del g.formsemestre_results_cache[fid]
|
||||
|
||||
else:
|
||||
# optimization when we invalidate all evaluations:
|
||||
EvaluationCache.invalidate_all_sems()
|
||||
if hasattr(g, "nt_cache"):
|
||||
del g.nt_cache
|
||||
if hasattr(g, "formsemestre_results_cache"):
|
||||
del g.formsemestre_results_cache
|
||||
SemInscriptionsCache.delete_many(formsemestre_ids)
|
||||
|
||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
||||
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
||||
|
||||
|
||||
class DefferedSemCacheManager:
|
||||
|
|
|
@ -282,7 +282,7 @@ class TypeParcours(object):
|
|||
return [
|
||||
ue_status
|
||||
for ue_status in ues_status
|
||||
if ue_status["coef_ue"] > 0
|
||||
if ue_status["coef_ue"]
|
||||
and isinstance(ue_status["moy"], float)
|
||||
and ue_status["moy"] < self.get_barre_ue(ue_status["ue"]["type"])
|
||||
]
|
||||
|
@ -587,7 +587,7 @@ class ParcoursILEPS(TypeParcours):
|
|||
# SESSION_ABBRV = 'A' # A1, A2, ...
|
||||
COMPENSATION_UE = False
|
||||
UNUSED_CODES = set((ADC, ATT, ATB, ATJ))
|
||||
ALLOWED_UE_TYPES = [UE_STANDARD, UE_OPTIONNELLE]
|
||||
ALLOWED_UE_TYPES = [UE_STANDARD, UE_OPTIONNELLE, UE_SPORT]
|
||||
# Barre moy gen. pour validation semestre:
|
||||
BARRE_MOY = 10.0
|
||||
# Barre pour UE ILEPS: 8/20 pour UE standards ("fondamentales")
|
||||
|
|
|
@ -51,6 +51,7 @@ import fcntl
|
|||
import subprocess
|
||||
import requests
|
||||
|
||||
from flask import flash
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
|
@ -124,6 +125,7 @@ def sco_dump_and_send_db():
|
|||
fcntl.flock(x, fcntl.LOCK_UN)
|
||||
|
||||
log("sco_dump_and_send_db: done.")
|
||||
flash("Données envoyées au serveur d'assistance")
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
|
@ -186,18 +188,28 @@ def _send_db(ano_db_name):
|
|||
|
||||
log("uploading anonymized dump...")
|
||||
files = {"file": (ano_db_name + ".dump", dump)}
|
||||
r = requests.post(
|
||||
scu.SCO_DUMP_UP_URL,
|
||||
files=files,
|
||||
data={
|
||||
"dept_name": sco_preferences.get_preference("DeptName"),
|
||||
"serial": _get_scodoc_serial(),
|
||||
"sco_user": str(current_user),
|
||||
"sent_by": sco_users.user_info(str(current_user))["nomcomplet"],
|
||||
"sco_version": sco_version.SCOVERSION,
|
||||
"sco_fullversion": scu.get_scodoc_version(),
|
||||
},
|
||||
)
|
||||
try:
|
||||
r = requests.post(
|
||||
scu.SCO_DUMP_UP_URL,
|
||||
files=files,
|
||||
data={
|
||||
"dept_name": sco_preferences.get_preference("DeptName"),
|
||||
"serial": _get_scodoc_serial(),
|
||||
"sco_user": str(current_user),
|
||||
"sent_by": sco_users.user_info(str(current_user))["nomcomplet"],
|
||||
"sco_version": sco_version.SCOVERSION,
|
||||
"sco_fullversion": scu.get_scodoc_version(),
|
||||
},
|
||||
)
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
raise ScoValueError(
|
||||
"""
|
||||
Impossible de joindre le serveur d'assistance (scodoc.org).
|
||||
Veuillez contacter le service informatique de votre établissement pour
|
||||
corriger la configuration de ScoDoc. Dans la plupart des cas, il
|
||||
s'agit d'un proxy mal configuré.
|
||||
"""
|
||||
) from exc
|
||||
return r
|
||||
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ def html_edit_formation_apc(
|
|||
"""
|
||||
parcours = formation.get_parcours()
|
||||
assert parcours.APC_SAE
|
||||
|
||||
ressources = formation.modules.filter_by(module_type=ModuleType.RESSOURCE).order_by(
|
||||
Module.semestre_id, Module.numero, Module.code
|
||||
)
|
||||
|
@ -68,6 +69,19 @@ def html_edit_formation_apc(
|
|||
).order_by(
|
||||
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
||||
)
|
||||
|
||||
ues_by_sem = {}
|
||||
ects_by_sem = {}
|
||||
for semestre_idx in semestre_ids:
|
||||
ues_by_sem[semestre_idx] = formation.ues.filter_by(
|
||||
semestre_idx=semestre_idx
|
||||
).order_by(UniteEns.semestre_idx, UniteEns.numero, UniteEns.acronyme)
|
||||
ects = [ue.ects for ue in ues_by_sem[semestre_idx]]
|
||||
if None in ects:
|
||||
ects_by_sem[semestre_idx] = '<span class="missing_ue_ects">manquant</span>'
|
||||
else:
|
||||
ects_by_sem[semestre_idx] = sum(ects)
|
||||
|
||||
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
|
||||
|
||||
icons = {
|
||||
|
@ -93,7 +107,8 @@ def html_edit_formation_apc(
|
|||
editable=editable,
|
||||
tag_editable=tag_editable,
|
||||
icons=icons,
|
||||
UniteEns=UniteEns,
|
||||
ues_by_sem=ues_by_sem,
|
||||
ects_by_sem=ects_by_sem,
|
||||
),
|
||||
]
|
||||
for semestre_idx in semestre_ids:
|
||||
|
|
|
@ -500,13 +500,20 @@ def module_edit(module_id=None):
|
|||
matieres = matieres.filter(UniteEns.semestre_idx == a_module.ue.semestre_idx)
|
||||
|
||||
if is_apc:
|
||||
# ne conserve que la 1ere matière de chaque UE,
|
||||
# et celle à laquelle ce module est rattaché
|
||||
matieres = [
|
||||
mat
|
||||
for mat in matieres
|
||||
if a_module.matiere.id == mat.id or mat.id == mat.ue.matieres.first().id
|
||||
]
|
||||
mat_names = [
|
||||
"S%s / %s" % (mat.ue.semestre_idx, mat.ue.acronyme) for mat in matieres
|
||||
]
|
||||
else:
|
||||
mat_names = ["%s / %s" % (mat.ue.acronyme, mat.titre or "") for mat in matieres]
|
||||
ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres]
|
||||
|
||||
ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres]
|
||||
module["ue_matiere_id"] = "%s!%s" % (module["ue_id"], module["matiere_id"])
|
||||
|
||||
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
||||
|
@ -544,14 +551,18 @@ def module_edit(module_id=None):
|
|||
# ne propose pas SAE et Ressources, sauf si déjà de ce type...
|
||||
module_types = (
|
||||
set(scu.ModuleType) - {scu.ModuleType.RESSOURCE, scu.ModuleType.SAE}
|
||||
) | {a_module.module_type or scu.ModuleType.STANDARD}
|
||||
) | {
|
||||
scu.ModuleType(a_module.module_type)
|
||||
if a_module.module_type
|
||||
else scu.ModuleType.STANDARD
|
||||
}
|
||||
|
||||
descr = [
|
||||
(
|
||||
"code",
|
||||
{
|
||||
"size": 10,
|
||||
"explanation": "code du module (doit être unique dans la formation)",
|
||||
"explanation": "code du module (issu du programme, exemple M1203 ou R2.01. Doit être unique dans la formation)",
|
||||
"allow_null": False,
|
||||
"validator": lambda val, field, formation_id=formation_id: check_module_code_unicity(
|
||||
val, field, formation_id, module_id=module_id
|
||||
|
@ -690,7 +701,10 @@ def module_edit(module_id=None):
|
|||
{
|
||||
"title": "Code Apogée",
|
||||
"size": 25,
|
||||
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
||||
"explanation": """(optionnel) code élément pédagogique Apogée ou liste de codes ELP
|
||||
séparés par des virgules (ce code est propre à chaque établissement, se rapprocher
|
||||
du référent Apogée).
|
||||
""",
|
||||
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
||||
},
|
||||
),
|
||||
|
@ -734,8 +748,11 @@ def module_edit(module_id=None):
|
|||
else:
|
||||
# l'UE de rattachement peut changer
|
||||
tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!")
|
||||
x, y = tf[2]["ue_matiere_id"].split("!")
|
||||
tf[2]["ue_id"] = int(x)
|
||||
tf[2]["matiere_id"] = int(y)
|
||||
old_ue_id = a_module.ue.id
|
||||
new_ue_id = int(tf[2]["ue_id"])
|
||||
new_ue_id = tf[2]["ue_id"]
|
||||
if (old_ue_id != new_ue_id) and in_use:
|
||||
new_ue = UniteEns.query.get_or_404(new_ue_id)
|
||||
if new_ue.semestre_idx != a_module.ue.semestre_idx:
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
"""
|
||||
import flask
|
||||
from flask import url_for, render_template
|
||||
from flask import flash, render_template, url_for
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
|
@ -89,6 +89,7 @@ _ueEditor = ndb.EditableTable(
|
|||
input_formators={
|
||||
"type": ndb.int_null_is_zero,
|
||||
"is_external": ndb.bool_or_str,
|
||||
"ects": ndb.float_null_is_null,
|
||||
},
|
||||
output_formators={
|
||||
"numero": ndb.int_null_is_zero,
|
||||
|
@ -107,8 +108,6 @@ def ue_list(*args, **kw):
|
|||
|
||||
def do_ue_create(args):
|
||||
"create an ue"
|
||||
from app.scodoc import sco_formations
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
# check duplicates
|
||||
ues = ue_list({"formation_id": args["formation_id"], "acronyme": args["acronyme"]})
|
||||
|
@ -117,6 +116,14 @@ def do_ue_create(args):
|
|||
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
|
||||
(chaque UE doit avoir un acronyme unique dans la formation)"""
|
||||
)
|
||||
if not "ue_code" in args:
|
||||
# évite les conflits de code
|
||||
while True:
|
||||
cursor = db.session.execute("select notes_newid_ucod();")
|
||||
code = cursor.fetchone()[0]
|
||||
if UniteEns.query.filter_by(ue_code=code).count() == 0:
|
||||
break
|
||||
args["ue_code"] = code
|
||||
# create
|
||||
ue_id = _ueEditor.create(cnx, args)
|
||||
|
||||
|
@ -128,6 +135,8 @@ def do_ue_create(args):
|
|||
formation = Formation.query.get(args["formation_id"])
|
||||
formation.invalidate_module_coefs()
|
||||
# news
|
||||
ue = UniteEns.query.get(ue_id)
|
||||
flash(f"UE créée (code {ue.ue_code})")
|
||||
formation = Formation.query.get(args["formation_id"])
|
||||
sco_news.add(
|
||||
typ=sco_news.NEWS_FORM,
|
||||
|
@ -296,7 +305,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||
(
|
||||
"numero",
|
||||
{
|
||||
"size": 2,
|
||||
"size": 4,
|
||||
"explanation": "numéro (1,2,3,4) de l'UE pour l'ordre d'affichage",
|
||||
"type": "int",
|
||||
},
|
||||
|
@ -339,6 +348,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||
"type": "float",
|
||||
"title": "ECTS",
|
||||
"explanation": "nombre de crédits ECTS",
|
||||
"allow_null": not is_apc, # ects requis en APC
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -462,8 +472,10 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||
"semestre_id": tf[2]["semestre_idx"],
|
||||
},
|
||||
)
|
||||
flash("UE créée")
|
||||
else:
|
||||
do_ue_edit(tf[2])
|
||||
flash("UE modifiée")
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.ue_table",
|
||||
|
@ -601,7 +613,12 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
|||
_add_ue_semestre_id(ues_externes, is_apc)
|
||||
ues.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
||||
ues_externes.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
||||
has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ues])) != len(ues)
|
||||
# Codes dupliqués (pour aider l'utilisateur)
|
||||
seen = set()
|
||||
duplicated_codes = {
|
||||
ue["ue_code"] for ue in ues if ue["ue_code"] in seen or seen.add(ue["ue_code"])
|
||||
}
|
||||
ues_with_duplicated_code = [ue for ue in ues if ue["ue_code"] in duplicated_codes]
|
||||
|
||||
has_perm_change = current_user.has_permission(Permission.ScoChangeFormation)
|
||||
# editable = (not locked) and has_perm_change
|
||||
|
@ -664,11 +681,17 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||
if msg:
|
||||
H.append('<p class="msg">' + msg + "</p>")
|
||||
|
||||
if has_duplicate_ue_codes:
|
||||
if ues_with_duplicated_code:
|
||||
H.append(
|
||||
"""<div class="ue_warning"><span>Attention: plusieurs UE de cette
|
||||
formation ont le même code. Il faut corriger cela ci-dessous,
|
||||
sinon les calculs d'ECTS seront erronés !</span></div>"""
|
||||
f"""<div class="ue_warning"><span>Attention: plusieurs UE de cette
|
||||
formation ont le même code : <tt>{
|
||||
', '.join([
|
||||
'<a class="stdlink" href="' + url_for( "notes.ue_edit", scodoc_dept=g.scodoc_dept, ue_id=ue["ue_id"] )
|
||||
+ '">' + ue["acronyme"] + " (code " + ue["ue_code"] + ")</a>"
|
||||
for ue in ues_with_duplicated_code ])
|
||||
}</tt>.
|
||||
Il faut corriger cela, sinon les capitalisations et ECTS seront
|
||||
erronés !</span></div>"""
|
||||
)
|
||||
|
||||
# Description de la formation
|
||||
|
@ -699,16 +722,16 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||
<a href="{url_for('notes.refcomp_show',
|
||||
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id)}">
|
||||
{formation.referentiel_competence.type_titre} {formation.referentiel_competence.specialite_long}
|
||||
</a> """
|
||||
</a> """
|
||||
msg_refcomp = "changer"
|
||||
H.append(
|
||||
f"""
|
||||
<ul>
|
||||
<li>{descr_refcomp} <a class="stdlink" href="{url_for('notes.refcomp_assoc_formation',
|
||||
<li>{descr_refcomp} <a class="stdlink" href="{url_for('notes.refcomp_assoc_formation',
|
||||
scodoc_dept=g.scodoc_dept, formation_id=formation_id)
|
||||
}">{msg_refcomp}</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
<li> <a class="stdlink" href="{
|
||||
url_for('notes.edit_modules_ue_coefs', scodoc_dept=g.scodoc_dept, formation_id=formation_id, semestre_idx=semestre_idx)
|
||||
}">éditer les coefficients des ressources et SAÉs</a>
|
||||
</li>
|
||||
|
@ -735,6 +758,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||
)
|
||||
)
|
||||
else:
|
||||
H.append('<div class="formation_classic_infos">')
|
||||
H.append(
|
||||
_ue_table_ues(
|
||||
parcours,
|
||||
|
@ -764,7 +788,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||
</ul>
|
||||
"""
|
||||
)
|
||||
|
||||
H.append("</div>")
|
||||
H.append("</div>") # formation_ue_list
|
||||
|
||||
if ues_externes:
|
||||
|
@ -913,10 +937,10 @@ def _ue_table_ues(
|
|||
cur_ue_semestre_id = None
|
||||
iue = 0
|
||||
for ue in ues:
|
||||
if ue["ects"]:
|
||||
ue["ects_str"] = ", %g ECTS" % ue["ects"]
|
||||
else:
|
||||
if ue["ects"] is None:
|
||||
ue["ects_str"] = ""
|
||||
else:
|
||||
ue["ects_str"] = ", %g ECTS" % ue["ects"]
|
||||
if editable:
|
||||
klass = "span_apo_edit"
|
||||
else:
|
||||
|
@ -930,13 +954,13 @@ def _ue_table_ues(
|
|||
|
||||
if cur_ue_semestre_id != ue["semestre_id"]:
|
||||
cur_ue_semestre_id = ue["semestre_id"]
|
||||
if iue > 0:
|
||||
H.append("</ul>")
|
||||
if ue["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
|
||||
lab = "Pas d'indication de semestre:"
|
||||
else:
|
||||
lab = "Semestre %s:" % ue["semestre_id"]
|
||||
H.append('<div class="ue_list_tit_sem">%s</div>' % lab)
|
||||
H.append(
|
||||
'<div class="ue_list_div"><div class="ue_list_tit_sem">%s</div>' % lab
|
||||
)
|
||||
H.append('<ul class="notes_ue_list">')
|
||||
H.append('<li class="notes_ue_list">')
|
||||
if iue != 0 and editable:
|
||||
|
@ -953,7 +977,6 @@ def _ue_table_ues(
|
|||
)
|
||||
else:
|
||||
H.append(arrow_none)
|
||||
iue += 1
|
||||
ue["acro_titre"] = str(ue["acronyme"])
|
||||
if ue["titre"] != ue["acronyme"]:
|
||||
ue["acro_titre"] += " " + str(ue["titre"])
|
||||
|
@ -1001,6 +1024,16 @@ def _ue_table_ues(
|
|||
delete_disabled_icon,
|
||||
)
|
||||
)
|
||||
if (iue >= len(ues) - 1) or ue["semestre_id"] != ues[iue + 1]["semestre_id"]:
|
||||
H.append(
|
||||
f"""</ul><ul><li><a href="{url_for('notes.ue_create', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=ue['formation_id'], semestre_idx=ue['semestre_id'])
|
||||
}">Ajouter une UE dans le semestre {ue['semestre_id'] or ''}</a></li></ul>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
iue += 1
|
||||
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
|
@ -1268,7 +1301,6 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
|
|||
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
|
||||
(chaque UE doit avoir un acronyme unique dans la formation)"""
|
||||
)
|
||||
|
||||
# On ne peut pas supprimer le code UE:
|
||||
if "ue_code" in args and not args["ue_code"]:
|
||||
del args["ue_code"]
|
||||
|
|
|
@ -53,7 +53,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||
|
||||
|
||||
def apo_semset_maq_status(
|
||||
semset_id="",
|
||||
semset_id: int,
|
||||
allow_missing_apo=False,
|
||||
allow_missing_decisions=False,
|
||||
allow_missing_csv=False,
|
||||
|
@ -65,7 +65,7 @@ def apo_semset_maq_status(
|
|||
):
|
||||
"""Page statut / tableau de bord"""
|
||||
if not semset_id:
|
||||
raise ValueError("invalid null semset_id")
|
||||
raise ScoValueError("invalid null semset_id")
|
||||
semset = sco_semset.SemSet(semset_id=semset_id)
|
||||
semset.fill_formsemestres()
|
||||
# autorise export meme si etudiants Apo manquants:
|
||||
|
|
|
@ -33,8 +33,7 @@ import os
|
|||
import time
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import url_for, g, request
|
||||
from flask_mail import Message
|
||||
from flask import url_for, g
|
||||
|
||||
from app import email
|
||||
from app import log
|
||||
|
@ -46,7 +45,6 @@ from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
|||
from app.scodoc import safehtml
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
|
||||
|
||||
def format_etud_ident(etud):
|
||||
|
@ -860,7 +858,7 @@ def list_scolog(etudid):
|
|||
return cursor.dictfetchall()
|
||||
|
||||
|
||||
def fill_etuds_info(etuds, add_admission=True):
|
||||
def fill_etuds_info(etuds: list[dict], add_admission=True):
|
||||
"""etuds est une liste d'etudiants (mappings)
|
||||
Pour chaque etudiant, ajoute ou formatte les champs
|
||||
-> informations pour fiche etudiant ou listes diverses
|
||||
|
@ -977,7 +975,10 @@ def etud_inscriptions_infos(etudid: int, ne="") -> dict:
|
|||
|
||||
|
||||
def descr_situation_etud(etudid: int, ne="") -> str:
|
||||
"""chaîne décrivant la situation actuelle de l'étudiant"""
|
||||
"""Chaîne décrivant la situation actuelle de l'étudiant
|
||||
XXX Obsolete, utiliser Identite.descr_situation_etud() dans
|
||||
les nouveaux codes
|
||||
"""
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
|
|
|
@ -405,7 +405,6 @@ def formsemestre_evaluations_cal(formsemestre_id):
|
|||
"""Page avec calendrier de toutes les evaluations de ce semestre"""
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
sem = formsemestre.to_dict()
|
||||
|
||||
evals = nt.get_evaluations_etats()
|
||||
nb_evals = len(evals)
|
||||
|
@ -416,8 +415,8 @@ def formsemestre_evaluations_cal(formsemestre_id):
|
|||
|
||||
today = time.strftime("%Y-%m-%d")
|
||||
|
||||
year = int(sem["annee_debut"])
|
||||
if sem["mois_debut_ord"] < 8:
|
||||
year = formsemestre.date_debut.year
|
||||
if formsemestre.date_debut.month < 8:
|
||||
year -= 1 # calendrier septembre a septembre
|
||||
events = {} # (day, halfday) : event
|
||||
for e in evals:
|
||||
|
@ -537,11 +536,10 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
|||
"""Experimental: un tableau indiquant pour chaque évaluation
|
||||
le nombre de jours avant la publication des notes.
|
||||
|
||||
N'indique pas les évaluations de ratrapage ni celles des modules de bonus/malus.
|
||||
N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus.
|
||||
"""
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
sem = formsemestre.to_dict()
|
||||
|
||||
evals = nt.get_evaluations_etats()
|
||||
T = []
|
||||
|
@ -607,7 +605,7 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
|||
origin="Généré par %s le " % sco_version.SCONAME
|
||||
+ scu.timedate_human_repr()
|
||||
+ "",
|
||||
filename=scu.make_filename("evaluations_delais_" + sem["titreannee"]),
|
||||
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
|
||||
)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
|
@ -635,16 +633,13 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
|
|||
'<span class="evallink"><a class="stdlink" href="evaluation_listenotes?moduleimpl_id=%s">voir toutes les notes du module</a></span>'
|
||||
% moduleimpl_id
|
||||
)
|
||||
mod_descr = (
|
||||
'<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s'
|
||||
% (
|
||||
moduleimpl_id,
|
||||
Mod["code"] or "",
|
||||
Mod["titre"] or "?",
|
||||
nomcomplet,
|
||||
resp,
|
||||
link,
|
||||
)
|
||||
mod_descr = '<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s' % (
|
||||
moduleimpl_id,
|
||||
Mod["code"] or "",
|
||||
Mod["titre"] or "?",
|
||||
nomcomplet,
|
||||
resp,
|
||||
link,
|
||||
)
|
||||
|
||||
etit = E["description"] or ""
|
||||
|
|
|
@ -39,6 +39,7 @@ from app.scodoc.gen_tables import GenTable
|
|||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_exceptions import ScoException
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
|
@ -179,7 +180,9 @@ def search_etud_in_dept(expnom=""):
|
|||
e["_nomprenom_target"] = target
|
||||
e["inscription_target"] = target
|
||||
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
|
||||
sco_groups.etud_add_group_infos(e, e["cursem"])
|
||||
sco_groups.etud_add_group_infos(
|
||||
e, e["cursem"]["formsemestre_id"] if e["cursem"] else None
|
||||
)
|
||||
|
||||
tab = GenTable(
|
||||
columns_ids=("nomprenom", "code_nip", "inscription", "groupes"),
|
||||
|
@ -221,7 +224,10 @@ def search_etuds_infos(expnom=None, code_nip=None):
|
|||
cnx = ndb.GetDBConnexion()
|
||||
if expnom and not may_be_nip:
|
||||
expnom = expnom.upper() # les noms dans la BD sont en uppercase
|
||||
etuds = sco_etud.etudident_list(cnx, args={"nom": expnom}, test="~")
|
||||
try:
|
||||
etuds = sco_etud.etudident_list(cnx, args={"nom": expnom}, test="~")
|
||||
except ScoException:
|
||||
etuds = []
|
||||
else:
|
||||
code_nip = code_nip or expnom
|
||||
if code_nip:
|
||||
|
|
|
@ -151,8 +151,14 @@ def formation_export(
|
|||
if mod["ects"] is None:
|
||||
del mod["ects"]
|
||||
|
||||
filename = f"scodoc_formation_{formation.departement.acronym}_{formation.acronyme or ''}_v{formation.version}"
|
||||
return scu.sendResult(
|
||||
F, name="formation", format=format, force_outer_xml_tag=False, attached=True
|
||||
F,
|
||||
name="formation",
|
||||
format=format,
|
||||
force_outer_xml_tag=False,
|
||||
attached=True,
|
||||
filename=filename,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ def formsemestre_createwithmodules():
|
|||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Création d'un semestre",
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
),
|
||||
|
@ -99,7 +99,7 @@ def formsemestre_editwithmodules(formsemestre_id):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Modification du semestre",
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
)
|
||||
|
@ -213,7 +213,10 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
# en APC, ne permet pas de changer de semestre
|
||||
semestre_id_list = [formsemestre.semestre_id]
|
||||
else:
|
||||
semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
|
||||
semestre_id_list = list(range(1, NB_SEM + 1))
|
||||
if not formation.is_apc():
|
||||
# propose "pas de semestre" seulement en classique
|
||||
semestre_id_list.insert(0, -1)
|
||||
|
||||
semestre_id_labels = []
|
||||
for sid in semestre_id_list:
|
||||
|
@ -341,6 +344,9 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
"explanation": "en BUT, on ne peut pas modifier le semestre après création"
|
||||
if formation.is_apc()
|
||||
else "",
|
||||
"attributes": ['onchange="change_semestre_id();"']
|
||||
if formation.is_apc()
|
||||
else "",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
@ -493,7 +499,8 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
{
|
||||
"input_type": "boolcheckbox",
|
||||
"title": "",
|
||||
"explanation": "Autoriser tous les enseignants associés à un module à y créer des évaluations",
|
||||
"explanation": """Autoriser tous les enseignants associés
|
||||
à un module à y créer des évaluations""",
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -534,11 +541,19 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
]
|
||||
|
||||
nbmod = 0
|
||||
if edit:
|
||||
templ_sep = "<tr><td>%(label)s</td><td><b>Responsable</b></td><td><b>Inscrire</b></td></tr>"
|
||||
else:
|
||||
templ_sep = "<tr><td>%(label)s</td><td><b>Responsable</b></td></tr>"
|
||||
|
||||
for semestre_id in semestre_ids:
|
||||
if formation.is_apc():
|
||||
# pour restreindre l'édition aux module du semestre sélectionné
|
||||
tr_class = f'class="sem{semestre_id}"'
|
||||
else:
|
||||
tr_class = ""
|
||||
if edit:
|
||||
templ_sep = f"""<tr {tr_class}><td>%(label)s</td><td><b>Responsable</b></td><td><b>Inscrire</b></td></tr>"""
|
||||
else:
|
||||
templ_sep = (
|
||||
f"""<tr {tr_class}><td>%(label)s</td><td><b>Responsable</b></td></tr>"""
|
||||
)
|
||||
modform.append(
|
||||
(
|
||||
"sep",
|
||||
|
@ -588,12 +603,12 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
)
|
||||
fcg += "</select>"
|
||||
itemtemplate = (
|
||||
"""<tr><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td><td>"""
|
||||
f"""<tr {tr_class}><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td><td>"""
|
||||
+ fcg
|
||||
+ "</td></tr>"
|
||||
)
|
||||
else:
|
||||
itemtemplate = """<tr><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td></tr>"""
|
||||
itemtemplate = f"""<tr {tr_class}><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td></tr>"""
|
||||
modform.append(
|
||||
(
|
||||
"MI" + str(mod["module_id"]),
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask import url_for
|
||||
from flask import render_template, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app import log
|
||||
|
@ -411,7 +411,7 @@ def formsemestre_status_menubar(sem):
|
|||
"enabled": sco_permissions_check.can_validate_sem(formsemestre_id),
|
||||
},
|
||||
{
|
||||
"title": "Editer les PV et archiver les résultats",
|
||||
"title": "Éditer les PV et archiver les résultats",
|
||||
"endpoint": "notes.formsemestre_archive",
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
"enabled": sco_permissions_check.can_edit_pv(formsemestre_id),
|
||||
|
@ -445,6 +445,7 @@ def retreive_formsemestre_from_request() -> int:
|
|||
"""Cherche si on a de quoi déduire le semestre affiché à partir des
|
||||
arguments de la requête:
|
||||
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
|
||||
Returns None si pas défini.
|
||||
"""
|
||||
if request.method == "GET":
|
||||
args = request.args
|
||||
|
@ -505,34 +506,17 @@ def formsemestre_page_title():
|
|||
return ""
|
||||
try:
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id).copy()
|
||||
formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||
except:
|
||||
log("can't find formsemestre_id %s" % formsemestre_id)
|
||||
return ""
|
||||
|
||||
fill_formsemestre(sem)
|
||||
|
||||
h = f"""<div class="formsemestre_page_title">
|
||||
<div class="infos">
|
||||
<span class="semtitle"><a class="stdlink" title="{sem['session_id']}"
|
||||
href="{url_for('notes.formsemestre_status',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=sem['formsemestre_id'])}"
|
||||
>{sem['titre']}</a><a
|
||||
title="{sem['etape_apo_str']}">{sem['num_sem']}</a>{sem['modalitestr']}</span><span
|
||||
class="dates"><a
|
||||
title="du {sem['date_debut']} au {sem['date_fin']} "
|
||||
>{sem['mois_debut']} - {sem['mois_fin']}</a></span><span
|
||||
class="resp"><a title="{sem['nomcomplet']}">{sem['resp']}</a></span><span
|
||||
class="nbinscrits"><a class="discretelink"
|
||||
href="{url_for("scolar.groups_view",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=sem['formsemestre_id'])}"
|
||||
>{sem['nbinscrits']} inscrits</a></span><span
|
||||
class="lock">{sem['locklink']}</span><span
|
||||
class="eye">{sem['eyelink']}</span>
|
||||
</div>
|
||||
{formsemestre_status_menubar(sem)}
|
||||
</div>
|
||||
"""
|
||||
h = render_template(
|
||||
"formsemestre_page_title.html",
|
||||
formsemestre=formsemestre,
|
||||
scu=scu,
|
||||
sem_menu_bar=formsemestre_status_menubar(formsemestre.to_dict()),
|
||||
)
|
||||
|
||||
return h
|
||||
|
||||
|
@ -595,11 +579,12 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
|
|||
"""Description du semestre sous forme de table exportable
|
||||
Liste des modules et de leurs coefficients
|
||||
"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
F = sco_formations.formation_list(args={"formation_id": formsemestre.formation_id})[
|
||||
0
|
||||
]
|
||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
||||
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=formsemestre_id, sort_by_ue=True
|
||||
|
@ -709,7 +694,7 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
|
|||
titles["coefficient"] = "Coef. éval."
|
||||
titles["evalcomplete_str"] = "Complète"
|
||||
titles["publish_incomplete_str"] = "Toujours Utilisée"
|
||||
title = "%s %s" % (parcours.SESSION_NAME.capitalize(), sem["titremois"])
|
||||
title = "%s %s" % (parcours.SESSION_NAME.capitalize(), formsemestre.titre_mois())
|
||||
|
||||
return GenTable(
|
||||
columns_ids=columns_ids,
|
||||
|
@ -986,7 +971,6 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
|
|||
def formsemestre_status(formsemestre_id=None):
|
||||
"""Tableau de bord semestre HTML"""
|
||||
# porté du DTML
|
||||
cnx = ndb.GetDBConnexion()
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=formsemestre_id
|
||||
|
@ -1077,7 +1061,7 @@ def formsemestre_status(formsemestre_id=None):
|
|||
"</p>",
|
||||
]
|
||||
|
||||
if use_ue_coefs:
|
||||
if use_ue_coefs and not formsemestre.formation.is_apc():
|
||||
H.append(
|
||||
"""
|
||||
<p class="infop">utilise les coefficients d'UE pour calculer la moyenne générale.</p>
|
||||
|
|
|
@ -585,15 +585,17 @@ def formsemestre_recap_parcours_table(
|
|||
else:
|
||||
H.append('<td colspan="%d"><em>en cours</em></td>')
|
||||
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
|
||||
# acronymes UEs auxquelles l'étudiant est inscrit:
|
||||
# XXX il est probable que l'on doive ici ajouter les
|
||||
# XXX UE capitalisées
|
||||
# acronymes UEs auxquelles l'étudiant est inscrit (ou capitalisé)
|
||||
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
etud_ue_status = {
|
||||
ue["ue_id"]: nt.get_etud_ue_status(etudid, ue["ue_id"]) for ue in ues
|
||||
}
|
||||
ues = [
|
||||
ue
|
||||
for ue in ues
|
||||
if etud_est_inscrit_ue(cnx, etudid, sem["formsemestre_id"], ue["ue_id"])
|
||||
or etud_ue_status[ue["ue_id"]]["is_capitalized"]
|
||||
]
|
||||
|
||||
for ue in ues:
|
||||
|
@ -644,7 +646,7 @@ def formsemestre_recap_parcours_table(
|
|||
code = decisions_ue[ue["ue_id"]]["code"]
|
||||
else:
|
||||
code = ""
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
ue_status = etud_ue_status[ue["ue_id"]]
|
||||
moy_ue = ue_status["moy"] if ue_status else ""
|
||||
explanation_ue = [] # list of strings
|
||||
if code == ADM:
|
||||
|
@ -1250,7 +1252,7 @@ def check_formation_ues(formation_id):
|
|||
for ue in ues:
|
||||
# formsemestres utilisant cette ue ?
|
||||
sems = ndb.SimpleDictFetch(
|
||||
"""SELECT DISTINCT sem.id AS formsemestre_id, sem.*
|
||||
"""SELECT DISTINCT sem.id AS formsemestre_id, sem.*
|
||||
FROM notes_formsemestre sem, notes_modules mod, notes_moduleimpl mi
|
||||
WHERE sem.formation_id = %(formation_id)s
|
||||
AND mod.id = mi.module_id
|
||||
|
@ -1269,11 +1271,11 @@ def check_formation_ues(formation_id):
|
|||
return "", {}
|
||||
# Genere message HTML:
|
||||
H = [
|
||||
"""<div class="ue_warning"><span>Attention:</span> les UE suivantes de cette formation
|
||||
"""<div class="ue_warning"><span>Attention:</span> les UE suivantes de cette formation
|
||||
sont utilisées dans des
|
||||
semestres de rangs différents (eg S1 et S3). <br/>Cela peut engendrer des problèmes pour
|
||||
la capitalisation des UE. Il serait préférable d'essayer de rectifier cette situation:
|
||||
soit modifier le programme de la formation (définir des UE dans chaque semestre),
|
||||
semestres de rangs différents (eg S1 et S3). <br/>Cela peut engendrer des problèmes pour
|
||||
la capitalisation des UE. Il serait préférable d'essayer de rectifier cette situation:
|
||||
soit modifier le programme de la formation (définir des UE dans chaque semestre),
|
||||
soit veiller à saisir le bon indice de semestre dans le menu lors de la validation d'une
|
||||
UE extérieure.
|
||||
<ul>
|
||||
|
@ -1286,7 +1288,11 @@ def check_formation_ues(formation_id):
|
|||
for x in ue_multiples[ue["ue_id"]]
|
||||
]
|
||||
slist = ", ".join(
|
||||
["%(titreannee)s (<em>semestre %(semestre_id)s</em>)" % s for s in sems]
|
||||
[
|
||||
"""%(titreannee)s (<em>semestre <b class="fontred">%(semestre_id)s</b></em>)"""
|
||||
% s
|
||||
for s in sems
|
||||
]
|
||||
)
|
||||
H.append("<li><b>%s</b> : %s</li>" % (ue["acronyme"], slist))
|
||||
H.append("</ul></div>")
|
||||
|
|
|
@ -124,7 +124,7 @@ def get_partition(partition_id):
|
|||
{"partition_id": partition_id},
|
||||
)
|
||||
if not r:
|
||||
raise ValueError("invalid partition_id (%s)" % partition_id)
|
||||
raise ScoValueError(f"Partition inconnue (déjà supprimée ?) ({partition_id})")
|
||||
return r[0]
|
||||
|
||||
|
||||
|
@ -321,7 +321,7 @@ def get_group_infos(group_id, etat=None): # was _getlisteetud
|
|||
t["etath"] = t["etat"]
|
||||
# Add membership for all partitions, 'partition_id' : group
|
||||
for etud in members: # long: comment eviter ces boucles ?
|
||||
etud_add_group_infos(etud, sem)
|
||||
etud_add_group_infos(etud, sem["formsemestre_id"])
|
||||
|
||||
if group["group_name"] != None:
|
||||
group_tit = "%s %s" % (group["partition_name"], group["group_name"])
|
||||
|
@ -413,12 +413,12 @@ def formsemestre_get_etud_groupnames(formsemestre_id, attr="group_name"):
|
|||
return R
|
||||
|
||||
|
||||
def etud_add_group_infos(etud, sem, sep=" "):
|
||||
def etud_add_group_infos(etud, formsemestre_id, sep=" "):
|
||||
"""Add informations on partitions and group memberships to etud (a dict with an etudid)"""
|
||||
etud[
|
||||
"partitions"
|
||||
] = collections.OrderedDict() # partition_id : group + partition_name
|
||||
if not sem:
|
||||
if not formsemestre_id:
|
||||
etud["groupes"] = ""
|
||||
return etud
|
||||
|
||||
|
@ -430,7 +430,7 @@ def etud_add_group_infos(etud, sem, sep=" "):
|
|||
and p.formsemestre_id = %(formsemestre_id)s
|
||||
ORDER BY p.numero
|
||||
""",
|
||||
{"etudid": etud["etudid"], "formsemestre_id": sem["formsemestre_id"]},
|
||||
{"etudid": etud["etudid"], "formsemestre_id": formsemestre_id},
|
||||
)
|
||||
|
||||
for info in infos:
|
||||
|
@ -439,13 +439,13 @@ def etud_add_group_infos(etud, sem, sep=" "):
|
|||
|
||||
# resume textuel des groupes:
|
||||
etud["groupes"] = sep.join(
|
||||
[g["group_name"] for g in infos if g["group_name"] != None]
|
||||
[gr["group_name"] for gr in infos if gr["group_name"] is not None]
|
||||
)
|
||||
etud["partitionsgroupes"] = sep.join(
|
||||
[
|
||||
g["partition_name"] + ":" + g["group_name"]
|
||||
for g in infos
|
||||
if g["group_name"] != None
|
||||
gr["partition_name"] + ":" + gr["group_name"]
|
||||
for gr in infos
|
||||
if gr["group_name"] is not None
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 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
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Exports groupes
|
||||
"""
|
||||
from flask import request
|
||||
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
import app.scodoc.sco_utils as scu
|
||||
import sco_version
|
||||
|
||||
|
||||
def groups_list_annotation(group_ids: list[int]) -> list[dict]:
|
||||
"""Renvoie la liste des annotations pour les groupes d"étudiants indiqués
|
||||
Arg: liste des id de groupes
|
||||
Clés: etudid, ine, nip, nom, prenom, date, comment
|
||||
"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
annotations = []
|
||||
for group_id in group_ids:
|
||||
cursor.execute(
|
||||
"""SELECT i.id AS etudid, i.code_nip, i.code_ine, i.nom, i.prenom, ea.date, ea.comment
|
||||
FROM group_membership gm, identite i, etud_annotations ea
|
||||
WHERE gm.group_id=%(group_ids)s
|
||||
AND gm.etudid=i.id
|
||||
AND i.id=ea.etudid
|
||||
""",
|
||||
{"group_ids": group_id},
|
||||
)
|
||||
annotations += cursor.dictfetchall()
|
||||
return annotations
|
||||
|
||||
|
||||
def groups_export_annotations(group_ids, formsemestre_id=None, format="html"):
|
||||
"""Les annotations"""
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids, formsemestre_id=formsemestre_id
|
||||
)
|
||||
annotations = groups_list_annotation(groups_infos.group_ids)
|
||||
for annotation in annotations:
|
||||
annotation["date_str"] = annotation["date"].strftime("%d/%m/%Y à %Hh%M")
|
||||
if format == "xls":
|
||||
columns_ids = ("etudid", "nom", "prenom", "date", "comment")
|
||||
else:
|
||||
columns_ids = ("etudid", "nom", "prenom", "date_str", "comment")
|
||||
table = GenTable(
|
||||
rows=annotations,
|
||||
columns_ids=columns_ids,
|
||||
titles={
|
||||
"etudid": "etudid",
|
||||
"nom": "Nom",
|
||||
"prenom": "Prénom",
|
||||
"date": "Date",
|
||||
"date_str": "Date",
|
||||
"comment": "Annotation",
|
||||
},
|
||||
origin="Généré par %s le " % sco_version.SCONAME
|
||||
+ scu.timedate_human_repr()
|
||||
+ "",
|
||||
page_title=f"Annotations sur les étudiants de {groups_infos.groups_titles}",
|
||||
caption="Annotations",
|
||||
base_url=groups_infos.base_url,
|
||||
html_sortable=True,
|
||||
html_class="table_leftalign",
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
)
|
||||
return table.make_page(format=format)
|
|
@ -826,6 +826,8 @@ def tab_absences_html(groups_infos, etat=None):
|
|||
% groups_infos.groups_query_args,
|
||||
"""<li><a class="stdlink" href="trombino?%s&format=pdflist">Liste d'appel avec photos</a></li>"""
|
||||
% groups_infos.groups_query_args,
|
||||
"""<li><a class="stdlink" href="groups_export_annotations?%s">Liste des annotations</a></li>"""
|
||||
% groups_infos.groups_query_args,
|
||||
"</ul>",
|
||||
]
|
||||
)
|
||||
|
|
|
@ -203,7 +203,7 @@ def sco_import_generate_excel_sample(
|
|||
for field in titles:
|
||||
if field == "groupes":
|
||||
sco_groups.etud_add_group_infos(
|
||||
etud, groups_infos.formsemestre, sep=";"
|
||||
etud, groups_infos.formsemestre_id, sep=";"
|
||||
)
|
||||
l.append(etud["partitionsgroupes"])
|
||||
else:
|
||||
|
|
|
@ -196,7 +196,10 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
|||
if len(etud["sems"]) < 2:
|
||||
continue
|
||||
prev_formsemestre = etud["sems"][1]
|
||||
sco_groups.etud_add_group_infos(etud, prev_formsemestre)
|
||||
sco_groups.etud_add_group_infos(
|
||||
etud,
|
||||
prev_formsemestre["formsemestre_id"] if prev_formsemestre else None,
|
||||
)
|
||||
|
||||
cursem_groups_by_name = dict(
|
||||
[
|
||||
|
|
|
@ -151,6 +151,8 @@ class Logo:
|
|||
Le format est renseigné au moment de la lecture (select) ou de la création (create) de l'objet
|
||||
"""
|
||||
self.logoname = secure_filename(logoname)
|
||||
if not self.logoname:
|
||||
self.logoname = "*** *** nom de logo invalide *** à changer ! *** ***"
|
||||
self.scodoc_dept_id = dept_id
|
||||
self.prefix = prefix or ""
|
||||
if self.scodoc_dept_id:
|
||||
|
@ -276,7 +278,7 @@ class Logo:
|
|||
if self.mm is None:
|
||||
return f'<logo name="{self.logoname}" width="?? mm" height="?? mm">'
|
||||
else:
|
||||
return f'<logo name="{self.logoname}" width="{self.mm[0]}mm"">'
|
||||
return f'<logo name="{self.logoname}" width="{self.mm[0]}mm">'
|
||||
|
||||
def last_modified(self):
|
||||
path = Path(self.filepath)
|
||||
|
|
|
@ -305,7 +305,10 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
|||
if can_change:
|
||||
c_link = (
|
||||
'<a class="discretelink" href="moduleimpl_inscriptions_edit?moduleimpl_id=%s">%s</a>'
|
||||
% (mod["moduleimpl_id"], mod["descri"])
|
||||
% (
|
||||
mod["moduleimpl_id"],
|
||||
mod["descri"] or "<i>(inscrire des étudiants)</i>",
|
||||
)
|
||||
)
|
||||
else:
|
||||
c_link = mod["descri"]
|
||||
|
|
|
@ -215,7 +215,9 @@ def ficheEtud(etudid=None):
|
|||
info["modifadresse"] = ""
|
||||
|
||||
# Groupes:
|
||||
sco_groups.etud_add_group_infos(info, info["cursem"])
|
||||
sco_groups.etud_add_group_infos(
|
||||
info, info["cursem"]["formsemestre_id"] if info["cursem"] else None
|
||||
)
|
||||
|
||||
# Parcours de l'étudiant
|
||||
if info["sems"]:
|
||||
|
|
|
@ -139,9 +139,7 @@ class SituationEtudParcoursGeneric(object):
|
|||
# pour le DUT, le dernier est toujours S4.
|
||||
# Ici: terminal si semestre == NB_SEM ou bien semestre_id==-1
|
||||
# (licences et autres formations en 1 seule session))
|
||||
self.semestre_non_terminal = (
|
||||
self.sem["semestre_id"] != self.parcours.NB_SEM
|
||||
) # True | False
|
||||
self.semestre_non_terminal = self.sem["semestre_id"] != self.parcours.NB_SEM
|
||||
if self.sem["semestre_id"] == NO_SEMESTRE_ID:
|
||||
self.semestre_non_terminal = False
|
||||
# Liste des semestres du parcours de cet étudiant:
|
||||
|
|
|
@ -220,12 +220,16 @@ class ScolarsPageTemplate(PageTemplate):
|
|||
PageTemplate.__init__(self, "ScolarsPageTemplate", [content])
|
||||
self.logo = None
|
||||
logo = find_logo(
|
||||
logoname="bul_pdf_background", dept_id=g.scodoc_dept_id, prefix=None
|
||||
logoname="bul_pdf_background", dept_id=g.scodoc_dept_id
|
||||
) or find_logo(
|
||||
logoname="bul_pdf_background", dept_id=g.scodoc_dept_id, prefix=""
|
||||
)
|
||||
if logo is None:
|
||||
# Also try to use PV background
|
||||
logo = find_logo(
|
||||
logoname="letter_background", dept_id=g.scodoc_dept_id, prefix=None
|
||||
logoname="letter_background", dept_id=g.scodoc_dept_id
|
||||
) or find_logo(
|
||||
logoname="letter_background", dept_id=g.scodoc_dept_id, prefix=""
|
||||
)
|
||||
if logo is not None:
|
||||
self.background_image_filename = logo.filepath
|
||||
|
|
|
@ -47,6 +47,11 @@ _SCO_PERMISSIONS = (
|
|||
),
|
||||
(1 << 25, "RelationsEntreprisesSend", "Envoyer des offres"),
|
||||
(1 << 26, "RelationsEntreprisesValidate", "Valide les entreprises"),
|
||||
# Api scodoc9
|
||||
(1 << 27, "APIView", "Voir"),
|
||||
(1 << 28, "APIEtudChangeGroups", "Modifier les groupes"),
|
||||
(1 << 29, "APIEditAllNotes", "Modifier toutes les notes"),
|
||||
(1 << 30, "APIAbsChange", "Saisir des absences"),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -175,7 +175,7 @@ def etud_photo_is_local(etud: dict, size="small"):
|
|||
return photo_pathname(etud["photo_filename"], size=size)
|
||||
|
||||
|
||||
def etud_photo_html(etud=None, etudid=None, title=None, size="small"):
|
||||
def etud_photo_html(etud: dict = None, etudid=None, title=None, size="small"):
|
||||
"""HTML img tag for the photo, either in small size (h90)
|
||||
or original size (size=="orig")
|
||||
"""
|
||||
|
@ -351,7 +351,8 @@ def copy_portal_photo_to_fs(etud):
|
|||
"""Copy the photo from portal (distant website) to local fs.
|
||||
Returns rel. path or None if copy failed, with a diagnostic message
|
||||
"""
|
||||
sco_etud.format_etud_ident(etud)
|
||||
if "nomprenom" not in etud:
|
||||
sco_etud.format_etud_ident(etud)
|
||||
url = photo_portal_url(etud)
|
||||
if not url:
|
||||
return None, "%(nomprenom)s: pas de code NIP" % etud
|
||||
|
|
|
@ -245,6 +245,7 @@ PREF_CATEGORIES = (
|
|||
),
|
||||
("pe", {"title": "Avis de poursuites d'études"}),
|
||||
("edt", {"title": "Connexion avec le logiciel d'emplois du temps"}),
|
||||
("debug", {"title": "Tests / mise au point"}),
|
||||
)
|
||||
|
||||
|
||||
|
@ -1296,11 +1297,21 @@ class BasePreferences(object):
|
|||
"labels": ["non", "oui"],
|
||||
},
|
||||
),
|
||||
(
|
||||
"bul_show_ue_coef",
|
||||
{
|
||||
"initvalue": 1,
|
||||
"title": "Afficher coefficient des UE sur les bulletins",
|
||||
"input_type": "boolcheckbox",
|
||||
"category": "bul",
|
||||
"labels": ["non", "oui"],
|
||||
},
|
||||
),
|
||||
(
|
||||
"bul_show_coef",
|
||||
{
|
||||
"initvalue": 1,
|
||||
"title": "Afficher coefficient des ue/modules sur les bulletins",
|
||||
"title": "Afficher coefficient des modules sur les bulletins",
|
||||
"input_type": "boolcheckbox",
|
||||
"category": "bul",
|
||||
"labels": ["non", "oui"],
|
||||
|
@ -1849,6 +1860,19 @@ class BasePreferences(object):
|
|||
"category": "edt",
|
||||
},
|
||||
),
|
||||
(
|
||||
"email_test_mode_address",
|
||||
{
|
||||
"title": "Adresse de test",
|
||||
"initvalue": "",
|
||||
"explanation": """si cette adresse est indiquée, TOUS les mails
|
||||
envoyés par ScoDoc de ce département vont aller vers elle
|
||||
AU LIEU DE LEUR DESTINATION NORMALE !""",
|
||||
"size": 30,
|
||||
"category": "debug",
|
||||
"only_global": True,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
self.prefs_name = set([x[0] for x in self.prefs_definition])
|
||||
|
@ -2114,7 +2138,7 @@ class BasePreferences(object):
|
|||
return form
|
||||
|
||||
|
||||
class SemPreferences(object):
|
||||
class SemPreferences:
|
||||
"""Preferences for a formsemestre"""
|
||||
|
||||
def __init__(self, formsemestre_id=None):
|
||||
|
@ -2270,9 +2294,8 @@ def doc_preferences():
|
|||
return "\n".join([" | ".join(x) for x in L])
|
||||
|
||||
|
||||
def bulletin_option_affichage(formsemestre_id: int) -> dict:
|
||||
def bulletin_option_affichage(formsemestre_id: int, prefs: SemPreferences) -> dict:
|
||||
"dict avec les options d'affichages (préférences) pour ce semestre"
|
||||
prefs = SemPreferences(formsemestre_id)
|
||||
fields = (
|
||||
"bul_show_abs",
|
||||
"bul_show_abs_modules",
|
||||
|
|
|
@ -206,12 +206,18 @@ class CourrierIndividuelTemplate(PageTemplate):
|
|||
background = find_logo(
|
||||
logoname="pvjury_background",
|
||||
dept_id=g.scodoc_dept_id,
|
||||
) or find_logo(
|
||||
logoname="pvjury_background",
|
||||
dept_id=g.scodoc_dept_id,
|
||||
prefix="",
|
||||
)
|
||||
else:
|
||||
background = find_logo(
|
||||
logoname="letter_background",
|
||||
dept_id=g.scodoc_dept_id,
|
||||
) or find_logo(
|
||||
logoname="letter_background",
|
||||
dept_id=g.scodoc_dept_id,
|
||||
prefix="",
|
||||
)
|
||||
if not self.background_image_filename and background is not None:
|
||||
|
|
|
@ -53,6 +53,7 @@ SCO_ROLES_DEFAULTS = {
|
|||
p.ScoUsersAdmin,
|
||||
p.ScoUsersView,
|
||||
p.ScoView,
|
||||
p.APIView,
|
||||
),
|
||||
# RespPE est le responsable poursuites d'études
|
||||
# il peut ajouter des tags sur les formations:
|
||||
|
|
|
@ -854,23 +854,27 @@ def formsemestre_import_etud_admission(
|
|||
apo_emailperso = etud.get("mailperso", "")
|
||||
if info["emailperso"] and not apo_emailperso:
|
||||
apo_emailperso = info["emailperso"]
|
||||
if (
|
||||
import_email
|
||||
and info["email"] != etud["mail"]
|
||||
or info["emailperso"] != apo_emailperso
|
||||
):
|
||||
sco_etud.adresse_edit(
|
||||
cnx,
|
||||
args={
|
||||
"etudid": etudid,
|
||||
"adresse_id": info["adresse_id"],
|
||||
"email": etud["mail"],
|
||||
"emailperso": apo_emailperso,
|
||||
},
|
||||
)
|
||||
# notifie seulement les changements d'adresse mail institutionnelle
|
||||
if info["email"] != etud["mail"]:
|
||||
changed_mails.append((info, etud["mail"]))
|
||||
if import_email:
|
||||
if not "mail" in etud:
|
||||
raise ScoValueError(
|
||||
"la réponse portail n'a pas le champs requis 'mail'"
|
||||
)
|
||||
if (
|
||||
info["email"] != etud["mail"]
|
||||
or info["emailperso"] != apo_emailperso
|
||||
):
|
||||
sco_etud.adresse_edit(
|
||||
cnx,
|
||||
args={
|
||||
"etudid": etudid,
|
||||
"adresse_id": info["adresse_id"],
|
||||
"email": etud["mail"],
|
||||
"emailperso": apo_emailperso,
|
||||
},
|
||||
)
|
||||
# notifie seulement les changements d'adresse mail institutionnelle
|
||||
if info["email"] != etud["mail"]:
|
||||
changed_mails.append((info, etud["mail"]))
|
||||
else:
|
||||
unknowns.append(code_nip)
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=sem["formsemestre_id"])
|
||||
|
|
|
@ -50,7 +50,7 @@ import pydot
|
|||
import requests
|
||||
|
||||
from flask import g, request
|
||||
from flask import url_for, make_response, jsonify
|
||||
from flask import flash, url_for, make_response, jsonify
|
||||
|
||||
from config import Config
|
||||
from app import log
|
||||
|
@ -608,7 +608,7 @@ def is_valid_filename(filename):
|
|||
return VALID_EXP.match(filename)
|
||||
|
||||
|
||||
def bul_filename(sem, etud, format):
|
||||
def bul_filename_old(sem: dict, etud: dict, format):
|
||||
"""Build a filename for this bulletin"""
|
||||
dt = time.strftime("%Y-%m-%d")
|
||||
filename = f"bul-{sem['titre_num']}-{dt}-{etud['nom']}.{format}"
|
||||
|
@ -616,6 +616,24 @@ def bul_filename(sem, etud, format):
|
|||
return filename
|
||||
|
||||
|
||||
def bul_filename(formsemestre, etud, format):
|
||||
"""Build a filename for this bulletin"""
|
||||
dt = time.strftime("%Y-%m-%d")
|
||||
filename = f"bul-{formsemestre.titre_num()}-{dt}-{etud.nom}.{format}"
|
||||
filename = make_filename(filename)
|
||||
return filename
|
||||
|
||||
|
||||
def flash_errors(form):
|
||||
"""Flashes form errors (version sommaire)"""
|
||||
for field, errors in form.errors.items():
|
||||
flash(
|
||||
"Erreur: voir le champs %s" % (getattr(form, field).label.text,),
|
||||
"warning",
|
||||
)
|
||||
# see https://getbootstrap.com/docs/4.0/components/alerts/
|
||||
|
||||
|
||||
def sendCSVFile(data, filename): # DEPRECATED utiliser send_file
|
||||
"""publication fichier CSV."""
|
||||
return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True)
|
||||
|
@ -635,21 +653,30 @@ class ScoDocJSONEncoder(json.JSONEncoder):
|
|||
return json.JSONEncoder.default(self, o)
|
||||
|
||||
|
||||
def sendJSON(data, attached=False):
|
||||
def sendJSON(data, attached=False, filename=None):
|
||||
js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
|
||||
return send_file(
|
||||
js, filename="sco_data.json", mime=JSON_MIMETYPE, attached=attached
|
||||
js, filename=filename or "sco_data.json", mime=JSON_MIMETYPE, attached=attached
|
||||
)
|
||||
|
||||
|
||||
def sendXML(data, tagname=None, force_outer_xml_tag=True, attached=False, quote=True):
|
||||
def sendXML(
|
||||
data,
|
||||
tagname=None,
|
||||
force_outer_xml_tag=True,
|
||||
attached=False,
|
||||
quote=True,
|
||||
filename=None,
|
||||
):
|
||||
if type(data) != list:
|
||||
data = [data] # always list-of-dicts
|
||||
if force_outer_xml_tag:
|
||||
data = [{tagname: data}]
|
||||
tagname += "_list"
|
||||
doc = sco_xml.simple_dictlist2xml(data, tagname=tagname, quote=quote)
|
||||
return send_file(doc, filename="sco_data.xml", mime=XML_MIMETYPE, attached=attached)
|
||||
return send_file(
|
||||
doc, filename=filename or "sco_data.xml", mime=XML_MIMETYPE, attached=attached
|
||||
)
|
||||
|
||||
|
||||
def sendResult(
|
||||
|
@ -659,6 +686,7 @@ def sendResult(
|
|||
force_outer_xml_tag=True,
|
||||
attached=False,
|
||||
quote_xml=True,
|
||||
filename=None,
|
||||
):
|
||||
if (format is None) or (format == "html"):
|
||||
return data
|
||||
|
@ -669,9 +697,10 @@ def sendResult(
|
|||
force_outer_xml_tag=force_outer_xml_tag,
|
||||
attached=attached,
|
||||
quote=quote_xml,
|
||||
filename=filename,
|
||||
)
|
||||
elif format == "json":
|
||||
return sendJSON(data, attached=attached)
|
||||
return sendJSON(data, attached=attached, filename=filename)
|
||||
else:
|
||||
raise ValueError("invalid format: %s" % format)
|
||||
|
||||
|
@ -789,7 +818,7 @@ def abbrev_prenom(prenom):
|
|||
|
||||
#
|
||||
def timedate_human_repr():
|
||||
"representation du temps courant pour utilisateur: a localiser"
|
||||
"representation du temps courant pour utilisateur"
|
||||
return time.strftime("%d/%m/%Y à %Hh%M")
|
||||
|
||||
|
||||
|
|
|
@ -14,16 +14,25 @@
|
|||
}
|
||||
main{
|
||||
--couleurPrincipale: rgb(240,250,255);
|
||||
--couleurFondTitresUE: rgb(206,255,235);
|
||||
--couleurFondTitresRes: rgb(125, 170, 255);
|
||||
--couleurFondTitresSAE: rgb(211, 255, 255);
|
||||
--couleurFondTitresUE: #b6ebff;
|
||||
--couleurFondTitresRes: #f8c844;
|
||||
--couleurFondTitresSAE: #c6ffab;
|
||||
--couleurSecondaire: #fec;
|
||||
--couleurIntense: #c09;
|
||||
--couleurSurlignage: rgba(232, 255, 132, 0.47);
|
||||
--couleurIntense: rgb(4, 16, 159);;
|
||||
--couleurSurlignage: rgba(255, 253, 110, 0.49);
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
display: none;
|
||||
}
|
||||
.releve a, .releve a:visited {
|
||||
color: navy;
|
||||
text-decoration: none;
|
||||
}
|
||||
.releve a:hover {
|
||||
color: red;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.ready .wait{display: none;}
|
||||
.ready main{display: block;}
|
||||
h2{
|
||||
|
@ -97,7 +106,8 @@ section>div:nth-child(1){
|
|||
.hide_coef .synthese em,
|
||||
.hide_coef .eval>em,
|
||||
.hide_date_inscr .dateInscription,
|
||||
.hide_ects .ects{
|
||||
.hide_ects .ects,
|
||||
.hide_rangs .rang{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -151,14 +161,19 @@ section>div:nth-child(1){
|
|||
column-gap: 4px;
|
||||
flex: none;
|
||||
}
|
||||
.infoSemestre>div:nth-child(1){
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.infoSemestre>div>div:nth-child(even){
|
||||
text-align: right;
|
||||
}
|
||||
.photo {
|
||||
border: none;
|
||||
margin-left: auto;
|
||||
}
|
||||
.rang{
|
||||
text-decoration: underline var(--couleurIntense);
|
||||
font-weight: bold;
|
||||
}
|
||||
.ue .rang{
|
||||
font-weight: 400;
|
||||
}
|
||||
.decision{
|
||||
margin: 5px 0;
|
||||
|
@ -186,6 +201,9 @@ section>div:nth-child(1){
|
|||
.synthese h3{
|
||||
background: var(--couleurFondTitresUE);
|
||||
}
|
||||
.synthese .ue>div{
|
||||
text-align: right;
|
||||
}
|
||||
.synthese em,
|
||||
.eval em{
|
||||
opacity: 0.6;
|
||||
|
@ -206,7 +224,6 @@ section>div:nth-child(1){
|
|||
scroll-margin-top: 60px;
|
||||
}
|
||||
.module, .ue {
|
||||
background: var(--couleurSecondaire);
|
||||
color: #000;
|
||||
padding: 4px 32px;
|
||||
border-radius: 4px;
|
||||
|
@ -218,6 +235,15 @@ section>div:nth-child(1){
|
|||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.ue {
|
||||
background: var(--couleurFondTitresRes);
|
||||
}
|
||||
.module {
|
||||
background: var(--couleurFondTitresRes);
|
||||
}
|
||||
.module h3 {
|
||||
background: var(--couleurFondTitresRes);
|
||||
}
|
||||
.module::before, .ue::before {
|
||||
content:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='26px' height='26px' fill='white'><path d='M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z' /></svg>");
|
||||
width: 26px;
|
||||
|
@ -308,6 +334,14 @@ h3{
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
section{
|
||||
padding: 16px;
|
||||
}
|
||||
.syntheseModule, .eval {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
/*.absences{
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
|
|
|
@ -138,7 +138,7 @@ div.head_message {
|
|||
border-radius: 8px;
|
||||
font-family : arial, verdana, sans-serif ;
|
||||
font-weight: bold;
|
||||
width: 40%;
|
||||
width: 70%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -287,15 +287,15 @@ div.logo-insidebar {
|
|||
width: 75px; /* la marge fait 130px */
|
||||
}
|
||||
div.logo-logo {
|
||||
margin-left: -5px;
|
||||
text-align: center ;
|
||||
}
|
||||
|
||||
div.logo-logo img {
|
||||
box-sizing: content-box;
|
||||
margin-top: -10px;
|
||||
width: 128px;
|
||||
margin-top: 10px; /* -10px */
|
||||
width: 135px; /* 128px */
|
||||
padding-right: 5px;
|
||||
margin-left: -75px;
|
||||
}
|
||||
div.sidebar-bottom {
|
||||
margin-top: 10px;
|
||||
|
@ -1297,7 +1297,7 @@ th.formsemestre_status_inscrits {
|
|||
text-align: center;
|
||||
}
|
||||
td.formsemestre_status_code {
|
||||
width: 2em;
|
||||
/* width: 2em; */
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
|
@ -1671,7 +1671,10 @@ div.formation_list_modules ul.notes_module_list {
|
|||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
span.missing_ue_ects {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
li.module_malus span.formation_module_tit {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
|
@ -1699,14 +1702,20 @@ ul.notes_ue_list {
|
|||
margin-top: 4px;
|
||||
margin-right: 1em;
|
||||
margin-left: 1em;
|
||||
padding-top: 1em;
|
||||
/* padding-top: 1em; */
|
||||
padding-bottom: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.formation_classic_infos ul.notes_ue_list {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
li.notes_ue_list {
|
||||
.formation_classic_infos li.notes_ue_list {
|
||||
margin-top: 9px;
|
||||
list-style-type: none;
|
||||
border: 1px solid maroon;
|
||||
border-radius: 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
span.ue_type_1 {
|
||||
color: green;
|
||||
|
@ -1749,6 +1758,7 @@ ul.notes_matiere_list {
|
|||
background-color: rgb(220,220,220);
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
border-top: 1px solid maroon;
|
||||
}
|
||||
|
||||
ul.notes_module_list {
|
||||
|
@ -1757,6 +1767,27 @@ ul.notes_module_list {
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
div.ue_list_div {
|
||||
border: 3px solid rgb(35, 0, 160);
|
||||
padding-left: 5px;
|
||||
padding-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
div.ue_list_tit_sem {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
color: orangered;
|
||||
display: list-item; /* This has to be "list-item" */
|
||||
list-style-type: disc; /* See https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type */
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
input.sco_tag_checkbox {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.notes_ue_list a.stdlink {
|
||||
color: #001084;
|
||||
text-decoration: underline;
|
||||
|
@ -1932,7 +1963,20 @@ table.notes_recapcomplet a:hover {
|
|||
div.notes_bulletin {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
div.bull_head {
|
||||
display: grid;
|
||||
justify-content: space-between;
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
div.bull_photo {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
span.bulletin_menubar_but {
|
||||
display: inline-block;
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
table.notes_bulletin {
|
||||
border-collapse: collapse;
|
||||
border: 2px solid rgb(100,100,240);
|
||||
|
@ -2072,12 +2116,6 @@ a.bull_link:hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table.bull_head {
|
||||
width: 100%;
|
||||
}
|
||||
td.bull_photo {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.bulletin_menubar {
|
||||
padding-left: 25px;
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 34 KiB |
|
@ -0,0 +1,14 @@
|
|||
// Formulaire formsemestre_createwithmodules
|
||||
|
||||
function change_semestre_id() {
|
||||
var semestre_id = $("#tf_semestre_id")[0].value;
|
||||
for (var i = -1; i < 12; i++) {
|
||||
$(".sem" + i).hide();
|
||||
}
|
||||
$(".sem" + semestre_id).show();
|
||||
}
|
||||
|
||||
|
||||
$(window).on('load', function () {
|
||||
change_semestre_id();
|
||||
});
|
|
@ -41,7 +41,7 @@ class releveBUT extends HTMLElement {
|
|||
}
|
||||
|
||||
set showData(data) {
|
||||
this.showInformations(data);
|
||||
// this.showInformations(data);
|
||||
this.showSemestre(data);
|
||||
this.showSynthese(data);
|
||||
this.showEvaluations(data);
|
||||
|
@ -68,13 +68,7 @@ class releveBUT extends HTMLElement {
|
|||
<div>
|
||||
<div class="wait"></div>
|
||||
<main class="releve">
|
||||
<!--------------------------->
|
||||
<!-- Info. étudiant -->
|
||||
<!--------------------------->
|
||||
<section class=etudiant>
|
||||
<img class=studentPic src="" alt="Photo de l'étudiant" width=100 height=120>
|
||||
<div class=infoEtudiant></div>
|
||||
</section>
|
||||
|
||||
|
||||
<!--------------------------------------------------------------------------------------->
|
||||
<!-- Zone spéciale pour que les IUT puisse ajouter des infos locales sur la passerelle -->
|
||||
|
@ -85,13 +79,13 @@ class releveBUT extends HTMLElement {
|
|||
<!-- Semestre -->
|
||||
<!--------------------------->
|
||||
<section>
|
||||
<h2>Semestre </h2>
|
||||
<div class=flex>
|
||||
<h2 id="identite_etudiant"></h2>
|
||||
<div>
|
||||
<div class=infoSemestre></div>
|
||||
<div>
|
||||
<div class=decision></div>
|
||||
<div class=dateInscription>Inscrit le </div>
|
||||
<em>Les moyennes servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.</em>
|
||||
<em>Les moyennes ci-dessus servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.</em>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -103,7 +97,7 @@ class releveBUT extends HTMLElement {
|
|||
<section>
|
||||
<div>
|
||||
<div>
|
||||
<h2>Synthèse</h2>
|
||||
<h2>Unités d'enseignement</h2>
|
||||
<em>La moyenne des ressources dans une UE dépend des poids donnés aux évaluations.</em>
|
||||
</div>
|
||||
<div class=CTA_Liste>
|
||||
|
@ -132,7 +126,7 @@ class releveBUT extends HTMLElement {
|
|||
|
||||
<section>
|
||||
<div>
|
||||
<h2>SAÉ</h2>
|
||||
<h2>Situations d'apprentissage et d'évaluation (SAÉ)</h2>
|
||||
<div class=CTA_Liste>
|
||||
Liste <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 15l-6-6-6 6" />
|
||||
|
@ -198,7 +192,8 @@ class releveBUT extends HTMLElement {
|
|||
/* Information sur le semestre */
|
||||
/*******************************/
|
||||
showSemestre(data) {
|
||||
this.shadow.querySelector("h2").innerHTML += data.semestre.numero;
|
||||
|
||||
this.shadow.querySelector("#identite_etudiant").innerHTML = ` ${data.etudiant.nomprenom} `;
|
||||
this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(data.semestre.inscription);
|
||||
let output = `
|
||||
<div>
|
||||
|
@ -212,7 +207,9 @@ class releveBUT extends HTMLElement {
|
|||
<div class=enteteSemestre>Absences</div>
|
||||
<div class=enteteSemestre>N.J. ${data.semestre.absences?.injustifie ?? "-"}</div>
|
||||
<div style="grid-column: 2">Total ${data.semestre.absences?.total ?? "-"}</div>
|
||||
</div>`;
|
||||
</div>
|
||||
<a class=photo href="${data.etudiant.fiche_url}"><img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0"></a>
|
||||
`;
|
||||
/*${data.semestre.groupes.map(groupe => {
|
||||
return `
|
||||
<div>
|
||||
|
@ -254,6 +251,7 @@ class releveBUT extends HTMLElement {
|
|||
</h3>
|
||||
<div>
|
||||
<div class=moyenne>Moyenne : ${dataUE.moyenne?.value || "-"}</div>
|
||||
<div class=rang>Rang : ${dataUE.moyenne?.rang} / ${dataUE.moyenne?.total}</div>
|
||||
<div class=info>
|
||||
Bonus : ${dataUE.bonus || 0} -
|
||||
Malus : ${dataUE.malus || 0}
|
||||
|
|
|
@ -57,12 +57,10 @@
|
|||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-info" role="alert">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-info alert-{{ category }}" role="alert">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
|
||||
{# application content needs to be provided in the app_content block #}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
{# -*- mode: jinja-html -*- #}
|
||||
{# Pied des bulletins HTML #}
|
||||
|
||||
<p>Situation actuelle:
|
||||
{% if inscription_courante %}
|
||||
<a class="stdlink" href="{{url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=inscription_courante.formsemestre_id)
|
||||
}}">{{inscription_str}}</a>
|
||||
{% else %}
|
||||
{{inscription_str}}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% if formsemestre.modalite == "EXT" %}
|
||||
<p><a href="{{
|
||||
url_for('notes.formsemestre_ext_edit_ue_validations',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
etudid=etud.id)}}"
|
||||
class="stdlink">
|
||||
Éditer les validations d'UE dans ce semestre extérieur
|
||||
</a></p>
|
||||
{% endif %}
|
||||
|
||||
{# Place du diagramme radar #}
|
||||
<form id="params">
|
||||
<input type="hidden" name="etudid" id="etudid" value="{{etud.id}}"/>
|
||||
<input type="hidden" name="formsemestre_id" id="formsemestre_id" value="{{formsemestre.id}}"/>
|
||||
</form>
|
||||
<div id="radar_bulletin"></div>
|
||||
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
{# -*- mode: jinja-html -*- #}
|
||||
{# L'en-tête des bulletins HTML #}
|
||||
{# was _formsemestre_bulletinetud_header_html #}
|
||||
|
||||
<div class="bull_head">
|
||||
<div class="bull_head_text">
|
||||
{% if not is_apc %}
|
||||
<h2><a class="discretelink" href="{{
|
||||
url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid,
|
||||
)}}">{{etud.nomprenom}}</a></h2>
|
||||
{% endif %}
|
||||
<form name="f" method="GET" action="{{request.base_url}}">
|
||||
<input type="hidden" name="formsemestre_id" value="{{formsemestre.id}}"></input>
|
||||
<input type="hidden" name="etudid" value="{{etud.id}}"></input>
|
||||
<input type="hidden" name="format" value="{{format}}"></input>
|
||||
Bulletin
|
||||
<span class="bull_liensemestre"><a href="{{
|
||||
url_for("notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id)}}">{{formsemestre.titre_mois()
|
||||
}}</a></span>
|
||||
|
||||
<div>
|
||||
<em>établi le {{time.strftime("%d/%m/%Y à %Hh%M")}} (notes sur 20)</em>
|
||||
<span class="rightjust">
|
||||
<select name="version" onchange="document.f.submit()" class="noprint">
|
||||
{% for (v, e) in (
|
||||
("short", "Version courte"),
|
||||
("selectedevals", "Version intermédiaire"),
|
||||
("long", "Version complète"),
|
||||
) %}
|
||||
<option value="{{v}}" {% if (v == version) %}selected{% endif %}>{{e}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
<span class="bulletin_menubar">
|
||||
<span class="bulletin_menubar_but">{{menu_autres_operations|safe}}</span>
|
||||
<a href="{{url_for(
|
||||
'notes.formsemestre_bulletinetud',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
etudid=etud.id,
|
||||
format='pdf',
|
||||
version=version,
|
||||
)}}">{{scu.ICON_PDF|safe}}</a>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% if not is_apc %}
|
||||
<div class="bull_photo"><a href="{{
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||
}}">{{etud.photo_html(title="fiche de " + etud["nom"])|safe}}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
|
@ -7,8 +7,13 @@
|
|||
|
||||
{% block app_content %}
|
||||
|
||||
{% include 'bul_head.html' %}
|
||||
|
||||
<releve-but></releve-but>
|
||||
<script src="/ScoDoc/static/js/releve-but.js"></script>
|
||||
|
||||
{% include 'bul_foot.html' %}
|
||||
|
||||
<script>
|
||||
let dataSrc = "{{bul_url|safe}}";
|
||||
fetch(dataSrc)
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
<a href="{{ url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept, ) }}">
|
||||
Liste des référentiels de compétences chargés</a>
|
||||
</li>
|
||||
{% if formation is not none %}
|
||||
<li>
|
||||
<a href="{{ url_for('notes.refcomp_assoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id) }}">
|
||||
Association à la formation {{ formation.acronyme }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{# Message flask : utilisé uniquement par les anciennes pages ScoDoc #}
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
<div class="head_message_container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for category, message in messages %}
|
||||
<div class="head_message alert-info alert-{{ category }}" role="alert">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</div>
|
|
@ -0,0 +1,50 @@
|
|||
{# -*- mode: jinja-html -*- #}
|
||||
{# Element HTML decrivant un semestre (barre de menu et infos) #}
|
||||
{# was formsemestre_page_title #}
|
||||
|
||||
<div class="formsemestre_page_title">
|
||||
<div class="infos">
|
||||
<span class="semtitle"><a class="stdlink"
|
||||
title="{{formsemestre.session_id}}"
|
||||
href="{{url_for('notes.formsemestre_status',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)}}"
|
||||
>TATO {{formsemestre.titre}}</a>
|
||||
{%- if formsemestre.semestre_id != -1 -%}
|
||||
<a
|
||||
title="{{formsemestre.etapes_apo_str()
|
||||
}}">, {{
|
||||
formsemestre.formation.get_parcours().SESSION_NAME}}
|
||||
{{formsemestre.semestre_id}}</a>
|
||||
{%- endif -%}
|
||||
{%- if formsemestre.modalite %} en {{formsemestre.modalite}}
|
||||
{%- endif %}</span><span
|
||||
class="dates"><a
|
||||
title="du {{formsemestre.date_debut.strftime('%d/%m/%Y')}}
|
||||
au {{formsemestre.date_fin.strftime('%d/%m/%Y')}} "
|
||||
>{{formsemestre.mois_debut()}} - {{formsemestre.mois_fin()}}</a></span><span
|
||||
class="resp"><a title="{{formsemestre.responsables_str(abbrev_prenom=False)}}">{{formsemestre.responsables_str()}}</a></span><span
|
||||
class="nbinscrits"><a class="discretelink"
|
||||
href="{{url_for('scolar.groups_view',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}}"
|
||||
>{{formsemestre.etuds_inscriptions|length}} inscrits</a></span><span
|
||||
class="lock">
|
||||
{%-if formsemestre.etat -%}
|
||||
<a href="{{ url_for( 'notes.formsemestre_change_lock',
|
||||
scodoc_dept=scodoc_dept, formsemestre_id=formsemestre.id )}}">{{
|
||||
scu.icontag("lock_img", border="0", title="Semestre verrouillé")|safe
|
||||
}}</a>
|
||||
{%- endif -%}
|
||||
</span><span class="eye"><a href="{{
|
||||
url_for('notes.formsemestre_change_publication_bul',
|
||||
scodoc_dept=scodoc_dept, formsemestre_id=formsemestre.id )
|
||||
}}">{%-
|
||||
if formsemestre.bul_hide_xml -%}}
|
||||
{{scu.icontag("hide_img", border="0", title="Bulletins NON publiés")|safe}}
|
||||
{%- else -%}
|
||||
{{scu.icontag("eye_img", border="0", title="Bulletins publiés")|safe}}
|
||||
{%- endif -%}
|
||||
</a></span>
|
||||
</div>
|
||||
{{sem_menu_bar|safe}}
|
||||
</div>
|
|
@ -65,6 +65,13 @@
|
|||
{% endfor %}
|
||||
</span>
|
||||
|
||||
{% if mod.ue.type != 0 and mod.module_type != 0 %}
|
||||
<span class="warning" title="Une UE de type spécial ne
|
||||
devrait contenir que des modules standards">
|
||||
type incompatible avec son UE de rattachement !
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="sco_tag_edit"><form><textarea data-module_id="{{mod.id}}"
|
||||
class="{% if tag_editable %}module_tag_editor{% else %}module_tag_editor_ro{% endif %}">{{mod.tags|join(', ', attribute='title')}}</textarea></form></span>
|
||||
|
||||
|
|
|
@ -3,11 +3,9 @@
|
|||
<div class="formation_list_ues">
|
||||
<div class="formation_list_ues_titre">Unités d'Enseignement (UEs)</div>
|
||||
{% for semestre_idx in semestre_ids %}
|
||||
<div class="formation_list_ues_sem">Semestre S{{semestre_idx}}</div>
|
||||
<div class="formation_list_ues_sem">Semestre S{{semestre_idx}} (ECTS: {{ects_by_sem[semestre_idx] | safe}})</div>
|
||||
<ul class="apc_ue_list">
|
||||
{% for ue in formation.ues.filter_by(semestre_idx=semestre_idx).order_by(
|
||||
UniteEns.semestre_idx, UniteEns.numero, UniteEns.acronyme
|
||||
) %}
|
||||
{% for ue in ues_by_sem[semestre_idx] %}
|
||||
<li class="notes_ue_list">
|
||||
{% if editable and not loop.first %}
|
||||
<a href="{{ url_for('notes.ue_move',
|
||||
|
@ -38,7 +36,8 @@
|
|||
{% set virg = joiner(", ") %}
|
||||
<span class="ue_code">(
|
||||
{%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%}
|
||||
{{ virg() }}{{ue.ects or 0}} ECTS)
|
||||
{{ virg() }}{{ue.ects if ue.ects is not none
|
||||
else '<span class="missing_ue_ects">aucun</span>'|safe}} ECTS)
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
@ -48,6 +47,9 @@
|
|||
}}">modifier</a>
|
||||
{% endif %}
|
||||
|
||||
{% if ue.type == 1 and ue.modules.count() == 0 %}
|
||||
<span class="warning" title="pas de module, donc pas de bonus calculé">aucun module rattaché !</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
|
@ -23,12 +23,10 @@
|
|||
|
||||
<div id="gtrcontent" class="gtrcontent">
|
||||
<div class="container">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-info" role="alert">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-info alert-{{ category }}" role="alert">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% if sco.sem %}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="sidebar">
|
||||
{# sidebar_common #}
|
||||
<a class="scodoc_title" href="{{
|
||||
url_for('scodoc.index', scodoc_dept=g.scodoc_dept) }}">ScoDoc 9.2a</a>
|
||||
url_for('scodoc.index', scodoc_dept=g.scodoc_dept) }}">ScoDoc {{ sco.SCOVERSION }}</a>
|
||||
<div id="authuser"><a id="authuserlink" href="{{
|
||||
url_for('users.user_info_page', scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
||||
}}">{{current_user.user_name}}</a>
|
||||
|
|
|
@ -16,6 +16,7 @@ from app.scodoc import sco_formsemestre_status
|
|||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_utils as scu
|
||||
import sco_version
|
||||
|
||||
scodoc_bp = Blueprint("scodoc", __name__)
|
||||
scolar_bp = Blueprint("scolar", __name__)
|
||||
|
@ -49,26 +50,29 @@ def close_dept_db_connection(arg):
|
|||
class ScoData:
|
||||
"""Classe utilisée pour passer des valeurs aux vues (templates)"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, etud=None, formsemestre=None):
|
||||
# Champs utilisés par toutes les pages ScoDoc (sidebar, en-tête)
|
||||
self.Permission = Permission
|
||||
self.scu = scu
|
||||
self.SCOVERSION = sco_version.SCOVERSION
|
||||
# -- Informations étudiant courant, si sélectionné:
|
||||
etudid = g.get("etudid", None)
|
||||
if not etudid:
|
||||
if request.method == "GET":
|
||||
etudid = request.args.get("etudid", None)
|
||||
elif request.method == "POST":
|
||||
etudid = request.form.get("etudid", None)
|
||||
|
||||
if etudid:
|
||||
if etud is None:
|
||||
etudid = g.get("etudid", None)
|
||||
if etudid is None:
|
||||
if request.method == "GET":
|
||||
etudid = request.args.get("etudid", None)
|
||||
elif request.method == "POST":
|
||||
etudid = request.form.get("etudid", None)
|
||||
if etudid is not None:
|
||||
etud = Identite.query.get_or_404(etudid)
|
||||
self.etud = etud
|
||||
if etud is not None:
|
||||
# Infos sur l'étudiant courant
|
||||
self.etud = Identite.query.get_or_404(etudid)
|
||||
ins = self.etud.inscription_courante()
|
||||
if ins:
|
||||
self.etud_cur_sem = ins.formsemestre
|
||||
self.nbabs, self.nbabsjust = sco_abs.get_abs_count_in_interval(
|
||||
etudid,
|
||||
etud.id,
|
||||
self.etud_cur_sem.date_debut.isoformat(),
|
||||
self.etud_cur_sem.date_fin.isoformat(),
|
||||
)
|
||||
|
@ -78,17 +82,22 @@ class ScoData:
|
|||
else:
|
||||
self.etud = None
|
||||
# --- Informations sur semestre courant, si sélectionné
|
||||
formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request()
|
||||
if formsemestre_id is None:
|
||||
if formsemestre is None:
|
||||
formsemestre_id = (
|
||||
sco_formsemestre_status.retreive_formsemestre_from_request()
|
||||
)
|
||||
if formsemestre_id is not None:
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
if formsemestre is None:
|
||||
self.sem = None
|
||||
self.sem_menu_bar = None
|
||||
else:
|
||||
self.sem = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
self.sem = formsemestre
|
||||
self.sem_menu_bar = sco_formsemestre_status.formsemestre_status_menubar(
|
||||
self.sem.to_dict()
|
||||
)
|
||||
# --- Préférences
|
||||
self.prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||
self.prefs = sco_preferences.SemPreferences(formsemestre.id)
|
||||
|
||||
|
||||
from app.views import scodoc, notes, scolar, absences, users, pn_modules, refcomp
|
||||
|
|
|
@ -611,8 +611,7 @@ def SignaleAbsenceGrSemestre(
|
|||
"""<option value="%(modimpl_id)s" %(sel)s>%(modname)s</option>\n"""
|
||||
% {
|
||||
"modimpl_id": modimpl["moduleimpl_id"],
|
||||
"modname": modimpl["module"]["code"]
|
||||
or ""
|
||||
"modname": (modimpl["module"]["code"] or "")
|
||||
+ " "
|
||||
+ (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]),
|
||||
"sel": sel,
|
||||
|
@ -624,7 +623,7 @@ def SignaleAbsenceGrSemestre(
|
|||
sel = "selected" # aucun module specifie
|
||||
H.append(
|
||||
"""<p>
|
||||
Module concerné par ces absences (%(optionel_txt)s):
|
||||
Module concerné par ces absences (%(optionel_txt)s):
|
||||
<select id="moduleimpl_id" name="moduleimpl_id"
|
||||
onchange="document.location='%(url)s&moduleimpl_id='+document.getElementById('moduleimpl_id').value">
|
||||
<option value="" %(sel)s>non spécifié</option>
|
||||
|
|
|
@ -32,10 +32,11 @@ Emmanuel Viennet, 2021
|
|||
"""
|
||||
|
||||
from operator import itemgetter
|
||||
import time
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import flask
|
||||
from flask import flash, jsonify, render_template, url_for
|
||||
from flask import abort, flash, jsonify, render_template, url_for
|
||||
from flask import current_app, g, request
|
||||
from flask_login import current_user
|
||||
from werkzeug.utils import redirect
|
||||
|
@ -68,10 +69,14 @@ from app.scodoc import sco_utils as scu
|
|||
from app.scodoc import notesdb as ndb
|
||||
from app import log, send_scodoc_alarm
|
||||
|
||||
from app.scodoc import scolog
|
||||
from app.scodoc.scolog import logdb
|
||||
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoInvalidIdType
|
||||
from app.scodoc.sco_exceptions import (
|
||||
AccessDenied,
|
||||
ScoException,
|
||||
ScoValueError,
|
||||
ScoInvalidIdType,
|
||||
)
|
||||
from app.scodoc import html_sco_header
|
||||
from app.pe import pe_view
|
||||
from app.scodoc import sco_abs
|
||||
|
@ -272,7 +277,7 @@ sco_publish(
|
|||
def formsemestre_bulletinetud(
|
||||
etudid=None,
|
||||
formsemestre_id=None,
|
||||
format="html",
|
||||
format=None,
|
||||
version="long",
|
||||
xml_with_decisions=False,
|
||||
force_publishing=False,
|
||||
|
@ -280,6 +285,7 @@ def formsemestre_bulletinetud(
|
|||
code_nip=None,
|
||||
code_ine=None,
|
||||
):
|
||||
format = format or "html"
|
||||
if not formsemestre_id:
|
||||
flask.abort(404, "argument manquant: formsemestre_id")
|
||||
if not isinstance(formsemestre_id, int):
|
||||
|
@ -307,12 +313,16 @@ def formsemestre_bulletinetud(
|
|||
if format == "json":
|
||||
r = bulletin_but.BulletinBUT(formsemestre)
|
||||
return jsonify(
|
||||
r.bulletin_etud(etud, formsemestre, force_publishing=force_publishing)
|
||||
r.bulletin_etud(
|
||||
etud,
|
||||
formsemestre,
|
||||
force_publishing=force_publishing,
|
||||
version=version,
|
||||
)
|
||||
)
|
||||
elif format == "html":
|
||||
return render_template(
|
||||
"but/bulletin.html",
|
||||
title=f"Bul. {etud.nom} - BUT",
|
||||
bul_url=url_for(
|
||||
"notes.formsemestre_bulletinetud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
|
@ -320,8 +330,21 @@ def formsemestre_bulletinetud(
|
|||
etudid=etudid,
|
||||
format="json",
|
||||
force_publishing=1, # pour ScoDoc lui même
|
||||
version=version,
|
||||
),
|
||||
sco=ScoData(),
|
||||
etud=etud,
|
||||
formsemestre=formsemestre,
|
||||
inscription_courante=etud.inscription_courante(),
|
||||
inscription_str=etud.inscription_descr()["inscription_str"],
|
||||
is_apc=formsemestre.formation.is_apc(),
|
||||
menu_autres_operations=sco_bulletins.make_menu_autres_operations(
|
||||
formsemestre, etud, "notes.formsemestre_bulletinetud", version
|
||||
),
|
||||
sco=ScoData(etud=etud),
|
||||
scu=scu,
|
||||
time=time,
|
||||
title=f"Bul. {etud.nom} - BUT",
|
||||
version=version,
|
||||
)
|
||||
|
||||
if not (etudid or code_nip or code_ine):
|
||||
|
@ -1862,7 +1885,6 @@ def formsemestre_bulletins_choice(
|
|||
formsemestre_id, title="", explanation="", choose_mail=False
|
||||
):
|
||||
"""Choix d'une version de bulletin"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header(title),
|
||||
"""
|
||||
|
@ -1925,7 +1947,7 @@ def formsemestre_bulletins_mailetuds(
|
|||
nb_send = 0
|
||||
for etudid in etudids:
|
||||
h, _ = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre,
|
||||
etudid,
|
||||
version=version,
|
||||
prefer_mail_perso=prefer_mail_perso,
|
||||
|
@ -2672,12 +2694,15 @@ def check_integrity_all():
|
|||
def moduleimpl_list(
|
||||
moduleimpl_id=None, formsemestre_id=None, module_id=None, format="json"
|
||||
):
|
||||
data = sco_moduleimpl.moduleimpl_list(
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
formsemestre_id=formsemestre_id,
|
||||
module_id=module_id,
|
||||
)
|
||||
return scu.sendResult(data, format=format)
|
||||
try:
|
||||
data = sco_moduleimpl.moduleimpl_list(
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
formsemestre_id=formsemestre_id,
|
||||
module_id=module_id,
|
||||
)
|
||||
return scu.sendResult(data, format=format)
|
||||
except ScoException:
|
||||
abort(404)
|
||||
|
||||
|
||||
@bp.route("/do_moduleimpl_withmodule_list") # ancien nom
|
||||
|
@ -2686,7 +2711,7 @@ def moduleimpl_list(
|
|||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def moduleimpl_withmodule_list(
|
||||
moduleimpl_id=None, formsemestre_id=None, module_id=None
|
||||
moduleimpl_id=None, formsemestre_id=None, module_id=None, format="json"
|
||||
):
|
||||
"""API ScoDoc 7"""
|
||||
data = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
|
|
|
@ -304,8 +304,9 @@ def _return_logo(name="header", dept_id="", small=False, strict: bool = True):
|
|||
# stockée dans /opt/scodoc-data/config/logos donc servie manuellement ici
|
||||
# from app.scodoc.sco_photos import _http_jpeg_file
|
||||
|
||||
logo = sco_logos.find_logo(name, dept_id, strict).select()
|
||||
logo = sco_logos.find_logo(name, dept_id, strict)
|
||||
if logo is not None:
|
||||
logo.select()
|
||||
suffix = logo.suffix
|
||||
if small:
|
||||
with PILImage.open(logo.filepath) as im:
|
||||
|
|
|
@ -68,8 +68,6 @@ from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
|||
import app
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import html_sidebar
|
||||
from app.scodoc import imageresize
|
||||
from app.scodoc import sco_import_etuds
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_archives_etud
|
||||
|
@ -87,12 +85,9 @@ from app.scodoc import sco_formsemestre_edit
|
|||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_edit
|
||||
from app.scodoc import sco_groups_exports
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_logos
|
||||
from app.scodoc import sco_news
|
||||
from app.scodoc import sco_page_etud
|
||||
from app.scodoc import sco_parcours_dut
|
||||
from app.scodoc import sco_permissions
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_photos
|
||||
from app.scodoc import sco_portal_apogee
|
||||
|
@ -364,6 +359,12 @@ sco_publish(
|
|||
methods=["GET", "POST"],
|
||||
)
|
||||
|
||||
sco_publish(
|
||||
"/groups_export_annotations",
|
||||
sco_groups_exports.groups_export_annotations,
|
||||
Permission.ScoView,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/groups_view")
|
||||
@scodoc
|
||||
|
@ -512,7 +513,7 @@ def etud_info(etudid=None, format="xml"):
|
|||
|
||||
sem = etud["cursem"]
|
||||
if sem:
|
||||
sco_groups.etud_add_group_infos(etud, sem)
|
||||
sco_groups.etud_add_group_infos(etud, sem["formsemestre_id"] if sem else None)
|
||||
d["insemestre"] = [
|
||||
{
|
||||
"current": "1",
|
||||
|
|
|
@ -19,31 +19,6 @@ depends_on = None
|
|||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
op.add_column("apc_competence", sa.Column("id_orebut", sa.Text(), nullable=True))
|
||||
op.drop_constraint(
|
||||
"apc_competence_referentiel_id_titre_key", "apc_competence", type_="unique"
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_apc_competence_id_orebut"),
|
||||
"apc_competence",
|
||||
["id_orebut"],
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences", sa.Column("annexe", sa.Text(), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences",
|
||||
sa.Column("type_structure", sa.Text(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences",
|
||||
sa.Column("type_departement", sa.Text(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences",
|
||||
sa.Column("version_orebut", sa.Text(), nullable=True),
|
||||
)
|
||||
|
||||
op.create_index(
|
||||
op.f("ix_notes_formsemestre_uecoef_formsemestre_id"),
|
||||
"notes_formsemestre_uecoef",
|
||||
|
@ -80,15 +55,10 @@ def downgrade():
|
|||
table_name="notes_formsemestre_uecoef",
|
||||
)
|
||||
|
||||
op.drop_column("apc_referentiel_competences", "version_orebut")
|
||||
op.drop_column("apc_referentiel_competences", "type_departement")
|
||||
op.drop_column("apc_referentiel_competences", "type_structure")
|
||||
op.drop_column("apc_referentiel_competences", "annexe")
|
||||
op.drop_index(op.f("ix_apc_competence_id_orebut"), table_name="apc_competence")
|
||||
op.create_unique_constraint(
|
||||
"apc_competence_referentiel_id_titre_key",
|
||||
"apc_competence",
|
||||
["referentiel_id", "titre"],
|
||||
)
|
||||
op.drop_column("apc_competence", "id_orebut")
|
||||
# ### end Alembic commands ###
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue