forked from ScoDoc/ScoDoc
Compare commits
95 Commits
8b98a0bc63
...
d637ffe70c
Author | SHA1 | Date |
---|---|---|
Arthur ZHU | d637ffe70c | |
Emmanuel Viennet | 2b1a3ee95e | |
Emmanuel Viennet | 54b1ce7bfb | |
Emmanuel Viennet | 831d14cf7d | |
Emmanuel Viennet | b8b3185901 | |
Emmanuel Viennet | 02989e6c88 | |
Emmanuel Viennet | a7324ac634 | |
Emmanuel Viennet | 3639d5db94 | |
Emmanuel Viennet | 7da5e0a5c3 | |
Emmanuel Viennet | 4334a83c17 | |
Emmanuel Viennet | da5445baa8 | |
Emmanuel Viennet | 69d494c02c | |
Emmanuel Viennet | 0b87714ac0 | |
Emmanuel Viennet | fa99cbf3d0 | |
Emmanuel Viennet | b6cedbd6b6 | |
Emmanuel Viennet | 55bd15a67b | |
Emmanuel Viennet | ec108a4454 | |
Emmanuel Viennet | 158ac7b1fc | |
Emmanuel Viennet | a5c0619102 | |
Emmanuel Viennet | 6f0b03242d | |
Emmanuel Viennet | af12191cc4 | |
Emmanuel Viennet | 126f719f7a | |
Emmanuel Viennet | b2893a3371 | |
Emmanuel Viennet | 00b6d19c0c | |
Emmanuel Viennet | 4477a25147 | |
Emmanuel Viennet | 53630f08de | |
Emmanuel Viennet | 782e291725 | |
Emmanuel Viennet | 58a6d16d12 | |
Emmanuel Viennet | 0a1264051c | |
Emmanuel Viennet | 4e1811e609 | |
Emmanuel Viennet | f7dbff782f | |
Sébastien Lehmann | acdd037483 | |
Emmanuel Viennet | 4daa9e8945 | |
Emmanuel Viennet | 68dec8e1f8 | |
Emmanuel Viennet | 9172282451 | |
Emmanuel Viennet | 9e3ad8efbc | |
Emmanuel Viennet | a467ef27db | |
Emmanuel Viennet | 24bfb8a13d | |
Emmanuel Viennet | 0e930d5fe4 | |
Emmanuel Viennet | 795ca343de | |
Emmanuel Viennet | 4c325b70de | |
Emmanuel Viennet | 72b8e04064 | |
Emmanuel Viennet | c362ccef0e | |
Emmanuel Viennet | fbae5d268f | |
Emmanuel Viennet | 2161d7bddc | |
Emmanuel Viennet | f2e9fbb8cd | |
Emmanuel Viennet | 4bb6f93b32 | |
Emmanuel Viennet | fbb4b0841b | |
Emmanuel Viennet | 0c57aa83ca | |
Emmanuel Viennet | 8b2edca257 | |
Emmanuel Viennet | 0ceb1c8046 | |
Emmanuel Viennet | b1ab7a4df9 | |
Emmanuel Viennet | d68f81b4ea | |
Emmanuel Viennet | 4140f11b7b | |
Emmanuel Viennet | d1ff47727b | |
Emmanuel Viennet | 3eb038b491 | |
Emmanuel Viennet | 5e144d8745 | |
Emmanuel Viennet | da1a2ccf43 | |
Emmanuel Viennet | af48eb8fb8 | |
leonard_montalbano | 858b922b5a | |
Emmanuel Viennet | 6b1ccfe400 | |
Emmanuel Viennet | 54b714fdbc | |
Emmanuel Viennet | 1b98e5f8dd | |
Emmanuel Viennet | 0098df4a8f | |
Emmanuel Viennet | 9b147b85ae | |
Emmanuel Viennet | b1a3a15a94 | |
Emmanuel Viennet | 20c8f22c7b | |
Emmanuel Viennet | d6c6a08828 | |
Emmanuel Viennet | 96130f1a75 | |
Emmanuel Viennet | 235556f825 | |
Emmanuel Viennet | d622b313b0 | |
Emmanuel Viennet | db9acb67dd | |
Emmanuel Viennet | 8a415984c6 | |
Emmanuel Viennet | 6157e54a5f | |
Jean-Marie PLACE | efe997fe55 | |
Emmanuel Viennet | ff948cb98d | |
Emmanuel Viennet | 4b63fe81e4 | |
Emmanuel Viennet | 88e15367b0 | |
Emmanuel Viennet | 5895e5c33c | |
Emmanuel Viennet | 9cf6cf838e | |
Emmanuel Viennet | 82e3de02f6 | |
Emmanuel Viennet | e3535aa4da | |
Emmanuel Viennet | e1adf93bf0 | |
Emmanuel Viennet | be2227f8a3 | |
Emmanuel Viennet | 9b9b2f270b | |
Emmanuel Viennet | 6fe77988a0 | |
Emmanuel Viennet | a1bb957eaf | |
Emmanuel Viennet | f2e21e0cc2 | |
Emmanuel Viennet | a65e9f9e08 | |
Emmanuel Viennet | e838321e0c | |
Emmanuel Viennet | 75b13ed9f7 | |
Emmanuel Viennet | 51d75acb68 | |
Emmanuel Viennet | 46c64ba78b | |
Emmanuel Viennet | b6a3bd2388 | |
Emmanuel Viennet | 407c3ef472 |
|
@ -169,5 +169,7 @@ Thumbs.db
|
|||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
copy
|
||||
|
|
|
@ -190,6 +190,7 @@ def create_app(config_class=DevConfig):
|
|||
|
||||
app.register_error_handler(ScoGenError, handle_sco_value_error)
|
||||
app.register_error_handler(ScoValueError, handle_sco_value_error)
|
||||
|
||||
app.register_error_handler(AccessDenied, handle_access_denied)
|
||||
app.register_error_handler(500, internal_server_error)
|
||||
app.register_error_handler(503, postgresql_server_error)
|
||||
|
|
|
@ -49,10 +49,11 @@ from app.api.auth import token_auth
|
|||
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_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("list_depts", methods=["GET"])
|
||||
@bp.route("/list_depts", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def list_depts():
|
||||
depts = models.Departement.query.filter_by(visible=True).all()
|
||||
|
@ -66,7 +67,7 @@ def etudiants():
|
|||
"""Liste de tous les étudiants actuellement inscrits à un semestre
|
||||
en cours.
|
||||
"""
|
||||
# Vérification de l'accès: permission Observateir sur tous les départements
|
||||
# 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")
|
||||
|
@ -78,3 +79,413 @@ def etudiants():
|
|||
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")
|
||||
|
|
|
@ -11,7 +11,7 @@ from time import time
|
|||
from typing import Optional
|
||||
|
||||
import cracklib # pylint: disable=import-error
|
||||
from flask import current_app, url_for, g
|
||||
from flask import current_app, g
|
||||
from flask_login import UserMixin, AnonymousUserMixin
|
||||
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
@ -136,6 +136,7 @@ class User(UserMixin, db.Model):
|
|||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
def get_reset_password_token(self, expires_in=600):
|
||||
"Un token pour réinitialiser son mot de passe"
|
||||
return jwt.encode(
|
||||
{"reset_password": self.id, "exp": time() + expires_in},
|
||||
current_app.config["SECRET_KEY"],
|
||||
|
@ -144,15 +145,17 @@ class User(UserMixin, db.Model):
|
|||
|
||||
@staticmethod
|
||||
def verify_reset_password_token(token):
|
||||
"Vérification du token de reéinitialisation du mot de passe"
|
||||
try:
|
||||
id = jwt.decode(
|
||||
user_id = jwt.decode(
|
||||
token, current_app.config["SECRET_KEY"], algorithms=["HS256"]
|
||||
)["reset_password"]
|
||||
except:
|
||||
return
|
||||
return User.query.get(id)
|
||||
return User.query.get(user_id)
|
||||
|
||||
def to_dict(self, include_email=True):
|
||||
"""l'utilisateur comme un dict, avec des champs supplémentaires"""
|
||||
data = {
|
||||
"date_expiration": self.date_expiration.isoformat() + "Z"
|
||||
if self.date_expiration
|
||||
|
@ -472,5 +475,5 @@ def get_super_admin():
|
|||
|
||||
|
||||
@login.user_loader
|
||||
def load_user(id):
|
||||
return User.query.get(int(id))
|
||||
def load_user(uid):
|
||||
return User.query.get(int(uid))
|
||||
|
|
|
@ -30,17 +30,18 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||
ue_idx = self.modimpl_coefs_df.index.get_loc(ue.id)
|
||||
etud_moy_module = self.sem_cube[etud_idx] # module x UE
|
||||
for modimpl in modimpls:
|
||||
coef = self.modimpl_coefs_df[modimpl.id][ue.id]
|
||||
if coef > 0:
|
||||
d[modimpl.module.code] = {
|
||||
"id": modimpl.id,
|
||||
"coef": coef,
|
||||
"moyenne": fmt_note(
|
||||
etud_moy_module[
|
||||
self.modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||
][ue_idx]
|
||||
),
|
||||
}
|
||||
if self.modimpl_inscr_df[str(modimpl.id)][etud.id]: # si inscrit
|
||||
coef = self.modimpl_coefs_df[modimpl.id][ue.id]
|
||||
if coef > 0:
|
||||
d[modimpl.module.code] = {
|
||||
"id": modimpl.id,
|
||||
"coef": coef,
|
||||
"moyenne": fmt_note(
|
||||
etud_moy_module[
|
||||
self.modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||
][ue_idx]
|
||||
),
|
||||
}
|
||||
return d
|
||||
|
||||
def etud_ue_results(self, etud, ue):
|
||||
|
@ -87,29 +88,30 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||
# except RuntimeWarning: # all nans in np.nanmean
|
||||
# pass
|
||||
modimpl_results = self.modimpls_results[modimpl.id]
|
||||
d[modimpl.module.code] = {
|
||||
"id": modimpl.id,
|
||||
"titre": modimpl.module.titre,
|
||||
"code_apogee": modimpl.module.code_apogee,
|
||||
"url": url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=modimpl.id,
|
||||
),
|
||||
"moyenne": {
|
||||
# # moyenne indicative de module: moyenne des UE, ignorant celles sans notes (nan)
|
||||
# "value": fmt_note(moy_indicative_mod),
|
||||
# "min": fmt_note(moyennes_etuds.min()),
|
||||
# "max": fmt_note(moyennes_etuds.max()),
|
||||
# "moy": fmt_note(moyennes_etuds.mean()),
|
||||
},
|
||||
"evaluations": [
|
||||
self.etud_eval_results(etud, e)
|
||||
for e in modimpl.evaluations
|
||||
if e.visibulletin
|
||||
and modimpl_results.evaluations_etat[e.id].is_complete
|
||||
],
|
||||
}
|
||||
if self.modimpl_inscr_df[str(modimpl.id)][etud.id]: # si inscrit
|
||||
d[modimpl.module.code] = {
|
||||
"id": modimpl.id,
|
||||
"titre": modimpl.module.titre,
|
||||
"code_apogee": modimpl.module.code_apogee,
|
||||
"url": url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=modimpl.id,
|
||||
),
|
||||
"moyenne": {
|
||||
# # moyenne indicative de module: moyenne des UE, ignorant celles sans notes (nan)
|
||||
# "value": fmt_note(moy_indicative_mod),
|
||||
# "min": fmt_note(moyennes_etuds.min()),
|
||||
# "max": fmt_note(moyennes_etuds.max()),
|
||||
# "moy": fmt_note(moyennes_etuds.mean()),
|
||||
},
|
||||
"evaluations": [
|
||||
self.etud_eval_results(etud, e)
|
||||
for e in modimpl.evaluations
|
||||
if e.visibulletin
|
||||
and modimpl_results.evaluations_etat[e.id].is_complete
|
||||
],
|
||||
}
|
||||
return d
|
||||
|
||||
def etud_eval_results(self, etud, e) -> dict:
|
||||
|
@ -145,6 +147,7 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||
def bulletin_etud(self, etud, formsemestre) -> dict:
|
||||
"""Le bulletin de l'étudiant dans ce semestre"""
|
||||
etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||
nb_inscrits = self.get_inscriptions_counts()[scu.INSCRIT]
|
||||
d = {
|
||||
"version": "0",
|
||||
"type": "BUT",
|
||||
|
@ -187,7 +190,7 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||
},
|
||||
"rang": { # classement wrt moyenne général, indicatif
|
||||
"value": self.etud_moy_gen_ranks[etud.id],
|
||||
"total": len(self.etuds),
|
||||
"total": nb_inscrits,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -210,7 +213,7 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||
"moy": "",
|
||||
"max": "",
|
||||
},
|
||||
"rang": {"value": "DEM", "total": len(self.etuds)},
|
||||
"rang": {"value": "DEM", "total": nb_inscrits},
|
||||
}
|
||||
)
|
||||
d.update(
|
||||
|
|
|
@ -68,14 +68,16 @@ def bulletin_but_xml_compat(
|
|||
"bulletin_but_xml_compat( formsemestre_id=%s, etudid=%s )"
|
||||
% (formsemestre_id, etudid)
|
||||
)
|
||||
sem = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
etud = Identite.query.get_or_404(etudid)
|
||||
results = bulletin_but.ResultatsSemestreBUT(sem)
|
||||
nb_inscrits = len(results.etuds)
|
||||
if sem.bul_hide_xml or force_publishing:
|
||||
published = "1"
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
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 = results.formsemestre.etuds_inscriptions[etudid].etat
|
||||
if (not formsemestre.bul_hide_xml) or force_publishing:
|
||||
published = 1
|
||||
else:
|
||||
published = "0"
|
||||
published = 0
|
||||
if xml_nodate:
|
||||
docdate = ""
|
||||
else:
|
||||
|
@ -84,12 +86,12 @@ def bulletin_but_xml_compat(
|
|||
"etudid": str(etudid),
|
||||
"formsemestre_id": str(formsemestre_id),
|
||||
"date": docdate,
|
||||
"publie": published,
|
||||
"publie": str(published),
|
||||
}
|
||||
if sem.etapes:
|
||||
el["etape_apo"] = sem.etapes[0].etape_apo or ""
|
||||
if formsemestre.etapes:
|
||||
el["etape_apo"] = formsemestre.etapes[0].etape_apo or ""
|
||||
n = 2
|
||||
for et in sem.etapes[1:]:
|
||||
for et in formsemestre.etapes[1:]:
|
||||
el["etape_apo" + str(n)] = et.etape_apo or ""
|
||||
n += 1
|
||||
x = Element("bulletinetud", **el)
|
||||
|
@ -117,79 +119,75 @@ def bulletin_but_xml_compat(
|
|||
)
|
||||
# Disponible pour publication ?
|
||||
if not published:
|
||||
return doc # stop !
|
||||
# Moyenne générale:
|
||||
doc.append(
|
||||
Element(
|
||||
"note",
|
||||
value=scu.fmt_note(results.etud_moy_gen[etud.id]),
|
||||
min=scu.fmt_note(results.etud_moy_gen.min()),
|
||||
max=scu.fmt_note(results.etud_moy_gen.max()),
|
||||
moy=scu.fmt_note(results.etud_moy_gen.mean()), # moyenne des moy. gen.
|
||||
)
|
||||
)
|
||||
rang = 0 # XXX TODO rang de l'étduiant selon la moy gen indicative
|
||||
bonus = 0 # XXX TODO valeur du bonus sport
|
||||
doc.append(Element("rang", value=str(rang), ninscrits=str(nb_inscrits)))
|
||||
# XXX TODO: ajouter "rang_group" : rangs dans les partitions
|
||||
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
||||
doc.append(Element("bonus_sport_culture", value=str(bonus)))
|
||||
# Liste les UE / modules /evals
|
||||
for ue in results.ues:
|
||||
rang_ue = 0 # XXX TODO rang de l'étudiant dans cette UE
|
||||
nb_inscrits_ue = (
|
||||
nb_inscrits # approx: compliqué de définir le "nb d'inscrit à une UE"
|
||||
)
|
||||
x_ue = Element(
|
||||
"ue",
|
||||
id=str(ue.id),
|
||||
numero=scu.quote_xml_attr(ue.numero),
|
||||
acronyme=scu.quote_xml_attr(ue.acronyme or ""),
|
||||
titre=scu.quote_xml_attr(ue.titre or ""),
|
||||
code_apogee=scu.quote_xml_attr(ue.code_apogee or ""),
|
||||
)
|
||||
doc.append(x_ue)
|
||||
if ue.type != sco_codes_parcours.UE_SPORT:
|
||||
v = results.etud_moy_ue[ue.id][etud.id]
|
||||
else:
|
||||
v = 0 # XXX TODO valeur bonus sport pour cet étudiant
|
||||
x_ue.append(
|
||||
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(
|
||||
scu.SCO_ENCODING
|
||||
) # stop !
|
||||
|
||||
if etat_inscription == scu.INSCRIT:
|
||||
# Moyenne générale:
|
||||
doc.append(
|
||||
Element(
|
||||
"note",
|
||||
value=scu.fmt_note(v),
|
||||
min=scu.fmt_note(results.etud_moy_ue[ue.id].min()),
|
||||
max=scu.fmt_note(results.etud_moy_ue[ue.id].max()),
|
||||
value=scu.fmt_note(results.etud_moy_gen[etud.id]),
|
||||
min=scu.fmt_note(results.etud_moy_gen.min()),
|
||||
max=scu.fmt_note(results.etud_moy_gen.max()),
|
||||
moy=scu.fmt_note(results.etud_moy_gen.mean()), # moyenne des moy. gen.
|
||||
)
|
||||
)
|
||||
x_ue.append(Element("ects", value=str(ue.ects if ue.ects else 0)))
|
||||
x_ue.append(Element("rang", value=str(rang_ue)))
|
||||
x_ue.append(Element("effectif", value=str(nb_inscrits_ue)))
|
||||
# Liste les modules rattachés à cette UE
|
||||
for modimpl in results.modimpls:
|
||||
# Liste ici uniquement les modules rattachés à cette UE
|
||||
if modimpl.module.ue.id == ue.id:
|
||||
mod_moy = scu.fmt_note(results.etud_moy_ue[ue.id][etud.id])
|
||||
coef = results.modimpl_coefs_df[modimpl.id][ue.id]
|
||||
x_mod = Element(
|
||||
"module",
|
||||
id=str(modimpl.id),
|
||||
code=str(modimpl.module.code or ""),
|
||||
coefficient=str(coef),
|
||||
numero=str(modimpl.module.numero or 0),
|
||||
titre=scu.quote_xml_attr(modimpl.module.titre or ""),
|
||||
abbrev=scu.quote_xml_attr(modimpl.module.abbrev or ""),
|
||||
code_apogee=scu.quote_xml_attr(modimpl.module.code_apogee or ""),
|
||||
rang = 0 # XXX TODO rang de l'étduiant selon la moy gen indicative
|
||||
bonus = 0 # XXX TODO valeur du bonus sport
|
||||
doc.append(Element("rang", value=str(rang), ninscrits=str(nb_inscrits)))
|
||||
# XXX TODO: ajouter "rang_group" : rangs dans les partitions
|
||||
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
||||
doc.append(Element("bonus_sport_culture", value=str(bonus)))
|
||||
# Liste les UE / modules /evals
|
||||
for ue in results.ues:
|
||||
rang_ue = 0 # XXX TODO rang de l'étudiant dans cette UE
|
||||
nb_inscrits_ue = (
|
||||
nb_inscrits # approx: compliqué de définir le "nb d'inscrit à une UE"
|
||||
)
|
||||
x_ue = Element(
|
||||
"ue",
|
||||
id=str(ue.id),
|
||||
numero=scu.quote_xml_attr(ue.numero),
|
||||
acronyme=scu.quote_xml_attr(ue.acronyme or ""),
|
||||
titre=scu.quote_xml_attr(ue.titre or ""),
|
||||
code_apogee=scu.quote_xml_attr(ue.code_apogee or ""),
|
||||
)
|
||||
doc.append(x_ue)
|
||||
if ue.type != sco_codes_parcours.UE_SPORT:
|
||||
v = results.etud_moy_ue[ue.id][etud.id]
|
||||
else:
|
||||
v = 0 # XXX TODO valeur bonus sport pour cet étudiant
|
||||
x_ue.append(
|
||||
Element(
|
||||
"note",
|
||||
value=scu.fmt_note(v),
|
||||
min=scu.fmt_note(results.etud_moy_ue[ue.id].min()),
|
||||
max=scu.fmt_note(results.etud_moy_ue[ue.id].max()),
|
||||
)
|
||||
x_ue.append(x_mod)
|
||||
x_mod.append(
|
||||
Element(
|
||||
"note",
|
||||
value=mod_moy,
|
||||
min=scu.fmt_note(results.etud_moy_ue[ue.id].min()),
|
||||
max=scu.fmt_note(results.etud_moy_ue[ue.id].max()),
|
||||
moy=scu.fmt_note(results.etud_moy_ue[ue.id].mean()),
|
||||
)
|
||||
x_ue.append(Element("ects", value=str(ue.ects if ue.ects else 0)))
|
||||
x_ue.append(Element("rang", value=str(rang_ue)))
|
||||
x_ue.append(Element("effectif", value=str(nb_inscrits_ue)))
|
||||
# Liste les modules rattachés à cette UE
|
||||
for modimpl in results.modimpls:
|
||||
# Liste ici uniquement les modules rattachés à cette UE
|
||||
if modimpl.module.ue.id == ue.id:
|
||||
# mod_moy = scu.fmt_note(results.etud_moy_ue[ue.id][etud.id])
|
||||
coef = results.modimpl_coefs_df[modimpl.id][ue.id]
|
||||
x_mod = Element(
|
||||
"module",
|
||||
id=str(modimpl.id),
|
||||
code=str(modimpl.module.code or ""),
|
||||
coefficient=str(coef),
|
||||
numero=str(modimpl.module.numero or 0),
|
||||
titre=scu.quote_xml_attr(modimpl.module.titre or ""),
|
||||
abbrev=scu.quote_xml_attr(modimpl.module.abbrev or ""),
|
||||
code_apogee=scu.quote_xml_attr(
|
||||
modimpl.module.code_apogee or ""
|
||||
),
|
||||
)
|
||||
)
|
||||
# XXX TODO rangs et effectifs
|
||||
# --- notes de chaque eval:
|
||||
if version != "short":
|
||||
|
@ -218,16 +216,17 @@ def bulletin_but_xml_compat(
|
|||
value=scu.fmt_note(
|
||||
results.modimpls_results[
|
||||
e.moduleimpl_id
|
||||
].evals_notes[e.id][etud.id]
|
||||
].evals_notes[e.id][etud.id],
|
||||
note_max=e.note_max,
|
||||
),
|
||||
)
|
||||
)
|
||||
# XXX TODO: Evaluations incomplètes ou futures: XXX
|
||||
# XXX TODO UE capitalisee (listee seulement si meilleure que l'UE courante)
|
||||
# XXX TODO UE capitalisee (listee seulement si meilleure que l'UE courante)
|
||||
|
||||
# --- Absences
|
||||
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
|
||||
nbabs, nbabsjust = sem.get_abs_count(etud.id)
|
||||
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
|
||||
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
|
||||
|
||||
# -------- LA SUITE EST COPIEE SANS MODIF DE sco_bulletins_xml.py ---------
|
||||
|
|
|
@ -29,8 +29,8 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
|||
"""
|
||||
try:
|
||||
root = ElementTree.XML(xml_data)
|
||||
except ElementTree.ParseError:
|
||||
raise ScoFormatError("fichier XML Orébut invalide")
|
||||
except ElementTree.ParseError as exc:
|
||||
raise ScoFormatError(f"fichier XML Orébut invalide (2): {exc.args}")
|
||||
if root.tag != "referentiel_competence":
|
||||
raise ScoFormatError("élément racine 'referentiel_competence' manquant")
|
||||
args = ApcReferentielCompetences.attr_from_xml(root.attrib)
|
||||
|
@ -54,8 +54,8 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
|||
composantes = competence.find("composantes_essentielles")
|
||||
for composante in composantes:
|
||||
libelle = "".join(composante.itertext()).strip()
|
||||
ce = ApcComposanteEssentielle(libelle=libelle)
|
||||
c.composantes_essentielles.append(ce)
|
||||
compo_ess = ApcComposanteEssentielle(libelle=libelle)
|
||||
c.composantes_essentielles.append(compo_ess)
|
||||
# --- NIVEAUX (années)
|
||||
niveaux = competence.find("niveaux")
|
||||
for niveau in niveaux:
|
||||
|
@ -77,16 +77,14 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
|||
a = ApcAnneeParcours(**ApcAnneeParcours.attr_from_xml(annee.attrib))
|
||||
parc.annees.append(a)
|
||||
for competence in annee.findall("competence"):
|
||||
nom = competence.attrib["nom"]
|
||||
comp_id_orebut = competence.attrib["id"]
|
||||
niveau = int(competence.attrib["niveau"])
|
||||
# Retrouve la competence
|
||||
comp = ref.competences.filter_by(titre=nom).all()
|
||||
if len(comp) == 0:
|
||||
raise ScoFormatError(f"competence {nom} référencée mais on définie")
|
||||
elif len(comp) > 1:
|
||||
raise ScoFormatError(f"competence {nom} ambigüe")
|
||||
comp = ref.competences.filter_by(id_orebut=comp_id_orebut).first()
|
||||
if comp is None:
|
||||
raise ScoFormatError(f"competence {comp_id_orebut} non définie")
|
||||
ass = ApcParcoursNiveauCompetence(
|
||||
niveau=niveau, annee_parcours=a, competence=comp[0]
|
||||
niveau=niveau, annee_parcours=a, competence=comp
|
||||
)
|
||||
db.session.add(ass)
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
import numpy as np
|
||||
|
||||
"""Quelques classes auxiliaires pour les calculs des notes
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class StatsMoyenne:
|
||||
"""Une moyenne d'un ensemble étudiants sur quelque chose
|
||||
|
|
|
@ -16,13 +16,13 @@ from app import models
|
|||
#
|
||||
def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
|
||||
"""Charge la matrice des inscriptions aux modules du semestre
|
||||
rows: etudid
|
||||
rows: etudid (inscrits au semestre, avec DEM et DEF)
|
||||
columns: moduleimpl_id (en chaîne)
|
||||
value: bool (0/1 inscrit ou pas)
|
||||
"""
|
||||
# méthode la moins lente: une requete par module, merge les dataframes
|
||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
||||
etudids = [i.etudid for i in formsemestre.get_inscrits(include_dem=False)]
|
||||
etudids = [inscr.etudid for inscr in formsemestre.inscriptions]
|
||||
df = pd.DataFrame(index=etudids, dtype=int)
|
||||
for moduleimpl_id in moduleimpl_ids:
|
||||
ins_df = pd.read_sql_query(
|
||||
|
|
|
@ -69,11 +69,13 @@ class ModuleImplResults:
|
|||
"nombre d'inscrits (non DEM) au module"
|
||||
self.evaluations_completes = []
|
||||
"séquence de booléens, indiquant les évals à prendre en compte."
|
||||
self.evaluations_completes_dict = {}
|
||||
"{ evaluation.id : bool } indique si à prendre en compte ou non."
|
||||
self.evaluations_etat = {}
|
||||
"{ evaluation_id: EvaluationEtat }"
|
||||
#
|
||||
self.evals_notes = None
|
||||
"""DataFrame, colonnes: EVALS, Lignes: etudid
|
||||
"""DataFrame, colonnes: EVALS, Lignes: etudid (inscrits au SEMESTRE)
|
||||
valeur: notes brutes, float ou NOTES_ATTENTE, NOTES_NEUTRALISE,
|
||||
NOTES_ABSENCE.
|
||||
Les NaN désignent les notes manquantes (non saisies).
|
||||
|
@ -103,7 +105,7 @@ class ModuleImplResults:
|
|||
|
||||
Évaluation "complete" (prise en compte dans les calculs) si:
|
||||
- soit tous les étudiants inscrits au module ont des notes
|
||||
- soit elle a été déclarée "à prise ne compte immédiate" (publish_incomplete)
|
||||
- soit elle a été déclarée "à prise en compte immédiate" (publish_incomplete)
|
||||
|
||||
Évaluation "attente" (prise en compte dans les calculs, mais il y
|
||||
manque des notes) ssi il y a des étudiants inscrits au semestre et au module
|
||||
|
@ -122,6 +124,7 @@ class ModuleImplResults:
|
|||
# dataFrame vide, index = tous les inscrits au SEMESTRE
|
||||
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
||||
self.evaluations_completes = []
|
||||
self.evaluations_completes_dict = {}
|
||||
for evaluation in moduleimpl.evaluations:
|
||||
eval_df = self._load_evaluation_notes(evaluation)
|
||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
||||
|
@ -131,6 +134,7 @@ class ModuleImplResults:
|
|||
== self.nb_inscrits_module
|
||||
) or evaluation.publish_incomplete # immédiate
|
||||
self.evaluations_completes.append(is_complete)
|
||||
self.evaluations_completes_dict[evaluation.id] = is_complete
|
||||
|
||||
# NULL en base => ABS (= -999)
|
||||
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
||||
|
@ -174,14 +178,12 @@ class ModuleImplResults:
|
|||
return eval_df
|
||||
|
||||
def _etudids(self):
|
||||
"""L'index du dataframe est la liste des étudiants inscrits au semestre,
|
||||
sans les démissionnaires.
|
||||
"""
|
||||
"""L'index du dataframe est la liste de tous les étudiants inscrits au semestre"""
|
||||
return [
|
||||
e.etudid
|
||||
for e in ModuleImpl.query.get(self.moduleimpl_id).formsemestre.get_inscrits(
|
||||
include_dem=False
|
||||
)
|
||||
inscr.etudid
|
||||
for inscr in ModuleImpl.query.get(
|
||||
self.moduleimpl_id
|
||||
).formsemestre.inscriptions
|
||||
]
|
||||
|
||||
def get_evaluations_coefs(self, moduleimpl: ModuleImpl) -> np.array:
|
||||
|
@ -270,7 +272,7 @@ def load_evaluations_poids(
|
|||
remplies par default_poids.
|
||||
Résultat: (evals_poids, liste de UE du semestre)
|
||||
"""
|
||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
||||
ues = modimpl.formsemestre.query_ues(with_sport=False).all()
|
||||
ue_ids = [ue.id for ue in ues]
|
||||
|
|
|
@ -31,8 +31,10 @@ import numpy as np
|
|||
import pandas as pd
|
||||
|
||||
|
||||
def compute_sem_moys_apc(etud_moy_ue_df, modimpl_coefs_df):
|
||||
"""Calcule la moyenne générale indicative
|
||||
def compute_sem_moys_apc(
|
||||
etud_moy_ue_df: pd.DataFrame, modimpl_coefs_df: pd.DataFrame
|
||||
) -> pd.Series:
|
||||
"""Calcule les moyennes générales indicatives de tous les étudiants
|
||||
= moyenne des moyennes d'UE, pondérée par la somme de leurs coefs
|
||||
|
||||
etud_moy_ue_df: DataFrame, colonnes ue_id, lignes etudid
|
||||
|
@ -46,10 +48,11 @@ def compute_sem_moys_apc(etud_moy_ue_df, modimpl_coefs_df):
|
|||
return moy_gen
|
||||
|
||||
|
||||
def comp_ranks_series(notes: pd.Series):
|
||||
"""Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur numérique)
|
||||
en tenant compte des ex-aequos
|
||||
Le resultat est: { etudid : rang } où rang est une chaine decrivant le rang
|
||||
def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
|
||||
"""Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur
|
||||
numérique) en tenant compte des ex-aequos.
|
||||
|
||||
Result: { etudid : rang:str } où rang est une chaine decrivant le rang.
|
||||
"""
|
||||
notes = notes.sort_values(ascending=False) # Serie, tri par ordre décroissant
|
||||
rangs = pd.Series(index=notes.index, dtype=str) # le rang est une chaîne
|
||||
|
|
|
@ -36,6 +36,7 @@ from app.models import UniteEns, Module, ModuleImpl, ModuleUECoef
|
|||
from app.comp import moy_mod
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.DataFrame:
|
||||
|
@ -56,8 +57,15 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
|||
.filter(UniteEns.type != sco_codes_parcours.UE_SPORT)
|
||||
.order_by(UniteEns.semestre_idx, UniteEns.numero, UniteEns.acronyme)
|
||||
)
|
||||
modules = Module.query.filter_by(formation_id=formation_id).order_by(
|
||||
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
||||
modules = (
|
||||
Module.query.filter_by(formation_id=formation_id)
|
||||
.filter(
|
||||
(Module.module_type == ModuleType.RESSOURCE)
|
||||
| (Module.module_type == ModuleType.SAE)
|
||||
)
|
||||
.order_by(
|
||||
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
||||
)
|
||||
)
|
||||
if semestre_idx is not None:
|
||||
ues = ues.filter_by(semestre_idx=semestre_idx)
|
||||
|
@ -76,7 +84,9 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
|||
query = query.filter(UniteEns.semestre_idx == semestre_idx)
|
||||
|
||||
for mod_coef in query:
|
||||
module_coefs_df[mod_coef.module_id][mod_coef.ue_id] = mod_coef.coef
|
||||
if mod_coef.module_id in module_coefs_df:
|
||||
module_coefs_df[mod_coef.module_id][mod_coef.ue_id] = mod_coef.coef
|
||||
# silently ignore coefs associated to other modules (ie when module_type is changed)
|
||||
|
||||
module_coefs_df.fillna(value=0, inplace=True)
|
||||
|
||||
|
@ -121,6 +131,7 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
|||
(DataFrames rendus par compute_module_moy, (etud x UE))
|
||||
Resultat: ndarray (etud x module x UE)
|
||||
"""
|
||||
assert len(modimpls_notes)
|
||||
modimpls_notes_arr = [df.values for df in modimpls_notes]
|
||||
modimpls_notes = np.stack(modimpls_notes_arr)
|
||||
# passe de (mod x etud x ue) à (etud x mod x UE)
|
||||
|
@ -128,9 +139,14 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
|||
|
||||
|
||||
def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
||||
"""Calcule le cube des notes du semestre
|
||||
(charge toutes les notes, calcule les moyenne des modules
|
||||
et assemble le cube)
|
||||
"""Construit le "cube" (tenseur) des notes du semestre.
|
||||
Charge toutes les notes (sql), calcule les moyennes des modules
|
||||
et assemble le cube.
|
||||
|
||||
etuds: tous les inscrits au semestre (avec dem. et def.)
|
||||
modimpls: _tous_ les modimpls de ce semestre
|
||||
UEs: X?X voir quelles sont les UE considérées ici
|
||||
|
||||
Resultat:
|
||||
sem_cube : ndarray (etuds x modimpls x UEs)
|
||||
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
||||
|
@ -145,8 +161,13 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||
modimpls_results[modimpl.id] = mod_results
|
||||
modimpls_notes.append(etuds_moy_module)
|
||||
if len(modimpls_notes):
|
||||
cube = notes_sem_assemble_cube(modimpls_notes)
|
||||
else:
|
||||
nb_etuds = formsemestre.etuds.count()
|
||||
cube = np.zeros((nb_etuds, 0, 0), dtype=float)
|
||||
return (
|
||||
notes_sem_assemble_cube(modimpls_notes),
|
||||
cube,
|
||||
modimpls_evals_poids,
|
||||
modimpls_results,
|
||||
)
|
||||
|
@ -162,14 +183,14 @@ def compute_ue_moys_apc(
|
|||
) -> 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]
|
||||
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]
|
||||
|
||||
sem_cube: notes moyennes aux modules
|
||||
ndarray (etuds x modimpls x UEs)
|
||||
(floats avec des NaN)
|
||||
etuds : listes des étudiants (dim. 0 du cube)
|
||||
etuds : liste des étudiants (dim. 0 du cube)
|
||||
modimpls : liste des modules à considérer (dim. 1 du cube)
|
||||
ues : liste des UE (dim. 2 du cube)
|
||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||
|
@ -178,8 +199,12 @@ def compute_ue_moys_apc(
|
|||
Resultat: DataFrame columns UE, rows etudid
|
||||
"""
|
||||
nb_etuds, nb_modules, nb_ues = sem_cube.shape
|
||||
assert len(etuds) == nb_etuds
|
||||
assert len(modimpls) == nb_modules
|
||||
if nb_modules == 0 or nb_etuds == 0:
|
||||
return pd.DataFrame(
|
||||
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
||||
)
|
||||
assert len(etuds) == nb_etuds
|
||||
assert len(ues) == nb_ues
|
||||
assert modimpl_inscr_df.shape[0] == nb_etuds
|
||||
assert modimpl_inscr_df.shape[1] == nb_modules
|
||||
|
@ -187,10 +212,6 @@ def compute_ue_moys_apc(
|
|||
assert modimpl_coefs_df.shape[1] == nb_modules
|
||||
modimpl_inscr = modimpl_inscr_df.values
|
||||
modimpl_coefs = modimpl_coefs_df.values
|
||||
if nb_etuds == 0:
|
||||
return pd.DataFrame(
|
||||
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
||||
)
|
||||
# Duplique les inscriptions sur les UEs:
|
||||
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues, axis=2)
|
||||
# Enlève les NaN du numérateur:
|
||||
|
@ -223,12 +244,12 @@ def compute_ue_moys_classic(
|
|||
ues: list,
|
||||
modimpl_inscr_df: pd.DataFrame,
|
||||
modimpl_coefs: np.array,
|
||||
) -> pd.DataFrame:
|
||||
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
||||
"""Calcul de la moyenne d'UE en mode classique.
|
||||
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]
|
||||
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]
|
||||
|
||||
sem_matrix: notes moyennes aux modules
|
||||
ndarray (etuds x modimpls)
|
||||
|
@ -241,6 +262,9 @@ def compute_ue_moys_classic(
|
|||
Résultat:
|
||||
- moyennes générales: pd.Series, index etudid
|
||||
- moyennes d'UE: DataFrame columns UE, rows etudid
|
||||
- coefficients d'UE: DataFrame, columns UE, rows etudid
|
||||
les coefficients effectifs de chaque UE pour chaque étudiant
|
||||
(sommes de coefs de modules pris en compte)
|
||||
"""
|
||||
nb_etuds, nb_modules = sem_matrix.shape
|
||||
assert len(modimpl_coefs) == nb_modules
|
||||
|
@ -281,4 +305,9 @@ def compute_ue_moys_classic(
|
|||
etud_moy_ue_df = pd.DataFrame(
|
||||
etud_moy_ue, index=modimpl_inscr_df.index, columns=[ue.id for ue in ues]
|
||||
)
|
||||
return etud_moy_gen_s, etud_moy_ue_df
|
||||
etud_coef_ue_df = pd.DataFrame(
|
||||
coefs.sum(axis=2).T,
|
||||
index=modimpl_inscr_df.index, # etudids
|
||||
columns=[ue.id for ue in ues],
|
||||
)
|
||||
return etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
"""Résultats semestres BUT
|
||||
"""
|
||||
import pandas as pd
|
||||
|
||||
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||
from app.comp.res_sem import NotesTableCompat
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
|
||||
|
||||
class ResultatsSemestreBUT(NotesTableCompat):
|
||||
|
@ -49,6 +50,10 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||
self.modimpl_inscr_df,
|
||||
self.modimpl_coefs_df,
|
||||
)
|
||||
# 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
|
||||
)
|
||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||
self.etud_moy_ue, self.modimpl_coefs_df
|
||||
)
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
||||
from app.comp.res_sem import NotesTableCompat
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
|
||||
|
@ -45,7 +46,11 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||
self.modimpl_idx = {m.id: i for i, m in enumerate(self.formsemestre.modimpls)}
|
||||
"l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]"
|
||||
|
||||
self.etud_moy_gen, self.etud_moy_ue = moy_ue.compute_ue_moys_classic(
|
||||
(
|
||||
self.etud_moy_gen,
|
||||
self.etud_moy_ue,
|
||||
self.etud_coef_ue_df,
|
||||
) = moy_ue.compute_ue_moys_classic(
|
||||
self.formsemestre,
|
||||
self.sem_matrix,
|
||||
self.ues,
|
||||
|
@ -60,6 +65,25 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||
"""
|
||||
return self.modimpls_results[moduleimpl_id].etuds_moy_module.get(etudid, "NI")
|
||||
|
||||
def get_mod_stats(self, moduleimpl_id: int) -> dict:
|
||||
"""Stats sur les notes obtenues dans un modimpl"""
|
||||
notes_series: pd.Series = self.modimpls_results[moduleimpl_id].etuds_moy_module
|
||||
nb_notes = len(notes_series)
|
||||
if not nb_notes:
|
||||
super().get_mod_stats(moduleimpl_id)
|
||||
return {
|
||||
# Series: Statistical methods from ndarray have been overridden to automatically
|
||||
# exclude missing data (currently represented as NaN)
|
||||
"moy": notes_series.mean(), # donc sans prendre en compte les NaN
|
||||
"max": notes_series.max(),
|
||||
"min": notes_series.min(),
|
||||
"nb_notes": nb_notes,
|
||||
"nb_missing": sum(notes_series.isna()),
|
||||
"nb_valid_evals": sum(
|
||||
self.modimpls_results[moduleimpl_id].evaluations_completes
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple:
|
||||
"""Calcule la matrice des notes du semestre
|
||||
|
|
|
@ -0,0 +1,397 @@
|
|||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
from collections import defaultdict, Counter
|
||||
from functools import cached_property
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from app.comp.aux import StatsMoyenne
|
||||
from app.comp.moy_mod import ModuleImplResults
|
||||
from app.models import FormSemestre, Identite, ModuleImpl
|
||||
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, ATT, DEF
|
||||
|
||||
# Il faut bien distinguer
|
||||
# - ce qui est caché de façon persistente (via redis):
|
||||
# ce sont les attributs listés dans `_cached_attrs`
|
||||
# le stockage et l'invalidation sont gérés dans sco_cache.py
|
||||
#
|
||||
# - les valeurs cachées durant le temps d'une requête
|
||||
# (durée de vie de l'instance de ResultatsSemestre)
|
||||
# qui sont notamment les attributs décorés par `@cached_property``
|
||||
#
|
||||
class ResultatsSemestre:
|
||||
_cached_attrs = (
|
||||
"etud_moy_gen_ranks",
|
||||
"etud_moy_gen",
|
||||
"etud_moy_ue",
|
||||
"modimpl_inscr_df",
|
||||
"modimpls_results",
|
||||
"etud_coef_ue_df",
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
self.formsemestre: FormSemestre = formsemestre
|
||||
# BUT ou standard ? (apc == "approche par compétences")
|
||||
self.is_apc = formsemestre.formation.is_apc()
|
||||
# Attributs "virtuels", définis dans les sous-classes
|
||||
# ResultatsSemestreBUT ou ResultatsSemestreClassic
|
||||
self.etud_moy_ue = {}
|
||||
"etud_moy_ue: DataFrame columns UE, rows etudid"
|
||||
self.etud_moy_gen = {}
|
||||
self.etud_moy_gen_ranks = {}
|
||||
self.modimpls_results: ModuleImplResults = None
|
||||
self.etud_coef_ue_df = None
|
||||
"""coefs d'UE effectifs pour chaque etudiant (pour form. classiques)"""
|
||||
|
||||
# TODO ?
|
||||
|
||||
def load_cached(self) -> bool:
|
||||
"Load cached dataframes, returns False si pas en cache"
|
||||
data = ResultatsSemestreCache.get(self.formsemestre.id)
|
||||
if not data:
|
||||
return False
|
||||
for attr in self._cached_attrs:
|
||||
setattr(self, attr, data[attr])
|
||||
return True
|
||||
|
||||
def store(self):
|
||||
"Cache our data"
|
||||
ResultatsSemestreCache.set(
|
||||
self.formsemestre.id,
|
||||
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
||||
)
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||
# voir ce qui est chargé / calculé ici et dans les sous-classes
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_inscriptions_counts(self) -> Counter:
|
||||
"""Nombre d'inscrits, défaillants, démissionnaires.
|
||||
|
||||
Exemple: res.get_inscriptions_counts()[scu.INSCRIT]
|
||||
|
||||
Result: a collections.Counter instance
|
||||
"""
|
||||
return Counter(ins.etat for ins in self.formsemestre.inscriptions)
|
||||
|
||||
@cached_property
|
||||
def etuds(self) -> list[Identite]:
|
||||
"Liste des inscrits au semestre, avec les démissionnaires et les défaillants"
|
||||
# nb: si la liste des inscrits change, ResultatsSemestre devient invalide
|
||||
return self.formsemestre.get_inscrits(include_demdef=True)
|
||||
|
||||
@cached_property
|
||||
def etud_index(self) -> dict[int, int]:
|
||||
"dict { etudid : indice dans les inscrits }"
|
||||
return {e.id: idx for idx, e in enumerate(self.etuds)}
|
||||
|
||||
@cached_property
|
||||
def etuds_dict(self) -> dict[int, Identite]:
|
||||
"""dict { etudid : Identite } inscrits au semestre,
|
||||
avec les démissionnaires et defs."""
|
||||
return {etud.id: etud for etud in self.etuds}
|
||||
|
||||
@cached_property
|
||||
def ues(self) -> list[UniteEns]:
|
||||
"""Liste des UEs du semestre
|
||||
(indices des DataFrames)
|
||||
"""
|
||||
return self.formsemestre.query_ues(with_sport=True).all()
|
||||
|
||||
@cached_property
|
||||
def modimpls(self):
|
||||
"""Liste des modimpls du semestre
|
||||
- triée par numéro de module en APC
|
||||
- triée par numéros d'UE/matières/modules pour les formations standard.
|
||||
"""
|
||||
modimpls = self.formsemestre.modimpls.all()
|
||||
if self.is_apc:
|
||||
modimpls.sort(key=lambda m: (m.module.numero, m.module.code))
|
||||
else:
|
||||
modimpls.sort(
|
||||
key=lambda m: (
|
||||
m.module.ue.numero,
|
||||
m.module.matiere.numero,
|
||||
m.module.numero,
|
||||
m.module.code,
|
||||
)
|
||||
)
|
||||
return modimpls
|
||||
|
||||
@cached_property
|
||||
def ressources(self):
|
||||
"Liste des ressources du semestre, triées par numéro de module"
|
||||
return [
|
||||
m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def saes(self):
|
||||
"Liste des SAÉs du semestre, triées par numéro de module"
|
||||
return [m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE]
|
||||
|
||||
@cached_property
|
||||
def ue_validables(self) -> list:
|
||||
"""Liste des UE du semestre qui doivent être validées
|
||||
(toutes sauf le sport)
|
||||
"""
|
||||
return self.formsemestre.query_ues().filter(UniteEns.type != UE_SPORT).all()
|
||||
|
||||
@cached_property
|
||||
def ue_au_dessus(self, seuil=10.0) -> pd.DataFrame:
|
||||
"""DataFrame columns UE, rows etudid, valeurs: bool
|
||||
Par exemple, pour avoir le nombre d'UE au dessus de 10 pour l'étudiant etudid
|
||||
nb_ues_ok = sum(res.ue_au_dessus().loc[etudid])
|
||||
"""
|
||||
return self.etud_moy_ue > (seuil - scu.NOTES_TOLERANCE)
|
||||
|
||||
|
||||
# Pour raccorder le code des anciens codes qui attendent une NoteTable
|
||||
class NotesTableCompat(ResultatsSemestre):
|
||||
"""Implementation partielle de NotesTable WIP TODO
|
||||
|
||||
Les méthodes définies dans cette classe sont là
|
||||
pour conserver la compatibilité abvec les codes anciens et
|
||||
il n'est pas recommandé de les utiliser dans de nouveaux
|
||||
développements (API malcommode et peu efficace).
|
||||
"""
|
||||
|
||||
_cached_attrs = ResultatsSemestre._cached_attrs + ()
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
super().__init__(formsemestre)
|
||||
|
||||
nb_etuds = len(self.etuds)
|
||||
self.bonus = defaultdict(lambda: 0.0) # XXX TODO
|
||||
self.ue_rangs = {u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.ues}
|
||||
self.mod_rangs = {
|
||||
m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.modimpls
|
||||
}
|
||||
self.moy_min = "NA"
|
||||
self.moy_max = "NA"
|
||||
self.moy_moy = "NA"
|
||||
self.expr_diagnostics = ""
|
||||
self.parcours = self.formsemestre.formation.get_parcours()
|
||||
|
||||
def get_etudids(self, sorted=False) -> list[int]:
|
||||
"""Liste des etudids inscrits, incluant les démissionnaires.
|
||||
Si sorted, triée par moy. générale décroissante
|
||||
Sinon, triée par ordre alphabetique de NOM
|
||||
"""
|
||||
# Note: pour avoir les inscrits non triés,
|
||||
# utiliser [ ins.etudid for ins in self.formsemestre.inscriptions ]
|
||||
if sorted:
|
||||
# Tri par moy. generale décroissante
|
||||
return [x[-1] for x in self.T]
|
||||
|
||||
return [x["etudid"] for x in self.inscrlist]
|
||||
|
||||
@cached_property
|
||||
def sem(self) -> dict:
|
||||
"""le formsemestre, comme un dict (nt.sem)"""
|
||||
return self.formsemestre.to_dict()
|
||||
|
||||
@cached_property
|
||||
def inscrlist(self) -> list[dict]: # utilisé par PE seulement
|
||||
"""Liste des inscrits au semestre (avec DEM et DEF),
|
||||
sous forme de dict etud,
|
||||
classée dans l'ordre alphabétique de noms.
|
||||
"""
|
||||
etuds = self.formsemestre.get_inscrits(include_demdef=True)
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
return [e.to_dict_scodoc7() for e in etuds]
|
||||
|
||||
@cached_property
|
||||
def stats_moy_gen(self):
|
||||
"""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()
|
||||
"""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):
|
||||
d = ue.to_dict()
|
||||
d.update(StatsMoyenne(self.etud_moy_ue[ue.id]).to_dict())
|
||||
ues.append(d)
|
||||
return ues
|
||||
|
||||
def get_modimpls_dict(self, ue_id=None):
|
||||
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
||||
triés par numéros (selon le type de formation)
|
||||
"""
|
||||
if ue_id is None:
|
||||
return [m.to_dict() for m in self.modimpls]
|
||||
else:
|
||||
return [m.to_dict() for m in self.modimpls if m.module.ue.id == ue_id]
|
||||
|
||||
def get_etud_decision_sem(self, etudid: int) -> dict:
|
||||
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
||||
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
|
||||
Si état défaillant, force le code a DEF
|
||||
"""
|
||||
if self.get_etud_etat(etudid) == DEF:
|
||||
return {
|
||||
"code": DEF,
|
||||
"assidu": False,
|
||||
"event_date": "",
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"code": ATT, # XXX TODO
|
||||
"assidu": True, # XXX TODO
|
||||
"event_date": "",
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
|
||||
def get_etud_etat(self, etudid: int) -> str:
|
||||
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
|
||||
ins = self.formsemestre.etuds_inscriptions.get(etudid, None)
|
||||
if ins is None:
|
||||
return ""
|
||||
return ins.etat
|
||||
|
||||
def get_etud_moy_gen(self, etudid): # -> float | str
|
||||
"""Moyenne générale de cet etudiant dans ce semestre.
|
||||
Prend(ra) en compte les UE capitalisées. (TODO) XXX
|
||||
Si apc, moyenne indicative.
|
||||
Si pas de notes: 'NA'
|
||||
"""
|
||||
return self.etud_moy_gen[etudid]
|
||||
|
||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||
"""La moyenne de l'étudiant dans le moduleimpl
|
||||
En APC, il s'agira d'une moyenne indicative sans valeur.
|
||||
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM)
|
||||
"""
|
||||
raise NotImplementedError() # virtual method
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||
return {
|
||||
"cur_moy_ue": self.etud_moy_ue[ue_id][etudid],
|
||||
"moy": self.etud_moy_ue[ue_id][etudid],
|
||||
"is_capitalized": False, # XXX TODO
|
||||
"coef_ue": coef_ue, # XXX TODO
|
||||
}
|
||||
|
||||
def get_etud_rang(self, etudid: int):
|
||||
return self.etud_moy_gen_ranks.get(etudid, 99999) # XXX
|
||||
|
||||
def get_etud_rang_group(self, etudid: int, group_id: int):
|
||||
return (None, 0) # XXX unimplemented TODO
|
||||
|
||||
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
|
||||
"""Liste d'informations (compat NotesTable) sur évaluations completes
|
||||
de ce module.
|
||||
Évaluation "complete" ssi toutes notes saisies ou en attente.
|
||||
"""
|
||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
evals_results = []
|
||||
for e in modimpl.evaluations:
|
||||
if self.modimpls_results[moduleimpl_id].evaluations_completes_dict[e.id]:
|
||||
d = e.to_dict()
|
||||
moduleimpl_results = self.modimpls_results[e.moduleimpl_id]
|
||||
d["heure_debut"] = e.heure_debut # datetime.time
|
||||
d["heure_fin"] = e.heure_fin
|
||||
d["jour"] = e.jour # datetime
|
||||
d["notes"] = {
|
||||
etud.id: {
|
||||
"etudid": etud.id,
|
||||
"value": moduleimpl_results.evals_notes[e.id][etud.id],
|
||||
}
|
||||
for etud in self.etuds
|
||||
}
|
||||
d["etat"] = {
|
||||
"evalattente": moduleimpl_results.evaluations_etat[e.id].nb_attente,
|
||||
}
|
||||
evals_results.append(d)
|
||||
return evals_results
|
||||
|
||||
def get_moduleimpls_attente(self):
|
||||
return [] # XXX TODO
|
||||
|
||||
def get_mod_stats(self, moduleimpl_id: int) -> dict:
|
||||
"""Stats sur les notes obtenues dans un modimpl
|
||||
Vide en APC
|
||||
"""
|
||||
return {
|
||||
"moy": "-",
|
||||
"max": "-",
|
||||
"min": "-",
|
||||
"nb_notes": "-",
|
||||
"nb_missing": "-",
|
||||
"nb_valid_evals": "-",
|
||||
}
|
||||
|
||||
def get_nom_short(self, etudid):
|
||||
"formatte nom d'un etud (pour table recap)"
|
||||
etud = self.identdict[etudid]
|
||||
return (
|
||||
(etud["nom_usuel"] or etud["nom"]).upper()
|
||||
+ " "
|
||||
+ etud["prenom"].capitalize()[:2]
|
||||
+ "."
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def T(self):
|
||||
return self.get_table_moyennes_triees()
|
||||
|
||||
def get_table_moyennes_triees(self) -> list:
|
||||
"""Result: liste de tuples
|
||||
moy_gen, moy_ue_0, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
|
||||
"""
|
||||
table_moyennes = []
|
||||
etuds_inscriptions = self.formsemestre.etuds_inscriptions
|
||||
|
||||
for etudid in etuds_inscriptions:
|
||||
moy_gen = self.etud_moy_gen.get(etudid, False)
|
||||
if moy_gen is False:
|
||||
# pas de moyenne: démissionnaire ou def
|
||||
t = ["-"] + ["0.00"] * len(self.ues) + ["NI"] * len(self.modimpls)
|
||||
else:
|
||||
moy_ues = self.etud_moy_ue.loc[etudid]
|
||||
t = [moy_gen] + list(moy_ues)
|
||||
# TODO UE capitalisées: ne pas afficher moyennes modules
|
||||
for modimpl in self.modimpls:
|
||||
val = self.get_etud_mod_moy(modimpl.id, etudid)
|
||||
t.append(val)
|
||||
t.append(etudid)
|
||||
table_moyennes.append(t)
|
||||
# tri par moyennes décroissantes,
|
||||
# en laissant les démissionnaires à la fin, par ordre alphabetique
|
||||
etuds = [ins.etud for ins in etuds_inscriptions.values()]
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
self._rang_alpha = {e.id: i for i, e in enumerate(etuds)}
|
||||
table_moyennes.sort(key=self._row_key)
|
||||
return table_moyennes
|
||||
|
||||
def _row_key(self, x):
|
||||
"""clé de tri par moyennes décroissantes,
|
||||
en laissant les demissionnaires à la fin, par ordre alphabetique.
|
||||
(moy_gen, rang_alpha)
|
||||
"""
|
||||
try:
|
||||
moy = -float(x[0])
|
||||
except (ValueError, TypeError):
|
||||
moy = 1000.0
|
||||
return (moy, self._rang_alpha[x[-1]])
|
||||
|
||||
@cached_property
|
||||
def identdict(self) -> dict:
|
||||
"""{ etudid : etud_dict } pour tous les inscrits au semestre"""
|
||||
return {
|
||||
ins.etud.id: ins.etud.to_dict_scodoc7()
|
||||
for ins in self.formsemestre.inscriptions
|
||||
}
|
|
@ -4,334 +4,35 @@
|
|||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
from collections import defaultdict
|
||||
from functools import cached_property
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from app.comp.aux import StatsMoyenne
|
||||
from app.models import FormSemestre, ModuleImpl
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
|
||||
"""Chargement des résultats de semestres (tous types)
|
||||
"""
|
||||
from flask import g
|
||||
|
||||
# Il faut bien distinguer
|
||||
# - ce qui est caché de façon persistente (via redis):
|
||||
# ce sont les attributs listés dans `_cached_attrs`
|
||||
# le stockage et l'invalidation sont gérés dans sco_cache.py
|
||||
#
|
||||
# - les valeurs cachées durant le temps d'une requête
|
||||
# (durée de vie de l'instance de ResultatsSemestre)
|
||||
# qui sont notamment les attributs décorés par `@cached_property``
|
||||
#
|
||||
class ResultatsSemestre:
|
||||
_cached_attrs = (
|
||||
"etud_moy_gen_ranks",
|
||||
"etud_moy_gen",
|
||||
"etud_moy_ue",
|
||||
"modimpl_inscr_df",
|
||||
"modimpls_results",
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
self.formsemestre = formsemestre
|
||||
# BUT ou standard ? (apc == "approche par compétences")
|
||||
self.is_apc = formsemestre.formation.is_apc()
|
||||
# Attributs "virtuels", définis pas les sous-classes
|
||||
# ResultatsSemestreBUT ou ResultatsSemestreStd
|
||||
self.etud_moy_ue = {}
|
||||
self.etud_moy_gen = {}
|
||||
self.etud_moy_gen_ranks = {}
|
||||
# TODO
|
||||
|
||||
def load_cached(self) -> bool:
|
||||
"Load cached dataframes, returns False si pas en cache"
|
||||
data = ResultatsSemestreCache.get(self.formsemestre.id)
|
||||
if not data:
|
||||
return False
|
||||
for attr in self._cached_attrs:
|
||||
setattr(self, attr, data[attr])
|
||||
return True
|
||||
|
||||
def store(self):
|
||||
"Cache our data"
|
||||
ResultatsSemestreCache.set(
|
||||
self.formsemestre.id,
|
||||
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
||||
)
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||
# voir ce qui est chargé / calculé ici et dans les sous-classes
|
||||
raise NotImplementedError()
|
||||
|
||||
@cached_property
|
||||
def etuds(self):
|
||||
"Liste des inscrits au semestre, sans les démissionnaires"
|
||||
# nb: si la liste des inscrits change, ResultatsSemestre devient invalide
|
||||
return self.formsemestre.get_inscrits(include_dem=False)
|
||||
|
||||
@cached_property
|
||||
def etud_index(self):
|
||||
"dict { etudid : indice dans les inscrits }"
|
||||
return {e.id: idx for idx, e in enumerate(self.etuds)}
|
||||
|
||||
@cached_property
|
||||
def ues(self):
|
||||
"Liste des UE du semestre"
|
||||
return self.formsemestre.query_ues().all()
|
||||
|
||||
@cached_property
|
||||
def modimpls(self):
|
||||
"""Liste des modimpls du semestre
|
||||
- triée par numéro de module en APC
|
||||
- triée par numéros d'UE/matières/modules pour les formations standard.
|
||||
"""
|
||||
modimpls = self.formsemestre.modimpls.all()
|
||||
if self.is_apc:
|
||||
modimpls.sort(key=lambda m: (m.module.numero, m.module.code))
|
||||
else:
|
||||
modimpls.sort(
|
||||
key=lambda m: (
|
||||
m.module.ue.numero,
|
||||
m.module.matiere.numero,
|
||||
m.module.numero,
|
||||
m.module.code,
|
||||
)
|
||||
)
|
||||
return modimpls
|
||||
|
||||
@cached_property
|
||||
def ressources(self):
|
||||
"Liste des ressources du semestre, triées par numéro de module"
|
||||
return [
|
||||
m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def saes(self):
|
||||
"Liste des SAÉs du semestre, triées par numéro de module"
|
||||
return [m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE]
|
||||
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
|
||||
|
||||
|
||||
# Pour raccorder le code des anciens codes qui attendent une NoteTable
|
||||
class NotesTableCompat(ResultatsSemestre):
|
||||
"""Implementation partielle de NotesTable WIP TODO
|
||||
def load_formsemestre_result(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
"""Returns ResultatsSemestre for this formsemestre.
|
||||
Suivant le type de formation, retour une instance de
|
||||
ResultatsSemestreClassic ou de ResultatsSemestreBUT.
|
||||
|
||||
Les méthodes définies dans cette classe sont là
|
||||
pour conserver la compatibilité abvec les codes anciens et
|
||||
il n'est pas recommandé de les utiliser dans de nouveaux
|
||||
développements (API malcommode et peu efficace).
|
||||
Search in local cache (g.formsemestre_result_cache)
|
||||
then global app cache (eg REDIS)
|
||||
If not in cache, build it and cache it.
|
||||
"""
|
||||
# --- Try local cache (within the same request context)
|
||||
if not hasattr(g, "formsemestre_result_cache"):
|
||||
g.formsemestre_result_cache = {} # pylint: disable=C0237
|
||||
else:
|
||||
if formsemestre.id in g.formsemestre_result_cache:
|
||||
return g.formsemestre_result_cache[formsemestre.id]
|
||||
|
||||
_cached_attrs = ResultatsSemestre._cached_attrs + ()
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
super().__init__(formsemestre)
|
||||
nb_etuds = len(self.etuds)
|
||||
self.bonus = defaultdict(lambda: 0.0) # XXX TODO
|
||||
self.ue_rangs = {u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.ues}
|
||||
self.mod_rangs = {
|
||||
m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.modimpls
|
||||
}
|
||||
self.moy_min = "NA"
|
||||
self.moy_max = "NA"
|
||||
|
||||
def get_etudids(self, sorted=False) -> list[int]:
|
||||
"""Liste des etudids inscrits, incluant les démissionnaires.
|
||||
Si sorted, triée par moy. générale décroissante
|
||||
Sinon, triée par ordre alphabetique de NOM
|
||||
"""
|
||||
# Note: pour avoir les inscrits non triés,
|
||||
# utiliser [ ins.etudid for ins in self.formsemestre.inscriptions ]
|
||||
if sorted:
|
||||
# Tri par moy. generale décroissante
|
||||
return [x[-1] for x in self.T]
|
||||
|
||||
return [x["etudid"] for x in self.inscrlist]
|
||||
|
||||
@cached_property
|
||||
def inscrlist(self) -> list[dict]: # utilisé par PE seulement
|
||||
"""Liste de dict etud, avec démissionnaires
|
||||
classée dans l'ordre alphabétique de noms.
|
||||
"""
|
||||
etuds = self.formsemestre.get_inscrits(include_dem=True)
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
return [e.to_dict_scodoc7() for e in etuds]
|
||||
|
||||
@cached_property
|
||||
def stats_moy_gen(self):
|
||||
"""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()
|
||||
"""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.ues:
|
||||
if filter_sport and ue.type == UE_SPORT:
|
||||
continue
|
||||
d = ue.to_dict()
|
||||
d.update(StatsMoyenne(self.etud_moy_ue[ue.id]).to_dict())
|
||||
ues.append(d)
|
||||
return ues
|
||||
|
||||
def get_modimpls_dict(self, ue_id=None):
|
||||
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
||||
triés par numéros (selon le type de formation)
|
||||
"""
|
||||
if ue_id is None:
|
||||
return [m.to_dict() for m in self.modimpls]
|
||||
else:
|
||||
return [m.to_dict() for m in self.modimpls if m.module.ue.id == ue_id]
|
||||
|
||||
def get_etud_decision_sem(self, etudid: int) -> dict:
|
||||
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
||||
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
|
||||
Si état défaillant, force le code a DEF
|
||||
"""
|
||||
if self.get_etud_etat(etudid) == DEF:
|
||||
return {
|
||||
"code": DEF,
|
||||
"assidu": False,
|
||||
"event_date": "",
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"code": ATT, # XXX TODO
|
||||
"assidu": True, # XXX TODO
|
||||
"event_date": "",
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
|
||||
def get_etud_etat(self, etudid: int) -> str:
|
||||
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
|
||||
ins = self.formsemestre.etuds_inscriptions.get(etudid, None)
|
||||
if ins is None:
|
||||
return ""
|
||||
return ins.etat
|
||||
|
||||
def get_etud_moy_gen(self, etudid): # -> float | str
|
||||
"""Moyenne générale de cet etudiant dans ce semestre.
|
||||
Prend en compte les UE capitalisées. (TODO)
|
||||
Si apc, moyenne indicative.
|
||||
Si pas de notes: 'NA'
|
||||
"""
|
||||
return self.etud_moy_gen[etudid]
|
||||
|
||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||
"""La moyenne de l'étudiant dans le moduleimpl
|
||||
En APC, il s'agira d'une moyenne indicative sans valeur.
|
||||
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM)
|
||||
"""
|
||||
raise NotImplementedError() # virtual method
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
||||
return {
|
||||
"cur_moy_ue": self.results.etud_moy_ue[ue_id][etudid],
|
||||
"is_capitalized": False, # XXX TODO
|
||||
}
|
||||
|
||||
def get_etud_rang(self, etudid: int):
|
||||
return self.etud_moy_gen_ranks.get(etudid, 99999) # XXX
|
||||
|
||||
def get_etud_rang_group(self, etudid: int, group_id: int):
|
||||
return (None, 0) # XXX unimplemented TODO
|
||||
|
||||
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
|
||||
"liste des évaluations valides dans un module"
|
||||
mi = ModuleImpl.query.get(moduleimpl_id)
|
||||
evals_results = []
|
||||
for e in mi.evaluations:
|
||||
d = e.to_dict()
|
||||
d["heure_debut"] = e.heure_debut # datetime.time
|
||||
d["heure_fin"] = e.heure_fin
|
||||
d["jour"] = e.jour # datetime
|
||||
d["notes"] = {
|
||||
etud.id: {
|
||||
"etudid": etud.id,
|
||||
"value": self.results.modimpls_evals_notes[e.moduleimpl_id][e.id][
|
||||
etud.id
|
||||
],
|
||||
}
|
||||
for etud in self.results.etuds
|
||||
}
|
||||
evals_results.append(d)
|
||||
return evals_results
|
||||
|
||||
def get_moduleimpls_attente(self):
|
||||
return [] # XXX TODO
|
||||
|
||||
def get_mod_stats(self, moduleimpl_id):
|
||||
return {
|
||||
"moy": "-",
|
||||
"max": "-",
|
||||
"min": "-",
|
||||
"nb_notes": "-",
|
||||
"nb_missing": "-",
|
||||
"nb_valid_evals": "-",
|
||||
}
|
||||
|
||||
def get_nom_short(self, etudid):
|
||||
"formatte nom d'un etud (pour table recap)"
|
||||
etud = self.identdict[etudid]
|
||||
return (
|
||||
(etud["nom_usuel"] or etud["nom"]).upper()
|
||||
+ " "
|
||||
+ etud["prenom"].capitalize()[:2]
|
||||
+ "."
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def T(self):
|
||||
return self.get_table_moyennes_triees()
|
||||
|
||||
def get_table_moyennes_triees(self) -> list:
|
||||
"""Result: liste de tuples
|
||||
moy_gen, moy_ue_0, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
|
||||
"""
|
||||
table_moyennes = []
|
||||
etuds_inscriptions = self.formsemestre.etuds_inscriptions
|
||||
|
||||
for etudid in etuds_inscriptions:
|
||||
moy_gen = self.etud_moy_gen.get(etudid, False)
|
||||
if moy_gen is False:
|
||||
# pas de moyenne: démissionnaire ou def
|
||||
t = ["-"] + ["0.00"] * len(self.ues) + ["NI"] * len(self.modimpls)
|
||||
else:
|
||||
moy_ues = self.etud_moy_ue.loc[etudid]
|
||||
t = [moy_gen] + list(moy_ues)
|
||||
# TODO UE capitalisées: ne pas afficher moyennes modules
|
||||
for modimpl in self.modimpls:
|
||||
val = self.get_etud_mod_moy(modimpl.id, etudid)
|
||||
t.append(val)
|
||||
t.append(etudid)
|
||||
table_moyennes.append(t)
|
||||
# tri par moyennes décroissantes,
|
||||
# en laissant les démissionnaires à la fin, par ordre alphabetique
|
||||
etuds = [ins.etud for ins in etuds_inscriptions.values()]
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
self._rang_alpha = {e.id: i for i, e in enumerate(etuds)}
|
||||
table_moyennes.sort(key=self._row_key)
|
||||
return table_moyennes
|
||||
|
||||
def _row_key(self, x):
|
||||
"""clé de tri par moyennes décroissantes,
|
||||
en laissant les demissionnaires à la fin, par ordre alphabetique.
|
||||
(moy_gen, rang_alpha)
|
||||
"""
|
||||
try:
|
||||
moy = -float(x[0])
|
||||
except (ValueError, TypeError):
|
||||
moy = 1000.0
|
||||
return (moy, self._rang_alpha[x[-1]])
|
||||
|
||||
@cached_property
|
||||
def identdict(self) -> dict:
|
||||
"""{ etudid : etud_dict } pour tous les inscrits au semestre"""
|
||||
return {
|
||||
ins.etud.id: ins.etud.to_dict_scodoc7()
|
||||
for ins in self.formsemestre.inscriptions
|
||||
}
|
||||
klass = (
|
||||
ResultatsSemestreBUT
|
||||
if formsemestre.formation.is_apc()
|
||||
else ResultatsSemestreClassic
|
||||
)
|
||||
return klass(formsemestre)
|
||||
|
|
|
@ -2,7 +2,7 @@ from app import db
|
|||
|
||||
|
||||
class Entreprise(db.Model):
|
||||
__tablename__ = "entreprises"
|
||||
__tablename__ = "are_entreprises"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
siret = db.Column(db.Text)
|
||||
nom = db.Column(db.Text)
|
||||
|
@ -35,10 +35,10 @@ class Entreprise(db.Model):
|
|||
|
||||
|
||||
class EntrepriseContact(db.Model):
|
||||
__tablename__ = "entreprise_contact"
|
||||
__tablename__ = "are_entreprise_contact"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
entreprise_id = db.Column(
|
||||
db.Integer, db.ForeignKey("entreprises.id", ondelete="cascade")
|
||||
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
||||
)
|
||||
nom = db.Column(db.Text)
|
||||
prenom = db.Column(db.Text)
|
||||
|
@ -76,10 +76,10 @@ class EntrepriseContact(db.Model):
|
|||
|
||||
|
||||
class EntrepriseOffre(db.Model):
|
||||
__tablename__ = "entreprise_offre"
|
||||
__tablename__ = "are_entreprise_offre"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
entreprise_id = db.Column(
|
||||
db.Integer, db.ForeignKey("entreprises.id", ondelete="cascade")
|
||||
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
||||
)
|
||||
date_ajout = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
intitule = db.Column(db.Text)
|
||||
|
@ -99,7 +99,7 @@ class EntrepriseOffre(db.Model):
|
|||
|
||||
|
||||
class EntrepriseLog(db.Model):
|
||||
__tablename__ = "entreprise_log"
|
||||
__tablename__ = "are_entreprise_log"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
authenticated_user = db.Column(db.Text)
|
||||
|
@ -108,9 +108,9 @@ class EntrepriseLog(db.Model):
|
|||
|
||||
|
||||
class EntrepriseEtudiant(db.Model):
|
||||
__tablename__ = "entreprise_etudiant"
|
||||
__tablename__ = "are_entreprise_etudiant"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
entreprise_id = db.Column(db.Integer, db.ForeignKey("entreprises.id"))
|
||||
entreprise_id = db.Column(db.Integer, db.ForeignKey("are_entreprises.id"))
|
||||
etudid = db.Column(db.Integer)
|
||||
type_offre = db.Column(db.Text)
|
||||
date_debut = db.Column(db.Date)
|
||||
|
@ -120,18 +120,18 @@ class EntrepriseEtudiant(db.Model):
|
|||
|
||||
|
||||
class EntrepriseEnvoiOffre(db.Model):
|
||||
__tablename__ = "entreprise_envoi_offre"
|
||||
__tablename__ = "are_entreprise_envoi_offre"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
sender_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||
receiver_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||
offre_id = db.Column(db.Integer, db.ForeignKey("entreprise_offre.id"))
|
||||
offre_id = db.Column(db.Integer, db.ForeignKey("are_entreprise_offre.id"))
|
||||
date_envoi = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
|
||||
|
||||
class EntrepriseEnvoiOffreEtudiant(db.Model):
|
||||
__tablename__ = "entreprise_envoi_offre_etudiant"
|
||||
__tablename__ = "are_entreprise_envoi_offre_etudiant"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
sender_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||
receiver_id = db.Column(db.Integer, db.ForeignKey("identite.id"))
|
||||
offre_id = db.Column(db.Integer, db.ForeignKey("entreprise_offre.id"))
|
||||
offre_id = db.Column(db.Integer, db.ForeignKey("are_entreprise_offre.id"))
|
||||
date_envoi = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
|
|
|
@ -245,7 +245,7 @@ class DeptForm(FlaskForm):
|
|||
|
||||
|
||||
def _make_dept_id_name():
|
||||
"""Cette section assute que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
|
||||
"""Cette section assure que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
|
||||
et détermine l'ordre d'affichage des DeptForm (GLOBAL d'abord, puis par ordre alpha de nom de département)
|
||||
-> [ (None, None), (dept_id, dept_name)... ]"""
|
||||
depts = [(None, GLOBAL)]
|
||||
|
|
|
@ -31,16 +31,10 @@ Formulaires création département
|
|||
|
||||
from flask import flash, url_for, redirect, render_template
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import SelectField, SubmitField, FormField, validators, FieldList
|
||||
from wtforms.fields.simple import StringField, HiddenField
|
||||
from wtforms import SubmitField, validators
|
||||
from wtforms.fields.simple import StringField, BooleanField
|
||||
|
||||
from app import AccessDenied
|
||||
from app.models import Departement
|
||||
from app.models import ScoPreference
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
from flask_login import current_user
|
||||
|
||||
|
||||
class CreateDeptForm(FlaskForm):
|
||||
|
@ -60,5 +54,10 @@ class CreateDeptForm(FlaskForm):
|
|||
validators.DataRequired("acronyme du département requis"),
|
||||
],
|
||||
)
|
||||
# description = StringField(label="Description")
|
||||
visible = BooleanField(
|
||||
"Visible sur page d'accueil",
|
||||
default=True,
|
||||
)
|
||||
submit = SubmitField("Valider")
|
||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||
|
|
|
@ -9,11 +9,24 @@ from datetime import datetime
|
|||
from enum import unique
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import class_mapper
|
||||
import sqlalchemy
|
||||
|
||||
from app import db
|
||||
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
# from https://stackoverflow.com/questions/2537471/method-of-iterating-over-sqlalchemy-models-defined-columns
|
||||
def attribute_names(cls):
|
||||
"liste ids (noms de colonnes) d'un modèle"
|
||||
return [
|
||||
prop.key
|
||||
for prop in class_mapper(cls).iterate_properties
|
||||
if isinstance(prop, sqlalchemy.orm.ColumnProperty)
|
||||
]
|
||||
|
||||
|
||||
class XMLModel:
|
||||
_xml_attribs = {} # to be overloaded
|
||||
id = "_"
|
||||
|
@ -24,21 +37,31 @@ class XMLModel:
|
|||
and renamed for our models.
|
||||
The mapping is specified by the _xml_attribs
|
||||
attribute in each model class.
|
||||
Keep only attributes corresponding to columns in our model:
|
||||
other XML attributes are simply ignored.
|
||||
"""
|
||||
return {cls._xml_attribs.get(k, k): v for (k, v) in args.items()}
|
||||
columns = attribute_names(cls)
|
||||
renamed_attributes = {cls._xml_attribs.get(k, k): v for (k, v) in args.items()}
|
||||
return {k: renamed_attributes[k] for k in renamed_attributes if k in columns}
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{self.__class__.__name__} {self.id} "{self.titre if hasattr(self, "titre") else ""}">'
|
||||
|
||||
|
||||
class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
"Référentiel de compétence d'une spécialité"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
||||
annexe = db.Column(db.Text())
|
||||
specialite = db.Column(db.Text())
|
||||
specialite_long = db.Column(db.Text())
|
||||
type_titre = db.Column(db.Text())
|
||||
type_structure = db.Column(db.Text())
|
||||
type_departement = db.Column(db.Text()) # "secondaire", "tertiaire"
|
||||
version_orebut = db.Column(db.Text())
|
||||
_xml_attribs = { # Orébut xml attrib : attribute
|
||||
"type": "type_titre",
|
||||
"version": "version_orebut",
|
||||
}
|
||||
# ScoDoc specific fields:
|
||||
scodoc_date_loaded = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
@ -64,9 +87,13 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||
"""
|
||||
return {
|
||||
"dept_id": self.dept_id,
|
||||
"annexe": self.annexe,
|
||||
"specialite": self.specialite,
|
||||
"specialite_long": self.specialite_long,
|
||||
"type_structure": self.type_structure,
|
||||
"type_departement": self.type_departement,
|
||||
"type_titre": self.type_titre,
|
||||
"version_orebut": self.version_orebut,
|
||||
"scodoc_date_loaded": self.scodoc_date_loaded.isoformat() + "Z"
|
||||
if self.scodoc_date_loaded
|
||||
else "",
|
||||
|
@ -77,23 +104,20 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||
|
||||
|
||||
class ApcCompetence(db.Model, XMLModel):
|
||||
__table_args__ = (
|
||||
# les compétences dans Orébut sont identifiées par leur "titre"
|
||||
# unique au sein d'un référentiel:
|
||||
db.UniqueConstraint(
|
||||
"referentiel_id", "titre", name="apc_competence_referentiel_id_titre_key"
|
||||
),
|
||||
)
|
||||
"Compétence"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
referentiel_id = db.Column(
|
||||
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
|
||||
)
|
||||
# les compétences dans Orébut sont identifiées par leur id unique
|
||||
id_orebut = db.Column(db.Text(), nullable=True, index=True, unique=True)
|
||||
titre = db.Column(db.Text(), nullable=False, index=True)
|
||||
titre_long = db.Column(db.Text())
|
||||
couleur = db.Column(db.Text())
|
||||
numero = db.Column(db.Integer) # ordre de présentation
|
||||
_xml_attribs = { # xml_attrib : attribute
|
||||
"name": "titre",
|
||||
"id": "id_orebut",
|
||||
"nom_court": "titre", # was name
|
||||
"libelle_long": "titre_long",
|
||||
}
|
||||
situations = db.relationship(
|
||||
|
@ -117,6 +141,7 @@ class ApcCompetence(db.Model, XMLModel):
|
|||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id_orebut": self.id_orebut,
|
||||
"titre": self.titre,
|
||||
"titre_long": self.titre_long,
|
||||
"couleur": self.couleur,
|
||||
|
@ -246,7 +271,10 @@ class ApcAnneeParcours(db.Model, XMLModel):
|
|||
return {
|
||||
"ordre": self.ordre,
|
||||
"competences": {
|
||||
x.competence.titre: {"niveau": x.niveau}
|
||||
x.competence.titre: {
|
||||
"niveau": x.niveau,
|
||||
"id_orebut": x.competence.id_orebut,
|
||||
}
|
||||
for x in self.niveaux_competences
|
||||
},
|
||||
}
|
||||
|
|
|
@ -12,8 +12,10 @@ class Departement(db.Model):
|
|||
"""Un département ScoDoc"""
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
acronym = db.Column(db.String(SHORT_STR_LEN), nullable=False, index=True)
|
||||
description = db.Column(db.Text())
|
||||
acronym = db.Column(
|
||||
db.String(SHORT_STR_LEN), nullable=False, index=True
|
||||
) # ne change jamais, voir la pref. DeptName
|
||||
description = db.Column(db.Text()) # pas utilisé par ScoDoc : voir DeptFullName
|
||||
date_creation = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
visible = db.Column(
|
||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
||||
|
@ -49,11 +51,11 @@ class Departement(db.Model):
|
|||
return dept
|
||||
|
||||
|
||||
def create_dept(acronym: str) -> Departement:
|
||||
def create_dept(acronym: str, visible=True) -> Departement:
|
||||
"Create new departement"
|
||||
from app.models import ScoPreference
|
||||
|
||||
departement = Departement(acronym=acronym)
|
||||
departement = Departement(acronym=acronym, visible=visible)
|
||||
p1 = ScoPreference(name="DeptName", value=acronym, departement=departement)
|
||||
db.session.add(p1)
|
||||
db.session.add(departement)
|
||||
|
|
|
@ -12,6 +12,7 @@ from app import db
|
|||
from app import models
|
||||
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc.sco_bac import Baccalaureat
|
||||
|
||||
|
||||
class Identite(db.Model):
|
||||
|
@ -42,14 +43,16 @@ class Identite(db.Model):
|
|||
boursier = db.Column(db.Boolean()) # True si boursier ('O' en ScoDoc7)
|
||||
photo_filename = db.Column(db.Text())
|
||||
# Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept
|
||||
code_nip = db.Column(db.Text())
|
||||
code_ine = db.Column(db.Text())
|
||||
code_nip = db.Column(db.Text(), index=True)
|
||||
code_ine = db.Column(db.Text(), index=True)
|
||||
# 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)
|
||||
#
|
||||
adresses = db.relationship("Adresse", lazy="dynamic", backref="etud")
|
||||
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
||||
# one-to-one relation:
|
||||
admission = db.relationship("Admission", backref="identite", lazy="dynamic")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Etud {self.id} {self.nom} {self.prenom}>"
|
||||
|
@ -294,6 +297,10 @@ class Admission(db.Model):
|
|||
# classement (1..Ngr) par le jury dans le groupe APB
|
||||
apb_classement_gr = db.Column(db.Integer)
|
||||
|
||||
def get_bac(self) -> Baccalaureat:
|
||||
"Le bac. utiliser bac.abbrev() pour avoir une chaine de caractères."
|
||||
return Baccalaureat(self.bac, specialite=self.specialite)
|
||||
|
||||
|
||||
# Suivi scolarité / débouchés
|
||||
class ItemSuivi(db.Model):
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
from app import db
|
||||
from app.comp import df_cache
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models.modules import Module
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_codes_parcours
|
||||
|
@ -97,19 +99,24 @@ class Formation(db.Model):
|
|||
for sem in self.formsemestres:
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=sem.id)
|
||||
|
||||
def force_semestre_modules_aux_ues(self) -> None:
|
||||
def sanitize_old_formation(self) -> None:
|
||||
"""
|
||||
Affecte à chaque module de cette formation le semestre de son UE de rattachement,
|
||||
Corrige si nécessaire certains champs issus d'anciennes versions de ScoDoc:
|
||||
- affecte à chaque module de cette formation le semestre de son UE de rattachement,
|
||||
si elle en a une.
|
||||
- si le module_type n'est pas renseigné, le met à STANDARD.
|
||||
|
||||
Devrait être appelé lorsqu'on change le type de formation vers le BUT, et aussi
|
||||
lorsqu'on change le semestre d'une UE BUT.
|
||||
Utile pour la migration des anciennes formations vers le BUT.
|
||||
Invalide les caches coefs/poids.
|
||||
|
||||
En cas de changement, invalide les caches coefs/poids.
|
||||
"""
|
||||
if not self.is_apc():
|
||||
return
|
||||
change = False
|
||||
for mod in self.modules:
|
||||
# --- Indices de semestres:
|
||||
if (
|
||||
mod.ue.semestre_idx is not None
|
||||
and mod.ue.semestre_idx > 0
|
||||
|
@ -118,6 +125,21 @@ class Formation(db.Model):
|
|||
mod.semestre_id = mod.ue.semestre_idx
|
||||
db.session.add(mod)
|
||||
change = True
|
||||
# --- Types de modules
|
||||
if mod.module_type is None:
|
||||
mod.module_type = scu.ModuleType.STANDARD
|
||||
db.session.add(mod)
|
||||
change = True
|
||||
# --- Numéros de modules
|
||||
if Module.query.filter_by(formation_id=self.id, numero=None).count() > 0:
|
||||
scu.objects_renumber(db, self.modules.all())
|
||||
# --- Types d'UE (avant de rendre le type non nullable)
|
||||
ues_sans_type = UniteEns.query.filter_by(formation_id=self.id, type=None)
|
||||
if ues_sans_type.count() > 0:
|
||||
for ue in ues_sans_type:
|
||||
ue.type = 0
|
||||
db.session.add(ue)
|
||||
|
||||
db.session.commit()
|
||||
if change:
|
||||
self.invalidate_module_coefs()
|
||||
|
|
|
@ -8,6 +8,7 @@ from functools import cached_property
|
|||
import flask_sqlalchemy
|
||||
|
||||
from app import db
|
||||
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
|
||||
|
@ -116,11 +117,18 @@ class FormSemestre(db.Model):
|
|||
d.pop("_sa_instance_state", None)
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
d["formsemestre_id"] = self.id
|
||||
d["date_debut"] = (
|
||||
self.date_debut.strftime("%d/%m/%Y") if self.date_debut else ""
|
||||
)
|
||||
d["date_fin"] = self.date_fin.strftime("%d/%m/%Y") if self.date_fin else ""
|
||||
if self.date_debut:
|
||||
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
|
||||
d["date_debut_iso"] = self.date_debut.isoformat()
|
||||
else:
|
||||
d["date_debut"] = d["date_debut_iso"] = ""
|
||||
if self.date_fin:
|
||||
d["date_fin"] = self.date_fin.strftime("%d/%m/%Y")
|
||||
d["date_fin_iso"] = self.date_fin.isoformat()
|
||||
else:
|
||||
d["date_fin"] = d["date_fin_iso"] = ""
|
||||
d["responsables"] = [u.id for u in self.responsables]
|
||||
|
||||
return d
|
||||
|
||||
def query_ues(self, with_sport=False) -> flask_sqlalchemy.BaseQuery:
|
||||
|
@ -158,6 +166,24 @@ class FormSemestre(db.Model):
|
|||
"""
|
||||
return (self.date_debut <= date_debut) and (date_fin <= self.date_fin)
|
||||
|
||||
def est_sur_une_annee(self):
|
||||
"""Test si sem est entièrement sur la même année scolaire.
|
||||
(ce n'est pas obligatoire mais si ce n'est pas le
|
||||
cas les exports Apogée risquent de mal fonctionner)
|
||||
Pivot au 1er août.
|
||||
"""
|
||||
if self.date_debut > self.date_fin:
|
||||
log(f"Warning: semestre {self.id} begins after ending !")
|
||||
annee_debut = self.date_debut.year
|
||||
if self.date_debut.month < 8: # août
|
||||
# considere que debut sur l'anne scolaire precedente
|
||||
annee_debut -= 1
|
||||
annee_fin = self.date_fin.year
|
||||
if self.date_fin.month < 9:
|
||||
# 9 (sept) pour autoriser un début en sept et une fin en aout
|
||||
annee_fin -= 1
|
||||
return annee_debut == annee_fin
|
||||
|
||||
def est_decale(self):
|
||||
"""Vrai si semestre "décalé"
|
||||
c'est à dire semestres impairs commençant entre janvier et juin
|
||||
|
@ -252,18 +278,19 @@ class FormSemestre(db.Model):
|
|||
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
||||
)
|
||||
|
||||
def get_inscrits(self, include_dem=False) -> list[Identite]:
|
||||
def get_inscrits(self, include_demdef=False) -> list[Identite]:
|
||||
"""Liste des étudiants inscrits à ce semestre
|
||||
Si all, tous les étudiants, avec les démissionnaires.
|
||||
Si include_demdef, tous les étudiants, avec les démissionnaires
|
||||
et défaillants.
|
||||
"""
|
||||
if include_dem:
|
||||
if include_demdef:
|
||||
return [ins.etud for ins in self.inscriptions]
|
||||
else:
|
||||
return [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
||||
|
||||
@cached_property
|
||||
def etuds_inscriptions(self) -> dict:
|
||||
"""Map { etudid : inscription }"""
|
||||
"""Map { etudid : inscription } (incluant DEM et DEF)"""
|
||||
return {ins.etud.id: ins for ins in self.inscriptions}
|
||||
|
||||
|
||||
|
|
|
@ -44,6 +44,9 @@ class ModuleImpl(db.Model):
|
|||
def __init__(self, **kwargs):
|
||||
super(ModuleImpl, self).__init__(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} {self.id} module={repr(self.module)}>"
|
||||
|
||||
def get_evaluations_poids(self) -> pd.DataFrame:
|
||||
"""Les poids des évaluations vers les UE (accès via cache)"""
|
||||
evaluations_poids = df_cache.EvaluationsPoidsCache.get(self.id)
|
||||
|
|
|
@ -49,9 +49,7 @@ class Module(db.Model):
|
|||
super(Module, self).__init__(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"<Module{ModuleType(self.module_type).name} id={self.id} code={self.code}>"
|
||||
)
|
||||
return f"<Module{ModuleType(self.module_type or ModuleType.STANDARD).name} id={self.id} code={self.code}>"
|
||||
|
||||
def to_dict(self):
|
||||
e = dict(self.__dict__)
|
||||
|
@ -131,9 +129,27 @@ class Module(db.Model):
|
|||
# à redéfinir les relationships...
|
||||
return sorted(self.ue_coefs, key=lambda x: x.ue.numero)
|
||||
|
||||
def ue_coefs_descr(self):
|
||||
"""List of tuples [ (ue_acronyme, coef) ]"""
|
||||
return [(c.ue.acronyme, c.coef) for c in self.get_ue_coefs_sorted()]
|
||||
def ue_coefs_list(self, include_zeros=True):
|
||||
"""Liste des coefs vers les UE (pour les modules APC).
|
||||
Si include_zeros, liste aussi les UE sans coef (donc nul) de ce semestre.
|
||||
Result: List of tuples [ (ue, coef) ]
|
||||
"""
|
||||
if not self.is_apc():
|
||||
return []
|
||||
if include_zeros:
|
||||
# Toutes les UE du même semestre:
|
||||
ues_semestre = (
|
||||
self.formation.ues.filter_by(semestre_idx=self.ue.semestre_idx)
|
||||
.order_by(UniteEns.numero)
|
||||
.all()
|
||||
)
|
||||
coefs_dict = self.get_ue_coef_dict()
|
||||
coefs_list = []
|
||||
for ue in ues_semestre:
|
||||
coefs_list.append((ue, coefs_dict.get(ue.id, 0.0)))
|
||||
return coefs_list
|
||||
# Liste seulement les coefs définis:
|
||||
return [(c.ue, c.coef) for c in self.get_ue_coefs_sorted()]
|
||||
|
||||
|
||||
class ModuleUECoef(db.Model):
|
||||
|
|
|
@ -46,7 +46,10 @@ class UniteEns(db.Model):
|
|||
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}(id={self.id}, formation_id={self.formation_id}, acronyme='{self.acronyme}')>"
|
||||
return f"""<{self.__class__.__name__}(id={self.id}, formation_id={
|
||||
self.formation_id}, acronyme='{self.acronyme}', semestre_idx={
|
||||
self.semestre_idx} {
|
||||
'EXTERNE' if self.is_external else ''})>"""
|
||||
|
||||
def to_dict(self):
|
||||
"""as a dict, with the same conversions as in ScoDoc7"""
|
||||
|
|
|
@ -9,6 +9,12 @@
|
|||
v 1.3 (python3)
|
||||
"""
|
||||
import html
|
||||
import re
|
||||
|
||||
# re validant dd/mm/yyyy
|
||||
DMY_REGEXP = re.compile(
|
||||
r"^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$"
|
||||
)
|
||||
|
||||
|
||||
def TrivialFormulator(
|
||||
|
@ -66,8 +72,8 @@ def TrivialFormulator(
|
|||
HTML elements:
|
||||
input_type : 'text', 'textarea', 'password',
|
||||
'radio', 'menu', 'checkbox',
|
||||
'hidden', 'separator', 'file', 'date', 'boolcheckbox',
|
||||
'text_suggest'
|
||||
'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation),
|
||||
'boolcheckbox', 'text_suggest'
|
||||
(default text)
|
||||
size : text field width
|
||||
rows, cols: textarea geometry
|
||||
|
@ -243,6 +249,8 @@ class TF(object):
|
|||
"Le champ '%s' doit être renseigné" % descr.get("title", field)
|
||||
)
|
||||
ok = 0
|
||||
elif val == "" or val == None:
|
||||
continue # allowed empty field, skip
|
||||
# type
|
||||
typ = descr.get("type", "string")
|
||||
if val != "" and val != None:
|
||||
|
@ -300,6 +308,10 @@ class TF(object):
|
|||
if not descr["validator"](val, field):
|
||||
msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field))
|
||||
ok = 0
|
||||
elif descr.get("input_type") == "datedmy":
|
||||
if not DMY_REGEXP.match(val):
|
||||
msg.append("valeur invalide (%s) pour la date '%s'" % (val, field))
|
||||
ok = 0
|
||||
# boolean checkbox
|
||||
if descr.get("input_type", None) == "boolcheckbox":
|
||||
if int(val):
|
||||
|
@ -564,7 +576,9 @@ class TF(object):
|
|||
'<input type="file" name="%s" size="%s" value="%s" %s>'
|
||||
% (field, size, values[field], attribs)
|
||||
)
|
||||
elif input_type == "date": # JavaScript widget for date input
|
||||
elif (
|
||||
input_type == "date" or input_type == "datedmy"
|
||||
): # JavaScript widget for date input
|
||||
lem.append(
|
||||
'<input type="text" name="%s" size="10" value="%s" class="datepicker">'
|
||||
% (field, values[field])
|
||||
|
|
|
@ -28,6 +28,44 @@
|
|||
from operator import mul
|
||||
import pprint
|
||||
|
||||
"""
|
||||
La fonction bonus_sport reçoit:
|
||||
|
||||
- notes_sport: la liste des notes des modules de sport et culture (une note par module de l'UE de type sport/culture);
|
||||
- coefs: un coef (float) pondérant chaque note (la plupart des bonus les ignorent);
|
||||
- infos: dictionnaire avec des données pouvant être utilisées pour les calculs.
|
||||
Ces données dépendent du type de formation.
|
||||
infos = {
|
||||
"moy" : la moyenne générale (float). 0. en BUT.
|
||||
"sem" : {
|
||||
"date_debut_iso" : "2010-08-01", # date de début de semestre
|
||||
}
|
||||
"moy_ues": {
|
||||
ue_id : { # ue_status
|
||||
"is_capitalized" : True|False,
|
||||
"moy" : float, # moyenne d'UE prise en compte (peut-être capitalisée)
|
||||
"sum_coefs": float, # > 0 si UE avec la moyenne calculée
|
||||
"cur_moy_ue": float, # moyenne de l'UE (sans capitalisation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Les notes passées sont:
|
||||
- pour les formations classiques, la moyenne dans le module, calculée comme d'habitude
|
||||
(moyenne pondérée des notes d'évaluations);
|
||||
- pour le BUT: pareil, *en ignorant* les éventuels poids des évaluations. Le coefficient
|
||||
de l'évaluation est pris en compte, mais pas les poids vers les UE.
|
||||
|
||||
Pour modifier les moyennes d'UE:
|
||||
- modifier infos["moy_ues"][ue_id][["cur_moy_ue"]
|
||||
et, seulement si l'UE n'est pas capitalisée, infos["moy_ues"][ue_id][["moy"]/
|
||||
|
||||
La valeur retournée est:
|
||||
- formations classiques: ajoutée à la moyenne générale
|
||||
- BUT: valeur multipliée par la somme des coefs modules sport ajoutée à chaque UE.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def bonus_iutv(notes_sport, coefs, infos=None):
|
||||
"""Calcul bonus modules optionels (sport, culture), règle IUT Villetaneuse
|
||||
|
@ -39,6 +77,7 @@ def bonus_iutv(notes_sport, coefs, infos=None):
|
|||
optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
|
||||
la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||
"""
|
||||
# breakpoint()
|
||||
bonus = sum([(x - 10) / 20.0 for x in notes_sport if x > 10])
|
||||
return bonus
|
||||
|
||||
|
@ -416,6 +455,20 @@ def bonus_iutbeziers(notes_sport, coefs, infos=None):
|
|||
return bonus
|
||||
|
||||
|
||||
def bonus_iutlr(notes_sport, coefs, infos=None):
|
||||
"""Calcul bonus modules optionels (sport, culture), règle IUT 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.1 et 20 : ajout de 1% de cette note sur la moyenne générale du semestre
|
||||
"""
|
||||
# les coefs sont ignorés
|
||||
# une seule note
|
||||
note_sport = notes_sport[0]
|
||||
if note_sport <= 10:
|
||||
return 0
|
||||
bonus = note_sport * 0.01 # 1%
|
||||
return bonus
|
||||
|
||||
|
||||
def bonus_demo(notes_sport, coefs, infos=None):
|
||||
"""Fausse fonction "bonus" pour afficher les informations disponibles
|
||||
et aider les développeurs.
|
||||
|
|
|
@ -58,6 +58,7 @@ from app.scodoc import sco_utils as scu
|
|||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc import sco_xml
|
||||
from app.scodoc.sco_exceptions import ScoPDFFormatError
|
||||
from app.scodoc.sco_pdf import SU
|
||||
from app import log
|
||||
|
||||
|
@ -539,17 +540,18 @@ class GenTable(object):
|
|||
#
|
||||
# titles = ["<para><b>%s</b></para>" % x for x in self.get_titles_list()]
|
||||
pdf_style_list = []
|
||||
Pt = [
|
||||
[Paragraph(SU(str(x)), CellStyle) for x in line]
|
||||
for line in (
|
||||
self.get_data_list(
|
||||
pdf_mode=True,
|
||||
pdf_style_list=pdf_style_list,
|
||||
with_titles=True,
|
||||
omit_hidden_lines=True,
|
||||
)
|
||||
)
|
||||
]
|
||||
data_list = self.get_data_list(
|
||||
pdf_mode=True,
|
||||
pdf_style_list=pdf_style_list,
|
||||
with_titles=True,
|
||||
omit_hidden_lines=True,
|
||||
)
|
||||
try:
|
||||
Pt = [
|
||||
[Paragraph(SU(str(x)), CellStyle) for x in line] for line in data_list
|
||||
]
|
||||
except ValueError as exc:
|
||||
raise ScoPDFFormatError(str(exc)) from exc
|
||||
pdf_style_list += self.pdf_table_style
|
||||
T = Table(Pt, repeatRows=1, colWidths=self.pdf_col_widths, style=pdf_style_list)
|
||||
|
||||
|
|
|
@ -313,7 +313,7 @@ def sco_footer():
|
|||
|
||||
|
||||
def html_sem_header(
|
||||
title, sem=None, with_page_header=True, with_h2=True, page_title=None, **args
|
||||
title, with_page_header=True, with_h2=True, page_title=None, **args
|
||||
):
|
||||
"Titre d'une page semestre avec lien vers tableau de bord"
|
||||
# sem now unused and thus optional...
|
||||
|
|
|
@ -35,13 +35,14 @@ from flask_login import current_user
|
|||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from sco_version import SCOVERSION
|
||||
|
||||
|
||||
def sidebar_common():
|
||||
"partie commune à toutes les sidebar"
|
||||
home_link = url_for("scodoc.index", scodoc_dept=g.scodoc_dept)
|
||||
H = [
|
||||
f"""<a class="scodoc_title" href="{home_link}">ScoDoc 9.1</a><br>
|
||||
f"""<a class="scodoc_title" href="{home_link}">ScoDoc {SCOVERSION}</a><br>
|
||||
<a href="{home_link}" class="sidebar">Accueil</a> <br>
|
||||
<div id="authuser"><a id="authuserlink" href="{
|
||||
url_for("users.user_info_page",
|
||||
|
|
|
@ -171,6 +171,7 @@ class NotesTable:
|
|||
|
||||
def __init__(self, formsemestre_id):
|
||||
log(f"NotesTable( formsemestre_id={formsemestre_id} )")
|
||||
# raise NotImplementedError() # XXX
|
||||
if not formsemestre_id:
|
||||
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
|
||||
self.formsemestre_id = formsemestre_id
|
||||
|
@ -409,7 +410,7 @@ class NotesTable:
|
|||
return ""
|
||||
|
||||
def get_etud_etat_html(self, etudid):
|
||||
etat = self.inscrdict[etudid]["etat"]
|
||||
|
||||
if etat == "I":
|
||||
return ""
|
||||
elif etat == "D":
|
||||
|
|
|
@ -108,13 +108,14 @@ def apo_compare_csv(A_file, B_file, autodetect=True):
|
|||
|
||||
def _load_apo_data(csvfile, autodetect=True):
|
||||
"Read data from request variable and build ApoData"
|
||||
data = csvfile.read()
|
||||
data_b = csvfile.read()
|
||||
if autodetect:
|
||||
data, message = sco_apogee_csv.fix_data_encoding(data)
|
||||
data_b, message = sco_apogee_csv.fix_data_encoding(data_b)
|
||||
if message:
|
||||
log("apo_compare_csv: %s" % message)
|
||||
if not data:
|
||||
if not data_b:
|
||||
raise ScoValueError("apo_compare_csv: no data")
|
||||
data = data_b.decode(sco_apogee_csv.APO_INPUT_ENCODING)
|
||||
apo_data = sco_apogee_csv.ApoData(data, orig_filename=csvfile.filename)
|
||||
return apo_data
|
||||
|
||||
|
|
|
@ -173,8 +173,10 @@ def guess_data_encoding(text, threshold=0.6):
|
|||
|
||||
|
||||
def fix_data_encoding(
|
||||
text, default_source_encoding=APO_INPUT_ENCODING, dest_encoding=APO_INPUT_ENCODING
|
||||
):
|
||||
text: bytes,
|
||||
default_source_encoding=APO_INPUT_ENCODING,
|
||||
dest_encoding=APO_INPUT_ENCODING,
|
||||
) -> bytes:
|
||||
"""Try to ensure that text is using dest_encoding
|
||||
returns converted text, and a message describing the conversion.
|
||||
"""
|
||||
|
@ -200,7 +202,7 @@ def fix_data_encoding(
|
|||
|
||||
|
||||
class StringIOFileLineWrapper(object):
|
||||
def __init__(self, data):
|
||||
def __init__(self, data: str):
|
||||
self.f = io.StringIO(data)
|
||||
self.lineno = 0
|
||||
|
||||
|
@ -655,7 +657,7 @@ class ApoEtud(dict):
|
|||
class ApoData(object):
|
||||
def __init__(
|
||||
self,
|
||||
data,
|
||||
data: str,
|
||||
periode=None,
|
||||
export_res_etape=True,
|
||||
export_res_sem=True,
|
||||
|
@ -693,7 +695,7 @@ class ApoData(object):
|
|||
"<h3>Erreur lecture du fichier Apogée <tt>%s</tt></h3><p>" % filename
|
||||
+ e.args[0]
|
||||
+ "</p>"
|
||||
)
|
||||
) from e
|
||||
self.etape_apogee = self.get_etape_apogee() # 'V1RT'
|
||||
self.vdi_apogee = self.get_vdi_apogee() # '111'
|
||||
self.etape = ApoEtapeVDI(etape=self.etape_apogee, vdi=self.vdi_apogee)
|
||||
|
@ -760,7 +762,6 @@ class ApoData(object):
|
|||
def read_csv(self, data: str):
|
||||
if not data:
|
||||
raise ScoFormatError("Fichier Apogée vide !")
|
||||
|
||||
f = StringIOFileLineWrapper(data) # pour traiter comme un fichier
|
||||
# check that we are at the begining of Apogee CSV
|
||||
line = f.readline().strip()
|
||||
|
@ -768,7 +769,10 @@ class ApoData(object):
|
|||
raise ScoFormatError("format incorrect: pas de XX-APO_TITRES-XX")
|
||||
|
||||
# 1-- En-tête: du début jusqu'à la balise XX-APO_VALEURS-XX
|
||||
idx = data.index("XX-APO_VALEURS-XX")
|
||||
try:
|
||||
idx = data.index("XX-APO_VALEURS-XX")
|
||||
except ValueError as exc:
|
||||
raise ScoFormatError("format incorrect: pas de XX-APO_VALEURS-XX") from exc
|
||||
self.header = data[:idx]
|
||||
|
||||
# 2-- Titres:
|
||||
|
@ -1178,7 +1182,7 @@ def nar_etuds_table(apo_data, NAR_Etuds):
|
|||
|
||||
|
||||
def export_csv_to_apogee(
|
||||
apo_csv_data,
|
||||
apo_csv_data: str,
|
||||
periode=None,
|
||||
dest_zip=None,
|
||||
export_res_etape=True,
|
||||
|
|
|
@ -405,7 +405,6 @@ def formsemestre_archive(formsemestre_id, group_ids=[]):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Archiver les PV et résultats du semestre",
|
||||
sem=sem,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
init_qtip=True,
|
||||
|
@ -524,7 +523,7 @@ def formsemestre_list_archives(formsemestre_id):
|
|||
}
|
||||
L.append(a)
|
||||
|
||||
H = [html_sco_header.html_sem_header("Archive des PV et résultats ", sem)]
|
||||
H = [html_sco_header.html_sem_header("Archive des PV et résultats ")]
|
||||
if not L:
|
||||
H.append("<p>aucune archive enregistrée</p>")
|
||||
else:
|
||||
|
|
|
@ -130,7 +130,7 @@ BACS_SSP = {(t[0], t[1]): t[2:] for t in _BACS}
|
|||
BACS_S = {t[0]: t[2:] for t in _BACS}
|
||||
|
||||
|
||||
class Baccalaureat(object):
|
||||
class Baccalaureat:
|
||||
def __init__(self, bac, specialite=""):
|
||||
self.bac = bac
|
||||
self.specialite = specialite
|
||||
|
|
|
@ -48,6 +48,9 @@ import app.scodoc.sco_utils as scu
|
|||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc import html_sco_header
|
||||
|
@ -136,7 +139,9 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
raise ValueError("invalid version code !")
|
||||
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
||||
if not nt.get_etud_etat(etudid):
|
||||
raise ScoValueError("Etudiant non inscrit à ce semestre")
|
||||
I = scu.DictDefault(defaultvalue="")
|
||||
|
@ -191,7 +196,9 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
I["decision_sem"] = ""
|
||||
I.update(infos)
|
||||
|
||||
I["etud_etat_html"] = nt.get_etud_etat_html(etudid)
|
||||
I["etud_etat_html"] = _get_etud_etat_html(
|
||||
formsemestre.etuds_inscriptions[etudid].etat
|
||||
)
|
||||
I["etud_etat"] = nt.get_etud_etat(etudid)
|
||||
I["filigranne"] = ""
|
||||
I["demission"] = ""
|
||||
|
@ -261,17 +268,18 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
# notes en attente dans ce semestre
|
||||
rang = scu.RANG_ATTENTE_STR
|
||||
rang_gr = scu.DictDefault(defaultvalue=scu.RANG_ATTENTE_STR)
|
||||
inscriptions_counts = nt.get_inscriptions_counts()
|
||||
I["rang"] = rang
|
||||
I["rang_gr"] = rang_gr
|
||||
I["gr_name"] = gr_name
|
||||
I["ninscrits_gr"] = ninscrits_gr
|
||||
I["nbetuds"] = len(nt.etud_moy_gen_ranks)
|
||||
I["nb_demissions"] = nt.nb_demissions
|
||||
I["nb_defaillants"] = nt.nb_defaillants
|
||||
I["nb_demissions"] = inscriptions_counts[scu.DEMISSION]
|
||||
I["nb_defaillants"] = inscriptions_counts[scu.DEF]
|
||||
if prefs["bul_show_rangs"]:
|
||||
I["rang_nt"] = "%s / %d" % (
|
||||
rang,
|
||||
I["nbetuds"] - nt.nb_demissions - nt.nb_defaillants,
|
||||
inscriptions_counts[scu.INSCRIT],
|
||||
)
|
||||
I["rang_txt"] = "Rang " + I["rang_nt"]
|
||||
else:
|
||||
|
@ -379,7 +387,8 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
I["ues"].append(u) # ne montre pas les UE si non inscrit
|
||||
|
||||
# Accès par matieres
|
||||
I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
|
||||
# voir si on supporte encore cela en #sco92 XXX
|
||||
# I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
|
||||
|
||||
#
|
||||
C = make_context_dict(I["sem"], I["etud"])
|
||||
|
@ -389,6 +398,18 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||
return C
|
||||
|
||||
|
||||
def _get_etud_etat_html(etat: str) -> str:
|
||||
"""chaine html représentant l'état (backward compat sco7)"""
|
||||
if etat == scu.INSCRIT: # "I"
|
||||
return ""
|
||||
elif etat == scu.DEMISSION: # "D"
|
||||
return ' <font color="red">(DEMISSIONNAIRE)</font> '
|
||||
elif etat == scu.DEF: # "DEF"
|
||||
return ' <font color="red">(DEFAILLANT)</font> '
|
||||
else:
|
||||
return ' <font color="red">(%s)</font> ' % etat
|
||||
|
||||
|
||||
def _sort_mod_by_matiere(modlist, nt, etudid):
|
||||
matmod = {} # { matiere_id : [] }
|
||||
for mod in modlist:
|
||||
|
|
|
@ -93,9 +93,9 @@ def make_xml_formsemestre_bulletinetud(
|
|||
)
|
||||
|
||||
if (not sem["bul_hide_xml"]) or force_publishing:
|
||||
published = "1"
|
||||
published = 1
|
||||
else:
|
||||
published = "0"
|
||||
published = 0
|
||||
if xml_nodate:
|
||||
docdate = ""
|
||||
else:
|
||||
|
@ -105,7 +105,7 @@ def make_xml_formsemestre_bulletinetud(
|
|||
"etudid": str(etudid),
|
||||
"formsemestre_id": str(formsemestre_id),
|
||||
"date": docdate,
|
||||
"publie": published,
|
||||
"publie": str(published),
|
||||
}
|
||||
if sem["etapes"]:
|
||||
el["etape_apo"] = str(sem["etapes"][0]) or ""
|
||||
|
@ -141,7 +141,9 @@ def make_xml_formsemestre_bulletinetud(
|
|||
|
||||
# Disponible pour publication ?
|
||||
if not published:
|
||||
return doc # stop !
|
||||
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(
|
||||
scu.SCO_ENCODING
|
||||
) # stop !
|
||||
|
||||
# Groupes:
|
||||
partitions = sco_groups.get_partitions_list(formsemestre_id, with_default=False)
|
||||
|
|
|
@ -155,16 +155,6 @@ class EvaluationCache(ScoDocCache):
|
|||
cls.delete_many(evaluation_ids)
|
||||
|
||||
|
||||
class ResultatsSemestreCache(ScoDocCache):
|
||||
"""Cache pour les résultats ResultatsSemestre.
|
||||
Clé: formsemestre_id
|
||||
Valeur: { un paquet de dataframes }
|
||||
"""
|
||||
|
||||
prefix = "RSEM"
|
||||
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
||||
|
||||
|
||||
class AbsSemEtudCache(ScoDocCache):
|
||||
"""Cache pour les comptes d'absences d'un étudiant dans un semestre.
|
||||
Ce cache étant indépendant des semestres, le compte peut être faux lorsqu'on
|
||||
|
@ -224,7 +214,9 @@ class NotesTableCache(ScoDocCache):
|
|||
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 and compute is True, build it and cache it.
|
||||
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"):
|
||||
|
@ -322,3 +314,14 @@ class DefferedSemCacheManager:
|
|||
while g.sem_to_invalidate:
|
||||
formsemestre_id = g.sem_to_invalidate.pop()
|
||||
invalidate_formsemestre(formsemestre_id)
|
||||
|
||||
|
||||
# ---- Nouvelles classes ScoDoc 9.2
|
||||
class ResultatsSemestreCache(ScoDocCache):
|
||||
"""Cache pour les résultats ResultatsSemestre.
|
||||
Clé: formsemestre_id
|
||||
Valeur: { un paquet de dataframes }
|
||||
"""
|
||||
|
||||
prefix = "RSEM"
|
||||
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
||||
|
|
|
@ -191,6 +191,7 @@ def compute_user_formula(
|
|||
return user_moy
|
||||
|
||||
|
||||
# XXX OBSOLETE
|
||||
def compute_moduleimpl_moyennes(nt, modimpl):
|
||||
"""Retourne dict { etudid : note_moyenne } pour tous les etuds inscrits
|
||||
au moduleimpl mod, la liste des evaluations "valides" (toutes notes entrées
|
||||
|
@ -228,22 +229,23 @@ def compute_moduleimpl_moyennes(nt, modimpl):
|
|||
|
||||
user_expr = moduleimpl_has_expression(modimpl)
|
||||
attente = False
|
||||
# recupere les notes de toutes les evaluations
|
||||
# récupere les notes de toutes les evaluations
|
||||
eval_rattr = None
|
||||
for e in evals:
|
||||
e["nb_inscrits"] = e["etat"]["nb_inscrits"]
|
||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(
|
||||
# XXX OBSOLETE
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
|
||||
e["evaluation_id"]
|
||||
) # toutes, y compris demissions
|
||||
# restreint aux étudiants encore inscrits à ce module
|
||||
notes = [
|
||||
NotesDB[etudid]["value"] for etudid in NotesDB if (etudid in insmod_set)
|
||||
notes_db[etudid]["value"] for etudid in notes_db if (etudid in insmod_set)
|
||||
]
|
||||
e["nb_notes"] = len(notes)
|
||||
e["nb_abs"] = len([x for x in notes if x is None])
|
||||
e["nb_neutre"] = len([x for x in notes if x == NOTES_NEUTRALISE])
|
||||
e["nb_att"] = len([x for x in notes if x == NOTES_ATTENTE])
|
||||
e["notes"] = NotesDB
|
||||
e["notes"] = notes_db
|
||||
|
||||
if e["etat"]["evalattente"]:
|
||||
attente = True
|
||||
|
|
|
@ -62,7 +62,8 @@ def html_edit_formation_apc(
|
|||
else:
|
||||
semestre_ids = [semestre_idx]
|
||||
other_modules = formation.modules.filter(
|
||||
Module.module_type != ModuleType.SAE, Module.module_type != ModuleType.RESSOURCE
|
||||
Module.module_type.is_distinct_from(ModuleType.SAE),
|
||||
Module.module_type.is_distinct_from(ModuleType.RESSOURCE),
|
||||
).order_by(
|
||||
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
||||
)
|
||||
|
@ -78,7 +79,8 @@ def html_edit_formation_apc(
|
|||
alt="supprimer",
|
||||
),
|
||||
"delete_disabled": scu.icontag(
|
||||
"delete_small_dis_img", title="Suppression impossible (module utilisé)"
|
||||
"delete_small_dis_img",
|
||||
title="Suppression impossible (utilisé dans des semestres)",
|
||||
),
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ from app import log
|
|||
from app.models import SHORT_STR_LEN
|
||||
from app.models.formations import Formation
|
||||
from app.models.modules import Module
|
||||
from app.models.ues import UniteEns
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
@ -304,9 +305,8 @@ def do_formation_edit(args):
|
|||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
sco_formations._formationEditor.edit(cnx, args)
|
||||
formation = Formation.query.get(args["formation_id"])
|
||||
formation: Formation = Formation.query.get(args["formation_id"])
|
||||
formation.invalidate_cached_sems()
|
||||
formation.force_semestre_modules_aux_ues()
|
||||
|
||||
|
||||
def module_move(module_id, after=0, redirect=True):
|
||||
|
@ -341,6 +341,7 @@ def module_move(module_id, after=0, redirect=True):
|
|||
db.session.add(module)
|
||||
db.session.add(neigh)
|
||||
db.session.commit()
|
||||
module.formation.invalidate_cached_sems()
|
||||
# redirect to ue_list page:
|
||||
if redirect:
|
||||
return flask.redirect(
|
||||
|
@ -355,16 +356,17 @@ def module_move(module_id, after=0, redirect=True):
|
|||
|
||||
def ue_move(ue_id, after=0, redirect=1):
|
||||
"""Move UE before/after previous one (decrement/increment numero)"""
|
||||
o = sco_edit_ue.ue_list({"ue_id": ue_id})[0]
|
||||
# log('ue_move %s (#%s) after=%s' % (ue_id, o['numero'], after))
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
redirect = int(redirect)
|
||||
after = int(after) # 0: deplace avant, 1 deplace apres
|
||||
if after not in (0, 1):
|
||||
raise ValueError('invalid value for "after"')
|
||||
formation_id = o["formation_id"]
|
||||
others = sco_edit_ue.ue_list({"formation_id": formation_id})
|
||||
others = ue.formation.ues.order_by(UniteEns.numero).all()
|
||||
if len({o.numero for o in others}) != len(others):
|
||||
# il y a des numeros identiques !
|
||||
scu.objects_renumber(db, others)
|
||||
if len(others) > 1:
|
||||
idx = [p["ue_id"] for p in others].index(ue_id)
|
||||
idx = [u.id for u in others].index(ue.id)
|
||||
neigh = None # object to swap with
|
||||
if after == 0 and idx > 0:
|
||||
neigh = others[idx - 1]
|
||||
|
@ -372,20 +374,19 @@ def ue_move(ue_id, after=0, redirect=1):
|
|||
neigh = others[idx + 1]
|
||||
if neigh: #
|
||||
# swap numero between partition and its neighbor
|
||||
# log('moving ue %s (neigh #%s)' % (ue_id, neigh['numero']))
|
||||
cnx = ndb.GetDBConnexion()
|
||||
o["numero"], neigh["numero"] = neigh["numero"], o["numero"]
|
||||
if o["numero"] == neigh["numero"]:
|
||||
neigh["numero"] -= 2 * after - 1
|
||||
sco_edit_ue._ueEditor.edit(cnx, o)
|
||||
sco_edit_ue._ueEditor.edit(cnx, neigh)
|
||||
ue.numero, neigh.numero = neigh.numero, ue.numero
|
||||
db.session.add(ue)
|
||||
db.session.add(neigh)
|
||||
db.session.commit()
|
||||
ue.formation.invalidate_cached_sems()
|
||||
|
||||
# redirect to ue_list page
|
||||
if redirect:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=o["formation_id"],
|
||||
semestre_idx=o["semestre_idx"],
|
||||
formation_id=ue.formation_id,
|
||||
semestre_idx=ue.semestre_idx,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -30,13 +30,18 @@
|
|||
"""
|
||||
import flask
|
||||
from flask import g, url_for, request
|
||||
from app.models.formations import Matiere
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.models import Formation
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError
|
||||
from app.scodoc.sco_exceptions import (
|
||||
ScoValueError,
|
||||
ScoLockedFormError,
|
||||
ScoNonEmptyFormationObject,
|
||||
)
|
||||
from app.scodoc import html_sco_header
|
||||
|
||||
_matiereEditor = ndb.EditableTable(
|
||||
|
@ -156,6 +161,16 @@ associé.
|
|||
return flask.redirect(dest_url)
|
||||
|
||||
|
||||
def can_delete_matiere(matiere: Matiere) -> tuple[bool, str]:
|
||||
"True si la matiere n'est pas utilisée dans des formsemestre"
|
||||
locked = matiere_is_locked(matiere.id)
|
||||
if locked:
|
||||
return False
|
||||
if any(m.modimpls.all() for m in matiere.modules):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def do_matiere_delete(oid):
|
||||
"delete matiere and attached modules"
|
||||
from app.scodoc import sco_formations
|
||||
|
@ -165,17 +180,16 @@ def do_matiere_delete(oid):
|
|||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
# check
|
||||
mat = matiere_list({"matiere_id": oid})[0]
|
||||
matiere = Matiere.query.get_or_404(oid)
|
||||
mat = matiere_list({"matiere_id": oid})[0] # compat sco7
|
||||
ue = sco_edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]
|
||||
locked = matiere_is_locked(mat["matiere_id"])
|
||||
if locked:
|
||||
log("do_matiere_delete: mat=%s" % mat)
|
||||
log("do_matiere_delete: ue=%s" % ue)
|
||||
log("do_matiere_delete: locked sems: %s" % locked)
|
||||
raise ScoLockedFormError()
|
||||
log("do_matiere_delete: matiere_id=%s" % oid)
|
||||
if not can_delete_matiere(matiere):
|
||||
# il y a au moins un modimpl dans un module de cette matière
|
||||
raise ScoNonEmptyFormationObject("Matière", matiere.titre)
|
||||
|
||||
log("do_matiere_delete: matiere_id=%s" % matiere.id)
|
||||
# delete all modules in this matiere
|
||||
mods = sco_edit_module.module_list({"matiere_id": oid})
|
||||
mods = sco_edit_module.module_list({"matiere_id": matiere.id})
|
||||
for mod in mods:
|
||||
sco_edit_module.do_module_delete(mod["module_id"])
|
||||
_matiereEditor.delete(cnx, oid)
|
||||
|
@ -194,11 +208,25 @@ def matiere_delete(matiere_id=None):
|
|||
"""Delete matière"""
|
||||
from app.scodoc import sco_edit_ue
|
||||
|
||||
M = matiere_list(args={"matiere_id": matiere_id})[0]
|
||||
UE = sco_edit_ue.ue_list(args={"ue_id": M["ue_id"]})[0]
|
||||
matiere = Matiere.query.get_or_404(matiere_id)
|
||||
if not can_delete_matiere(matiere):
|
||||
# il y a au moins un modimpl dans un module de cette matière
|
||||
raise ScoNonEmptyFormationObject(
|
||||
"Matière",
|
||||
matiere.titre,
|
||||
dest_url=url_for(
|
||||
"notes.ue_table",
|
||||
formation_id=matiere.ue.formation_id,
|
||||
semestre_idx=matiere.ue.semestre_idx,
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
),
|
||||
)
|
||||
|
||||
mat = matiere_list(args={"matiere_id": matiere_id})[0]
|
||||
UE = sco_edit_ue.ue_list(args={"ue_id": mat["ue_id"]})[0]
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Suppression d'une matière"),
|
||||
"<h2>Suppression de la matière %(titre)s" % M,
|
||||
"<h2>Suppression de la matière %(titre)s" % mat,
|
||||
" dans l'UE (%(acronyme)s))</h2>" % UE,
|
||||
]
|
||||
dest_url = url_for(
|
||||
|
@ -210,7 +238,7 @@ def matiere_delete(matiere_id=None):
|
|||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
(("matiere_id", {"input_type": "hidden"}),),
|
||||
initvalues=M,
|
||||
initvalues=mat,
|
||||
submitlabel="Confirmer la suppression",
|
||||
cancelbutton="Annuler",
|
||||
)
|
||||
|
|
|
@ -43,7 +43,12 @@ from app import models
|
|||
from app.models import Formation
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError, ScoGenError
|
||||
from app.scodoc.sco_exceptions import (
|
||||
ScoValueError,
|
||||
ScoLockedFormError,
|
||||
ScoGenError,
|
||||
ScoNonEmptyFormationObject,
|
||||
)
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_edit_matiere
|
||||
|
@ -330,20 +335,37 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None):
|
|||
)
|
||||
|
||||
|
||||
def can_delete_module(module):
|
||||
"True si le module n'est pas utilisée dans des formsemestre"
|
||||
return len(module.modimpls.all()) == 0
|
||||
|
||||
|
||||
def do_module_delete(oid):
|
||||
"delete module"
|
||||
from app.scodoc import sco_formations
|
||||
|
||||
mod = module_list({"module_id": oid})[0]
|
||||
if module_is_locked(mod["module_id"]):
|
||||
module = Module.query.get_or_404(oid)
|
||||
mod = module_list({"module_id": oid})[0] # sco7
|
||||
if module_is_locked(module.id):
|
||||
raise ScoLockedFormError()
|
||||
if not can_delete_module(module):
|
||||
raise ScoNonEmptyFormationObject(
|
||||
"Module",
|
||||
msg=module.titre,
|
||||
dest_url=url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=module.formation_id,
|
||||
semestre_idx=module.ue.semestre_idx,
|
||||
),
|
||||
)
|
||||
|
||||
# S'il y a des moduleimpls, on ne peut pas detruire le module !
|
||||
mods = sco_moduleimpl.moduleimpl_list(module_id=oid)
|
||||
if mods:
|
||||
err_page = f"""<h3>Destruction du module impossible car il est utilisé dans des semestres existants !</h3>
|
||||
<p class="help">Il faut d'abord supprimer le semestre. Mais il est peut être préférable de
|
||||
laisser ce programme intact et d'en créer une nouvelle version pour la modifier.
|
||||
<p class="help">Il faut d'abord supprimer le semestre (ou en retirer ce module). Mais il est peut être préférable de
|
||||
laisser ce programme intact et d'en créer une nouvelle version pour la modifier sans affecter les semestres déjà en place.
|
||||
</p>
|
||||
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=mod["formation_id"])}">reprendre</a>
|
||||
|
@ -365,12 +387,21 @@ def do_module_delete(oid):
|
|||
|
||||
def module_delete(module_id=None):
|
||||
"""Delete a module"""
|
||||
if not module_id:
|
||||
raise ScoValueError("invalid module !")
|
||||
modules = module_list(args={"module_id": module_id})
|
||||
if not modules:
|
||||
raise ScoValueError("Module inexistant !")
|
||||
mod = modules[0]
|
||||
module = Module.query.get_or_404(module_id)
|
||||
mod = module_list(args={"module_id": module_id})[0] # sco7
|
||||
|
||||
if not can_delete_module(module):
|
||||
raise ScoNonEmptyFormationObject(
|
||||
"Module",
|
||||
msg=module.titre,
|
||||
dest_url=url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=module.formation_id,
|
||||
semestre_idx=module.ue.semestre_idx,
|
||||
),
|
||||
)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Suppression d'un module"),
|
||||
"""<h2>Suppression du module %(titre)s (%(code)s)</h2>""" % mod,
|
||||
|
@ -523,9 +554,11 @@ def module_edit(module_id=None):
|
|||
),
|
||||
]
|
||||
if is_apc:
|
||||
coefs_descr = a_module.ue_coefs_descr()
|
||||
if coefs_descr:
|
||||
coefs_descr_txt = ", ".join(["%s: %s" % x for x in coefs_descr])
|
||||
coefs_lst = a_module.ue_coefs_list()
|
||||
if coefs_lst:
|
||||
coefs_descr_txt = ", ".join(
|
||||
[f"{ue.acronyme}: {c}" for (ue, c) in coefs_lst]
|
||||
)
|
||||
else:
|
||||
coefs_descr_txt = """<span class="missing_value">non définis</span>"""
|
||||
descr += [
|
||||
|
|
|
@ -42,7 +42,12 @@ from app import log
|
|||
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError
|
||||
from app.scodoc.sco_exceptions import (
|
||||
ScoGenError,
|
||||
ScoValueError,
|
||||
ScoLockedFormError,
|
||||
ScoNonEmptyFormationObject,
|
||||
)
|
||||
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
|
@ -130,64 +135,83 @@ def do_ue_create(args):
|
|||
return ue_id
|
||||
|
||||
|
||||
def can_delete_ue(ue: UniteEns) -> bool:
|
||||
"""True si l'UE n'est pas utilisée dans des formsemestre
|
||||
et n'a pas de module rattachés
|
||||
"""
|
||||
# "pas un seul module de cette UE n'a de modimpl...""
|
||||
return (not len(ue.modules.all())) and not any(m.modimpls.all() for m in ue.modules)
|
||||
|
||||
|
||||
def do_ue_delete(ue_id, delete_validations=False, force=False):
|
||||
"delete UE and attached matieres (but not modules)"
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_parcours_dut
|
||||
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
if not can_delete_ue(ue):
|
||||
raise ScoNonEmptyFormationObject(
|
||||
"UE",
|
||||
msg=ue.titre,
|
||||
dest_url=url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=ue.formation_id,
|
||||
semestre_idx=ue.semestre_idx,
|
||||
),
|
||||
)
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue_id, delete_validations))
|
||||
log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue.id, delete_validations))
|
||||
# check
|
||||
ue = ue_list({"ue_id": ue_id})
|
||||
if not ue:
|
||||
raise ScoValueError("UE inexistante !")
|
||||
ue = ue[0]
|
||||
if ue_is_locked(ue["ue_id"]):
|
||||
raise ScoLockedFormError()
|
||||
# if ue_is_locked(ue.id):
|
||||
# raise ScoLockedFormError()
|
||||
# Il y a-t-il des etudiants ayant validé cette UE ?
|
||||
# si oui, propose de supprimer les validations
|
||||
validations = sco_parcours_dut.scolar_formsemestre_validation_list(
|
||||
cnx, args={"ue_id": ue_id}
|
||||
cnx, args={"ue_id": ue.id}
|
||||
)
|
||||
if validations and not delete_validations and not force:
|
||||
return scu.confirm_dialog(
|
||||
"<p>%d étudiants ont validé l'UE %s (%s)</p><p>Si vous supprimez cette UE, ces validations vont être supprimées !</p>"
|
||||
% (len(validations), ue["acronyme"], ue["titre"]),
|
||||
% (len(validations), ue.acronyme, ue.titre),
|
||||
dest_url="",
|
||||
target_variable="delete_validations",
|
||||
cancel_url=url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=str(ue["formation_id"]),
|
||||
formation_id=ue.formation_id,
|
||||
semestre_idx=ue.semestre_idx,
|
||||
),
|
||||
parameters={"ue_id": ue_id, "dialog_confirmed": 1},
|
||||
parameters={"ue_id": ue.id, "dialog_confirmed": 1},
|
||||
)
|
||||
if delete_validations:
|
||||
log("deleting all validations of UE %s" % ue_id)
|
||||
log("deleting all validations of UE %s" % ue.id)
|
||||
ndb.SimpleQuery(
|
||||
"DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s",
|
||||
{"ue_id": ue_id},
|
||||
{"ue_id": ue.id},
|
||||
)
|
||||
|
||||
# delete all matiere in this UE
|
||||
mats = sco_edit_matiere.matiere_list({"ue_id": ue_id})
|
||||
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
|
||||
for mat in mats:
|
||||
sco_edit_matiere.do_matiere_delete(mat["matiere_id"])
|
||||
# delete uecoef and events
|
||||
ndb.SimpleQuery(
|
||||
"DELETE FROM notes_formsemestre_uecoef WHERE ue_id=%(ue_id)s",
|
||||
{"ue_id": ue_id},
|
||||
{"ue_id": ue.id},
|
||||
)
|
||||
ndb.SimpleQuery("DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue_id})
|
||||
ndb.SimpleQuery("DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue.id})
|
||||
cnx = ndb.GetDBConnexion()
|
||||
_ueEditor.delete(cnx, ue_id)
|
||||
# > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement utilisé: acceptable de tout invalider ?):
|
||||
_ueEditor.delete(cnx, ue.id)
|
||||
# > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement
|
||||
# utilisé: acceptable de tout invalider):
|
||||
sco_cache.invalidate_formsemestre()
|
||||
# news
|
||||
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
|
||||
F = sco_formations.formation_list(args={"formation_id": ue.formation_id})[0]
|
||||
sco_news.add(
|
||||
typ=sco_news.NEWS_FORM,
|
||||
object=ue["formation_id"],
|
||||
object=ue.formation_id,
|
||||
text="Modification de la formation %(acronyme)s" % F,
|
||||
max_frequency=3,
|
||||
)
|
||||
|
@ -197,11 +221,11 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
|
|||
url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=ue["formation_id"],
|
||||
formation_id=ue.formation_id,
|
||||
semestre_idx=ue.semestre_idx,
|
||||
)
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def ue_create(formation_id=None):
|
||||
|
@ -211,8 +235,6 @@ def ue_create(formation_id=None):
|
|||
|
||||
def ue_edit(ue_id=None, create=False, formation_id=None):
|
||||
"""Modification ou création d'une UE"""
|
||||
from app.scodoc import sco_formations
|
||||
|
||||
create = int(create)
|
||||
if not create:
|
||||
U = ue_list(args={"ue_id": ue_id})
|
||||
|
@ -444,34 +466,53 @@ def next_ue_numero(formation_id, semestre_id=None):
|
|||
|
||||
def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
|
||||
"""Delete an UE"""
|
||||
ues = ue_list(args={"ue_id": ue_id})
|
||||
if not ues:
|
||||
raise ScoValueError("UE inexistante !")
|
||||
ue = ues[0]
|
||||
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
"<h2>Suppression de l'UE %(titre)s (%(acronyme)s))</h2>" % ue,
|
||||
dest_url="",
|
||||
parameters={"ue_id": ue_id},
|
||||
cancel_url=url_for(
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
if ue.modules.all():
|
||||
raise ScoValueError(
|
||||
f"""Suppression de l'UE {ue.titre} impossible car
|
||||
des modules (ou SAÉ ou ressources) lui sont rattachés.""",
|
||||
dest_url=url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=str(ue["formation_id"]),
|
||||
formation_id=ue.formation.id,
|
||||
semestre_idx=ue.semestre_idx,
|
||||
),
|
||||
)
|
||||
if not can_delete_ue(ue):
|
||||
raise ScoNonEmptyFormationObject(
|
||||
"UE",
|
||||
msg=ue.titre,
|
||||
dest_url=url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=ue.formation_id,
|
||||
semestre_idx=ue.semestre_idx,
|
||||
),
|
||||
)
|
||||
|
||||
return do_ue_delete(ue_id, delete_validations=delete_validations)
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
f"<h2>Suppression de l'UE {ue.titre} ({ue.acronyme})</h2>",
|
||||
dest_url="",
|
||||
parameters={"ue_id": ue.id},
|
||||
cancel_url=url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=ue.formation_id,
|
||||
semestre_idx=ue.semestre_idx,
|
||||
),
|
||||
)
|
||||
|
||||
return do_ue_delete(ue.id, delete_validations=delete_validations)
|
||||
|
||||
|
||||
def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
||||
"""Liste des matières et modules d'une formation, avec liens pour
|
||||
éditer (si non verrouillée).
|
||||
"""
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formsemestre_validation
|
||||
|
||||
formation = Formation.query.get(formation_id)
|
||||
formation: Formation = Formation.query.get(formation_id)
|
||||
if not formation:
|
||||
raise ScoValueError("invalid formation_id")
|
||||
parcours = formation.get_parcours()
|
||||
|
@ -594,12 +635,16 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||
descr_refcomp = ""
|
||||
msg_refcomp = "associer à un référentiel de compétences"
|
||||
else:
|
||||
descr_refcomp = f"{formation.referentiel_competence.type_titre} {formation.referentiel_competence.specialite_long}"
|
||||
descr_refcomp = f"""Référentiel de compétences:
|
||||
<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> """
|
||||
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>
|
||||
|
@ -1010,12 +1055,14 @@ def _ue_table_modules(
|
|||
H.append(arrow_none)
|
||||
im += 1
|
||||
if mod["nb_moduleimpls"] == 0 and editable:
|
||||
H.append(
|
||||
'<a class="smallbutton" href="module_delete?module_id=%s">%s</a>'
|
||||
% (mod["module_id"], delete_icon)
|
||||
)
|
||||
icon = delete_icon
|
||||
else:
|
||||
H.append(delete_disabled_icon)
|
||||
icon = delete_disabled_icon
|
||||
H.append(
|
||||
'<a class="smallbutton" href="module_delete?module_id=%s">%s</a>'
|
||||
% (mod["module_id"], icon)
|
||||
)
|
||||
|
||||
H.append("</span>")
|
||||
|
||||
mod_editable = (
|
||||
|
@ -1167,7 +1214,6 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
|
|||
if not dont_invalidate_cache:
|
||||
# Invalide les semestres utilisant cette formation:
|
||||
formation.invalidate_cached_sems()
|
||||
formation.force_semestre_modules_aux_ues()
|
||||
|
||||
|
||||
# essai edition en ligne:
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
apo_csv_get()
|
||||
|
||||
API:
|
||||
apo_csv_store( annee_scolaire, sem_id)
|
||||
# apo_csv_store(csv_data, annee_scolaire, sem_id)
|
||||
store maq file (archive)
|
||||
|
||||
apo_csv_get(etape_apo, annee_scolaire, sem_id, vdi_apo=None)
|
||||
|
@ -101,7 +101,7 @@ ApoCSVArchive = ApoCSVArchiver()
|
|||
|
||||
def apo_csv_store(csv_data: str, annee_scolaire, sem_id):
|
||||
"""
|
||||
csv_data: maquette content (string)
|
||||
csv_data: maquette content (str))
|
||||
annee_scolaire: int (2016)
|
||||
sem_id: 0 (année ?), 1 (premier semestre de l'année) ou 2 (deuxième semestre)
|
||||
:return: etape_apo du fichier CSV stocké
|
||||
|
@ -378,7 +378,7 @@ e.associate_sco( apo_data)
|
|||
print apo_csv_list_stored_archives()
|
||||
|
||||
|
||||
apo_csv_store(csv_data, annee_scolaire, sem_id)
|
||||
# apo_csv_store(csv_data, annee_scolaire, sem_id)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ from app.scodoc import sco_preferences
|
|||
from app.scodoc import sco_semset
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_apogee_csv import APO_PORTAL_ENCODING, APO_INPUT_ENCODING
|
||||
from app.scodoc.sco_apogee_csv import APO_INPUT_ENCODING, APO_OUTPUT_ENCODING
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
|
@ -585,7 +585,7 @@ def _view_etuds_page(semset_id, title="", etuds=[], keys=(), format="html"):
|
|||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
def view_apo_csv_store(semset_id="", csvfile=None, data="", autodetect=False):
|
||||
def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=False):
|
||||
"""Store CSV data
|
||||
Le semset identifie l'annee scolaire et le semestre
|
||||
Si csvfile, lit depuis FILE, sinon utilise data
|
||||
|
@ -593,9 +593,8 @@ def view_apo_csv_store(semset_id="", csvfile=None, data="", autodetect=False):
|
|||
if not semset_id:
|
||||
raise ValueError("invalid null semset_id")
|
||||
semset = sco_semset.SemSet(semset_id=semset_id)
|
||||
|
||||
if csvfile:
|
||||
data = csvfile.read()
|
||||
data = csvfile.read() # bytes
|
||||
if autodetect:
|
||||
# check encoding (although documentation states that users SHOULD upload LATIN1)
|
||||
data, message = sco_apogee_csv.fix_data_encoding(data)
|
||||
|
@ -605,19 +604,26 @@ def view_apo_csv_store(semset_id="", csvfile=None, data="", autodetect=False):
|
|||
log("view_apo_csv_store: autodetection of encoding disabled by user")
|
||||
if not data:
|
||||
raise ScoValueError("view_apo_csv_store: no data")
|
||||
|
||||
# data est du bytes, encodé en APO_INPUT_ENCODING
|
||||
data_str = data.decode(APO_INPUT_ENCODING)
|
||||
# check si etape maquette appartient bien au semset
|
||||
apo_data = sco_apogee_csv.ApoData(
|
||||
data, periode=semset["sem_id"]
|
||||
data_str, periode=semset["sem_id"]
|
||||
) # parse le fichier -> exceptions
|
||||
if apo_data.etape not in semset["etapes"]:
|
||||
raise ScoValueError(
|
||||
"Le code étape de ce fichier ne correspond pas à ceux de cet ensemble"
|
||||
)
|
||||
|
||||
sco_etape_apogee.apo_csv_store(data, semset["annee_scolaire"], semset["sem_id"])
|
||||
sco_etape_apogee.apo_csv_store(data_str, semset["annee_scolaire"], semset["sem_id"])
|
||||
|
||||
return flask.redirect("apo_semset_maq_status?semset_id=" + semset_id)
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.apo_semset_maq_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
semset_id=semset_id,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def view_apo_csv_download_and_store(etape_apo="", semset_id=""):
|
||||
|
@ -629,9 +635,9 @@ def view_apo_csv_download_and_store(etape_apo="", semset_id=""):
|
|||
data = sco_portal_apogee.get_maquette_apogee(
|
||||
etape=etape_apo, annee_scolaire=semset["annee_scolaire"]
|
||||
)
|
||||
# here, data is utf8
|
||||
# here, data is str
|
||||
# but we store and generate latin1 files, to ease further import in Apogée
|
||||
data = data.decode(APO_PORTAL_ENCODING).encode(APO_INPUT_ENCODING) # XXX #py3
|
||||
data = data.encode(APO_OUTPUT_ENCODING)
|
||||
return view_apo_csv_store(semset_id, data=data, autodetect=False)
|
||||
|
||||
|
||||
|
@ -669,7 +675,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
|||
sem_id = semset["sem_id"]
|
||||
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
|
||||
if format == "raw":
|
||||
scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
|
||||
return scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
|
||||
|
||||
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ def evaluation_check_absences(evaluation_id):
|
|||
Justs = set([x["etudid"] for x in Just]) # ensemble des etudiants avec justif
|
||||
|
||||
# Les notes:
|
||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
ValButAbs = [] # une note mais noté absent
|
||||
AbsNonSignalee = [] # note ABS mais pas noté absent
|
||||
ExcNonSignalee = [] # note EXC mais pas noté absent
|
||||
|
@ -94,8 +94,8 @@ def evaluation_check_absences(evaluation_id):
|
|||
for etudid, _ in sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation_id, getallstudents=True
|
||||
):
|
||||
if etudid in NotesDB:
|
||||
val = NotesDB[etudid]["value"]
|
||||
if etudid in notes_db:
|
||||
val = notes_db[etudid]["value"]
|
||||
if (
|
||||
val != None and val != scu.NOTES_NEUTRALISE and val != scu.NOTES_ATTENTE
|
||||
) and etudid in As:
|
||||
|
@ -222,7 +222,6 @@ def formsemestre_check_absences_html(formsemestre_id):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Vérification absences aux évaluations de ce semestre",
|
||||
sem,
|
||||
),
|
||||
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.
|
||||
Sont listés tous les modules avec des évaluations.<br/>Aucune action n'est effectuée:
|
||||
|
|
|
@ -306,8 +306,8 @@ def do_evaluation_delete(evaluation_id):
|
|||
raise AccessDenied(
|
||||
"Modification évaluation impossible pour %s" % current_user.get_nomplogin()
|
||||
)
|
||||
NotesDB = do_evaluation_get_all_notes(evaluation_id) # { etudid : value }
|
||||
notes = [x["value"] for x in NotesDB.values()]
|
||||
notes_db = do_evaluation_get_all_notes(evaluation_id) # { etudid : value }
|
||||
notes = [x["value"] for x in notes_db.values()]
|
||||
if notes:
|
||||
raise ScoValueError(
|
||||
"Impossible de supprimer cette évaluation: il reste des notes"
|
||||
|
|
|
@ -170,7 +170,7 @@ def evaluation_create_form(
|
|||
(
|
||||
"jour",
|
||||
{
|
||||
"input_type": "date",
|
||||
"input_type": "datedmy",
|
||||
"title": "Date",
|
||||
"size": 12,
|
||||
"explanation": "date de l'examen, devoir ou contrôle",
|
||||
|
|
|
@ -467,7 +467,6 @@ def formsemestre_evaluations_cal(formsemestre_id):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Evaluations du semestre",
|
||||
sem,
|
||||
cssstyles=["css/calabs.css"],
|
||||
),
|
||||
'<div class="cal_evaluations">',
|
||||
|
|
|
@ -52,7 +52,7 @@ class InvalidNoteValue(ScoException):
|
|||
# Exception qui stoque dest_url, utilisee dans Zope standard_error_message
|
||||
class ScoValueError(ScoException):
|
||||
def __init__(self, msg, dest_url=None):
|
||||
ScoException.__init__(self, msg)
|
||||
super().__init__(msg)
|
||||
self.dest_url = dest_url
|
||||
|
||||
|
||||
|
@ -60,6 +60,21 @@ class ScoFormatError(ScoValueError):
|
|||
pass
|
||||
|
||||
|
||||
class ScoPDFFormatError(ScoValueError):
|
||||
"erreur génération PDF (templates platypus, ...)"
|
||||
|
||||
def __init__(self, msg, dest_url=None):
|
||||
super().__init__(
|
||||
f"""Erreur dans un format pdf:
|
||||
<p>{msg}</p>
|
||||
<p>Vérifiez les paramètres (polices de caractères, balisage)
|
||||
dans les paramètres ou préférences.
|
||||
</p>
|
||||
""",
|
||||
dest_url=dest_url,
|
||||
)
|
||||
|
||||
|
||||
class ScoInvalidDept(ScoValueError):
|
||||
"""departement invalide"""
|
||||
|
||||
|
@ -72,20 +87,57 @@ class ScoConfigurationError(ScoValueError):
|
|||
pass
|
||||
|
||||
|
||||
class ScoLockedFormError(ScoException):
|
||||
def __init__(self, msg=""):
|
||||
class ScoLockedFormError(ScoValueError):
|
||||
"Modification d'une formation verrouillée"
|
||||
|
||||
def __init__(self, msg="", dest_url=None):
|
||||
msg = (
|
||||
"Cette formation est verrouillée (car il y a un semestre verrouillé qui s'y réfère). "
|
||||
+ str(msg)
|
||||
)
|
||||
ScoException.__init__(self, msg)
|
||||
super().__init__(msg=msg, dest_url=dest_url)
|
||||
|
||||
|
||||
class ScoNonEmptyFormationObject(ScoValueError):
|
||||
"""On ne peut pas supprimer un module/matiere ou UE si des formsemestre s'y réfèrent"""
|
||||
|
||||
def __init__(self, type_objet="objet'", msg="", dest_url=None):
|
||||
msg = f"""<h3>{type_objet} "{msg}" utilisé dans des semestres: suppression impossible.</h3>
|
||||
<p class="help">Il faut d'abord supprimer le semestre (ou en retirer ce {type_objet}).
|
||||
Mais il est peut-être préférable de laisser ce programme intact et d'en créer une
|
||||
nouvelle version pour la modifier sans affecter les semestres déjà en place.
|
||||
</p>
|
||||
"""
|
||||
super().__init__(msg=msg, dest_url=dest_url)
|
||||
|
||||
|
||||
class ScoInvalidIdType(ScoValueError):
|
||||
"""Pour les clients qui s'obstinnent à utiliser des bookmarks ou
|
||||
historiques anciens avec des ID ScoDoc7"""
|
||||
|
||||
def __init__(self, msg=""):
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
msg = f"""<h3>Adresse de page invalide</h3>
|
||||
<p class="help">
|
||||
Vous utilisez un lien invalide, qui correspond probablement
|
||||
à une ancienne version du logiciel. <br>
|
||||
Au besoin, mettre à jour vos marque-pages.
|
||||
</p>
|
||||
<p> Si le problème persiste, merci de contacter l'assistance
|
||||
via la liste de diffusion <a href="{scu.SCO_USERS_LIST}">Notes</a>
|
||||
ou le salon Discord.
|
||||
</p>
|
||||
<p>Message serveur: <tt>{msg}</tt></p>
|
||||
"""
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class ScoGenError(ScoException):
|
||||
"exception avec affichage d'une page explicative ad-hoc"
|
||||
|
||||
def __init__(self, msg=""):
|
||||
ScoException.__init__(self, msg)
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class AccessDenied(ScoGenError):
|
||||
|
@ -101,7 +153,7 @@ class APIInvalidParams(Exception):
|
|||
status_code = 400
|
||||
|
||||
def __init__(self, message, status_code=None, payload=None):
|
||||
Exception.__init__(self)
|
||||
super().__init__()
|
||||
self.message = message
|
||||
if status_code is not None:
|
||||
self.status_code = status_code
|
||||
|
|
|
@ -27,21 +27,22 @@
|
|||
|
||||
"""Operations de base sur les formsemestres
|
||||
"""
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
import time
|
||||
from operator import itemgetter
|
||||
import time
|
||||
|
||||
from flask import g, request
|
||||
|
||||
import app
|
||||
from app import log
|
||||
from app.models import Departement
|
||||
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app import log
|
||||
from app.scodoc.sco_codes_parcours import NO_SEMESTRE_ID
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidIdType
|
||||
from app.scodoc.sco_vdi import ApoEtapeVDI
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
@ -97,7 +98,7 @@ def get_formsemestre(formsemestre_id, raise_soft_exc=False):
|
|||
if formsemestre_id in g.stored_get_formsemestre:
|
||||
return g.stored_get_formsemestre[formsemestre_id]
|
||||
if not isinstance(formsemestre_id, int):
|
||||
raise ValueError("formsemestre_id must be an integer !")
|
||||
raise ScoInvalidIdType("formsemestre_id must be an integer !")
|
||||
sems = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})
|
||||
if not sems:
|
||||
log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id)
|
||||
|
@ -450,7 +451,7 @@ def sem_in_annee_scolaire(sem, year=False):
|
|||
)
|
||||
|
||||
|
||||
def sem_une_annee(sem):
|
||||
def sem_une_annee(sem): # XXX deprecated: use FormSemestre.est_sur_une_annee()
|
||||
"""Test si sem est entièrement sur la même année scolaire.
|
||||
(ce n'est pas obligatoire mais si ce n'est pas le cas les exports Apogée ne vont pas fonctionner)
|
||||
pivot au 1er août.
|
||||
|
|
|
@ -84,7 +84,7 @@ def formsemestre_custommenu_edit(formsemestre_id):
|
|||
scu.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
||||
)
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Modification du menu du semestre ", sem),
|
||||
html_sco_header.html_sem_header("Modification du menu du semestre "),
|
||||
"""<p class="help">Ce menu, spécifique à chaque semestre, peut être utilisé pour placer des liens vers vos applications préférées.</p>
|
||||
<p class="help">Procédez en plusieurs fois si vous voulez ajouter plusieurs items.</p>""",
|
||||
]
|
||||
|
|
|
@ -96,7 +96,6 @@ def formsemestre_editwithmodules(formsemestre_id):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Modification du semestre",
|
||||
sem,
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
|
@ -198,10 +197,13 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
NB_SEM = parcours.NB_SEM
|
||||
else:
|
||||
NB_SEM = 10 # fallback, max 10 semestres
|
||||
semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
|
||||
if NB_SEM == 1:
|
||||
semestre_id_list = [-1]
|
||||
else:
|
||||
semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
|
||||
semestre_id_labels = []
|
||||
for sid in semestre_id_list:
|
||||
if sid == "-1":
|
||||
if sid == -1:
|
||||
semestre_id_labels.append("pas de semestres")
|
||||
else:
|
||||
semestre_id_labels.append(f"S{sid}")
|
||||
|
@ -251,7 +253,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
"date_debut",
|
||||
{
|
||||
"title": "Date de début", # j/m/a
|
||||
"input_type": "date",
|
||||
"input_type": "datedmy",
|
||||
"explanation": "j/m/a",
|
||||
"size": 9,
|
||||
"allow_null": False,
|
||||
|
@ -261,7 +263,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
"date_fin",
|
||||
{
|
||||
"title": "Date de fin", # j/m/a
|
||||
"input_type": "date",
|
||||
"input_type": "datedmy",
|
||||
"explanation": "j/m/a",
|
||||
"size": 9,
|
||||
"allow_null": False,
|
||||
|
@ -329,6 +331,8 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
"labels": modalites_titles,
|
||||
},
|
||||
),
|
||||
]
|
||||
modform.append(
|
||||
(
|
||||
"semestre_id",
|
||||
{
|
||||
|
@ -338,7 +342,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||
"labels": semestre_id_labels,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
etapes = sco_portal_apogee.get_etapes_apogee_dept()
|
||||
# Propose les etapes renvoyées par le portail
|
||||
# et ajoute les étapes du semestre qui ne sont pas dans la liste (soit la liste a changé, soit l'étape a été ajoutée manuellement)
|
||||
|
@ -895,7 +899,6 @@ def formsemestre_clone(formsemestre_id):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Copie du semestre",
|
||||
sem,
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
|
@ -909,7 +912,7 @@ def formsemestre_clone(formsemestre_id):
|
|||
"date_debut",
|
||||
{
|
||||
"title": "Date de début", # j/m/a
|
||||
"input_type": "date",
|
||||
"input_type": "datedmy",
|
||||
"explanation": "j/m/a",
|
||||
"size": 9,
|
||||
"allow_null": False,
|
||||
|
@ -919,7 +922,7 @@ def formsemestre_clone(formsemestre_id):
|
|||
"date_fin",
|
||||
{
|
||||
"title": "Date de fin", # j/m/a
|
||||
"input_type": "date",
|
||||
"input_type": "datedmy",
|
||||
"explanation": "j/m/a",
|
||||
"size": 9,
|
||||
"allow_null": False,
|
||||
|
@ -1235,7 +1238,7 @@ def formsemestre_delete(formsemestre_id):
|
|||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Suppression du semestre", sem),
|
||||
html_sco_header.html_sem_header("Suppression du semestre"),
|
||||
"""<div class="ue_warning"><span>Attention !</span>
|
||||
<p class="help">A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement,
|
||||
<b>un semestre ne doit jamais être supprimé</b> (on perd la mémoire des notes et de tous les événements liés à ce semestre !).</p>
|
||||
|
@ -1514,7 +1517,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
|||
</p>
|
||||
"""
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Coefficients des UE du semestre", sem),
|
||||
html_sco_header.html_sem_header("Coefficients des UE du semestre"),
|
||||
help,
|
||||
]
|
||||
#
|
||||
|
@ -1626,7 +1629,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
|||
formsemestre_id=formsemestre_id
|
||||
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
|
||||
|
||||
header = html_sco_header.html_sem_header("Coefficients des UE du semestre", sem)
|
||||
header = html_sco_header.html_sem_header("Coefficients des UE du semestre")
|
||||
return (
|
||||
header
|
||||
+ "\n".join(z)
|
||||
|
|
|
@ -154,7 +154,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
|
|||
"date_debut",
|
||||
{
|
||||
"title": "Date de début", # j/m/a
|
||||
"input_type": "date",
|
||||
"input_type": "datedmy",
|
||||
"explanation": "j/m/a (peut être approximatif)",
|
||||
"size": 9,
|
||||
"allow_null": False,
|
||||
|
@ -164,7 +164,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
|
|||
"date_fin",
|
||||
{
|
||||
"title": "Date de fin", # j/m/a
|
||||
"input_type": "date",
|
||||
"input_type": "datedmy",
|
||||
"explanation": "j/m/a (peut être approximatif)",
|
||||
"size": 9,
|
||||
"allow_null": False,
|
||||
|
|
|
@ -374,7 +374,6 @@ def formsemestre_inscription_with_modules(
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Inscription de %s dans ce semestre" % etud["nomprenom"],
|
||||
sem,
|
||||
)
|
||||
]
|
||||
F = html_sco_header.sco_footer()
|
||||
|
@ -802,7 +801,6 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Inscriptions multiples parmi les étudiants du semestre ",
|
||||
sem,
|
||||
)
|
||||
]
|
||||
insd = list_inscrits_ailleurs(formsemestre_id)
|
||||
|
|
|
@ -35,7 +35,10 @@ from flask import url_for
|
|||
from flask_login import current_user
|
||||
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.models import Module
|
||||
from app.models import formsemestre
|
||||
from app.models.formsemestre import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.notesdb as ndb
|
||||
|
@ -722,7 +725,7 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
|
|||
% (request.base_url, formsemestre_id, with_evals),
|
||||
page_title=title,
|
||||
html_title=html_sco_header.html_sem_header(
|
||||
"Description du semestre", sem, with_page_header=False
|
||||
"Description du semestre", with_page_header=False
|
||||
),
|
||||
pdf_title=title,
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
|
@ -915,34 +918,35 @@ def html_expr_diagnostic(diagnostics):
|
|||
|
||||
def formsemestre_status_head(formsemestre_id=None, page_title=None):
|
||||
"""En-tête HTML des pages "semestre" """
|
||||
semlist = sco_formsemestre.do_formsemestre_list(
|
||||
args={"formsemestre_id": formsemestre_id}
|
||||
)
|
||||
if not semlist:
|
||||
raise ScoValueError("Session inexistante (elle a peut être été supprimée ?)")
|
||||
sem = semlist[0]
|
||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
||||
sem = FormSemestre.query.get(formsemestre_id)
|
||||
if not sem:
|
||||
raise ScoValueError("Semestre inexistant (il a peut être été supprimé ?)")
|
||||
formation = sem.formation
|
||||
parcours = formation.get_parcours()
|
||||
|
||||
page_title = page_title or "Modules de "
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
page_title, sem, with_page_header=False, with_h2=False
|
||||
page_title, with_page_header=False, with_h2=False
|
||||
),
|
||||
f"""<table>
|
||||
<tr><td class="fichetitre2">Formation: </td><td>
|
||||
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=F['formation_id'])}"
|
||||
class="discretelink" title="Formation {F['acronyme']}, v{F['version']}">{F['titre']}</a>""",
|
||||
<a href="{url_for('notes.ue_table',
|
||||
scodoc_dept=g.scodoc_dept, formation_id=sem.formation.id)}"
|
||||
class="discretelink" title="Formation {
|
||||
formation.acronyme}, v{formation.version}">{formation.titre}</a>
|
||||
""",
|
||||
]
|
||||
if sem["semestre_id"] >= 0:
|
||||
H.append(", %s %s" % (parcours.SESSION_NAME, sem["semestre_id"]))
|
||||
if sem["modalite"]:
|
||||
H.append(" en %(modalite)s" % sem)
|
||||
if sem["etapes"]:
|
||||
if sem.semestre_id >= 0:
|
||||
H.append(", %s %s" % (parcours.SESSION_NAME, sem.semestre_id))
|
||||
if sem.modalite:
|
||||
H.append(f" en {sem.modalite}")
|
||||
if sem.etapes:
|
||||
H.append(
|
||||
" (étape <b><tt>%s</tt></b>)"
|
||||
% (sem["etapes_apo_str"] or "-")
|
||||
f""" (étape <b><tt>{
|
||||
sem.etapes_apo_str() or "-"
|
||||
}</tt></b>)"""
|
||||
)
|
||||
H.append("</td></tr>")
|
||||
|
||||
|
@ -965,18 +969,16 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
|
|||
)
|
||||
H.append("</table>")
|
||||
sem_warning = ""
|
||||
if sem["bul_hide_xml"]:
|
||||
if sem.bul_hide_xml:
|
||||
sem_warning += "Bulletins non publiés sur le portail. "
|
||||
if sem["block_moyennes"]:
|
||||
if sem.block_moyennes:
|
||||
sem_warning += "Calcul des moyennes bloqué !"
|
||||
if sem_warning:
|
||||
H.append('<p class="fontorange"><em>' + sem_warning + "</em></p>")
|
||||
if sem["semestre_id"] >= 0 and not sco_formsemestre.sem_une_annee(sem):
|
||||
if sem.semestre_id >= 0 and not sem.est_sur_une_annee():
|
||||
H.append(
|
||||
'<p class="fontorange"><em>Attention: ce semestre couvre plusieurs années scolaires !</em></p>'
|
||||
)
|
||||
# elif sco_preferences.get_preference( 'bul_display_publication', formsemestre_id):
|
||||
# H.append('<p><em>Bulletins publiés sur le portail</em></p>')
|
||||
|
||||
return "".join(H)
|
||||
|
||||
|
@ -990,6 +992,9 @@ def formsemestre_status(formsemestre_id=None):
|
|||
formsemestre_id=formsemestre_id
|
||||
)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
# WIP formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
# WIP nt = res_sem.load_formsemestre_result(formsemestre)
|
||||
|
||||
# Construit la liste de tous les enseignants de ce semestre:
|
||||
mails_enseignants = set(
|
||||
[sco_users.user_info(ens_id)["email"] for ens_id in sem["responsables"]]
|
||||
|
@ -1119,7 +1124,7 @@ def formsemestre_tableau_modules(
|
|||
mod_descr = "Module " + (mod.titre or "")
|
||||
if mod.is_apc():
|
||||
coef_descr = ", ".join(
|
||||
[f"{ue_acro}: {co}" for ue_acro, co in mod.ue_coefs_descr()]
|
||||
[f"{ue.acronyme}: {co}" for ue, co in mod.ue_coefs_list()]
|
||||
)
|
||||
if coef_descr:
|
||||
mod_descr += " Coefs: " + coef_descr
|
||||
|
|
|
@ -847,9 +847,7 @@ def formsemestre_validation_auto(formsemestre_id):
|
|||
"Formulaire saisie automatisee des decisions d'un semestre"
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Saisie automatique des décisions du semestre", sem
|
||||
),
|
||||
html_sco_header.html_sem_header("Saisie automatique des décisions du semestre"),
|
||||
"""
|
||||
<ul>
|
||||
<li>Seuls les étudiants qui obtiennent le semestre seront affectés (code ADM, moyenne générale et
|
||||
|
|
|
@ -284,7 +284,9 @@ def get_group_infos(group_id, etat=None): # was _getlisteetud
|
|||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
group = get_group(group_id)
|
||||
sem = sco_formsemestre.get_formsemestre(group["formsemestre_id"])
|
||||
sem = sco_formsemestre.get_formsemestre(
|
||||
group["formsemestre_id"], raise_soft_exc=True
|
||||
)
|
||||
|
||||
members = get_group_members(group_id, etat=etat)
|
||||
# add human readable description of state:
|
||||
|
@ -1431,18 +1433,19 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
|
|||
|
||||
|
||||
def do_evaluation_listeetuds_groups(
|
||||
evaluation_id, groups=None, getallstudents=False, include_dems=False
|
||||
evaluation_id, groups=None, getallstudents=False, include_demdef=False
|
||||
):
|
||||
"""Donne la liste des etudids inscrits a cette evaluation dans les
|
||||
groupes indiqués.
|
||||
Si getallstudents==True, donne tous les etudiants inscrits a cette
|
||||
evaluation.
|
||||
Si include_dems, compte aussi les etudiants démissionnaires
|
||||
Si include_demdef, compte aussi les etudiants démissionnaires et défaillants
|
||||
(sinon, par défaut, seulement les 'I')
|
||||
|
||||
Résultat: [ (etudid, etat) ], où etat='I', 'D', 'DEF'
|
||||
"""
|
||||
# nb: pour notes_table / do_evaluation_etat, getallstudents est vrai et include_dems faux
|
||||
# nb: pour notes_table / do_evaluation_etat, getallstudents est vrai et
|
||||
# include_demdef faux
|
||||
fromtables = [
|
||||
"notes_moduleimpl_inscription Im",
|
||||
"notes_formsemestre_inscription Isem",
|
||||
|
@ -1474,7 +1477,7 @@ def do_evaluation_listeetuds_groups(
|
|||
and E.id = %(evaluation_id)s
|
||||
"""
|
||||
)
|
||||
if not include_dems:
|
||||
if not include_demdef:
|
||||
req += " and Isem.etat='I'"
|
||||
req += r
|
||||
cnx = ndb.GetDBConnexion()
|
||||
|
|
|
@ -550,9 +550,9 @@ def _import_one_student(
|
|||
formsemestre_id = values["codesemestre"]
|
||||
try:
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
except ValueError as exc:
|
||||
except (ValueError, TypeError) as exc:
|
||||
raise ScoValueError(
|
||||
f"valeur invalide dans la colonne codesemestre, ligne {linenum+1}"
|
||||
f"valeur invalide ou manquante dans la colonne codesemestre, ligne {linenum+1}"
|
||||
) from exc
|
||||
# recupere liste des groupes:
|
||||
if formsemestre_id not in GroupIdInferers:
|
||||
|
|
|
@ -292,6 +292,8 @@ def formsemestre_inscr_passage(
|
|||
etuds = [int(x) for x in etuds.split(",") if x]
|
||||
elif isinstance(etuds, int):
|
||||
etuds = [etuds]
|
||||
elif etuds and isinstance(etuds[0], str):
|
||||
etuds = [int(x) for x in etuds]
|
||||
|
||||
auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(sem)
|
||||
etuds_set = set(etuds)
|
||||
|
@ -418,7 +420,7 @@ def build_page(
|
|||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Passages dans le semestre", sem, with_page_header=False
|
||||
"Passages dans le semestre", with_page_header=False
|
||||
),
|
||||
"""<form method="post" action="%s">""" % request.base_url,
|
||||
"""<input type="hidden" name="formsemestre_id" value="%(formsemestre_id)s"/>
|
||||
|
|
|
@ -201,6 +201,7 @@ def do_evaluation_listenotes(
|
|||
note_sur_20 = tf[2]["note_sur_20"]
|
||||
hide_groups = tf[2]["hide_groups"]
|
||||
with_emails = tf[2]["with_emails"]
|
||||
group_ids = [x for x in tf[2]["group_ids"] if x != ""]
|
||||
return (
|
||||
_make_table_notes(
|
||||
tf[1],
|
||||
|
@ -208,7 +209,7 @@ def do_evaluation_listenotes(
|
|||
format=format,
|
||||
note_sur_20=note_sur_20,
|
||||
anonymous_listing=anonymous_listing,
|
||||
group_ids=tf[2]["group_ids"],
|
||||
group_ids=group_ids,
|
||||
hide_groups=hide_groups,
|
||||
with_emails=with_emails,
|
||||
mode=mode,
|
||||
|
@ -308,7 +309,7 @@ def _make_table_notes(
|
|||
anonymous_lst_key = "etudid"
|
||||
|
||||
etudid_etats = sco_groups.do_evaluation_listeetuds_groups(
|
||||
E["evaluation_id"], groups, include_dems=True
|
||||
E["evaluation_id"], groups, include_demdef=True
|
||||
)
|
||||
for etudid, etat in etudid_etats:
|
||||
css_row_class = None
|
||||
|
@ -652,11 +653,11 @@ def _add_eval_columns(
|
|||
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
|
||||
evaluation_id = e["evaluation_id"]
|
||||
e_o = Evaluation.query.get(evaluation_id) # XXX en attendant ré-écriture
|
||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
for row in rows:
|
||||
etudid = row["etudid"]
|
||||
if etudid in NotesDB:
|
||||
val = NotesDB[etudid]["value"]
|
||||
if etudid in notes_db:
|
||||
val = notes_db[etudid]["value"]
|
||||
if val is None:
|
||||
nb_abs += 1
|
||||
if val == scu.NOTES_ATTENTE:
|
||||
|
@ -673,12 +674,12 @@ def _add_eval_columns(
|
|||
nb_notes = nb_notes + 1
|
||||
sum_notes += val
|
||||
val_fmt = scu.fmt_note(val, keep_numeric=keep_numeric)
|
||||
comment = NotesDB[etudid]["comment"]
|
||||
comment = notes_db[etudid]["comment"]
|
||||
if comment is None:
|
||||
comment = ""
|
||||
explanation = "%s (%s) %s" % (
|
||||
NotesDB[etudid]["date"].strftime("%d/%m/%y %Hh%M"),
|
||||
sco_users.user_info(NotesDB[etudid]["uid"])["nomcomplet"],
|
||||
notes_db[etudid]["date"].strftime("%d/%m/%y %Hh%M"),
|
||||
sco_users.user_info(notes_db[etudid]["uid"])["nomcomplet"],
|
||||
comment,
|
||||
)
|
||||
else:
|
||||
|
|
|
@ -36,6 +36,7 @@ from app.auth.models import User
|
|||
from app.models import ModuleImpl
|
||||
from app.models.evaluations import Evaluation
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoInvalidIdType
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
from app.scodoc import html_sco_header
|
||||
|
@ -156,33 +157,36 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0):
|
|||
return htmlutils.make_menu("actions", menuEval, alone=True)
|
||||
|
||||
|
||||
def _ue_coefs_html(coefs_descr) -> str:
|
||||
def _ue_coefs_html(coefs_lst) -> str:
|
||||
""" """
|
||||
max_coef = max([x[1] for x in coefs_descr]) if coefs_descr else 1.0
|
||||
max_coef = max([x[1] for x in coefs_lst]) if coefs_lst else 1.0
|
||||
H = """
|
||||
<div id="modimpl_coefs">
|
||||
<div>Coefficients vers les UE</div>
|
||||
"""
|
||||
if coefs_descr:
|
||||
H += f"""
|
||||
if coefs_lst:
|
||||
H += (
|
||||
f"""
|
||||
<div class="coefs_histo" style="--max:{max_coef}">
|
||||
""" + "\n".join(
|
||||
[
|
||||
f"""<div style="--coef:{coef}"><div>{coef}</div>{ue_acronyme}</div>"""
|
||||
for ue_acronyme, coef in coefs_descr
|
||||
]
|
||||
"""
|
||||
+ "\n".join(
|
||||
[
|
||||
f"""<div style="--coef:{coef}"><div>{coef}</div>{ue.acronyme}</div>"""
|
||||
for ue, coef in coefs_lst
|
||||
]
|
||||
)
|
||||
+ "</div>"
|
||||
)
|
||||
else:
|
||||
H += """<div class="missing_value">non définis</span>"""
|
||||
H += """
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
H += """<div class="missing_value">non définis</div>"""
|
||||
H += "</div>"
|
||||
return H
|
||||
|
||||
|
||||
def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
"""Tableau de bord module (liste des evaluations etc)"""
|
||||
if not isinstance(moduleimpl_id, int):
|
||||
raise ScoInvalidIdType("moduleimpl_id must be an integer !")
|
||||
modimpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
||||
M = modimpl.to_dict()
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
|
@ -256,7 +260,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||
H.append(scu.icontag("lock32_img", title="verrouillé"))
|
||||
H.append("""</td><td class="fichetitre2">""")
|
||||
if modimpl.module.is_apc():
|
||||
H.append(_ue_coefs_html(modimpl.module.ue_coefs_descr()))
|
||||
H.append(_ue_coefs_html(modimpl.module.ue_coefs_list()))
|
||||
else:
|
||||
H.append(f"Coef. dans le semestre: {modimpl.module.coefficient}")
|
||||
H.append("""</td><td></td></tr>""")
|
||||
|
@ -323,9 +327,10 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||
#
|
||||
if not modimpl.check_apc_conformity():
|
||||
H.append(
|
||||
"""<ul class="tf-msg"><li class="tf-msg warning conformite">Les poids des évaluations de ce module ne sont pas encore conformes au PN.
|
||||
"""<div class="warning conformite">Les poids des évaluations de ce module ne sont
|
||||
pas encore conformes au PN.
|
||||
Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE.
|
||||
</li></ul>"""
|
||||
</div>"""
|
||||
)
|
||||
#
|
||||
if has_expression and nt.expr_diagnostics:
|
||||
|
|
|
@ -168,7 +168,7 @@ def can_change_groups(formsemestre_id):
|
|||
"Vrai si l'utilisateur peut changer les groupes dans ce semestre"
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||
if not sem["etat"]:
|
||||
return False # semestre verrouillé
|
||||
if current_user.has_permission(Permission.ScoEtudChangeGroups):
|
||||
|
|
|
@ -307,7 +307,7 @@ class PlacementRunner:
|
|||
self.evaluation_id,
|
||||
self.groups,
|
||||
getallstudents=get_all_students,
|
||||
include_dems=True,
|
||||
include_demdef=True,
|
||||
)
|
||||
listetud = [] # liste de couples (nom,prenom)
|
||||
for etudid, etat in etudid_etats:
|
||||
|
|
|
@ -544,7 +544,7 @@ def check_paiement_etuds(etuds):
|
|||
etud["paiementinscription_str"] = "(pb cnx Apogée)"
|
||||
|
||||
|
||||
def get_maquette_apogee(etape="", annee_scolaire=""):
|
||||
def get_maquette_apogee(etape="", annee_scolaire="") -> str:
|
||||
"""Maquette CSV Apogee pour une étape et une annee scolaire"""
|
||||
maquette_url = get_maquette_url()
|
||||
if not maquette_url:
|
||||
|
|
|
@ -279,6 +279,7 @@ class BasePreferences(object):
|
|||
{
|
||||
"initvalue": "Dept",
|
||||
"title": "Nom abrégé du département",
|
||||
"explanation": "acronyme: par exemple R&T, ORTF, HAL",
|
||||
"size": 12,
|
||||
"category": "general",
|
||||
"only_global": True,
|
||||
|
@ -289,7 +290,7 @@ class BasePreferences(object):
|
|||
{
|
||||
"initvalue": "nom du département",
|
||||
"title": "Nom complet du département",
|
||||
"explanation": "inutilisé par défaut",
|
||||
"explanation": "apparaît sur la page d'accueil",
|
||||
"size": 40,
|
||||
"category": "general",
|
||||
"only_global": True,
|
||||
|
@ -761,7 +762,7 @@ class BasePreferences(object):
|
|||
{
|
||||
"initvalue": "Helvetica",
|
||||
"title": "Police de caractère principale",
|
||||
"explanation": "pour les pdf",
|
||||
"explanation": "pour les pdf (Helvetica est recommandée)",
|
||||
"size": 25,
|
||||
"category": "pdf",
|
||||
},
|
||||
|
@ -2149,7 +2150,7 @@ class SemPreferences(object):
|
|||
) # a bug !
|
||||
sem = sco_formsemestre.get_formsemestre(self.formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Préférences du semestre", sem),
|
||||
html_sco_header.html_sem_header("Préférences du semestre"),
|
||||
"""
|
||||
<p class="help">Les paramètres définis ici ne s'appliqueront qu'à ce semestre.</p>
|
||||
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
|
||||
|
|
|
@ -541,7 +541,6 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Décisions du jury pour le semestre",
|
||||
sem,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
),
|
||||
|
@ -627,7 +626,6 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Edition du PV de jury %s" % etuddescr,
|
||||
sem=sem,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
init_qtip=True,
|
||||
|
@ -804,7 +802,6 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Édition des lettres individuelles",
|
||||
sem=sem,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
init_qtip=True,
|
||||
|
|
|
@ -37,7 +37,8 @@ from flask import make_response
|
|||
|
||||
from app import log
|
||||
from app.but import bulletin_but
|
||||
from app.comp.res_classic import ResultatsSemestreClassic
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models.etudiants import Identite
|
||||
|
||||
|
@ -303,14 +304,16 @@ def make_formsemestre_recapcomplet(
|
|||
sem = sco_formsemestre.do_formsemestre_list(
|
||||
args={"formsemestre_id": formsemestre_id}
|
||||
)[0]
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
# XXX EXPERIMENTAL
|
||||
# nt = ResultatsSemestreClassic(formsemestre)
|
||||
parcours = formsemestre.formation.get_parcours()
|
||||
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # sco91
|
||||
# sco92 :
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
|
||||
#
|
||||
if formsemestre.formation.is_apc():
|
||||
nt.apc_recompute_moyennes()
|
||||
# if formsemestre.formation.is_apc():
|
||||
# nt.apc_recompute_moyennes()
|
||||
#
|
||||
partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups(
|
||||
formsemestre_id
|
||||
|
@ -379,8 +382,6 @@ def make_formsemestre_recapcomplet(
|
|||
h += ["code_nip", "etudid"]
|
||||
F.append(h)
|
||||
|
||||
ue_index = [] # indices des moy UE dans l (pour appliquer style css)
|
||||
|
||||
def fmtnum(val): # conversion en nombre pour cellules excel
|
||||
if keep_numeric:
|
||||
try:
|
||||
|
@ -431,9 +432,14 @@ def make_formsemestre_recapcomplet(
|
|||
else:
|
||||
l = [rank, nt.get_nom_short(etudid)] # rang, nom,
|
||||
|
||||
e["admission"] = {}
|
||||
if not hidebac:
|
||||
bac = sco_bac.Baccalaureat(e["bac"], e["specialite"])
|
||||
l.append(bac.abbrev())
|
||||
e["admission"] = nt.etuds_dict[etudid].admission.first()
|
||||
if e["admission"]:
|
||||
bac = nt.etuds_dict[etudid].admission[0].get_bac()
|
||||
l.append(bac.abbrev())
|
||||
else:
|
||||
l.append("")
|
||||
|
||||
if format[:3] == "xls" or format == "csv": # tous les groupes
|
||||
for partition in partitions:
|
||||
|
@ -458,6 +464,12 @@ def make_formsemestre_recapcomplet(
|
|||
for partition in partitions:
|
||||
l.append(rang_gr[partition["partition_id"]])
|
||||
|
||||
# Nombre d'UE au dessus de 10
|
||||
# t[i] est une chaine :-)
|
||||
# nb_ue_ok = sum(
|
||||
# [t[i] > 10 for i, ue in enumerate(ues, start=1) if ue["type"] != UE_SPORT]
|
||||
# )
|
||||
ue_index = [] # indices des moy UE dans l (pour appliquer style css)
|
||||
for i, ue in enumerate(ues, start=1):
|
||||
if ue["type"] != UE_SPORT:
|
||||
l.append(
|
||||
|
@ -486,7 +498,7 @@ def make_formsemestre_recapcomplet(
|
|||
j += 1
|
||||
if not hidebac:
|
||||
for k in admission_extra_cols:
|
||||
l.append(e[k])
|
||||
l.append(getattr(e["admission"], k, ""))
|
||||
l.append(
|
||||
nt.identdict[etudid]["code_nip"] or ""
|
||||
) # avant-derniere colonne = code_nip
|
||||
|
@ -536,7 +548,7 @@ def make_formsemestre_recapcomplet(
|
|||
# n'affiche pas la moyenne d'UE dans ce cas
|
||||
if not hidemodules:
|
||||
l.append("")
|
||||
ue_index.append(len(l) - 1)
|
||||
# ue_index.append(len(l) - 1)
|
||||
if not hidemodules and not ue["is_external"]:
|
||||
for modimpl in modimpls:
|
||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
||||
|
@ -654,7 +666,9 @@ def make_formsemestre_recapcomplet(
|
|||
if disable_etudlink:
|
||||
etudlink = "%(name)s"
|
||||
else:
|
||||
etudlink = '<a href="formsemestre_bulletinetud?formsemestre_id=%(formsemestre_id)s&etudid=%(etudid)s&version=selectedevals" id="%(etudid)s" class="etudinfo">%(name)s</a>'
|
||||
etudlink = """<a
|
||||
href="formsemestre_bulletinetud?formsemestre_id=%(formsemestre_id)s&etudid=%(etudid)s&version=selectedevals"
|
||||
id="%(etudid)s" class="etudinfo">%(name)s</a>"""
|
||||
ir = 0
|
||||
nblines = len(F) - 1
|
||||
for l in F[1:]:
|
||||
|
@ -701,9 +715,7 @@ def make_formsemestre_recapcomplet(
|
|||
idx_col_moy = idx_col_gr + 1
|
||||
cssclass = "recap_col_moy"
|
||||
try:
|
||||
if float(nsn[idx_col_moy]) < (
|
||||
nt.parcours.BARRE_MOY - scu.NOTES_TOLERANCE
|
||||
):
|
||||
if float(nsn[idx_col_moy]) < (parcours.BARRE_MOY - scu.NOTES_TOLERANCE):
|
||||
cssclass = "recap_col_moy_inf"
|
||||
except:
|
||||
pass
|
||||
|
@ -718,11 +730,11 @@ def make_formsemestre_recapcomplet(
|
|||
|
||||
if (ir < (nblines - 4)) or (ir == nblines - 3):
|
||||
try:
|
||||
if float(nsn[i]) < nt.parcours.get_barre_ue(
|
||||
if float(nsn[i]) < parcours.get_barre_ue(
|
||||
ue["type"]
|
||||
): # NOTES_BARRE_UE
|
||||
cssclass = "recap_col_ue_inf"
|
||||
elif float(nsn[i]) >= nt.parcours.NOTES_BARRE_VALID_UE:
|
||||
elif float(nsn[i]) >= parcours.NOTES_BARRE_VALID_UE:
|
||||
cssclass = "recap_col_ue_val"
|
||||
except:
|
||||
pass
|
||||
|
@ -732,7 +744,7 @@ def make_formsemestre_recapcomplet(
|
|||
ir == nblines - 3
|
||||
): # si moyenne generale module < barre ue, surligne:
|
||||
try:
|
||||
if float(nsn[i]) < nt.parcours.get_barre_ue(ue["type"]):
|
||||
if float(nsn[i]) < parcours.get_barre_ue(ue["type"]):
|
||||
cssclass = "recap_col_moy_inf"
|
||||
except:
|
||||
pass
|
||||
|
@ -790,6 +802,11 @@ def make_formsemestre_recapcomplet(
|
|||
for cod in cods:
|
||||
H.append("<tr><td>%s</td><td>%d</td></tr>" % (cod, codes_nb[cod]))
|
||||
H.append("</table>")
|
||||
# Avertissements
|
||||
if formsemestre.formation.is_apc():
|
||||
H.append(
|
||||
"""<p class="help">Pour les formations par compétences (comme le BUT), la moyenne générale est purement indicative et ne devrait pas être communiquée aux étudiants.</p>"""
|
||||
)
|
||||
return "\n".join(H), "", "html"
|
||||
elif format == "csv":
|
||||
CSV = scu.CSV_LINESEP.join(
|
||||
|
|
|
@ -49,16 +49,12 @@ from app.scodoc import sco_etud
|
|||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
from app.scodoc import sco_parcours_dut
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
import sco_version
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app import log
|
||||
from app.scodoc.sco_codes_parcours import code_semestre_validant
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_pdf import SU
|
||||
|
||||
MAX_ETUD_IN_DESCR = 20
|
||||
|
||||
|
@ -121,9 +117,9 @@ def _categories_and_results(etuds, category, result):
|
|||
categories[etud[category]] = True
|
||||
results[etud[result]] = True
|
||||
categories = list(categories.keys())
|
||||
categories.sort()
|
||||
categories.sort(key=scu.heterogeneous_sorting_key)
|
||||
results = list(results.keys())
|
||||
results.sort()
|
||||
results.sort(key=scu.heterogeneous_sorting_key)
|
||||
return categories, results
|
||||
|
||||
|
||||
|
@ -166,7 +162,7 @@ def _results_by_category(
|
|||
l["sumpercent"] = "%2.1f%%" % ((100.0 * l["sum"]) / tot)
|
||||
#
|
||||
codes = list(results.keys())
|
||||
codes.sort()
|
||||
codes.sort(key=scu.heterogeneous_sorting_key)
|
||||
|
||||
bottom_titles = []
|
||||
if C: # ligne du bas avec totaux:
|
||||
|
@ -314,7 +310,7 @@ def formsemestre_report_counts(
|
|||
"type_admission",
|
||||
"boursier_prec",
|
||||
]
|
||||
keys.sort()
|
||||
keys.sort(key=scu.heterogeneous_sorting_key)
|
||||
F = [
|
||||
"""<form name="f" method="get" action="%s"><p>
|
||||
Colonnes: <select name="result" onchange="document.f.submit()">"""
|
||||
|
@ -497,7 +493,7 @@ def table_suivi_cohorte(
|
|||
P.append(p)
|
||||
|
||||
# 4-- regroupe par indice de semestre S_i
|
||||
indices_sems = list(set([s["semestre_id"] for s in sems]))
|
||||
indices_sems = list({s["semestre_id"] for s in sems})
|
||||
indices_sems.sort()
|
||||
for p in P:
|
||||
p.nb_etuds = 0 # nombre total d'etudiants dans la periode
|
||||
|
@ -788,9 +784,9 @@ def _gen_form_selectetuds(
|
|||
):
|
||||
"""HTML form pour choix criteres selection etudiants"""
|
||||
bacs = list(bacs)
|
||||
bacs.sort()
|
||||
bacs.sort(key=scu.heterogeneous_sorting_key)
|
||||
bacspecialites = list(bacspecialites)
|
||||
bacspecialites.sort()
|
||||
bacspecialites.sort(key=scu.heterogeneous_sorting_key)
|
||||
# on peut avoir un mix de chaines vides et d'entiers:
|
||||
annee_bacs = [int(x) if x else 0 for x in annee_bacs]
|
||||
annee_bacs.sort()
|
||||
|
|
|
@ -308,13 +308,13 @@ def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
|
|||
# XXX imaginer un redirect + msg erreur
|
||||
raise AccessDenied("Modification des notes impossible pour %s" % current_user)
|
||||
#
|
||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
etudid_etats = sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation_id, getallstudents=True, include_dems=False
|
||||
evaluation_id, getallstudents=True, include_demdef=False
|
||||
)
|
||||
notes = []
|
||||
for etudid, _ in etudid_etats: # pour tous les inscrits
|
||||
if etudid not in NotesDB: # pas de note
|
||||
if etudid not in notes_db: # pas de note
|
||||
notes.append((etudid, value))
|
||||
# Check value
|
||||
L, invalids, _, _, _ = _check_notes(notes, E, M["module"])
|
||||
|
@ -393,18 +393,18 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
|||
):
|
||||
# On a le droit de modifier toutes les notes
|
||||
# recupere les etuds ayant une note
|
||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
elif sco_permissions_check.can_edit_notes(
|
||||
current_user, E["moduleimpl_id"], allow_ens=True
|
||||
):
|
||||
# Enseignant associé au module: ne peut supprimer que les notes qu'il a saisi
|
||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
|
||||
evaluation_id, by_uid=current_user.id
|
||||
)
|
||||
else:
|
||||
raise AccessDenied("Modification des notes impossible pour %s" % current_user)
|
||||
|
||||
notes = [(etudid, scu.NOTES_SUPPRESS) for etudid in NotesDB.keys()]
|
||||
notes = [(etudid, scu.NOTES_SUPPRESS) for etudid in notes_db.keys()]
|
||||
|
||||
if not dialog_confirmed:
|
||||
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||
|
@ -482,7 +482,7 @@ def notes_add(
|
|||
inscrits = {
|
||||
x[0]
|
||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation_id, getallstudents=True, include_dems=True
|
||||
evaluation_id, getallstudents=True, include_demdef=True
|
||||
)
|
||||
}
|
||||
for (etudid, value) in notes:
|
||||
|
@ -493,7 +493,7 @@ def notes_add(
|
|||
"etudiant %s: valeur de note invalide (%s)" % (etudid, value)
|
||||
)
|
||||
# Recherche notes existantes
|
||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
# Met a jour la base
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
|
@ -507,7 +507,7 @@ def notes_add(
|
|||
try:
|
||||
for (etudid, value) in notes:
|
||||
changed = False
|
||||
if etudid not in NotesDB:
|
||||
if etudid not in notes_db:
|
||||
# nouvelle note
|
||||
if value != scu.NOTES_SUPPRESS:
|
||||
if do_it:
|
||||
|
@ -530,7 +530,7 @@ def notes_add(
|
|||
changed = True
|
||||
else:
|
||||
# il y a deja une note
|
||||
oldval = NotesDB[etudid]["value"]
|
||||
oldval = notes_db[etudid]["value"]
|
||||
if type(value) != type(oldval):
|
||||
changed = True
|
||||
elif type(value) == type(1.0) and (
|
||||
|
@ -597,7 +597,7 @@ def notes_add(
|
|||
nb_changed += 1
|
||||
if has_existing_decision(M, E, etudid):
|
||||
existing_decisions.append(etudid)
|
||||
except:
|
||||
except Exception as exc:
|
||||
log("*** exception in notes_add")
|
||||
if do_it:
|
||||
cnx.rollback() # abort
|
||||
|
@ -606,7 +606,7 @@ def notes_add(
|
|||
formsemestre_id=M["formsemestre_id"]
|
||||
) # > modif notes (exception)
|
||||
sco_cache.EvaluationCache.delete(evaluation_id)
|
||||
raise ScoGenError("Erreur enregistrement note: merci de ré-essayer")
|
||||
raise ScoGenError("Erreur enregistrement note: merci de ré-essayer") from exc
|
||||
if do_it:
|
||||
cnx.commit()
|
||||
sco_cache.invalidate_formsemestre(
|
||||
|
@ -833,7 +833,7 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
|||
etudids = [
|
||||
x[0]
|
||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation_id, groups, getallstudents=getallstudents, include_dems=True
|
||||
evaluation_id, groups, getallstudents=getallstudents, include_demdef=True
|
||||
)
|
||||
]
|
||||
|
||||
|
@ -1079,7 +1079,7 @@ def _form_saisie_notes(E, M, group_ids, destination=""):
|
|||
etudids = [
|
||||
x[0]
|
||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation_id, getallstudents=True, include_dems=True
|
||||
evaluation_id, getallstudents=True, include_demdef=True
|
||||
)
|
||||
]
|
||||
if not etudids:
|
||||
|
|
|
@ -395,6 +395,8 @@ def do_semset_add_sem(semset_id, formsemestre_id):
|
|||
"""Add a sem to a semset"""
|
||||
if not semset_id:
|
||||
raise ScoValueError("empty semset_id")
|
||||
if formsemestre_id == "":
|
||||
raise ScoValueError("pas de semestre choisi !")
|
||||
s = SemSet(semset_id=semset_id)
|
||||
# check for valid formsemestre_id
|
||||
_ = sco_formsemestre.get_formsemestre(formsemestre_id) # raise exc
|
||||
|
|
|
@ -44,6 +44,7 @@ from app.scodoc import sco_groups_view
|
|||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_trombino
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_exceptions import ScoPDFFormatError
|
||||
from app.scodoc.sco_pdf import *
|
||||
|
||||
|
||||
|
@ -268,7 +269,10 @@ def pdf_trombino_tours(
|
|||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
)
|
||||
document.build(objects)
|
||||
try:
|
||||
document.build(objects)
|
||||
except (ValueError, KeyError) as exc:
|
||||
raise ScoPDFFormatError(str(exc)) from exc
|
||||
data = report.getvalue()
|
||||
|
||||
return scu.sendPDFFile(data, filename)
|
||||
|
|
|
@ -220,7 +220,6 @@ def external_ue_create_form(formsemestre_id, etudid):
|
|||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Ajout d'une UE externe pour %(nomprenom)s" % etud,
|
||||
sem,
|
||||
javascripts=["js/sco_ue_external.js"],
|
||||
),
|
||||
"""<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE
|
||||
|
|
|
@ -93,6 +93,7 @@ MODULE_TYPE_NAMES = {
|
|||
ModuleType.MALUS: "Malus",
|
||||
ModuleType.RESSOURCE: "Ressource",
|
||||
ModuleType.SAE: "SAÉ",
|
||||
None: "Module",
|
||||
}
|
||||
|
||||
MALUS_MAX = 20.0
|
||||
|
@ -897,6 +898,11 @@ def sort_dates(L, reverse=False):
|
|||
raise
|
||||
|
||||
|
||||
def heterogeneous_sorting_key(x):
|
||||
"key to sort non homogeneous sequences"
|
||||
return (float(x), "") if isinstance(x, (bool, float, int)) else (-1e34, str(x))
|
||||
|
||||
|
||||
def query_portal(req, msg="Portail Apogee", timeout=3):
|
||||
"""Retreives external data using HTTP request
|
||||
(used to connect to Apogee portal, or ScoDoc server)
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
:host{
|
||||
font-family: Verdana;
|
||||
background: #222;
|
||||
display: block;
|
||||
padding: 12px 32px;
|
||||
color: #FFF;
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
}
|
||||
h1{
|
||||
font-weight: 100;
|
||||
}
|
||||
/**********************/
|
||||
/* Zone parcours */
|
||||
/**********************/
|
||||
.parcours{
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.parcours>div{
|
||||
background: #09c;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
transition: 0.1s;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.parcours>div:hover,
|
||||
.competence>div:hover{
|
||||
color: #ccc;
|
||||
}
|
||||
.parcours>.focus{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/**********************/
|
||||
/* Zone compétences */
|
||||
/**********************/
|
||||
.competences{
|
||||
display: grid;
|
||||
margin-top: 8px;
|
||||
row-gap: 4px;
|
||||
}
|
||||
.competences>div{
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
width: var(--competence-size);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.comp1{background:#a44}
|
||||
.comp2{background:#84a}
|
||||
.comp3{background:#a84}
|
||||
.comp4{background:#8a4}
|
||||
.comp5{background:#4a8}
|
||||
.comp6{background:#48a}
|
||||
|
||||
.competences>.focus{
|
||||
outline: 2px solid;
|
||||
}
|
||||
|
||||
/**********************/
|
||||
/* Zone AC */
|
||||
/**********************/
|
||||
h2{
|
||||
display: table;
|
||||
padding: 8px 16px;
|
||||
font-size: 20px;
|
||||
border-radius: 16px 0;
|
||||
}
|
||||
.ACs{
|
||||
padding-right: 4px;
|
||||
}
|
||||
.AC li{
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: start;
|
||||
gap: 4px;
|
||||
margin-bottom: 4px;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
.AC li>div:nth-child(1){
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.AC li>div:nth-child(2){
|
||||
padding-bottom: 2px;
|
||||
}
|
|
@ -77,6 +77,17 @@ section>div:nth-child(1){
|
|||
display: flex !important;
|
||||
}
|
||||
|
||||
.listeOff .ue::before,
|
||||
.listeOff .module::before,
|
||||
.moduleOnOff .ue::before,
|
||||
.moduleOnOff .module::before{
|
||||
transform: rotate(0);
|
||||
}
|
||||
.listeOff .moduleOnOff .ue::before,
|
||||
.listeOff .moduleOnOff .module::before{
|
||||
transform: rotate(180deg) !important;
|
||||
}
|
||||
|
||||
/***********************/
|
||||
/* Options d'affichage */
|
||||
/***********************/
|
||||
|
@ -118,11 +129,16 @@ section>div:nth-child(1){
|
|||
/************/
|
||||
/* Semestre */
|
||||
/************/
|
||||
.flex{
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
.infoSemestre{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
flex: none;
|
||||
}
|
||||
.infoSemestre>div{
|
||||
border: 1px solid var(--couleurIntense);
|
||||
|
@ -141,7 +157,12 @@ section>div:nth-child(1){
|
|||
.rang{
|
||||
text-decoration: underline var(--couleurIntense);
|
||||
}
|
||||
|
||||
.decision{
|
||||
margin: 5px 0;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
text-decoration: underline var(--couleurIntense);
|
||||
}
|
||||
.enteteSemestre{
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
|
@ -174,8 +195,21 @@ section>div:nth-child(1){
|
|||
display: flex;
|
||||
gap: 16px;
|
||||
margin: 4px 0 2px 0;
|
||||
overflow: auto;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.module::before, .ue::before {
|
||||
content:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='26px' height='26px' fill='black'><path d='M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z' /></svg>");
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
margin-left: -13px;
|
||||
transform: rotate(180deg);
|
||||
transition: 0.2s;
|
||||
}
|
||||
h3{
|
||||
display: flex;
|
||||
|
|
|
@ -239,6 +239,17 @@ div.box-chercheetud {
|
|||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* Page accueil général */
|
||||
span.dept_full_name {
|
||||
font-style: italic;
|
||||
}
|
||||
span.dept_visible {
|
||||
color: rgb(6, 158, 6);
|
||||
}
|
||||
span.dept_cache {
|
||||
color: rgb(194, 5, 5);
|
||||
}
|
||||
|
||||
div.table_etud_in_accessible_depts {
|
||||
margin-left: 3em;
|
||||
margin-bottom: 2em;
|
||||
|
@ -278,11 +289,13 @@ div.logo-insidebar {
|
|||
div.logo-logo {
|
||||
text-align: center ;
|
||||
}
|
||||
|
||||
div.logo-logo img {
|
||||
box-sizing: content-box;
|
||||
margin-top: 20px;
|
||||
width: 55px; /* 100px */
|
||||
padding-right: 50px;
|
||||
margin-top: -10px;
|
||||
width: 128px;
|
||||
padding-right: 5px;
|
||||
margin-left: -75px;
|
||||
}
|
||||
div.sidebar-bottom {
|
||||
margin-top: 10px;
|
||||
|
@ -1344,7 +1357,13 @@ div.moduleimpl_type_ressource {
|
|||
|
||||
div#modimpl_coefs {
|
||||
position: absolute;
|
||||
border: 1px solid;
|
||||
padding-top: 3px;
|
||||
padding-left: 3px;
|
||||
padding-right: 5px;
|
||||
background-color: #d3d3d378;
|
||||
}
|
||||
|
||||
.coefs_histo{
|
||||
height: 32px;
|
||||
display: flex;
|
||||
|
@ -1652,7 +1671,10 @@ li.notes_ue_list {
|
|||
margin-top: 9px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
span.ue_type_1 {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
span.ue_code {
|
||||
font-family: Courier, monospace;
|
||||
font-weight: normal;
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,108 @@
|
|||
class ref_competences extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.shadow = this.attachShadow({ mode: 'open' });
|
||||
|
||||
/* Template de base */
|
||||
this.shadow.innerHTML = `
|
||||
<div class=parcours></div>
|
||||
<div class=competences></div>
|
||||
<div class=ACs></div>
|
||||
`;
|
||||
|
||||
/* Style du module */
|
||||
const styles = document.createElement('link');
|
||||
styles.setAttribute('rel', 'stylesheet');
|
||||
if (location.href.split("/")[3] == "ScoDoc") {
|
||||
styles.setAttribute('href', '/ScoDoc/static/css/ref-competences.css');
|
||||
} else {
|
||||
styles.setAttribute('href', 'ref-competences.css');
|
||||
}
|
||||
|
||||
this.shadow.appendChild(styles);
|
||||
}
|
||||
|
||||
set setData(data) {
|
||||
this.data = data;
|
||||
this.parcours();
|
||||
}
|
||||
|
||||
parcours() {
|
||||
let parcoursDIV = this.shadow.querySelector(".parcours");
|
||||
Object.entries(this.data.parcours).forEach(([cle, parcours]) => {
|
||||
let div = document.createElement("div");
|
||||
div.innerText = parcours.libelle;
|
||||
div.addEventListener("click", (event) => { this.competences(event, cle) })
|
||||
parcoursDIV.appendChild(div);
|
||||
})
|
||||
this.initCompetences();
|
||||
}
|
||||
|
||||
initCompetences() {
|
||||
this.competencesNumber = {};
|
||||
let i = 0;
|
||||
Object.keys(this.data.competences).forEach(competence => {
|
||||
this.competencesNumber[competence] = 1 + i++ % 6;
|
||||
})
|
||||
}
|
||||
|
||||
competences(event, cle) {
|
||||
this.shadow.querySelector(".parcours>.focus")?.classList.remove("focus");
|
||||
event.currentTarget.classList.add("focus");
|
||||
let divCompetences = this.shadow.querySelector(".competences");
|
||||
|
||||
this.shadow.querySelector(".competences").innerHTML = "";
|
||||
|
||||
/* Création des compétences */
|
||||
let competencesBucket = [];
|
||||
Object.entries(this.data.parcours[cle].annees).forEach(([annee, dataAnnee]) => {
|
||||
Object.entries(dataAnnee.competences).forEach(([competence, niveauCle]) => {
|
||||
let numComp = this.competencesNumber[competence];
|
||||
let divCompetence = document.createElement("div");
|
||||
divCompetence.innerText = `${competence} ${niveauCle.niveau}`;
|
||||
divCompetence.style.gridRowStart = annee;
|
||||
divCompetence.style.gridColumnStart = competence;
|
||||
divCompetence.className = "comp" + numComp;
|
||||
divCompetence.dataset.competence = `${competence} ${niveauCle.niveau}`;
|
||||
divCompetence.addEventListener("click", (event) => { this.AC(event, competence, niveauCle.niveau, annee, numComp) })
|
||||
divCompetences.appendChild(divCompetence);
|
||||
|
||||
competencesBucket.push(competence);
|
||||
})
|
||||
})
|
||||
|
||||
/* Affectation de la taille des éléments */
|
||||
//divCompetences.style.setProperty("--competence-size", `calc(${100 / competencesBucket.length}% )`);
|
||||
let gridTemplate = "";
|
||||
Object.keys(this.data.competences).forEach(competence => {
|
||||
if (competencesBucket.indexOf(competence) == -1) {
|
||||
gridTemplate += `[${competence}] 0`;
|
||||
} else {
|
||||
gridTemplate += `[${competence}] 1fr`;
|
||||
}
|
||||
})
|
||||
this.shadow.querySelector(".competences").style.gridTemplateColumns = gridTemplate;
|
||||
|
||||
/* Réaffectation des focus */
|
||||
this.shadow.querySelectorAll(".AC").forEach(ac => {
|
||||
this.shadow.querySelector(`[data-competence="${ac.dataset.competence}"]`).classList.add("focus");
|
||||
});
|
||||
}
|
||||
|
||||
AC(event, competence, niveau, annee, numComp) {
|
||||
event.currentTarget.classList.toggle("focus");
|
||||
if (this.shadow.querySelector(`.ACs [data-competence="${competence} ${niveau}"]`)) {
|
||||
this.shadow.querySelector(`.ACs [data-competence="${competence} ${niveau}"]`).remove();
|
||||
} else {
|
||||
let output = `
|
||||
<ul class=AC data-competence="${competence} ${niveau}">
|
||||
<h2 class=comp${numComp}>${competence} ${niveau}</h2>
|
||||
`;
|
||||
Object.entries(this.data.competences[competence].niveaux["BUT" + annee].app_critiques).forEach(([num, contenu]) => {
|
||||
output += `<li><div class=comp${numComp}>${num}</div><div>${contenu.libelle}</div></li>`;
|
||||
})
|
||||
this.shadow.querySelector(".ACs").innerHTML += output + "</ul>";
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('ref-competences', ref_competences);
|
|
@ -1,42 +1,49 @@
|
|||
/* Module par Seb. L. */
|
||||
class releveBUT extends HTMLElement {
|
||||
constructor(){
|
||||
constructor() {
|
||||
super();
|
||||
this.shadow = this.attachShadow({mode: 'open'});
|
||||
this.shadow = this.attachShadow({ mode: 'open' });
|
||||
|
||||
/* Config par defaut */
|
||||
this.config = {
|
||||
showURL: true
|
||||
};
|
||||
|
||||
|
||||
/* Template du module */
|
||||
this.shadow.innerHTML = this.template();
|
||||
|
||||
|
||||
/* Style du module */
|
||||
const styles = document.createElement('link');
|
||||
styles.setAttribute('rel', 'stylesheet');
|
||||
styles.setAttribute('href', '/ScoDoc/static/css/releve-but.css');
|
||||
this.shadow.appendChild(styles);
|
||||
/* variante "ScoDoc" ou "Passerelle" (ENT) ? */
|
||||
if (location.href.split("/")[3] == "ScoDoc") { /* un peu osé... */
|
||||
styles.setAttribute('href', '/ScoDoc/static/css/releve-but.css');
|
||||
} else {
|
||||
// Passerelle
|
||||
styles.setAttribute('href', '/assets/styles/releve-but.css');
|
||||
}
|
||||
this.shadow.appendChild(styles);
|
||||
}
|
||||
listeOnOff() {
|
||||
this.parentElement.parentElement.classList.toggle("listeOff");
|
||||
this.parentElement.parentElement.querySelectorAll(".moduleOnOff").forEach(e=>{
|
||||
this.parentElement.parentElement.querySelectorAll(".moduleOnOff").forEach(e => {
|
||||
e.classList.remove("moduleOnOff")
|
||||
})
|
||||
}
|
||||
moduleOnOff(){
|
||||
moduleOnOff() {
|
||||
this.parentElement.classList.toggle("moduleOnOff");
|
||||
}
|
||||
goTo(){
|
||||
goTo() {
|
||||
let module = this.dataset.module;
|
||||
this.parentElement.parentElement.parentElement.parentElement.querySelector("#Module_" + module).scrollIntoView();
|
||||
}
|
||||
|
||||
set setConfig(config){
|
||||
set setConfig(config) {
|
||||
this.config.showURL = config.showURL ?? this.config.showURL;
|
||||
}
|
||||
|
||||
set showData(data) {
|
||||
set showData(data) {
|
||||
this.showInformations(data);
|
||||
this.showSemestre(data);
|
||||
this.showSynthese(data);
|
||||
|
@ -46,7 +53,7 @@ class releveBUT extends HTMLElement {
|
|||
|
||||
this.shadow.querySelectorAll(".CTA_Liste").forEach(e => {
|
||||
e.addEventListener("click", this.listeOnOff)
|
||||
})
|
||||
})
|
||||
this.shadow.querySelectorAll(".ue, .module").forEach(e => {
|
||||
e.addEventListener("click", this.moduleOnOff)
|
||||
})
|
||||
|
@ -57,7 +64,7 @@ class releveBUT extends HTMLElement {
|
|||
this.shadow.children[0].classList.add("ready");
|
||||
}
|
||||
|
||||
template(){
|
||||
template() {
|
||||
return `
|
||||
<div>
|
||||
<div class="wait"></div>
|
||||
|
@ -75,10 +82,15 @@ class releveBUT extends HTMLElement {
|
|||
<!--------------------------->
|
||||
<section>
|
||||
<h2>Semestre </h2>
|
||||
<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>
|
||||
<div class=infoSemestre></div>
|
||||
<div class=flex>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!--------------------------->
|
||||
|
@ -91,8 +103,7 @@ class releveBUT extends HTMLElement {
|
|||
<em>La moyenne des ressources dans une UE dépend des poids donnés aux évaluations.</em>
|
||||
</div>
|
||||
<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">
|
||||
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" />
|
||||
</svg>
|
||||
</div>
|
||||
|
@ -107,8 +118,7 @@ class releveBUT extends HTMLElement {
|
|||
<div>
|
||||
<h2>Ressources</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">
|
||||
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" />
|
||||
</svg>
|
||||
</div>
|
||||
|
@ -120,8 +130,7 @@ class releveBUT extends HTMLElement {
|
|||
<div>
|
||||
<h2>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">
|
||||
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" />
|
||||
</svg>
|
||||
</div>
|
||||
|
@ -140,8 +149,8 @@ class releveBUT extends HTMLElement {
|
|||
this.shadow.querySelector(".studentPic").src = data.etudiant.photo_url || "default_Student.svg";
|
||||
|
||||
let output = '';
|
||||
|
||||
if(this.config.showURL){
|
||||
|
||||
if (this.config.showURL) {
|
||||
output += `<a href="${data.etudiant.fiche_url}" class=info_etudiant>`;
|
||||
} else {
|
||||
output += `<div class=info_etudiant>`;
|
||||
|
@ -165,7 +174,7 @@ class releveBUT extends HTMLElement {
|
|||
</div>
|
||||
<div>${data.formation.titre}</div>
|
||||
`;
|
||||
if(this.config.showURL){
|
||||
if (this.config.showURL) {
|
||||
output += `</a>`;
|
||||
} else {
|
||||
output += `</div>`;
|
||||
|
@ -187,21 +196,21 @@ class releveBUT extends HTMLElement {
|
|||
<div>Max. promo. :</div><div>${data.semestre.notes.max}</div>
|
||||
<div>Moy. promo. :</div><div>${data.semestre.notes.moy}</div>
|
||||
<div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
|
||||
</div>
|
||||
${data.semestre.groupes.map(groupe => {
|
||||
</div>`;
|
||||
/*${data.semestre.groupes.map(groupe => {
|
||||
return `
|
||||
<div>
|
||||
<div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div>
|
||||
<div class=rang>Rang :</div><div class=rang>${groupe.rang.value} / ${groupe.rang.total}</div>
|
||||
<div>Max. groupe :</div><div>${groupe.notes.max}</div>
|
||||
<div>Moy. groupe :</div><div>${groupe.notes.min}</div>
|
||||
<div>Min. groupe :</div><div>${groupe.notes.min}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join("")
|
||||
}
|
||||
`;
|
||||
<div>
|
||||
<div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div>
|
||||
<div class=rang>Rang :</div><div class=rang>${groupe.rang.value} / ${groupe.rang.total}</div>
|
||||
<div>Max. groupe :</div><div>${groupe.notes.max}</div>
|
||||
<div>Moy. groupe :</div><div>${groupe.notes.min}</div>
|
||||
<div>Min. groupe :</div><div>${groupe.notes.min}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join("")
|
||||
}*/
|
||||
this.shadow.querySelector(".infoSemestre").innerHTML = output;
|
||||
/*this.shadow.querySelector(".decision").innerHTML = data.semestre.decision.code;*/
|
||||
}
|
||||
|
||||
/*******************************/
|
||||
|
@ -211,6 +220,7 @@ class releveBUT extends HTMLElement {
|
|||
let output = ``;
|
||||
Object.entries(data.ues).forEach(([ue, dataUE]) => {
|
||||
output += `
|
||||
|
||||
<div>
|
||||
<div class=ue>
|
||||
<h3>
|
||||
|
@ -255,7 +265,7 @@ class releveBUT extends HTMLElement {
|
|||
})
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/*******************************/
|
||||
/* Evaluations */
|
||||
/*******************************/
|
||||
|
@ -333,8 +343,8 @@ class releveBUT extends HTMLElement {
|
|||
/********************/
|
||||
/* Fonctions d'aide */
|
||||
/********************/
|
||||
URL(href, content){
|
||||
if(this.config.showURL){
|
||||
URL(href, content) {
|
||||
if (this.config.showURL) {
|
||||
return `<a href=${href}>${content}</a>`;
|
||||
} else {
|
||||
return content;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
{% block app_content %}
|
||||
<h1>Modification du compte ScoDoc <tt>{{form.user_name.data}}</tt></h1>
|
||||
<div class="help">
|
||||
<p>Identifiez-vous avez votre mot de passe actuel</p>
|
||||
<p>Identifiez-vous avec votre mot de passe actuel</p>
|
||||
</div>
|
||||
<form method=post>
|
||||
{{ form.user_name }}
|
||||
|
|
|
@ -1,17 +1,40 @@
|
|||
{# -*- mode: jinja-html -*- #}
|
||||
{% extends "sco_page.html" %}
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
<h2>Référentiel de compétences {{ref.type_titre}} {{ref.specialite_long}}</h2>
|
||||
|
||||
<div>
|
||||
|
||||
Chargé le {{ref.scodoc_date_loaded.strftime("%d/%m/%Y à %H:%M") if ref.scodoc_date_loaded else ""}} à partir du fichier <tt>{{ref.scodoc_orig_filename or "(inconnu)"}}</tt>.
|
||||
<ref-competences></ref-competences>
|
||||
|
||||
<script src="/ScoDoc/static/js/ref_competences.js"></script>
|
||||
|
||||
<div class="help">
|
||||
Référentiel chargé le {{ref.scodoc_date_loaded.strftime("%d/%m/%Y à %H:%M") if ref.scodoc_date_loaded else ""}} à partir du fichier <tt>{{ref.scodoc_orig_filename or "(inconnu)"}}</tt>.
|
||||
</div>
|
||||
|
||||
|
||||
<div class="part2">
|
||||
<a class="stdlink" href="{{url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept)}}">revenir à la liste des référentiels</a>
|
||||
<a class="stdlink"
|
||||
href="{{url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept)}}"
|
||||
>revenir à la liste des référentiels</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{super()}}
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
let data_url = "{{data_source}}";
|
||||
$.getJSON(data_url, function (data) {
|
||||
document.querySelector("ref-competences").setData = data;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
|
@ -24,17 +24,16 @@
|
|||
{{icons.arrow_none|safe}}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if editable and not ue.modules.count() %}
|
||||
|
||||
<a class="smallbutton" href="{{ url_for('notes.ue_delete',
|
||||
scodoc_dept=g.scodoc_dept, ue_id=ue.id)
|
||||
}}">{{icons.delete|safe}}</a>
|
||||
{% else %}
|
||||
{{icons.delete_disabled|safe}}
|
||||
{% endif %}
|
||||
|
||||
}}">{% if editable and not ue.modules.count() %}{{icons.delete|safe}}{% else %}{{icons.delete_disabled|safe}}{% endif %}</a>
|
||||
|
||||
<span class="ue_type_{{ue.type}}">
|
||||
<b>{{ue.acronyme}}</b> <a class="discretelink" href="{{
|
||||
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
|
||||
>{{ue.titre}}</a>
|
||||
</span>
|
||||
|
||||
{% if editable and not ue.is_locked() %}
|
||||
<a class="stdlink" href="{{ url_for('notes.ue_edit',
|
||||
|
|
|
@ -49,8 +49,12 @@
|
|||
{{ moment.lang(g.locale) }}
|
||||
<script src="/ScoDoc/static/libjs/menu.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||
<script src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
|
||||
<script src="/ScoDoc/static/js/scodoc.js"></script>
|
||||
<script src="/ScoDoc/static/DataTables/datatables.min.js"></script>
|
||||
<script>
|
||||
|
|
|
@ -8,10 +8,9 @@
|
|||
|
||||
{{ exc | safe }}
|
||||
|
||||
<p class="footer">
|
||||
<p>
|
||||
{% if g.scodoc_dept %}
|
||||
<a href="{{ exc.dest_url or url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}">retour page d'accueil
|
||||
departement {{ g.scodoc_dept }}</a>
|
||||
<a href="{{ exc.dest_url or url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}">continuer</a>
|
||||
{% else %}
|
||||
<a href="{{ exc.dest_url or url_for('scodoc.index') }}">retour page d'accueil</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -13,11 +13,25 @@
|
|||
|
||||
<ul class="main">
|
||||
{% for dept in depts %}
|
||||
{% if dept.visible or current_user.is_administrator() %}
|
||||
<li>
|
||||
<a class="stdlink {{'link_accessible' if current_user.has_permission(Permission.ScoView, dept=dept.acronym) else 'link_unauthorized'}}"
|
||||
href="{{url_for('scolar.index_html', scodoc_dept=dept.acronym)}}">Département
|
||||
{{dept.preferences.filter_by(name="DeptName").first().value}}</a>
|
||||
{{dept.preferences.filter_by(name="DeptName").first().value}}
|
||||
</a>
|
||||
<span class="dept_full_name">
|
||||
{{ dept.preferences.filter_by( name="DeptFullName" ).first().value or "" }}
|
||||
</span>
|
||||
{% if current_user.is_administrator() %}
|
||||
<span {% if dept.visible %}class="dept_visible">visible{% else %}class="dept_cache">caché aux utilisateurs{% endif %}
|
||||
</span>
|
||||
<a href="{{ url_for('scodoc.toggle_dept_vis', dept_id=dept.id) }}">
|
||||
{% if dept.visible %}cacher{% else %}rendre visible{% endif %}
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<li>
|
||||
<b>Aucun département défini !</b>
|
||||
|
@ -44,8 +58,8 @@
|
|||
</div> -->
|
||||
|
||||
<div style="margin-top: 1cm;">
|
||||
Service réservé aux personnels et enseignants, basé sur <a href="{{url_for('scodoc.about')}}">le logiciel libre
|
||||
ScoDoc.</a>
|
||||
Service <b>réservé aux personnels et enseignants</b>, basé sur
|
||||
<a href="{{scu.SCO_WEBSITE}}">le logiciel libre ScoDoc.</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -4,36 +4,36 @@
|
|||
<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 9.2a</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)
|
||||
url_for('users.user_info_page', scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
||||
}}">{{current_user.user_name}}</a>
|
||||
<br /><a id="deconnectlink" href="{{url_for(" auth.logout")}}">déconnexion</a>
|
||||
<br /><a id="deconnectlink" href="{{url_for('auth.logout')}}">déconnexion</a>
|
||||
</div>
|
||||
|
||||
{% block sidebar_dept %}
|
||||
<h2 class="insidebar">Dépt. {{ sco.prefs["DeptName"] }}</h2>
|
||||
<a href="{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}" class="sidebar">Accueil</a> <br />
|
||||
{% if sco.prefs["DeptIntranetURL"] %}
|
||||
<a href="{{ sco.prefs[" DeptIntranetURL"] }}" class="sidebar">
|
||||
<a href="{{ sco.prefs["DeptIntranetURL"] }}" class="sidebar">
|
||||
{{ sco.prefs["DeptIntranetTitle"] }}</a>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% endblock %}
|
||||
|
||||
<h2 class="insidebar">Scolarité</h2>
|
||||
<a href="{{url_for(" scolar.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br>
|
||||
<a href="{{url_for(" notes.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br>
|
||||
<a href="{{url_for(" absences.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Absences</a> <br>
|
||||
<a href="{{url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br>
|
||||
<a href="{{url_for('notes.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br>
|
||||
<a href="{{url_for('absences.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Absences</a> <br>
|
||||
|
||||
{% if current_user.has_permission(sco.Permission.ScoUsersAdmin)
|
||||
or current_user.has_permission(sco.Permission.ScoUsersView)
|
||||
%}
|
||||
<a href="{{url_for(" users.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Utilisateurs</a> <br />
|
||||
<a href="{{url_for('users.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Utilisateurs</a> <br />
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.has_permission(sco.Permission.ScoChangePreferences) %}
|
||||
<a href="{{url_for(" scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Paramétrage</a> <br>
|
||||
<a href="{{url_for('scolar.edit_preferences', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Paramétrage</a> <br>
|
||||
{% endif %}
|
||||
{# /sidebar_common #}
|
||||
|
||||
|
@ -49,7 +49,7 @@
|
|||
<div class="etud-insidebar">
|
||||
{% if sco.etud %}
|
||||
<h2 id="insidebar-etud"><a href="{{url_for(
|
||||
" scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=sco.etud.id )}}" class="sidebar">
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=sco.etud.id )}}" class="sidebar">
|
||||
<span class="fontred">{{sco.etud.nomprenom}}</span></a>
|
||||
</h2>
|
||||
<b>Absences</b>
|
||||
|
|
|
@ -940,9 +940,7 @@ def EtatAbsencesGr(
|
|||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
),
|
||||
html_title=html_sco_header.html_sem_header(
|
||||
"%s" % title, sem, with_page_header=False
|
||||
)
|
||||
html_title=html_sco_header.html_sem_header("%s" % title, with_page_header=False)
|
||||
+ "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br/>" % (debut, fin),
|
||||
base_url="%s&formsemestre_id=%s&debut=%s&fin=%s"
|
||||
% (groups_infos.base_url, formsemestre_id, debut, fin),
|
||||
|
@ -1133,8 +1131,8 @@ def AddBilletAbsenceForm(etudid):
|
|||
scu.get_request_args(),
|
||||
(
|
||||
("etudid", {"input_type": "hidden"}),
|
||||
("begin", {"input_type": "date"}),
|
||||
("end", {"input_type": "date"}),
|
||||
("begin", {"input_type": "datedmy"}),
|
||||
("end", {"input_type": "datedmy"}),
|
||||
(
|
||||
"justified",
|
||||
{"input_type": "boolcheckbox", "default": 0, "title": "Justifiée"},
|
||||
|
|
|
@ -72,12 +72,7 @@ from app import log, send_scodoc_alarm
|
|||
from app.scodoc import scolog
|
||||
from app.scodoc.scolog import logdb
|
||||
|
||||
from app.scodoc.sco_exceptions import (
|
||||
ScoValueError,
|
||||
ScoLockedFormError,
|
||||
ScoGenError,
|
||||
AccessDenied,
|
||||
)
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoInvalidIdType
|
||||
from app.scodoc import html_sco_header
|
||||
from app.pe import pe_view
|
||||
from app.scodoc import sco_abs
|
||||
|
@ -284,9 +279,12 @@ def formsemestre_bulletinetud(
|
|||
force_publishing=False,
|
||||
prefer_mail_perso=False,
|
||||
code_nip=None,
|
||||
code_ine=None,
|
||||
):
|
||||
if not formsemestre_id:
|
||||
flask.abort(404, "argument manquant: formsemestre_id")
|
||||
if not isinstance(formsemestre_id, int):
|
||||
raise ScoInvalidIdType("formsemestre_id must be an integer !")
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
if formsemestre.formation.is_apc() and format != "oldjson":
|
||||
if etudid:
|
||||
|
@ -295,6 +293,14 @@ def formsemestre_bulletinetud(
|
|||
etud = models.Identite.query.filter_by(
|
||||
code_nip=str(code_nip)
|
||||
).first_or_404()
|
||||
elif code_ine:
|
||||
etud = models.Identite.query.filter_by(
|
||||
code_ine=str(code_ine)
|
||||
).first_or_404()
|
||||
else:
|
||||
raise ScoValueError(
|
||||
"Paramètre manquant: spécifier code_nip ou etudid ou code_ine"
|
||||
)
|
||||
if format == "json":
|
||||
r = bulletin_but.BulletinBUT(formsemestre)
|
||||
return jsonify(r.bulletin_etud(etud, formsemestre))
|
||||
|
@ -312,8 +318,10 @@ def formsemestre_bulletinetud(
|
|||
sco=ScoData(),
|
||||
)
|
||||
|
||||
if not (etudid or code_nip):
|
||||
raise ScoValueError("Paramètre manquant: spécifier code_nip ou etudid")
|
||||
if not (etudid or code_nip or code_ine):
|
||||
raise ScoValueError(
|
||||
"Paramètre manquant: spécifier code_nip ou etudid ou code_ine"
|
||||
)
|
||||
if format == "oldjson":
|
||||
format = "json"
|
||||
return sco_bulletins.formsemestre_bulletinetud(
|
||||
|
@ -744,6 +752,10 @@ def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None):
|
|||
DEPRECATED: use formsemestre_list()
|
||||
"""
|
||||
current_app.logger.debug("Warning: calling deprecated XMLgetFormsemestres")
|
||||
if not formsemestre_id:
|
||||
return flask.abort(404, "argument manquant: formsemestre_id")
|
||||
if not isinstance(formsemestre_id, int):
|
||||
return flask.abort(404, "formsemestre_id must be an integer !")
|
||||
args = {}
|
||||
if etape_apo:
|
||||
args["etape_apo"] = etape_apo
|
||||
|
@ -969,7 +981,6 @@ def edit_moduleimpl_resp(moduleimpl_id):
|
|||
html_sco_header.html_sem_header(
|
||||
'Modification du responsable du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
|
||||
% (moduleimpl_id, M["module"]["titre"]),
|
||||
sem,
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
|
@ -1090,7 +1101,6 @@ def edit_moduleimpl_expr(moduleimpl_id):
|
|||
html_sco_header.html_sem_header(
|
||||
'Modification règle de calcul du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
|
||||
% (moduleimpl_id, M["module"]["titre"]),
|
||||
sem,
|
||||
),
|
||||
_EXPR_HELP
|
||||
% {
|
||||
|
@ -1197,7 +1207,6 @@ def view_module_abs(moduleimpl_id, format="html"):
|
|||
'Absences du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
|
||||
% (moduleimpl_id, M["module"]["titre"]),
|
||||
page_title="Absences du module %s" % (M["module"]["titre"]),
|
||||
sem=sem,
|
||||
)
|
||||
]
|
||||
if not T and format == "html":
|
||||
|
@ -1246,7 +1255,6 @@ def edit_ue_expr(formsemestre_id, ue_id):
|
|||
html_sco_header.html_sem_header(
|
||||
"Modification règle de calcul de l'UE %s (%s)"
|
||||
% (ue["acronyme"], ue["titre"]),
|
||||
sem,
|
||||
),
|
||||
_EXPR_HELP % {"target": "de l'UE", "objs": "modules", "ordre": ""},
|
||||
]
|
||||
|
@ -1384,7 +1392,7 @@ def formsemestre_enseignants_list(formsemestre_id, format="html"):
|
|||
html_class="table_leftalign",
|
||||
filename=scu.make_filename("Enseignants-" + sem["titreannee"]),
|
||||
html_title=html_sco_header.html_sem_header(
|
||||
"Enseignants du semestre", sem, with_page_header=False
|
||||
"Enseignants du semestre", with_page_header=False
|
||||
),
|
||||
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
|
||||
caption="Tous les enseignants (responsables ou associés aux modules de ce semestre) apparaissent. Le nombre de saisies d'absences est le nombre d'opérations d'ajout effectuées sur ce semestre, sans tenir compte des annulations ou double saisies.",
|
||||
|
@ -1897,7 +1905,7 @@ def formsemestre_bulletins_choice(
|
|||
"""Choix d'une version de bulletin"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header(title, sem),
|
||||
html_sco_header.html_sem_header(title),
|
||||
"""
|
||||
<form name="f" method="GET" action="%s">
|
||||
<input type="hidden" name="formsemestre_id" value="%s"></input>
|
||||
|
|
|
@ -49,6 +49,11 @@ def refcomp_show(refcomp_id):
|
|||
ref=ref,
|
||||
title="Référentiel de compétences",
|
||||
sco=ScoData(),
|
||||
data_source=url_for(
|
||||
"notes.refcomp",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
refcomp_id=refcomp_id,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@ -172,11 +177,13 @@ def refcomp_load(formation_id=None):
|
|||
filename = secure_filename(f.filename)
|
||||
try:
|
||||
xml_data = f.read()
|
||||
ref = orebut_import_refcomp(
|
||||
_ = orebut_import_refcomp(
|
||||
xml_data, dept_id=g.scodoc_dept_id, orig_filename=filename
|
||||
)
|
||||
except TypeError as exc:
|
||||
raise ScoFormatError("fichier XML Orébut invalide") from exc
|
||||
raise ScoFormatError(
|
||||
f"fichier XML Orébut invalide (1): {exc.args}"
|
||||
) from exc
|
||||
except ScoFormatError:
|
||||
raise
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ from wtforms.fields.simple import BooleanField, StringField, TextAreaField, Hidd
|
|||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
||||
|
||||
import app
|
||||
from app import db
|
||||
from app.forms.main import config_forms
|
||||
from app.forms.main.create_dept import CreateDeptForm
|
||||
from app.models import Departement, Identite
|
||||
|
@ -82,15 +83,14 @@ from PIL import Image as PILImage
|
|||
@bp.route("/ScoDoc/index")
|
||||
def index():
|
||||
"Page d'accueil: liste des départements"
|
||||
depts = (
|
||||
Departement.query.filter_by(visible=True).order_by(Departement.acronym).all()
|
||||
)
|
||||
depts = Departement.query.filter_by().order_by(Departement.acronym).all()
|
||||
return render_template(
|
||||
"scodoc.html",
|
||||
title=sco_version.SCONAME,
|
||||
current_app=flask.current_app,
|
||||
depts=depts,
|
||||
Permission=Permission,
|
||||
scu=scu,
|
||||
)
|
||||
|
||||
|
||||
|
@ -108,7 +108,11 @@ def create_dept():
|
|||
if request.method == "POST" and form.cancel.data: # cancel button
|
||||
return redirect(url_for("scodoc.index"))
|
||||
if form.validate_on_submit():
|
||||
departements.create_dept(form.acronym.data)
|
||||
departements.create_dept(
|
||||
form.acronym.data,
|
||||
visible=form.visible.data,
|
||||
# description=form.description.data,
|
||||
)
|
||||
flash(f"Département {form.acronym.data} créé.")
|
||||
return redirect(url_for("scodoc.index"))
|
||||
return render_template(
|
||||
|
@ -118,6 +122,17 @@ def create_dept():
|
|||
)
|
||||
|
||||
|
||||
@bp.route("/ScoDoc/toggle_dept_vis/<dept_id>", methods=["GET", "POST"])
|
||||
@admin_required
|
||||
def toggle_dept_vis(dept_id):
|
||||
"""Cache ou rend visible un dept"""
|
||||
dept = Departement.query.get_or_404(dept_id)
|
||||
dept.visible = not dept.visible
|
||||
db.session.add(dept)
|
||||
db.session.commit()
|
||||
return redirect(url_for("scodoc.index"))
|
||||
|
||||
|
||||
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
|
||||
@login_required
|
||||
def table_etud_in_accessible_depts():
|
||||
|
@ -190,6 +205,7 @@ def search_inscr_etud_by_nip(code_nip, format="json", __ac_name="", __ac_passwor
|
|||
|
||||
@bp.route("/ScoDoc/about")
|
||||
@bp.route("/ScoDoc/Scolarite/<scodoc_dept>/about")
|
||||
@login_required
|
||||
def about(scodoc_dept=None):
|
||||
"version info"
|
||||
return render_template(
|
||||
|
|
|
@ -1701,8 +1701,7 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
|
|||
cnx = ndb.GetDBConnexion()
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Etudiants du %s" % (group["group_name"] or "semestre"),
|
||||
sem,
|
||||
"Etudiants du %s" % (group["group_name"] or "semestre")
|
||||
),
|
||||
'<table class="sortable" id="listegroupe">',
|
||||
"<tr><th>Nom</th><th>Nom usuel</th><th>Prénom</th><th>Mail</th><th>NIP (ScoDoc)</th><th>Apogée</th></tr>",
|
||||
|
|
|
@ -151,8 +151,9 @@ def user_info(user_name, format="json"):
|
|||
@scodoc7func
|
||||
def create_user_form(user_name=None, edit=0, all_roles=1):
|
||||
"form. création ou edition utilisateur"
|
||||
if user_name is not None: # scodoc7func converti en int !
|
||||
user_name = str(user_name)
|
||||
auth_dept = current_user.dept
|
||||
auth_username = current_user.user_name
|
||||
from_mail = current_user.email
|
||||
initvalues = {}
|
||||
edit = int(edit)
|
||||
|
@ -204,7 +205,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||
administrable_dept_acronyms = sorted(
|
||||
set(
|
||||
[
|
||||
x.dept
|
||||
x.dept or ""
|
||||
for x in UserRole.query.filter_by(user=current_user)
|
||||
if x.role.has_permission(Permission.ScoUsersAdmin) and x.dept
|
||||
]
|
||||
|
@ -249,7 +250,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||
r.name + "_" + (dept or "") for (r, dept) in displayed_roles
|
||||
]
|
||||
displayed_roles_labels = [f"{dept}: {r.name}" for (r, dept) in displayed_roles]
|
||||
disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer
|
||||
disabled_roles = {} # pour désactiver les roles que l'on ne peut pas éditer
|
||||
for i in range(len(displayed_roles_strings)):
|
||||
if displayed_roles_strings[i] not in editable_roles_strings:
|
||||
disabled_roles[i] = True
|
||||
|
@ -375,7 +376,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||
can_choose_dept = True
|
||||
else:
|
||||
selectable_dept_acronyms = set(administrable_dept_acronyms)
|
||||
if edit: # ajoute dept actuel de l'utilisateur
|
||||
if edit and the_user.dept is not None: # ajoute dept actuel de l'utilisateur
|
||||
selectable_dept_acronyms |= {the_user.dept}
|
||||
if len(selectable_dept_acronyms) > 1:
|
||||
can_choose_dept = True
|
||||
|
@ -389,6 +390,9 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||
"explanation": """département de rattachement de l'utilisateur""",
|
||||
"labels": selectable_dept_acronyms,
|
||||
"allowed_values": selectable_dept_acronyms,
|
||||
"default": g.scodoc_dept
|
||||
if g.scodoc_dept in selectable_dept_acronyms
|
||||
else "",
|
||||
},
|
||||
)
|
||||
)
|
||||
|
@ -422,7 +426,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||
"date_expiration",
|
||||
{
|
||||
"title": "Date d'expiration", # j/m/a
|
||||
"input_type": "date",
|
||||
"input_type": "datedmy",
|
||||
"explanation": "j/m/a, laisser vide si pas de limite",
|
||||
"size": 9,
|
||||
"allow_null": True,
|
||||
|
@ -541,10 +545,10 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||
vals["active"] = vals["status"] == ""
|
||||
# Département:
|
||||
if auth_dept: # pas super-admin
|
||||
if vals["dept"] not in selectable_dept_acronyms:
|
||||
if ("dept" in vals) and (vals["dept"] not in selectable_dept_acronyms):
|
||||
del vals["dept"] # ne change pas de dept
|
||||
# traitement des roles: ne doit pas affecter les roles
|
||||
# que l'on en controle pas:
|
||||
# Traitement des roles: ne doit pas affecter les rôles
|
||||
# que l'on en contrôle pas:
|
||||
for role in orig_roles_strings: # { "Ens_RT", "Secr_CJ", ... }
|
||||
if role and not role in editable_roles_strings:
|
||||
roles.add(role)
|
||||
|
@ -573,7 +577,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||
# A: envoi de welcome + procedure de reset
|
||||
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
|
||||
# C: Aucun envoi (mot de passe saisi dans le formulaire)
|
||||
if vals["welcome:list"] == "1":
|
||||
if vals["welcome"] == "1":
|
||||
if vals["reset_password:list"] == "1":
|
||||
mode = Mode.WELCOME_AND_CHANGE_PASSWORD
|
||||
else:
|
||||
|
@ -743,6 +747,8 @@ def user_info_page(user_name=None):
|
|||
"""
|
||||
from app.scodoc.sco_permissions_check import can_handle_passwd
|
||||
|
||||
if user_name is not None: # scodoc7func converti en int !
|
||||
user_name = str(user_name)
|
||||
# peut on divulguer ces infos ?
|
||||
if not can_handle_passwd(current_user, allow_admindepts=True):
|
||||
raise AccessDenied("Vous n'avez pas la permission de voir cette page")
|
||||
|
@ -800,6 +806,8 @@ def form_change_password(user_name=None):
|
|||
"""Formulaire de changement mot de passe de l'utilisateur user_name.
|
||||
Un utilisateur peut toujours changer son propre mot de passe.
|
||||
"""
|
||||
if user_name is not None: # scodoc7func converti en int !
|
||||
user_name = str(user_name)
|
||||
if not user_name:
|
||||
user = current_user
|
||||
else:
|
||||
|
@ -848,6 +856,8 @@ def form_change_password(user_name=None):
|
|||
@scodoc7func
|
||||
def change_password(user_name, password, password2):
|
||||
"Change the password for user given by user_name"
|
||||
if user_name is not None: # scodoc7func converti en int !
|
||||
user_name = str(user_name)
|
||||
u = User.query.filter_by(user_name=user_name).first()
|
||||
# Check access permission
|
||||
if not can_handle_passwd(u):
|
||||
|
@ -907,6 +917,8 @@ def change_password(user_name, password, password2):
|
|||
@permission_required(Permission.ScoUsersAdmin)
|
||||
def toggle_active_user(user_name: str = None):
|
||||
"""Change active status of a user account"""
|
||||
if user_name is not None: # scodoc7func converti en int !
|
||||
user_name = str(user_name)
|
||||
u = User.query.filter_by(user_name=user_name).first()
|
||||
if not u:
|
||||
raise ScoValueError("invalid user_name")
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
"""Evolution ref. Orebut
|
||||
|
||||
Revision ID: 197c658cefbb
|
||||
Revises: 91be8a06d423
|
||||
Create Date: 2022-01-05 22:25:12.384647
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "197c658cefbb"
|
||||
down_revision = "91be8a06d423"
|
||||
branch_labels = None
|
||||
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"],
|
||||
unique=True,
|
||||
)
|
||||
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),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
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 ###
|
|
@ -1,283 +0,0 @@
|
|||
"""creation tables relations entreprises
|
||||
|
||||
Revision ID: f3b62d64efa3
|
||||
Revises: 91be8a06d423
|
||||
Create Date: 2021-12-24 10:36:27.150085
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "f3b62d64efa3"
|
||||
down_revision = "91be8a06d423"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"entreprise_log",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"date",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("authenticated_user", sa.Text(), nullable=True),
|
||||
sa.Column("object", sa.Integer(), nullable=True),
|
||||
sa.Column("text", sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"entreprise_etudiant",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("entreprise_id", sa.Integer(), nullable=True),
|
||||
sa.Column("etudid", sa.Integer(), nullable=True),
|
||||
sa.Column("type_offre", sa.Text(), nullable=True),
|
||||
sa.Column("date_debut", sa.Date(), nullable=True),
|
||||
sa.Column("date_fin", sa.Date(), nullable=True),
|
||||
sa.Column("formation_text", sa.Text(), nullable=True),
|
||||
sa.Column("formation_scodoc", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["entreprise_id"], ["entreprises.id"], ondelete="cascade"
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"entreprise_offre",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("entreprise_id", sa.Integer(), nullable=True),
|
||||
sa.Column(
|
||||
"date_ajout",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("intitule", sa.Text(), nullable=True),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("type_offre", sa.Text(), nullable=True),
|
||||
sa.Column("missions", sa.Text(), nullable=True),
|
||||
sa.Column("duree", sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["entreprise_id"], ["entreprises.id"], ondelete="cascade"
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"entreprise_envoi_offre",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("sender_id", sa.Integer(), nullable=True),
|
||||
sa.Column("receiver_id", sa.Integer(), nullable=True),
|
||||
sa.Column("offre_id", sa.Integer(), nullable=True),
|
||||
sa.Column(
|
||||
"date_envoi",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["offre_id"],
|
||||
["entreprise_offre.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["sender_id"],
|
||||
["user.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["receiver_id"],
|
||||
["user.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"entreprise_envoi_offre_etudiant",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("sender_id", sa.Integer(), nullable=True),
|
||||
sa.Column("receiver_id", sa.Integer(), nullable=True),
|
||||
sa.Column("offre_id", sa.Integer(), nullable=True),
|
||||
sa.Column(
|
||||
"date_envoi",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["offre_id"],
|
||||
["entreprise_offre.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["sender_id"],
|
||||
["user.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["receiver_id"],
|
||||
["identite.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
op.drop_constraint(
|
||||
"entreprise_contact_entreprise_corresp_id_fkey",
|
||||
"entreprise_contact",
|
||||
type_="foreignkey",
|
||||
)
|
||||
op.drop_table("entreprise_correspondant")
|
||||
op.add_column("entreprise_contact", sa.Column("nom", sa.Text(), nullable=True))
|
||||
op.add_column("entreprise_contact", sa.Column("prenom", sa.Text(), nullable=True))
|
||||
op.add_column(
|
||||
"entreprise_contact", sa.Column("telephone", sa.Text(), nullable=True)
|
||||
)
|
||||
op.add_column("entreprise_contact", sa.Column("mail", sa.Text(), nullable=True))
|
||||
op.add_column("entreprise_contact", sa.Column("poste", sa.Text(), nullable=True))
|
||||
op.add_column("entreprise_contact", sa.Column("service", sa.Text(), nullable=True))
|
||||
op.drop_column("entreprise_contact", "description")
|
||||
op.drop_column("entreprise_contact", "enseignant")
|
||||
op.drop_column("entreprise_contact", "date")
|
||||
op.drop_column("entreprise_contact", "type_contact")
|
||||
op.drop_column("entreprise_contact", "etudid")
|
||||
op.drop_column("entreprise_contact", "entreprise_corresp_id")
|
||||
|
||||
op.add_column("entreprises", sa.Column("siret", sa.Text(), nullable=True))
|
||||
op.drop_index("ix_entreprises_dept_id", table_name="entreprises")
|
||||
op.drop_constraint("entreprises_dept_id_fkey", "entreprises", type_="foreignkey")
|
||||
op.drop_column("entreprises", "qualite_relation")
|
||||
op.drop_column("entreprises", "note")
|
||||
op.drop_column("entreprises", "contact_origine")
|
||||
op.drop_column("entreprises", "plus10salaries")
|
||||
op.drop_column("entreprises", "privee")
|
||||
op.drop_column("entreprises", "secteur")
|
||||
op.drop_column("entreprises", "date_creation")
|
||||
op.drop_column("entreprises", "dept_id")
|
||||
op.drop_column("entreprises", "localisation")
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
"entreprises",
|
||||
sa.Column("localisation", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprises",
|
||||
sa.Column("dept_id", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprises",
|
||||
sa.Column(
|
||||
"date_creation",
|
||||
postgresql.TIMESTAMP(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
autoincrement=False,
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprises",
|
||||
sa.Column("secteur", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprises",
|
||||
sa.Column("privee", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprises",
|
||||
sa.Column("plus10salaries", sa.BOOLEAN(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprises",
|
||||
sa.Column("contact_origine", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprises", sa.Column("note", sa.TEXT(), autoincrement=False, nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"entreprises",
|
||||
sa.Column("qualite_relation", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"entreprises_dept_id_fkey", "entreprises", "departement", ["dept_id"], ["id"]
|
||||
)
|
||||
op.create_index("ix_entreprises_dept_id", "entreprises", ["dept_id"], unique=False)
|
||||
op.drop_column("entreprises", "siret")
|
||||
op.add_column(
|
||||
"entreprise_contact",
|
||||
sa.Column(
|
||||
"entreprise_corresp_id", sa.INTEGER(), autoincrement=False, nullable=True
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprise_contact",
|
||||
sa.Column("etudid", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprise_contact",
|
||||
sa.Column("type_contact", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprise_contact",
|
||||
sa.Column(
|
||||
"date",
|
||||
postgresql.TIMESTAMP(timezone=True),
|
||||
autoincrement=False,
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprise_contact",
|
||||
sa.Column("enseignant", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"entreprise_contact",
|
||||
sa.Column("description", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.create_table(
|
||||
"entreprise_correspondant",
|
||||
sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column("entreprise_id", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column("nom", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("prenom", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("civilite", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("fonction", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("phone1", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("phone2", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("mobile", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("mail1", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("mail2", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("fax", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("note", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["entreprise_id"],
|
||||
["entreprises.id"],
|
||||
name="entreprise_correspondant_entreprise_id_fkey",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name="entreprise_correspondant_pkey"),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"entreprise_contact_entreprise_corresp_id_fkey",
|
||||
"entreprise_contact",
|
||||
"entreprise_correspondant",
|
||||
["entreprise_corresp_id"],
|
||||
["id"],
|
||||
)
|
||||
op.drop_column("entreprise_contact", "service")
|
||||
op.drop_column("entreprise_contact", "poste")
|
||||
op.drop_column("entreprise_contact", "mail")
|
||||
op.drop_column("entreprise_contact", "telephone")
|
||||
op.drop_column("entreprise_contact", "prenom")
|
||||
op.drop_column("entreprise_contact", "nom")
|
||||
|
||||
op.drop_table("entreprise_envoi_offre")
|
||||
op.drop_table("entreprise_envoi_offre_etudiant")
|
||||
op.drop_table("entreprise_offre")
|
||||
op.drop_table("entreprise_etudiant")
|
||||
op.drop_table("entreprise_log")
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,165 @@
|
|||
"""tables application relations entreprises
|
||||
|
||||
Revision ID: ee3f2eab6f08
|
||||
Revises: f40fbaf5831c
|
||||
Create Date: 2022-01-24 10:44:09.706261
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ee3f2eab6f08'
|
||||
down_revision = 'f40fbaf5831c'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('are_entreprise_log',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('date', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('authenticated_user', sa.Text(), nullable=True),
|
||||
sa.Column('object', sa.Integer(), nullable=True),
|
||||
sa.Column('text', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('are_entreprises',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('siret', sa.Text(), nullable=True),
|
||||
sa.Column('nom', sa.Text(), nullable=True),
|
||||
sa.Column('adresse', sa.Text(), nullable=True),
|
||||
sa.Column('codepostal', sa.Text(), nullable=True),
|
||||
sa.Column('ville', sa.Text(), nullable=True),
|
||||
sa.Column('pays', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('are_entreprise_contact',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('entreprise_id', sa.Integer(), nullable=True),
|
||||
sa.Column('nom', sa.Text(), nullable=True),
|
||||
sa.Column('prenom', sa.Text(), nullable=True),
|
||||
sa.Column('telephone', sa.Text(), nullable=True),
|
||||
sa.Column('mail', sa.Text(), nullable=True),
|
||||
sa.Column('poste', sa.Text(), nullable=True),
|
||||
sa.Column('service', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['entreprise_id'], ['are_entreprises.id'], ondelete='cascade'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('are_entreprise_etudiant',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('entreprise_id', sa.Integer(), nullable=True),
|
||||
sa.Column('etudid', sa.Integer(), nullable=True),
|
||||
sa.Column('type_offre', sa.Text(), nullable=True),
|
||||
sa.Column('date_debut', sa.Date(), nullable=True),
|
||||
sa.Column('date_fin', sa.Date(), nullable=True),
|
||||
sa.Column('formation_text', sa.Text(), nullable=True),
|
||||
sa.Column('formation_scodoc', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['entreprise_id'], ['are_entreprises.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('are_entreprise_offre',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('entreprise_id', sa.Integer(), nullable=True),
|
||||
sa.Column('date_ajout', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('intitule', sa.Text(), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('type_offre', sa.Text(), nullable=True),
|
||||
sa.Column('missions', sa.Text(), nullable=True),
|
||||
sa.Column('duree', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['entreprise_id'], ['are_entreprises.id'], ondelete='cascade'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('are_entreprise_envoi_offre',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('sender_id', sa.Integer(), nullable=True),
|
||||
sa.Column('receiver_id', sa.Integer(), nullable=True),
|
||||
sa.Column('offre_id', sa.Integer(), nullable=True),
|
||||
sa.Column('date_envoi', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['offre_id'], ['are_entreprise_offre.id'], ),
|
||||
sa.ForeignKeyConstraint(['receiver_id'], ['user.id'], ),
|
||||
sa.ForeignKeyConstraint(['sender_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('are_entreprise_envoi_offre_etudiant',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('sender_id', sa.Integer(), nullable=True),
|
||||
sa.Column('receiver_id', sa.Integer(), nullable=True),
|
||||
sa.Column('offre_id', sa.Integer(), nullable=True),
|
||||
sa.Column('date_envoi', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['offre_id'], ['are_entreprise_offre.id'], ),
|
||||
sa.ForeignKeyConstraint(['receiver_id'], ['identite.id'], ),
|
||||
sa.ForeignKeyConstraint(['sender_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.drop_table('entreprise_contact')
|
||||
op.drop_table('entreprise_correspondant')
|
||||
op.drop_index('ix_entreprises_dept_id', table_name='entreprises')
|
||||
op.drop_table('entreprises')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('entreprises',
|
||||
sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('entreprises_id_seq'::regclass)"), autoincrement=True, nullable=False),
|
||||
sa.Column('nom', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('adresse', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('ville', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('codepostal', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('pays', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('localisation', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('dept_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column('date_creation', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
|
||||
sa.Column('secteur', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('privee', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('plus10salaries', sa.BOOLEAN(), autoincrement=False, nullable=True),
|
||||
sa.Column('contact_origine', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('note', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('qualite_relation', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.ForeignKeyConstraint(['dept_id'], ['departement.id'], name='entreprises_dept_id_fkey'),
|
||||
sa.PrimaryKeyConstraint('id', name='entreprises_pkey'),
|
||||
postgresql_ignore_search_path=False
|
||||
)
|
||||
op.create_index('ix_entreprises_dept_id', 'entreprises', ['dept_id'], unique=False)
|
||||
op.create_table('entreprise_correspondant',
|
||||
sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('entreprise_correspondant_id_seq'::regclass)"), autoincrement=True, nullable=False),
|
||||
sa.Column('entreprise_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column('nom', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('prenom', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('civilite', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('fonction', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('phone1', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('phone2', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('mobile', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('mail1', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('mail2', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('fax', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('note', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.ForeignKeyConstraint(['entreprise_id'], ['entreprises.id'], name='entreprise_correspondant_entreprise_id_fkey'),
|
||||
sa.PrimaryKeyConstraint('id', name='entreprise_correspondant_pkey'),
|
||||
postgresql_ignore_search_path=False
|
||||
)
|
||||
op.create_table('entreprise_contact',
|
||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column('entreprise_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column('entreprise_corresp_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column('etudid', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column('type_contact', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('date', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
|
||||
sa.Column('enseignant', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column('description', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.ForeignKeyConstraint(['entreprise_corresp_id'], ['entreprise_correspondant.id'], name='entreprise_contact_entreprise_corresp_id_fkey'),
|
||||
sa.ForeignKeyConstraint(['entreprise_id'], ['entreprises.id'], name='entreprise_contact_entreprise_id_fkey'),
|
||||
sa.PrimaryKeyConstraint('id', name='entreprise_contact_pkey')
|
||||
)
|
||||
op.drop_table('are_entreprise_envoi_offre_etudiant')
|
||||
op.drop_table('are_entreprise_envoi_offre')
|
||||
op.drop_table('are_entreprise_offre')
|
||||
op.drop_table('are_entreprise_etudiant')
|
||||
op.drop_table('are_entreprise_contact')
|
||||
op.drop_table('are_entreprises')
|
||||
op.drop_table('are_entreprise_log')
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,34 @@
|
|||
"""index ine et nip
|
||||
|
||||
Revision ID: f40fbaf5831c
|
||||
Revises: 91be8a06d423
|
||||
Create Date: 2022-01-10 15:13:06.867903
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "f40fbaf5831c"
|
||||
down_revision = "197c658cefbb"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_index(
|
||||
op.f("ix_identite_code_ine"), "identite", ["code_ine"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_identite_code_nip"), "identite", ["code_nip"], unique=False
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f("ix_identite_code_nip"), table_name="identite")
|
||||
op.drop_index(op.f("ix_identite_code_ine"), table_name="identite")
|
||||
# ### end Alembic commands ###
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue