forked from ScoDoc/ScoDoc
Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
9511e3c2c3 | |||
|
1cfcb831ca | ||
|
738f795f21 | ||
|
48bca602cc | ||
|
a7374d7428 | ||
60f9c0ff90 | |||
f5220025af | |||
1aa9003ea2 | |||
c07a8a5e64 | |||
2124566487 | |||
d1f90a29e9 | |||
8d3deed6ac | |||
8dd39c46e7 | |||
4a86fa9c57 | |||
4db455d1ed | |||
1439c0ee75 | |||
2bc70503ec | |||
0bb6fba46a | |||
74e37f31b3 | |||
f9da8c1d8d | |||
331d42f5ee | |||
f98a10ea25 | |||
dbf765328a | |||
4de74b160e | |||
7648f98848 | |||
b45d7cd4f5 | |||
7292c76cc2 | |||
c8d77023a8 | |||
37539f4bac | |||
91ec694e6d | |||
4e9ac3d3e2 | |||
0963462e31 |
26
.pylintrc
26
.pylintrc
@ -1,10 +1,24 @@
|
|||||||
|
|
||||||
[MASTER]
|
[MASTER]
|
||||||
load-plugins=pylint_flask_sqlalchemy,pylint_flask
|
|
||||||
|
|
||||||
[MESSAGES CONTROL]
|
# List of plugins (as comma separated values of python module names) to load,
|
||||||
# pylint and black disagree...
|
# usually to register additional checkers.
|
||||||
disable=bad-continuation
|
load-plugins=pylint_flask
|
||||||
|
|
||||||
[TYPECHECK]
|
[TYPECHECK]
|
||||||
ignored-classes=Permission,SQLObject,Registrant,scoped_session
|
# List of class names for which member attributes should not be checked (useful
|
||||||
|
# for classes with dynamically set attributes). This supports the use of
|
||||||
|
# qualified names.
|
||||||
|
ignored-classes=Permission,
|
||||||
|
SQLObject,
|
||||||
|
Registrant,
|
||||||
|
scoped_session,
|
||||||
|
func
|
||||||
|
|
||||||
|
# List of module names for which member attributes should not be checked
|
||||||
|
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||||
|
# and thus existing member attributes cannot be deduced by static analysis). It
|
||||||
|
# supports qualified module names, as well as Unix pattern matching.
|
||||||
|
ignored-modules=entreprises
|
||||||
|
|
||||||
|
good-names=d,df,e,f,i,j,k,n,nt,t,u,ue,v,x,y,z,H,F
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from flask import Blueprint
|
|||||||
from flask import request, g
|
from flask import request, g
|
||||||
from app import db
|
from app import db
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import ScoException
|
from app.scodoc.sco_exceptions import AccessDenied, ScoException
|
||||||
|
|
||||||
api_bp = Blueprint("api", __name__)
|
api_bp = Blueprint("api", __name__)
|
||||||
api_web_bp = Blueprint("apiweb", __name__)
|
api_web_bp = Blueprint("apiweb", __name__)
|
||||||
@ -15,12 +15,24 @@ API_CLIENT_ERROR = 400 # erreur dans les paramètres fournis par le client
|
|||||||
|
|
||||||
|
|
||||||
@api_bp.errorhandler(ScoException)
|
@api_bp.errorhandler(ScoException)
|
||||||
|
@api_web_bp.errorhandler(ScoException)
|
||||||
@api_bp.errorhandler(404)
|
@api_bp.errorhandler(404)
|
||||||
def api_error_handler(e):
|
def api_error_handler(e):
|
||||||
"erreurs API => json"
|
"erreurs API => json"
|
||||||
return scu.json_error(404, message=str(e))
|
return scu.json_error(404, message=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@api_bp.errorhandler(AccessDenied)
|
||||||
|
@api_web_bp.errorhandler(AccessDenied)
|
||||||
|
def permission_denied_error_handler(exc):
|
||||||
|
"""
|
||||||
|
Renvoie message d'erreur pour l'erreur 403
|
||||||
|
"""
|
||||||
|
return scu.json_error(
|
||||||
|
403, f"operation non autorisee ({exc.args[0] if exc.args else ''})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def requested_format(default_format="json", allowed_formats=None):
|
def requested_format(default_format="json", allowed_formats=None):
|
||||||
"""Extract required format from query string.
|
"""Extract required format from query string.
|
||||||
* default value is json. A list of allowed formats may be provided
|
* default value is json. A list of allowed formats may be provided
|
||||||
@ -54,7 +66,6 @@ def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model
|
|||||||
|
|
||||||
from app.api import tokens
|
from app.api import tokens
|
||||||
from app.api import (
|
from app.api import (
|
||||||
absences,
|
|
||||||
assiduites,
|
assiduites,
|
||||||
billets_absences,
|
billets_absences,
|
||||||
departements,
|
departements,
|
||||||
@ -65,6 +76,7 @@ from app.api import (
|
|||||||
jury,
|
jury,
|
||||||
justificatifs,
|
justificatifs,
|
||||||
logos,
|
logos,
|
||||||
|
moduleimpl,
|
||||||
partitions,
|
partitions,
|
||||||
semset,
|
semset,
|
||||||
users,
|
users,
|
||||||
|
@ -1,263 +0,0 @@
|
|||||||
##############################################################################
|
|
||||||
# ScoDoc
|
|
||||||
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
|
||||||
# See LICENSE
|
|
||||||
##############################################################################
|
|
||||||
"""ScoDoc 9 API : Absences
|
|
||||||
"""
|
|
||||||
|
|
||||||
from flask_json import as_json
|
|
||||||
|
|
||||||
from app import db
|
|
||||||
from app.api import api_bp as bp, API_CLIENT_ERROR
|
|
||||||
from app.scodoc.sco_utils import json_error
|
|
||||||
from app.decorators import scodoc, permission_required
|
|
||||||
from app.models import Identite
|
|
||||||
|
|
||||||
from app.scodoc import notesdb as ndb
|
|
||||||
from app.scodoc import sco_abs
|
|
||||||
|
|
||||||
from app.scodoc.sco_groups import get_group_members
|
|
||||||
from app.scodoc.sco_permissions import Permission
|
|
||||||
|
|
||||||
|
|
||||||
# TODO XXX revoir routes web API et calcul des droits
|
|
||||||
@bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
|
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
@as_json
|
|
||||||
def absences(etudid: int = None):
|
|
||||||
"""
|
|
||||||
Liste des absences de cet étudiant
|
|
||||||
|
|
||||||
Exemple de résultat:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"jour": "2022-04-15",
|
|
||||||
"matin": true,
|
|
||||||
"estabs": true,
|
|
||||||
"estjust": true,
|
|
||||||
"description": "",
|
|
||||||
"begin": "2022-04-15 08:00:00",
|
|
||||||
"end": "2022-04-15 11:59:59"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jour": "2022-04-15",
|
|
||||||
"matin": false,
|
|
||||||
"estabs": true,
|
|
||||||
"estjust": false,
|
|
||||||
"description": "",
|
|
||||||
"begin": "2022-04-15 12:00:00",
|
|
||||||
"end": "2022-04-15 17:59:59"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
etud = db.session.get(Identite, etudid)
|
|
||||||
if etud is None:
|
|
||||||
return json_error(404, message="etudiant inexistant")
|
|
||||||
# Absences de l'étudiant
|
|
||||||
ndb.open_db_connection()
|
|
||||||
abs_list = sco_abs.list_abs_date(etud.id)
|
|
||||||
for absence in abs_list:
|
|
||||||
absence["jour"] = absence["jour"].isoformat()
|
|
||||||
return abs_list
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/absences/etudid/<int:etudid>/just", methods=["GET"])
|
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
@as_json
|
|
||||||
def absences_just(etudid: int = None):
|
|
||||||
"""
|
|
||||||
Retourne la liste des absences justifiées d'un étudiant donné
|
|
||||||
|
|
||||||
etudid : l'etudid d'un étudiant
|
|
||||||
nip: le code nip d'un étudiant
|
|
||||||
ine : le code ine d'un étudiant
|
|
||||||
|
|
||||||
Exemple de résultat :
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"jour": "2022-04-15",
|
|
||||||
"matin": true,
|
|
||||||
"estabs": true,
|
|
||||||
"estjust": true,
|
|
||||||
"description": "",
|
|
||||||
"begin": "2022-04-15 08:00:00",
|
|
||||||
"end": "2022-04-15 11:59:59"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jour": "Fri, 15 Apr 2022 00:00:00 GMT",
|
|
||||||
"matin": false,
|
|
||||||
"estabs": true,
|
|
||||||
"estjust": true,
|
|
||||||
"description": "",
|
|
||||||
"begin": "2022-04-15 12:00:00",
|
|
||||||
"end": "2022-04-15 17:59:59"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
etud = db.session.get(Identite, etudid)
|
|
||||||
if etud is None:
|
|
||||||
return json_error(404, message="etudiant inexistant")
|
|
||||||
|
|
||||||
# Absences justifiées de l'étudiant
|
|
||||||
abs_just = [
|
|
||||||
absence for absence in sco_abs.list_abs_date(etud.id) if absence["estjust"]
|
|
||||||
]
|
|
||||||
for absence in abs_just:
|
|
||||||
absence["jour"] = absence["jour"].isoformat()
|
|
||||||
return abs_just
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
|
||||||
"/absences/abs_group_etat/<int:group_id>",
|
|
||||||
methods=["GET"],
|
|
||||||
)
|
|
||||||
@bp.route(
|
|
||||||
"/absences/abs_group_etat/group_id/<int:group_id>/date_debut/<string:date_debut>/date_fin/<string:date_fin>",
|
|
||||||
methods=["GET"],
|
|
||||||
)
|
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
@as_json
|
|
||||||
def abs_groupe_etat(group_id: int, date_debut=None, date_fin=None):
|
|
||||||
"""
|
|
||||||
Liste des absences d'un groupe (possibilité de choisir entre deux dates)
|
|
||||||
|
|
||||||
group_id = l'id du groupe
|
|
||||||
date_debut = None par défaut, sinon la date ISO du début de notre filtre
|
|
||||||
date_fin = None par défaut, sinon la date ISO de la fin de notre filtre
|
|
||||||
|
|
||||||
Exemple de résultat :
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"etudid": 1,
|
|
||||||
"list_abs": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"etudid": 2,
|
|
||||||
"list_abs": [
|
|
||||||
{
|
|
||||||
"jour": "Fri, 15 Apr 2022 00:00:00 GMT",
|
|
||||||
"matin": true,
|
|
||||||
"estabs": true,
|
|
||||||
"estjust": true,
|
|
||||||
"description": "",
|
|
||||||
"begin": "2022-04-15 08:00:00",
|
|
||||||
"end": "2022-04-15 11:59:59"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jour": "Fri, 15 Apr 2022 00:00:00 GMT",
|
|
||||||
"matin": false,
|
|
||||||
"estabs": true,
|
|
||||||
"estjust": false,
|
|
||||||
"description": "",
|
|
||||||
"begin": "2022-04-15 12:00:00",
|
|
||||||
"end": "2022-04-15 17:59:59"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
members = get_group_members(group_id)
|
|
||||||
|
|
||||||
data = []
|
|
||||||
# Filtre entre les deux dates renseignées
|
|
||||||
for member in members:
|
|
||||||
absence = {
|
|
||||||
"etudid": member["etudid"],
|
|
||||||
"list_abs": sco_abs.list_abs_date(member["etudid"], date_debut, date_fin),
|
|
||||||
}
|
|
||||||
data.append(absence)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
# XXX TODO EV: A REVOIR (data json dans le POST + modifier les routes)
|
|
||||||
# @bp.route(
|
|
||||||
# "/absences/etudid/<int:etudid>/list_abs/<string:list_abs>/reset_etud_abs",
|
|
||||||
# methods=["POST"],
|
|
||||||
# defaults={"just_or_not": 0},
|
|
||||||
# )
|
|
||||||
# @bp.route(
|
|
||||||
# "/absences/etudid/<int:etudid>/list_abs/<string:list_abs>/reset_etud_abs/only_not_just",
|
|
||||||
# methods=["POST"],
|
|
||||||
# defaults={"just_or_not": 1},
|
|
||||||
# )
|
|
||||||
# @bp.route(
|
|
||||||
# "/absences/etudid/<int:etudid>/list_abs/<string:list_abs>/reset_etud_abs/only_just",
|
|
||||||
# methods=["POST"],
|
|
||||||
# defaults={"just_or_not": 2},
|
|
||||||
# )
|
|
||||||
# @token_auth.login_required
|
|
||||||
# @token_permission_required(Permission.APIAbsChange)
|
|
||||||
# def reset_etud_abs(etudid: int, list_abs: str, just_or_not: int = 0):
|
|
||||||
# """
|
|
||||||
# Set la liste des absences d'un étudiant sur tout un semestre.
|
|
||||||
# (les absences existant pour cet étudiant sur cette période sont effacées)
|
|
||||||
|
|
||||||
# etudid : l'id d'un étudiant
|
|
||||||
# list_abs : json d'absences
|
|
||||||
# just_or_not : 0 (pour les absences justifiées et non justifiées),
|
|
||||||
# 1 (pour les absences justifiées),
|
|
||||||
# 2 (pour les absences non justifiées)
|
|
||||||
# """
|
|
||||||
# # Toutes les absences
|
|
||||||
# if just_or_not == 0:
|
|
||||||
# # suppression des absences et justificatif déjà existant pour éviter les doublons
|
|
||||||
# for abs in list_abs:
|
|
||||||
# # Récupération de la date au format iso
|
|
||||||
# jour = abs["jour"].isoformat()
|
|
||||||
# if abs["matin"] is True:
|
|
||||||
# annule_absence(etudid, jour, True)
|
|
||||||
# annule_justif(etudid, jour, True)
|
|
||||||
# else:
|
|
||||||
# annule_absence(etudid, jour, False)
|
|
||||||
# annule_justif(etudid, jour, False)
|
|
||||||
|
|
||||||
# # Ajout de la liste d'absences en base
|
|
||||||
# add_abslist(list_abs)
|
|
||||||
|
|
||||||
# # Uniquement les absences justifiées
|
|
||||||
# elif just_or_not == 1:
|
|
||||||
# list_abs_not_just = []
|
|
||||||
# # Trie des absences justifiées
|
|
||||||
# for abs in list_abs:
|
|
||||||
# if abs["estjust"] is False:
|
|
||||||
# list_abs_not_just.append(abs)
|
|
||||||
# # suppression des absences et justificatif déjà existant pour éviter les doublons
|
|
||||||
# for abs in list_abs:
|
|
||||||
# # Récupération de la date au format iso
|
|
||||||
# jour = abs["jour"].isoformat()
|
|
||||||
# if abs["matin"] is True:
|
|
||||||
# annule_absence(etudid, jour, True)
|
|
||||||
# annule_justif(etudid, jour, True)
|
|
||||||
# else:
|
|
||||||
# annule_absence(etudid, jour, False)
|
|
||||||
# annule_justif(etudid, jour, False)
|
|
||||||
|
|
||||||
# # Ajout de la liste d'absences en base
|
|
||||||
# add_abslist(list_abs_not_just)
|
|
||||||
|
|
||||||
# # Uniquement les absences non justifiées
|
|
||||||
# elif just_or_not == 2:
|
|
||||||
# list_abs_just = []
|
|
||||||
# # Trie des absences non justifiées
|
|
||||||
# for abs in list_abs:
|
|
||||||
# if abs["estjust"] is True:
|
|
||||||
# list_abs_just.append(abs)
|
|
||||||
# # suppression des absences et justificatif déjà existant pour éviter les doublons
|
|
||||||
# for abs in list_abs:
|
|
||||||
# # Récupération de la date au format iso
|
|
||||||
# jour = abs["jour"].isoformat()
|
|
||||||
# if abs["matin"] is True:
|
|
||||||
# annule_absence(etudid, jour, True)
|
|
||||||
# annule_justif(etudid, jour, True)
|
|
||||||
# else:
|
|
||||||
# annule_absence(etudid, jour, False)
|
|
||||||
# annule_justif(etudid, jour, False)
|
|
||||||
|
|
||||||
# # Ajout de la liste d'absences en base
|
|
||||||
# add_abslist(list_abs_just)
|
|
@ -7,17 +7,17 @@
|
|||||||
"""
|
"""
|
||||||
ScoDoc 9 API : accès aux évaluations
|
ScoDoc 9 API : accès aux évaluations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
from flask_login import login_required
|
from flask_login import current_user, login_required
|
||||||
|
|
||||||
import app
|
import app
|
||||||
|
from app import log, db
|
||||||
from app.api import api_bp as bp, api_web_bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import Evaluation, ModuleImpl, FormSemestre
|
from app.models import Evaluation, ModuleImpl, FormSemestre
|
||||||
from app.scodoc import sco_evaluation_db, sco_saisie_notes
|
from app.scodoc import sco_evaluation_db, sco_saisie_notes
|
||||||
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ import app.scodoc.sco_utils as scu
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@as_json
|
@as_json
|
||||||
def evaluation(evaluation_id: int):
|
def get_evaluation(evaluation_id: int):
|
||||||
"""Description d'une évaluation.
|
"""Description d'une évaluation.
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -47,7 +47,7 @@ def evaluation(evaluation_id: int):
|
|||||||
'UE1.3': 1.0
|
'UE1.3': 1.0
|
||||||
},
|
},
|
||||||
'publish_incomplete': False,
|
'publish_incomplete': False,
|
||||||
'visi_bulletin': True
|
'visibulletin': True
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||||
@ -181,3 +181,97 @@ def evaluation_set_notes(evaluation_id: int):
|
|||||||
return sco_saisie_notes.save_notes(
|
return sco_saisie_notes.save_notes(
|
||||||
evaluation, notes, comment=data.get("comment", "")
|
evaluation, notes, comment=data.get("comment", "")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/moduleimpl/<int:moduleimpl_id>/evaluation/create", methods=["POST"])
|
||||||
|
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>/evaluation/create", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEnsView) # permission gérée dans la fonction
|
||||||
|
@as_json
|
||||||
|
def evaluation_create(moduleimpl_id: int):
|
||||||
|
"""Création d'une évaluation.
|
||||||
|
The request content type should be "application/json",
|
||||||
|
and contains:
|
||||||
|
{
|
||||||
|
"description" : str,
|
||||||
|
"evaluation_type" : int, // {0,1,2} default 0 (normale)
|
||||||
|
"date_debut" : date_iso, // optionnel
|
||||||
|
"date_fin" : date_iso, // optionnel
|
||||||
|
"note_max" : float, // si non spécifié, 20.0
|
||||||
|
"numero" : int, // ordre de présentation, default tri sur date
|
||||||
|
"visibulletin" : boolean , //default true
|
||||||
|
"publish_incomplete" : boolean , //default false
|
||||||
|
"coefficient" : float, // si non spécifié, 1.0
|
||||||
|
"poids" : { ue_id : poids } // optionnel
|
||||||
|
}
|
||||||
|
Result: l'évaluation créée.
|
||||||
|
"""
|
||||||
|
moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
||||||
|
if not moduleimpl.can_edit_evaluation(current_user):
|
||||||
|
return scu.json_error(403, "opération non autorisée")
|
||||||
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
|
|
||||||
|
try:
|
||||||
|
evaluation = Evaluation.create(moduleimpl=moduleimpl, **data)
|
||||||
|
except ValueError:
|
||||||
|
return scu.json_error(400, "paramètre incorrect")
|
||||||
|
except ScoValueError as exc:
|
||||||
|
return scu.json_error(
|
||||||
|
400, f"paramètre de type incorrect ({exc.args[0] if exc.args else ''})"
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(evaluation)
|
||||||
|
db.session.commit()
|
||||||
|
# Les poids vers les UEs:
|
||||||
|
poids = data.get("poids")
|
||||||
|
if poids is not None:
|
||||||
|
if not isinstance(poids, dict):
|
||||||
|
log("API error: canceling evaluation creation")
|
||||||
|
db.session.delete(evaluation)
|
||||||
|
db.session.commit()
|
||||||
|
return scu.json_error(
|
||||||
|
400, "paramètre de type incorrect (poids must be a dict)"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
evaluation.set_ue_poids_dict(data["poids"])
|
||||||
|
except ScoValueError as exc:
|
||||||
|
log("API error: canceling evaluation creation")
|
||||||
|
db.session.delete(evaluation)
|
||||||
|
db.session.commit()
|
||||||
|
return scu.json_error(
|
||||||
|
400,
|
||||||
|
f"erreur enregistrement des poids ({exc.args[0] if exc.args else ''})",
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
return evaluation.to_dict_api()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/evaluation/<int:evaluation_id>/delete", methods=["POST"])
|
||||||
|
@api_web_bp.route("/evaluation/<int:evaluation_id>/delete", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEnsView) # permission gérée dans la fonction
|
||||||
|
@as_json
|
||||||
|
def evaluation_delete(evaluation_id: int):
|
||||||
|
"""Suppression d'une évaluation.
|
||||||
|
Efface aussi toutes ses notes
|
||||||
|
"""
|
||||||
|
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = (
|
||||||
|
query.join(ModuleImpl)
|
||||||
|
.join(FormSemestre)
|
||||||
|
.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
)
|
||||||
|
evaluation = query.first_or_404()
|
||||||
|
dept = evaluation.moduleimpl.formsemestre.departement
|
||||||
|
app.set_sco_dept(dept.acronym)
|
||||||
|
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
||||||
|
raise AccessDenied("evaluation_delete")
|
||||||
|
|
||||||
|
sco_saisie_notes.evaluation_suppress_alln(
|
||||||
|
evaluation_id=evaluation_id, dialog_confirmed=True
|
||||||
|
)
|
||||||
|
evaluation.delete()
|
||||||
|
return "ok"
|
||||||
|
@ -21,8 +21,6 @@ from app.models import (
|
|||||||
ApcNiveau,
|
ApcNiveau,
|
||||||
ApcParcours,
|
ApcParcours,
|
||||||
Formation,
|
Formation,
|
||||||
FormSemestre,
|
|
||||||
ModuleImpl,
|
|
||||||
UniteEns,
|
UniteEns,
|
||||||
)
|
)
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
@ -249,54 +247,6 @@ def referentiel_competences(formation_id: int):
|
|||||||
return formation.referentiel_competence.to_dict()
|
return formation.referentiel_competence.to_dict()
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/moduleimpl/<int:moduleimpl_id>")
|
|
||||||
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>")
|
|
||||||
@login_required
|
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
@as_json
|
|
||||||
def moduleimpl(moduleimpl_id: int):
|
|
||||||
"""
|
|
||||||
Retourne un moduleimpl en fonction de son id
|
|
||||||
|
|
||||||
moduleimpl_id : l'id d'un moduleimpl
|
|
||||||
|
|
||||||
Exemple de résultat :
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"formsemestre_id": 1,
|
|
||||||
"module_id": 1,
|
|
||||||
"responsable_id": 2,
|
|
||||||
"moduleimpl_id": 1,
|
|
||||||
"ens": [],
|
|
||||||
"module": {
|
|
||||||
"heures_tp": 0,
|
|
||||||
"code_apogee": "",
|
|
||||||
"titre": "Initiation aux réseaux informatiques",
|
|
||||||
"coefficient": 1,
|
|
||||||
"module_type": 2,
|
|
||||||
"id": 1,
|
|
||||||
"ects": null,
|
|
||||||
"abbrev": "Init aux réseaux informatiques",
|
|
||||||
"ue_id": 1,
|
|
||||||
"code": "R101",
|
|
||||||
"formation_id": 1,
|
|
||||||
"heures_cours": 0,
|
|
||||||
"matiere_id": 1,
|
|
||||||
"heures_td": 0,
|
|
||||||
"semestre_id": 1,
|
|
||||||
"numero": 10,
|
|
||||||
"module_id": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
query = ModuleImpl.query.filter_by(id=moduleimpl_id)
|
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
modimpl: ModuleImpl = query.first_or_404()
|
|
||||||
return modimpl.to_dict(convert_objects=True)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/set_ue_parcours/<int:ue_id>", methods=["POST"])
|
@bp.route("/set_ue_parcours/<int:ue_id>", methods=["POST"])
|
||||||
@api_web_bp.route("/set_ue_parcours/<int:ue_id>", methods=["POST"])
|
@api_web_bp.route("/set_ue_parcours/<int:ue_id>", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -212,7 +212,7 @@ def bulletins(formsemestre_id: int, version: str = "long"):
|
|||||||
@as_json
|
@as_json
|
||||||
def formsemestre_programme(formsemestre_id: int):
|
def formsemestre_programme(formsemestre_id: int):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des Ues, ressources et SAE d'un semestre
|
Retourne la liste des UEs, ressources et SAEs d'un semestre
|
||||||
|
|
||||||
formsemestre_id : l'id d'un formsemestre
|
formsemestre_id : l'id d'un formsemestre
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from flask import flash, g, request, url_for
|
from flask import g, request, url_for
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
|
||||||
|
69
app/api/moduleimpl.py
Normal file
69
app/api/moduleimpl.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
ScoDoc 9 API : accès aux moduleimpl
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import g
|
||||||
|
from flask_json import as_json
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
|
from app.api import api_bp as bp, api_web_bp
|
||||||
|
from app.decorators import scodoc, permission_required
|
||||||
|
from app.models import (
|
||||||
|
FormSemestre,
|
||||||
|
ModuleImpl,
|
||||||
|
)
|
||||||
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||||
|
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
@as_json
|
||||||
|
def moduleimpl(moduleimpl_id: int):
|
||||||
|
"""
|
||||||
|
Retourne un moduleimpl en fonction de son id
|
||||||
|
|
||||||
|
moduleimpl_id : l'id d'un moduleimpl
|
||||||
|
|
||||||
|
Exemple de résultat :
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"formsemestre_id": 1,
|
||||||
|
"module_id": 1,
|
||||||
|
"responsable_id": 2,
|
||||||
|
"moduleimpl_id": 1,
|
||||||
|
"ens": [],
|
||||||
|
"module": {
|
||||||
|
"heures_tp": 0,
|
||||||
|
"code_apogee": "",
|
||||||
|
"titre": "Initiation aux réseaux informatiques",
|
||||||
|
"coefficient": 1,
|
||||||
|
"module_type": 2,
|
||||||
|
"id": 1,
|
||||||
|
"ects": null,
|
||||||
|
"abbrev": "Init aux réseaux informatiques",
|
||||||
|
"ue_id": 1,
|
||||||
|
"code": "R101",
|
||||||
|
"formation_id": 1,
|
||||||
|
"heures_cours": 0,
|
||||||
|
"matiere_id": 1,
|
||||||
|
"heures_td": 0,
|
||||||
|
"semestre_id": 1,
|
||||||
|
"numero": 10,
|
||||||
|
"module_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
query = ModuleImpl.query.filter_by(id=moduleimpl_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
modimpl: ModuleImpl = query.first_or_404()
|
||||||
|
return modimpl.to_dict(convert_objects=True)
|
@ -220,20 +220,9 @@ def group_remove_etud(group_id: int, etudid: int):
|
|||||||
group = query.first_or_404()
|
group = query.first_or_404()
|
||||||
if not group.partition.formsemestre.etat:
|
if not group.partition.formsemestre.etat:
|
||||||
return json_error(403, "formsemestre verrouillé")
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if etud in group.etuds:
|
|
||||||
group.etuds.remove(etud)
|
group.remove_etud(etud)
|
||||||
db.session.commit()
|
|
||||||
Scolog.logdb(
|
|
||||||
method="group_remove_etud",
|
|
||||||
etudid=etud.id,
|
|
||||||
msg=f"Retrait du groupe {group.group_name} de {group.partition.partition_name}",
|
|
||||||
commit=True,
|
|
||||||
)
|
|
||||||
# Update parcours
|
|
||||||
group.partition.formsemestre.update_inscriptions_parcours_from_groups(
|
|
||||||
etudid=etudid
|
|
||||||
)
|
|
||||||
sco_cache.invalidate_formsemestre(group.partition.formsemestre_id)
|
|
||||||
return {"group_id": group_id, "etudid": etudid}
|
return {"group_id": group_id, "etudid": etudid}
|
||||||
|
|
||||||
|
|
||||||
|
@ -276,11 +276,10 @@ class BulletinBUT:
|
|||||||
"coef": fmt_note(e.coefficient)
|
"coef": fmt_note(e.coefficient)
|
||||||
if e.evaluation_type == scu.EVALUATION_NORMALE
|
if e.evaluation_type == scu.EVALUATION_NORMALE
|
||||||
else None,
|
else None,
|
||||||
"date": e.jour.isoformat() if e.jour else None,
|
"date_debut": e.date_debut.isoformat() if e.date_debut else None,
|
||||||
|
"date_fin": e.date_fin.isoformat() if e.date_fin else None,
|
||||||
"description": e.description,
|
"description": e.description,
|
||||||
"evaluation_type": e.evaluation_type,
|
"evaluation_type": e.evaluation_type,
|
||||||
"heure_debut": e.heure_debut.strftime("%H:%M") if e.heure_debut else None,
|
|
||||||
"heure_fin": e.heure_fin.strftime("%H:%M") if e.heure_debut else None,
|
|
||||||
"note": {
|
"note": {
|
||||||
"value": fmt_note(
|
"value": fmt_note(
|
||||||
eval_notes[etud.id],
|
eval_notes[etud.id],
|
||||||
@ -298,6 +297,12 @@ class BulletinBUT:
|
|||||||
)
|
)
|
||||||
if has_request_context()
|
if has_request_context()
|
||||||
else "na",
|
else "na",
|
||||||
|
# deprecated (supprimer avant #sco9.7)
|
||||||
|
"date": e.date_debut.isoformat() if e.date_debut else None,
|
||||||
|
"heure_debut": e.date_debut.time().isoformat("minutes")
|
||||||
|
if e.date_debut
|
||||||
|
else None,
|
||||||
|
"heure_fin": e.date_fin.time().isoformat("minutes") if e.date_fin else None,
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@ -202,12 +202,11 @@ def bulletin_but_xml_compat(
|
|||||||
if e.visibulletin or version == "long":
|
if e.visibulletin or version == "long":
|
||||||
x_eval = Element(
|
x_eval = Element(
|
||||||
"evaluation",
|
"evaluation",
|
||||||
jour=e.jour.isoformat() if e.jour else "",
|
date_debut=e.date_debut.isoformat()
|
||||||
heure_debut=e.heure_debut.isoformat()
|
if e.date_debut
|
||||||
if e.heure_debut
|
|
||||||
else "",
|
else "",
|
||||||
heure_fin=e.heure_fin.isoformat()
|
date_fin=e.date_fin.isoformat()
|
||||||
if e.heure_debut
|
if e.date_debut
|
||||||
else "",
|
else "",
|
||||||
coefficient=str(e.coefficient),
|
coefficient=str(e.coefficient),
|
||||||
# pas les poids en XML compat
|
# pas les poids en XML compat
|
||||||
@ -215,6 +214,12 @@ def bulletin_but_xml_compat(
|
|||||||
description=quote_xml_attr(e.description),
|
description=quote_xml_attr(e.description),
|
||||||
# notes envoyées sur 20, ceci juste pour garder trace:
|
# notes envoyées sur 20, ceci juste pour garder trace:
|
||||||
note_max_origin=str(e.note_max),
|
note_max_origin=str(e.note_max),
|
||||||
|
# --- deprecated
|
||||||
|
jour=e.date_debut.isoformat()
|
||||||
|
if e.date_debut
|
||||||
|
else "",
|
||||||
|
heure_debut=e.heure_debut(),
|
||||||
|
heure_fin=e.heure_fin(),
|
||||||
)
|
)
|
||||||
x_mod.append(x_eval)
|
x_mod.append(x_eval)
|
||||||
try:
|
try:
|
||||||
|
@ -76,6 +76,13 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
|||||||
f"""
|
f"""
|
||||||
<div class="titre_niveaux">
|
<div class="titre_niveaux">
|
||||||
<b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b>
|
<b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b>
|
||||||
|
<a style="margin-left: 32px;" class="stdlink" target="_blank" rel="noopener noreferrer"
|
||||||
|
href={
|
||||||
|
url_for("notes.validation_rcues", scodoc_dept=g.scodoc_dept,
|
||||||
|
etudid=deca.etud.id,
|
||||||
|
formsemestre_id=formsemestre_2.id if formsemestre_2 else formsemestre_1.id
|
||||||
|
)
|
||||||
|
}>visualiser son cursus</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="but_explanation">{deca.explanation}</div>
|
<div class="but_explanation">{deca.explanation}</div>
|
||||||
<div class="but_annee">
|
<div class="but_annee">
|
||||||
|
@ -250,7 +250,7 @@ class ModuleImplResults:
|
|||||||
).reshape(-1, 1)
|
).reshape(-1, 1)
|
||||||
|
|
||||||
# was _list_notes_evals_titles
|
# was _list_notes_evals_titles
|
||||||
def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list:
|
def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list[Evaluation]:
|
||||||
"Liste des évaluations complètes"
|
"Liste des évaluations complètes"
|
||||||
return [
|
return [
|
||||||
e for e in moduleimpl.evaluations if self.evaluations_completes_dict[e.id]
|
e for e in moduleimpl.evaluations if self.evaluations_completes_dict[e.id]
|
||||||
|
@ -78,7 +78,11 @@ def compute_sem_moys_apc_using_ects(
|
|||||||
else:
|
else:
|
||||||
ects = ects_df.to_numpy()
|
ects = ects_df.to_numpy()
|
||||||
# ects est maintenant un array nb_etuds x nb_ues
|
# ects est maintenant un array nb_etuds x nb_ues
|
||||||
|
|
||||||
moy_gen = (etud_moy_ue_df * ects).sum(axis=1) / ects.sum(axis=1)
|
moy_gen = (etud_moy_ue_df * ects).sum(axis=1) / ects.sum(axis=1)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
# peut arriver si aucun module... on ignore
|
||||||
|
moy_gen = pd.Series(np.NaN, index=etud_moy_ue_df.index)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
if None in ects:
|
if None in ects:
|
||||||
formation = db.session.get(Formation, formation_id)
|
formation = db.session.get(Formation, formation_id)
|
||||||
|
@ -394,7 +394,10 @@ def compute_ue_moys_classic(
|
|||||||
if sco_preferences.get_preference("use_ue_coefs", formsemestre.id):
|
if sco_preferences.get_preference("use_ue_coefs", formsemestre.id):
|
||||||
# Cas avec coefficients d'UE forcés: (on met à zéro l'UE bonus)
|
# Cas avec coefficients d'UE forcés: (on met à zéro l'UE bonus)
|
||||||
etud_coef_ue_df = pd.DataFrame(
|
etud_coef_ue_df = pd.DataFrame(
|
||||||
{ue.id: ue.coefficient if ue.type != UE_SPORT else 0.0 for ue in ues},
|
{
|
||||||
|
ue.id: (ue.coefficient or 0.0) if ue.type != UE_SPORT else 0.0
|
||||||
|
for ue in ues
|
||||||
|
},
|
||||||
index=modimpl_inscr_df.index,
|
index=modimpl_inscr_df.index,
|
||||||
columns=[ue.id for ue in ues],
|
columns=[ue.id for ue in ues],
|
||||||
)
|
)
|
||||||
|
@ -53,8 +53,8 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
self.store()
|
self.store()
|
||||||
t2 = time.time()
|
t2 = time.time()
|
||||||
log(
|
log(
|
||||||
f"""ResultatsSemestreBUT: cached formsemestre_id={formsemestre.id
|
f"""+++ ResultatsSemestreBUT: cached [{formsemestre.id
|
||||||
} ({(t1-t0):g}s +{(t2-t1):g}s)"""
|
}] ({(t1-t0):g}s +{(t2-t1):g}s) +++"""
|
||||||
)
|
)
|
||||||
|
|
||||||
def compute(self):
|
def compute(self):
|
||||||
|
@ -50,8 +50,8 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
self.store()
|
self.store()
|
||||||
t2 = time.time()
|
t2 = time.time()
|
||||||
log(
|
log(
|
||||||
f"""ResultatsSemestreClassic: cached formsemestre_id={
|
f"""+++ ResultatsSemestreClassic: cached formsemestre_id={
|
||||||
formsemestre.id} ({(t1-t0):g}s +{(t2-t1):g}s)"""
|
formsemestre.id} ({(t1-t0):g}s +{(t2-t1):g}s) +++"""
|
||||||
)
|
)
|
||||||
# recalculé (aussi rapide que de les cacher)
|
# recalculé (aussi rapide que de les cacher)
|
||||||
self.moy_min = self.etud_moy_gen.min()
|
self.moy_min = self.etud_moy_gen.min()
|
||||||
|
@ -80,8 +80,8 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
self.moy_gen_rangs_by_group = None # virtual
|
self.moy_gen_rangs_by_group = None # virtual
|
||||||
self.modimpl_inscr_df: pd.DataFrame = None
|
self.modimpl_inscr_df: pd.DataFrame = None
|
||||||
"Inscriptions: row etudid, col modimlpl_id"
|
"Inscriptions: row etudid, col modimlpl_id"
|
||||||
self.modimpls_results: ModuleImplResults = None
|
self.modimpls_results: dict[int, ModuleImplResults] = None
|
||||||
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
|
"Résultats de chaque modimpl (classique ou BUT)"
|
||||||
self.etud_coef_ue_df = None
|
self.etud_coef_ue_df = None
|
||||||
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
|
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
|
||||||
self.modimpl_coefs_df: pd.DataFrame = None
|
self.modimpl_coefs_df: pd.DataFrame = None
|
||||||
@ -192,6 +192,17 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
*[mr.etudids_attente for mr in self.modimpls_results.values()]
|
*[mr.etudids_attente for mr in self.modimpls_results.values()]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# # Etat des évaluations
|
||||||
|
# # (se substitue à do_evaluation_etat, sans les moyennes par groupes)
|
||||||
|
# def get_evaluations_etats(evaluation_id: int) -> dict:
|
||||||
|
# """Renvoie dict avec les clés:
|
||||||
|
# last_modif
|
||||||
|
# nb_evals_completes
|
||||||
|
# nb_evals_en_cours
|
||||||
|
# nb_evals_vides
|
||||||
|
# attente
|
||||||
|
# """
|
||||||
|
|
||||||
# --- JURY...
|
# --- JURY...
|
||||||
def get_formsemestre_validations(self) -> ValidationsSemestre:
|
def get_formsemestre_validations(self) -> ValidationsSemestre:
|
||||||
"""Load validations if not already stored, set attribute and return value"""
|
"""Load validations if not already stored, set attribute and return value"""
|
||||||
|
@ -16,7 +16,13 @@ from app import db, log
|
|||||||
from app.comp import moy_sem
|
from app.comp import moy_sem
|
||||||
from app.comp.aux_stats import StatsMoyenne
|
from app.comp.aux_stats import StatsMoyenne
|
||||||
from app.comp.res_common import ResultatsSemestre
|
from app.comp.res_common import ResultatsSemestre
|
||||||
from app.models import Identite, FormSemestre, ModuleImpl, ScolarAutorisationInscription
|
from app.models import (
|
||||||
|
Evaluation,
|
||||||
|
Identite,
|
||||||
|
FormSemestre,
|
||||||
|
ModuleImpl,
|
||||||
|
ScolarAutorisationInscription,
|
||||||
|
)
|
||||||
from app.scodoc.codes_cursus import UE_SPORT, DEF
|
from app.scodoc.codes_cursus import UE_SPORT, DEF
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
@ -389,7 +395,7 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
"ects_total": ects_total,
|
"ects_total": ects_total,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
|
def get_modimpl_evaluations_completes(self, moduleimpl_id: int) -> list[Evaluation]:
|
||||||
"""Liste d'informations (compat NotesTable) sur évaluations completes
|
"""Liste d'informations (compat NotesTable) sur évaluations completes
|
||||||
de ce module.
|
de ce module.
|
||||||
Évaluation "complete" ssi toutes notes saisies ou en attente.
|
Évaluation "complete" ssi toutes notes saisies ou en attente.
|
||||||
@ -398,34 +404,24 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
modimpl_results = self.modimpls_results.get(moduleimpl_id)
|
modimpl_results = self.modimpls_results.get(moduleimpl_id)
|
||||||
if not modimpl_results:
|
if not modimpl_results:
|
||||||
return [] # safeguard
|
return [] # safeguard
|
||||||
evals_results = []
|
evaluations = []
|
||||||
for e in modimpl.evaluations:
|
for e in modimpl.evaluations:
|
||||||
if modimpl_results.evaluations_completes_dict.get(e.id, False):
|
if modimpl_results.evaluations_completes_dict.get(e.id, False):
|
||||||
d = e.to_dict()
|
evaluations.append(e)
|
||||||
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": modimpl_results.evals_notes[e.id][etud.id],
|
|
||||||
}
|
|
||||||
for etud in self.etuds
|
|
||||||
}
|
|
||||||
d["etat"] = {
|
|
||||||
"evalattente": modimpl_results.evaluations_etat[e.id].nb_attente,
|
|
||||||
}
|
|
||||||
evals_results.append(d)
|
|
||||||
elif e.id not in modimpl_results.evaluations_completes_dict:
|
elif e.id not in modimpl_results.evaluations_completes_dict:
|
||||||
# ne devrait pas arriver ? XXX
|
# ne devrait pas arriver ? XXX
|
||||||
log(
|
log(
|
||||||
f"Warning: 220213 get_evals_in_mod {e.id} not in mod {moduleimpl_id} ?"
|
f"Warning: 220213 get_modimpl_evaluations_completes {e.id} not in mod {moduleimpl_id} ?"
|
||||||
)
|
)
|
||||||
return evals_results
|
return evaluations
|
||||||
|
|
||||||
|
def get_evaluations_etats(self) -> list[dict]:
|
||||||
|
"""Liste de toutes les évaluations du semestre
|
||||||
|
[ {...evaluation et son etat...} ]"""
|
||||||
|
# TODO: à moderniser (voir dans ResultatsSemestre)
|
||||||
|
# utilisé par
|
||||||
|
# do_evaluation_etat_in_sem
|
||||||
|
|
||||||
def get_evaluations_etats(self):
|
|
||||||
"""[ {...evaluation et son etat...} ]"""
|
|
||||||
# TODO: à moderniser
|
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
|
|
||||||
if not hasattr(self, "_evaluations_etats"):
|
if not hasattr(self, "_evaluations_etats"):
|
||||||
|
@ -85,7 +85,9 @@ Adresses d'origine:
|
|||||||
)
|
)
|
||||||
|
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
f"""email sent to{' (mode test)' if email_test_mode_address else ''}: {msg.recipients}
|
f"""email sent to{
|
||||||
|
' (mode test)' if email_test_mode_address else ''
|
||||||
|
}: {msg.recipients}
|
||||||
from sender {msg.sender}
|
from sender {msg.sender}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@ -98,7 +100,8 @@ def get_from_addr(dept_acronym: str = None):
|
|||||||
"""L'adresse "from" à utiliser pour envoyer un mail
|
"""L'adresse "from" à utiliser pour envoyer un mail
|
||||||
|
|
||||||
Si le departement est spécifié, ou si l'attribut `g.scodoc_dept`existe,
|
Si le departement est spécifié, ou si l'attribut `g.scodoc_dept`existe,
|
||||||
prend le `email_from_addr` des préférences de ce département si ce champ est non vide.
|
prend le `email_from_addr` des préférences de ce département si ce champ
|
||||||
|
est non vide.
|
||||||
Sinon, utilise le paramètre global `email_from_addr`.
|
Sinon, utilise le paramètre global `email_from_addr`.
|
||||||
Sinon, la variable de config `SCODOC_MAIL_FROM`.
|
Sinon, la variable de config `SCODOC_MAIL_FROM`.
|
||||||
"""
|
"""
|
||||||
|
@ -5,17 +5,28 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from app import db
|
from flask import g, url_for
|
||||||
|
from flask_login import current_user
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from app import db, log
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
from app.models.events import ScolarNews
|
||||||
from app.models.moduleimpls import ModuleImpl
|
from app.models.moduleimpls import ModuleImpl
|
||||||
from app.models.notes import NotesNotes
|
from app.models.notes import NotesNotes
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc import sco_cache
|
||||||
import app.scodoc.notesdb as ndb
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
from app.scodoc.sco_xml import quote_xml_attr
|
||||||
|
|
||||||
|
MAX_EVALUATION_DURATION = datetime.timedelta(days=365)
|
||||||
|
NOON = datetime.time(12, 00)
|
||||||
DEFAULT_EVALUATION_TIME = datetime.time(8, 0)
|
DEFAULT_EVALUATION_TIME = datetime.time(8, 0)
|
||||||
|
|
||||||
|
VALID_EVALUATION_TYPES = {0, 1, 2}
|
||||||
|
|
||||||
|
|
||||||
class Evaluation(db.Model):
|
class Evaluation(db.Model):
|
||||||
"""Evaluation (contrôle, examen, ...)"""
|
"""Evaluation (contrôle, examen, ...)"""
|
||||||
@ -27,15 +38,15 @@ class Evaluation(db.Model):
|
|||||||
moduleimpl_id = db.Column(
|
moduleimpl_id = db.Column(
|
||||||
db.Integer, db.ForeignKey("notes_moduleimpl.id"), index=True
|
db.Integer, db.ForeignKey("notes_moduleimpl.id"), index=True
|
||||||
)
|
)
|
||||||
jour = db.Column(db.Date)
|
date_debut = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||||
heure_debut = db.Column(db.Time)
|
date_fin = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||||
heure_fin = db.Column(db.Time)
|
|
||||||
description = db.Column(db.Text)
|
description = db.Column(db.Text)
|
||||||
note_max = db.Column(db.Float)
|
note_max = db.Column(db.Float)
|
||||||
coefficient = db.Column(db.Float)
|
coefficient = db.Column(db.Float)
|
||||||
visibulletin = db.Column(
|
visibulletin = db.Column(
|
||||||
db.Boolean, nullable=False, default=True, server_default="true"
|
db.Boolean, nullable=False, default=True, server_default="true"
|
||||||
)
|
)
|
||||||
|
"visible sur les bulletins version intermédiaire"
|
||||||
publish_incomplete = db.Column(
|
publish_incomplete = db.Column(
|
||||||
db.Boolean, nullable=False, default=False, server_default="false"
|
db.Boolean, nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
@ -50,47 +61,146 @@ class Evaluation(db.Model):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"""<Evaluation {self.id} {
|
return f"""<Evaluation {self.id} {
|
||||||
self.jour.isoformat() if self.jour else ''} "{
|
self.date_debut.isoformat() if self.date_debut else ''} "{
|
||||||
self.description[:16] if self.description else ''}">"""
|
self.description[:16] if self.description else ''}">"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(
|
||||||
|
cls,
|
||||||
|
moduleimpl: ModuleImpl = None,
|
||||||
|
date_debut: datetime.datetime = None,
|
||||||
|
date_fin: datetime.datetime = None,
|
||||||
|
description=None,
|
||||||
|
note_max=None,
|
||||||
|
coefficient=None,
|
||||||
|
visibulletin=None,
|
||||||
|
publish_incomplete=None,
|
||||||
|
evaluation_type=None,
|
||||||
|
numero=None,
|
||||||
|
**kw, # ceci pour absorber les éventuel arguments excedentaires
|
||||||
|
):
|
||||||
|
"""Create an evaluation. Check permission and all arguments.
|
||||||
|
Ne crée pas les poids vers les UEs.
|
||||||
|
"""
|
||||||
|
if not moduleimpl.can_edit_evaluation(current_user):
|
||||||
|
raise AccessDenied(
|
||||||
|
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||||
|
)
|
||||||
|
args = locals()
|
||||||
|
del args["cls"]
|
||||||
|
del args["kw"]
|
||||||
|
check_convert_evaluation_args(moduleimpl, args)
|
||||||
|
# Check numeros
|
||||||
|
Evaluation.moduleimpl_evaluation_renumber(moduleimpl, only_if_unumbered=True)
|
||||||
|
if not "numero" in args or args["numero"] is None:
|
||||||
|
args["numero"] = cls.get_new_numero(moduleimpl, args["date_debut"])
|
||||||
|
#
|
||||||
|
evaluation = Evaluation(**args)
|
||||||
|
sco_cache.invalidate_formsemestre(formsemestre_id=moduleimpl.formsemestre_id)
|
||||||
|
url = url_for(
|
||||||
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=moduleimpl.id,
|
||||||
|
)
|
||||||
|
log(f"created evaluation in {moduleimpl.module.titre_str()}")
|
||||||
|
ScolarNews.add(
|
||||||
|
typ=ScolarNews.NEWS_NOTE,
|
||||||
|
obj=moduleimpl.id,
|
||||||
|
text=f"""Création d'une évaluation dans <a href="{url}">{
|
||||||
|
moduleimpl.module.titre_str()}</a>""",
|
||||||
|
url=url,
|
||||||
|
)
|
||||||
|
return evaluation
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_new_numero(
|
||||||
|
cls, moduleimpl: ModuleImpl, date_debut: datetime.datetime
|
||||||
|
) -> int:
|
||||||
|
"""Get a new numero for an evaluation in this moduleimpl
|
||||||
|
If necessary, renumber existing evals to make room for a new one.
|
||||||
|
"""
|
||||||
|
n = None
|
||||||
|
# Détermine le numero grâce à la date
|
||||||
|
# Liste des eval existantes triées par date, la plus ancienne en tete
|
||||||
|
evaluations = moduleimpl.evaluations.order_by(Evaluation.date_debut).all()
|
||||||
|
if date_debut is not None:
|
||||||
|
next_eval = None
|
||||||
|
t = date_debut
|
||||||
|
for e in evaluations:
|
||||||
|
if e.date_debut and e.date_debut > t:
|
||||||
|
next_eval = e
|
||||||
|
break
|
||||||
|
if next_eval:
|
||||||
|
n = _moduleimpl_evaluation_insert_before(evaluations, next_eval)
|
||||||
|
else:
|
||||||
|
n = None # à placer en fin
|
||||||
|
if n is None: # pas de date ou en fin:
|
||||||
|
if evaluations:
|
||||||
|
n = evaluations[-1].numero + 1
|
||||||
|
else:
|
||||||
|
n = 0 # the only one
|
||||||
|
return n
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
"delete evaluation (commit) (check permission)"
|
||||||
|
from app.scodoc import sco_evaluation_db
|
||||||
|
|
||||||
|
modimpl: ModuleImpl = self.moduleimpl
|
||||||
|
if not modimpl.can_edit_evaluation(current_user):
|
||||||
|
raise AccessDenied(
|
||||||
|
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||||
|
)
|
||||||
|
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
|
||||||
|
self.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"
|
||||||
|
)
|
||||||
|
log(f"deleting evaluation {self}")
|
||||||
|
db.session.delete(self)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# inval cache pour ce semestre
|
||||||
|
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
|
||||||
|
# news
|
||||||
|
url = url_for(
|
||||||
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=modimpl.id,
|
||||||
|
)
|
||||||
|
ScolarNews.add(
|
||||||
|
typ=ScolarNews.NEWS_NOTE,
|
||||||
|
obj=modimpl.id,
|
||||||
|
text=f"""Suppression d'une évaluation dans <a href="{
|
||||||
|
url
|
||||||
|
}">{modimpl.module.titre}</a>""",
|
||||||
|
url=url,
|
||||||
|
)
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
"Représentation dict (riche, compat ScoDoc 7)"
|
"Représentation dict (riche, compat ScoDoc 7)"
|
||||||
e = dict(self.__dict__)
|
e_dict = dict(self.__dict__)
|
||||||
e.pop("_sa_instance_state", None)
|
e_dict.pop("_sa_instance_state", None)
|
||||||
# ScoDoc7 output_formators
|
# ScoDoc7 output_formators
|
||||||
e["evaluation_id"] = self.id
|
e_dict["evaluation_id"] = self.id
|
||||||
e["jour"] = e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
|
e_dict["date_debut"] = self.date_debut.isoformat() if self.date_debut else None
|
||||||
if self.jour is None:
|
e_dict["date_fin"] = self.date_debut.isoformat() if self.date_fin else None
|
||||||
e["date_debut"] = None
|
e_dict["numero"] = self.numero or 0
|
||||||
e["date_fin"] = None
|
e_dict["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
|
||||||
else:
|
|
||||||
e["date_debut"] = datetime.datetime.combine(
|
# Deprecated
|
||||||
self.jour, self.heure_debut or datetime.time(0, 0)
|
e_dict["jour"] = self.date_debut.strftime("%d/%m/%Y") if self.date_debut else ""
|
||||||
).isoformat()
|
|
||||||
e["date_fin"] = datetime.datetime.combine(
|
return evaluation_enrich_dict(self, e_dict)
|
||||||
self.jour, self.heure_fin or datetime.time(0, 0)
|
|
||||||
).isoformat()
|
|
||||||
e["numero"] = ndb.int_null_is_zero(e["numero"])
|
|
||||||
e["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
|
|
||||||
return evaluation_enrich_dict(e)
|
|
||||||
|
|
||||||
def to_dict_api(self) -> dict:
|
def to_dict_api(self) -> dict:
|
||||||
"Représentation dict pour API JSON"
|
"Représentation dict pour API JSON"
|
||||||
if self.jour is None:
|
|
||||||
date_debut = None
|
|
||||||
date_fin = None
|
|
||||||
else:
|
|
||||||
date_debut = datetime.datetime.combine(
|
|
||||||
self.jour, self.heure_debut or datetime.time(0, 0)
|
|
||||||
).isoformat()
|
|
||||||
date_fin = datetime.datetime.combine(
|
|
||||||
self.jour, self.heure_fin or datetime.time(0, 0)
|
|
||||||
).isoformat()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"coefficient": self.coefficient,
|
"coefficient": self.coefficient,
|
||||||
"date_debut": date_debut,
|
"date_debut": self.date_debut.isoformat() if self.date_debut else "",
|
||||||
"date_fin": date_fin,
|
"date_fin": self.date_fin.isoformat() if self.date_fin else "",
|
||||||
"description": self.description,
|
"description": self.description,
|
||||||
"evaluation_type": self.evaluation_type,
|
"evaluation_type": self.evaluation_type,
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
@ -99,39 +209,135 @@ class Evaluation(db.Model):
|
|||||||
"numero": self.numero,
|
"numero": self.numero,
|
||||||
"poids": self.get_ue_poids_dict(),
|
"poids": self.get_ue_poids_dict(),
|
||||||
"publish_incomplete": self.publish_incomplete,
|
"publish_incomplete": self.publish_incomplete,
|
||||||
"visi_bulletin": self.visibulletin,
|
"visibulletin": self.visibulletin,
|
||||||
|
# Deprecated (supprimer avant #sco9.7)
|
||||||
|
"date": self.date_debut.date().isoformat() if self.date_debut else "",
|
||||||
|
"heure_debut": self.date_debut.time().isoformat()
|
||||||
|
if self.date_debut
|
||||||
|
else "",
|
||||||
|
"heure_fin": self.date_fin.time().isoformat() if self.date_fin else "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def to_dict_bul(self) -> dict:
|
||||||
|
"dict pour les bulletins json"
|
||||||
|
# c'est la version API avec quelques champs legacy en plus
|
||||||
|
e_dict = self.to_dict_api()
|
||||||
|
# Pour les bulletins (json ou xml), quote toujours la description
|
||||||
|
e_dict["description"] = quote_xml_attr(self.description or "")
|
||||||
|
# deprecated fields:
|
||||||
|
e_dict["evaluation_id"] = self.id
|
||||||
|
e_dict["jour"] = e_dict["date_debut"] # chaine iso
|
||||||
|
e_dict["heure_debut"] = (
|
||||||
|
self.date_debut.time().isoformat() if self.date_debut else ""
|
||||||
|
)
|
||||||
|
e_dict["heure_fin"] = self.date_fin.time().isoformat() if self.date_fin else ""
|
||||||
|
|
||||||
|
return e_dict
|
||||||
|
|
||||||
def from_dict(self, data):
|
def from_dict(self, data):
|
||||||
"""Set evaluation attributes from given dict values."""
|
"""Set evaluation attributes from given dict values."""
|
||||||
check_evaluation_args(data)
|
check_convert_evaluation_args(self.moduleimpl, data)
|
||||||
|
if data.get("numero") is None:
|
||||||
|
data["numero"] = Evaluation.get_max_numero(self.moduleimpl.id) + 1
|
||||||
for k in self.__dict__.keys():
|
for k in self.__dict__.keys():
|
||||||
if k != "_sa_instance_state" and k != "id" and k in data:
|
if k != "_sa_instance_state" and k != "id" and k in data:
|
||||||
setattr(self, k, data[k])
|
setattr(self, k, data[k])
|
||||||
|
|
||||||
def descr_heure(self) -> str:
|
@classmethod
|
||||||
"Description de la plage horaire pour affichages"
|
def get_max_numero(cls, moduleimpl_id: int) -> int:
|
||||||
if self.heure_debut and (
|
"""Return max numero among evaluations in this
|
||||||
not self.heure_fin or self.heure_fin == self.heure_debut
|
moduleimpl (0 if None)
|
||||||
|
"""
|
||||||
|
max_num = (
|
||||||
|
db.session.query(sa.sql.functions.max(Evaluation.numero))
|
||||||
|
.filter_by(moduleimpl_id=moduleimpl_id)
|
||||||
|
.first()[0]
|
||||||
|
)
|
||||||
|
return max_num or 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def moduleimpl_evaluation_renumber(
|
||||||
|
cls, moduleimpl: ModuleImpl, only_if_unumbered=False
|
||||||
):
|
):
|
||||||
return f"""à {self.heure_debut.strftime("%Hh%M")}"""
|
"""Renumber evaluations in this moduleimpl, according to their date. (numero=0: oldest one)
|
||||||
elif self.heure_debut and self.heure_fin:
|
Needed because previous versions of ScoDoc did not have eval numeros
|
||||||
return f"""de {self.heure_debut.strftime("%Hh%M")} à {self.heure_fin.strftime("%Hh%M")}"""
|
Note: existing numeros are ignored
|
||||||
|
"""
|
||||||
|
# Liste des eval existantes triées par date, la plus ancienne en tete
|
||||||
|
evaluations = moduleimpl.evaluations.order_by(
|
||||||
|
Evaluation.date_debut, Evaluation.numero
|
||||||
|
).all()
|
||||||
|
all_numbered = all(e.numero is not None for e in evaluations)
|
||||||
|
if all_numbered and only_if_unumbered:
|
||||||
|
return # all ok
|
||||||
|
|
||||||
|
# Reset all numeros:
|
||||||
|
i = 1
|
||||||
|
for e in evaluations:
|
||||||
|
e.numero = i
|
||||||
|
db.session.add(e)
|
||||||
|
i += 1
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def descr_heure(self) -> str:
|
||||||
|
"Description de la plage horaire pour affichages ('de 13h00 à 14h00')"
|
||||||
|
if self.date_debut and (not self.date_fin or self.date_fin == self.date_debut):
|
||||||
|
return f"""à {self.date_debut.strftime("%Hh%M")}"""
|
||||||
|
elif self.date_debut and self.date_fin:
|
||||||
|
return f"""de {self.date_debut.strftime("%Hh%M")
|
||||||
|
} à {self.date_fin.strftime("%Hh%M")}"""
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def descr_duree(self) -> str:
|
def descr_duree(self) -> str:
|
||||||
"Description de la durée pour affichages"
|
"Description de la durée pour affichages ('3h' ou '2h30')"
|
||||||
if self.heure_debut is None and self.heure_fin is None:
|
if self.date_debut is None or self.date_fin is None:
|
||||||
return ""
|
return ""
|
||||||
debut = self.heure_debut or DEFAULT_EVALUATION_TIME
|
minutes = (self.date_fin - self.date_debut).seconds // 60
|
||||||
fin = self.heure_fin or DEFAULT_EVALUATION_TIME
|
duree = f"{minutes // 60}h"
|
||||||
d = (fin.hour * 60 + fin.minute) - (debut.hour * 60 + debut.minute)
|
minutes = minutes % 60
|
||||||
duree = f"{d//60}h"
|
if minutes != 0:
|
||||||
if d % 60:
|
duree += f"{minutes:02d}"
|
||||||
duree += f"{d%60:02d}"
|
|
||||||
return duree
|
return duree
|
||||||
|
|
||||||
|
def descr_date(self) -> str:
|
||||||
|
"""Description de la date pour affichages
|
||||||
|
'sans date'
|
||||||
|
'le 21/9/2021 à 13h'
|
||||||
|
'le 21/9/2021 de 13h à 14h30'
|
||||||
|
'du 21/9/2021 à 13h30 au 23/9/2021 à 15h'
|
||||||
|
"""
|
||||||
|
if self.date_debut is None:
|
||||||
|
return "sans date"
|
||||||
|
|
||||||
|
def _h(dt: datetime.datetime) -> str:
|
||||||
|
if dt.minute:
|
||||||
|
return dt.strftime("%Hh%M")
|
||||||
|
return f"{dt.hour}h"
|
||||||
|
|
||||||
|
if self.date_fin is None:
|
||||||
|
return f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut)}"
|
||||||
|
if self.date_debut.date() == self.date_fin.date(): # même jour
|
||||||
|
if self.date_debut.time() == self.date_fin.time():
|
||||||
|
return (
|
||||||
|
f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut)}"
|
||||||
|
)
|
||||||
|
return f"""le {self.date_debut.strftime('%d/%m/%Y')} de {
|
||||||
|
_h(self.date_debut)} à {_h(self.date_fin)}"""
|
||||||
|
# évaluation sur plus d'une journée
|
||||||
|
return f"""du {self.date_debut.strftime('%d/%m/%Y')} à {
|
||||||
|
_h(self.date_debut)} au {self.date_fin.strftime('%d/%m/%Y')} à {_h(self.date_fin)}"""
|
||||||
|
|
||||||
|
def heure_debut(self) -> str:
|
||||||
|
"""L'heure de début (sans la date), en ISO.
|
||||||
|
Chaine vide si non renseignée."""
|
||||||
|
return self.date_debut.time().isoformat("minutes") if self.date_debut else ""
|
||||||
|
|
||||||
|
def heure_fin(self) -> str:
|
||||||
|
"""L'heure de fin (sans la date), en ISO.
|
||||||
|
Chaine vide si non renseignée."""
|
||||||
|
return self.date_fin.time().isoformat("minutes") if self.date_fin else ""
|
||||||
|
|
||||||
def clone(self, not_copying=()):
|
def clone(self, not_copying=()):
|
||||||
"""Clone, not copying the given attrs
|
"""Clone, not copying the given attrs
|
||||||
Attention: la copie n'a pas d'id avant le prochain commit
|
Attention: la copie n'a pas d'id avant le prochain commit
|
||||||
@ -146,19 +352,19 @@ class Evaluation(db.Model):
|
|||||||
return copy
|
return copy
|
||||||
|
|
||||||
def is_matin(self) -> bool:
|
def is_matin(self) -> bool:
|
||||||
"Evaluation ayant lieu le matin (faux si pas de date)"
|
"Evaluation commençant le matin (faux si pas de date)"
|
||||||
heure_debut_dt = self.heure_debut or datetime.time(8, 00)
|
if not self.date_debut:
|
||||||
# 8:00 au cas ou pas d'heure (note externe?)
|
return False
|
||||||
return bool(self.jour) and heure_debut_dt < datetime.time(12, 00)
|
return self.date_debut.time() < NOON
|
||||||
|
|
||||||
def is_apresmidi(self) -> bool:
|
def is_apresmidi(self) -> bool:
|
||||||
"Evaluation ayant lieu l'après midi (faux si pas de date)"
|
"Evaluation commençant l'après midi (faux si pas de date)"
|
||||||
heure_debut_dt = self.heure_debut or datetime.time(8, 00)
|
if not self.date_debut:
|
||||||
# 8:00 au cas ou pas d'heure (note externe?)
|
return False
|
||||||
return bool(self.jour) and heure_debut_dt >= datetime.time(12, 00)
|
return self.date_debut.time() >= NOON
|
||||||
|
|
||||||
def set_default_poids(self) -> bool:
|
def set_default_poids(self) -> bool:
|
||||||
"""Initialize les poids bvers les UE à leurs valeurs par défaut
|
"""Initialize les poids vers les UE à leurs valeurs par défaut
|
||||||
C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon.
|
C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon.
|
||||||
Les poids existants ne sont pas modifiés.
|
Les poids existants ne sont pas modifiés.
|
||||||
Return True if (uncommited) modification, False otherwise.
|
Return True if (uncommited) modification, False otherwise.
|
||||||
@ -191,6 +397,8 @@ class Evaluation(db.Model):
|
|||||||
L = []
|
L = []
|
||||||
for ue_id, poids in ue_poids_dict.items():
|
for ue_id, poids in ue_poids_dict.items():
|
||||||
ue = db.session.get(UniteEns, ue_id)
|
ue = db.session.get(UniteEns, ue_id)
|
||||||
|
if ue is None:
|
||||||
|
raise ScoValueError("poids vers une UE inexistante")
|
||||||
ue_poids = EvaluationUEPoids(evaluation=self, ue=ue, poids=poids)
|
ue_poids = EvaluationUEPoids(evaluation=self, ue=ue, poids=poids)
|
||||||
L.append(ue_poids)
|
L.append(ue_poids)
|
||||||
db.session.add(ue_poids)
|
db.session.add(ue_poids)
|
||||||
@ -274,88 +482,158 @@ class EvaluationUEPoids(db.Model):
|
|||||||
return f"<EvaluationUEPoids {self.evaluation} {self.ue} poids={self.poids}>"
|
return f"<EvaluationUEPoids {self.evaluation} {self.ue} poids={self.poids}>"
|
||||||
|
|
||||||
|
|
||||||
# Fonction héritée de ScoDoc7 à refactorer
|
# Fonction héritée de ScoDoc7
|
||||||
def evaluation_enrich_dict(e: dict):
|
def evaluation_enrich_dict(e: Evaluation, e_dict: dict):
|
||||||
"""add or convert some fields in an evaluation dict"""
|
"""add or convert some fields in an evaluation dict"""
|
||||||
# For ScoDoc7 compat
|
# For ScoDoc7 compat
|
||||||
heure_debut_dt = e["heure_debut"] or datetime.time(
|
e_dict["heure_debut"] = e.date_debut.strftime("%Hh%M") if e.date_debut else ""
|
||||||
8, 00
|
e_dict["heure_fin"] = e.date_fin.strftime("%Hh%M") if e.date_fin else ""
|
||||||
) # au cas ou pas d'heure (note externe?)
|
e_dict["jour_iso"] = e.date_debut.isoformat() if e.date_debut else ""
|
||||||
heure_fin_dt = e["heure_fin"] or datetime.time(8, 00)
|
# Calcule durée en minutes
|
||||||
e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"])
|
e_dict["descrheure"] = e.descr_heure()
|
||||||
e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"])
|
e_dict["descrduree"] = e.descr_duree()
|
||||||
e["jour_iso"] = ndb.DateDMYtoISO(e["jour"])
|
|
||||||
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
|
|
||||||
d = ndb.TimeDuration(heure_debut, heure_fin)
|
|
||||||
if d is not None:
|
|
||||||
m = d % 60
|
|
||||||
e["duree"] = "%dh" % (d / 60)
|
|
||||||
if m != 0:
|
|
||||||
e["duree"] += "%02d" % m
|
|
||||||
else:
|
|
||||||
e["duree"] = ""
|
|
||||||
if heure_debut and (not heure_fin or heure_fin == heure_debut):
|
|
||||||
e["descrheure"] = " à " + heure_debut
|
|
||||||
elif heure_debut and heure_fin:
|
|
||||||
e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin)
|
|
||||||
else:
|
|
||||||
e["descrheure"] = ""
|
|
||||||
# matin, apresmidi: utile pour se referer aux absences:
|
# matin, apresmidi: utile pour se referer aux absences:
|
||||||
|
# note août 2023: si l'évaluation s'étend sur plusieurs jours,
|
||||||
if e["jour"] and heure_debut_dt < datetime.time(12, 00):
|
# cet indicateur n'a pas grand sens
|
||||||
e["matin"] = 1
|
if e.date_debut and e.date_debut.time() < datetime.time(12, 00):
|
||||||
|
e_dict["matin"] = 1
|
||||||
else:
|
else:
|
||||||
e["matin"] = 0
|
e_dict["matin"] = 0
|
||||||
if e["jour"] and heure_fin_dt > datetime.time(12, 00):
|
if e.date_fin and e.date_fin.time() > datetime.time(12, 00):
|
||||||
e["apresmidi"] = 1
|
e_dict["apresmidi"] = 1
|
||||||
else:
|
else:
|
||||||
e["apresmidi"] = 0
|
e_dict["apresmidi"] = 0
|
||||||
return e
|
return e_dict
|
||||||
|
|
||||||
|
|
||||||
def check_evaluation_args(args):
|
def check_convert_evaluation_args(moduleimpl: ModuleImpl, data: dict):
|
||||||
"Check coefficient, dates and duration, raises exception if invalid"
|
"""Check coefficient, dates and duration, raises exception if invalid.
|
||||||
moduleimpl_id = args["moduleimpl_id"]
|
Convert date and time strings to date and time objects.
|
||||||
# check bareme
|
|
||||||
note_max = args.get("note_max", None)
|
Set required default value for unspecified fields.
|
||||||
if note_max is None:
|
May raise ScoValueError.
|
||||||
raise ScoValueError("missing note_max")
|
"""
|
||||||
|
# --- description
|
||||||
|
data["description"] = data.get("description", "") or ""
|
||||||
|
if len(data["description"]) > scu.MAX_TEXT_LEN:
|
||||||
|
raise ScoValueError("description too large")
|
||||||
|
|
||||||
|
# --- evaluation_type
|
||||||
|
try:
|
||||||
|
data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0)
|
||||||
|
if not data["evaluation_type"] in VALID_EVALUATION_TYPES:
|
||||||
|
raise ScoValueError("invalid evaluation_type value")
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError("invalid evaluation_type value") from exc
|
||||||
|
|
||||||
|
# --- note_max (bareme)
|
||||||
|
note_max = data.get("note_max", 20.0) or 20.0
|
||||||
try:
|
try:
|
||||||
note_max = float(note_max)
|
note_max = float(note_max)
|
||||||
except ValueError:
|
except ValueError as exc:
|
||||||
raise ScoValueError("Invalid note_max value")
|
raise ScoValueError("invalid note_max value") from exc
|
||||||
if note_max < 0:
|
if note_max < 0:
|
||||||
raise ScoValueError("Invalid note_max value (must be positive or null)")
|
raise ScoValueError("invalid note_max value (must be positive or null)")
|
||||||
# check coefficient
|
data["note_max"] = note_max
|
||||||
coef = args.get("coefficient", None)
|
# --- coefficient
|
||||||
if coef is None:
|
coef = data.get("coefficient", 1.0) or 1.0
|
||||||
raise ScoValueError("missing coefficient")
|
|
||||||
try:
|
try:
|
||||||
coef = float(coef)
|
coef = float(coef)
|
||||||
except ValueError:
|
except ValueError as exc:
|
||||||
raise ScoValueError("Invalid coefficient value")
|
raise ScoValueError("invalid coefficient value") from exc
|
||||||
if coef < 0:
|
if coef < 0:
|
||||||
raise ScoValueError("Invalid coefficient value (must be positive or null)")
|
raise ScoValueError("invalid coefficient value (must be positive or null)")
|
||||||
# check date
|
data["coefficient"] = coef
|
||||||
jour = args.get("jour", None)
|
# --- date de l'évaluation
|
||||||
args["jour"] = jour
|
formsemestre = moduleimpl.formsemestre
|
||||||
if jour:
|
date_debut = data.get("date_debut", None)
|
||||||
modimpl = db.session.get(ModuleImpl, moduleimpl_id)
|
if date_debut:
|
||||||
formsemestre = modimpl.formsemestre
|
if isinstance(date_debut, str):
|
||||||
y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
|
data["date_debut"] = datetime.datetime.fromisoformat(date_debut)
|
||||||
jour = datetime.date(y, m, d)
|
if data["date_debut"].tzinfo is None:
|
||||||
if (jour > formsemestre.date_fin) or (jour < formsemestre.date_debut):
|
data["date_debut"] = scu.TIME_ZONE.localize(data["date_debut"])
|
||||||
|
if not (
|
||||||
|
formsemestre.date_debut
|
||||||
|
<= data["date_debut"].date()
|
||||||
|
<= formsemestre.date_fin
|
||||||
|
):
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
|
f"""La date de début de l'évaluation ({
|
||||||
% (d, m, y),
|
data["date_debut"].strftime("%d/%m/%Y")
|
||||||
|
}) n'est pas dans le semestre !""",
|
||||||
dest_url="javascript:history.back();",
|
dest_url="javascript:history.back();",
|
||||||
)
|
)
|
||||||
heure_debut = args.get("heure_debut", None)
|
date_fin = data.get("date_fin", None)
|
||||||
args["heure_debut"] = heure_debut
|
if date_fin:
|
||||||
heure_fin = args.get("heure_fin", None)
|
if isinstance(date_fin, str):
|
||||||
args["heure_fin"] = heure_fin
|
data["date_fin"] = datetime.datetime.fromisoformat(date_fin)
|
||||||
if jour and ((not heure_debut) or (not heure_fin)):
|
if data["date_fin"].tzinfo is None:
|
||||||
raise ScoValueError("Les heures doivent être précisées")
|
data["date_fin"] = scu.TIME_ZONE.localize(data["date_fin"])
|
||||||
d = ndb.TimeDuration(heure_debut, heure_fin)
|
if not (
|
||||||
if d and ((d < 0) or (d > 60 * 12)):
|
formsemestre.date_debut <= data["date_fin"].date() <= formsemestre.date_fin
|
||||||
|
):
|
||||||
|
raise ScoValueError(
|
||||||
|
f"""La date de fin de l'évaluation ({
|
||||||
|
data["date_fin"].strftime("%d/%m/%Y")
|
||||||
|
}) n'est pas dans le semestre !""",
|
||||||
|
dest_url="javascript:history.back();",
|
||||||
|
)
|
||||||
|
if date_debut and date_fin:
|
||||||
|
duration = data["date_fin"] - data["date_debut"]
|
||||||
|
if duration.total_seconds() < 0 or duration > MAX_EVALUATION_DURATION:
|
||||||
raise ScoValueError("Heures de l'évaluation incohérentes !")
|
raise ScoValueError("Heures de l'évaluation incohérentes !")
|
||||||
|
# # --- heures
|
||||||
|
# heure_debut = data.get("heure_debut", None)
|
||||||
|
# if heure_debut and not isinstance(heure_debut, datetime.time):
|
||||||
|
# if date_format == "dmy":
|
||||||
|
# data["heure_debut"] = heure_to_time(heure_debut)
|
||||||
|
# else: # ISO
|
||||||
|
# data["heure_debut"] = datetime.time.fromisoformat(heure_debut)
|
||||||
|
# heure_fin = data.get("heure_fin", None)
|
||||||
|
# if heure_fin and not isinstance(heure_fin, datetime.time):
|
||||||
|
# if date_format == "dmy":
|
||||||
|
# data["heure_fin"] = heure_to_time(heure_fin)
|
||||||
|
# else: # ISO
|
||||||
|
# data["heure_fin"] = datetime.time.fromisoformat(heure_fin)
|
||||||
|
|
||||||
|
|
||||||
|
def heure_to_time(heure: str) -> datetime.time:
|
||||||
|
"Convert external heure ('10h22' or '10:22') to a time"
|
||||||
|
t = heure.strip().upper().replace("H", ":")
|
||||||
|
h, m = t.split(":")[:2]
|
||||||
|
return datetime.time(int(h), int(m))
|
||||||
|
|
||||||
|
|
||||||
|
def _time_duration_HhM(heure_debut: str, heure_fin: str) -> int:
|
||||||
|
"""duree (nb entier de minutes) entre deux heures a notre format
|
||||||
|
ie 12h23
|
||||||
|
"""
|
||||||
|
if heure_debut and heure_fin:
|
||||||
|
h0, m0 = [int(x) for x in heure_debut.split("h")]
|
||||||
|
h1, m1 = [int(x) for x in heure_fin.split("h")]
|
||||||
|
d = (h1 - h0) * 60 + (m1 - m0)
|
||||||
|
return d
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _moduleimpl_evaluation_insert_before(
|
||||||
|
evaluations: list[Evaluation], next_eval: Evaluation
|
||||||
|
) -> int:
|
||||||
|
"""Renumber evaluations such that an evaluation with can be inserted before next_eval
|
||||||
|
Returns numero suitable for the inserted evaluation
|
||||||
|
"""
|
||||||
|
if next_eval:
|
||||||
|
n = next_eval.numero
|
||||||
|
if n is None:
|
||||||
|
Evaluation.moduleimpl_evaluation_renumber(next_eval.moduleimpl)
|
||||||
|
n = next_eval.numero
|
||||||
|
else:
|
||||||
|
n = 1
|
||||||
|
# all numeros >= n are incremented
|
||||||
|
for e in evaluations:
|
||||||
|
if e.numero >= n:
|
||||||
|
e.numero += 1
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.commit()
|
||||||
|
return n
|
||||||
|
@ -30,6 +30,7 @@ from app.models.but_refcomp import (
|
|||||||
)
|
)
|
||||||
from app.models.config import ScoDocSiteConfig
|
from app.models.config import ScoDocSiteConfig
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
from app.models.evaluations import Evaluation
|
||||||
from app.models.formations import Formation
|
from app.models.formations import Formation
|
||||||
from app.models.groups import GroupDescr, Partition
|
from app.models.groups import GroupDescr, Partition
|
||||||
from app.models.moduleimpls import ModuleImpl, ModuleImplInscription
|
from app.models.moduleimpls import ModuleImpl, ModuleImplInscription
|
||||||
@ -350,6 +351,21 @@ class FormSemestre(db.Model):
|
|||||||
_cache[key] = ues
|
_cache[key] = ues
|
||||||
return ues
|
return ues
|
||||||
|
|
||||||
|
def get_evaluations(self) -> list[Evaluation]:
|
||||||
|
"Liste de toutes les évaluations du semestre, triées par module/numero"
|
||||||
|
return (
|
||||||
|
Evaluation.query.join(ModuleImpl)
|
||||||
|
.filter_by(formsemestre_id=self.id)
|
||||||
|
.join(Module)
|
||||||
|
.order_by(
|
||||||
|
Module.numero,
|
||||||
|
Module.code,
|
||||||
|
Evaluation.numero,
|
||||||
|
Evaluation.date_debut.desc(),
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def modimpls_sorted(self) -> list[ModuleImpl]:
|
def modimpls_sorted(self) -> list[ModuleImpl]:
|
||||||
"""Liste des modimpls du semestre (y compris bonus)
|
"""Liste des modimpls du semestre (y compris bonus)
|
||||||
@ -825,7 +841,7 @@ class FormSemestre(db.Model):
|
|||||||
Les groupes de parcours sont ceux de la partition scu.PARTITION_PARCOURS
|
Les groupes de parcours sont ceux de la partition scu.PARTITION_PARCOURS
|
||||||
et leur nom est le code du parcours (eg "Cyber").
|
et leur nom est le code du parcours (eg "Cyber").
|
||||||
|
|
||||||
Si etudid est sépcifié, n'affecte que cet étudiant,
|
Si etudid est spécifié, n'affecte que cet étudiant,
|
||||||
sinon traite tous les inscrits du semestre.
|
sinon traite tous les inscrits du semestre.
|
||||||
"""
|
"""
|
||||||
if self.formation.referentiel_competence_id is None:
|
if self.formation.referentiel_competence_id is None:
|
||||||
|
@ -11,8 +11,8 @@ from operator import attrgetter
|
|||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import SHORT_STR_LEN
|
from app.models import Scolog, GROUPNAME_STR_LEN, SHORT_STR_LEN
|
||||||
from app.models import GROUPNAME_STR_LEN
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
|
|
||||||
@ -83,6 +83,14 @@ class Partition(db.Model):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def formsemestre_remove_etud(cls, formsemestre_id: int, etud: "Identite"):
|
||||||
|
"retire l'étudiant de toutes les partitions de ce semestre"
|
||||||
|
for group in GroupDescr.query.join(Partition).filter_by(
|
||||||
|
formsemestre_id=formsemestre_id
|
||||||
|
):
|
||||||
|
group.remove_etud(etud)
|
||||||
|
|
||||||
def is_parcours(self) -> bool:
|
def is_parcours(self) -> bool:
|
||||||
"Vrai s'il s'agit de la partition de parcours"
|
"Vrai s'il s'agit de la partition de parcours"
|
||||||
return self.partition_name == scu.PARTITION_PARCOURS
|
return self.partition_name == scu.PARTITION_PARCOURS
|
||||||
@ -248,6 +256,24 @@ class GroupDescr(db.Model):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def remove_etud(self, etud: "Identite"):
|
||||||
|
"Enlève l'étudiant de ce groupe s'il en fait partie (ne fait rien sinon)"
|
||||||
|
if etud in self.etuds:
|
||||||
|
self.etuds.remove(etud)
|
||||||
|
db.session.commit()
|
||||||
|
Scolog.logdb(
|
||||||
|
method="group_remove_etud",
|
||||||
|
etudid=etud.id,
|
||||||
|
msg=f"Retrait du groupe {self.group_name} de {self.partition.partition_name}",
|
||||||
|
commit=True,
|
||||||
|
)
|
||||||
|
# Update parcours
|
||||||
|
if self.partition.partition_name == scu.PARTITION_PARCOURS:
|
||||||
|
self.partition.formsemestre.update_inscriptions_parcours_from_groups(
|
||||||
|
etudid=etud.id
|
||||||
|
)
|
||||||
|
sco_cache.invalidate_formsemestre(self.partition.formsemestre_id)
|
||||||
|
|
||||||
|
|
||||||
group_membership = db.Table(
|
group_membership = db.Table(
|
||||||
"group_membership",
|
"group_membership",
|
||||||
|
@ -101,6 +101,49 @@ class ModuleImpl(db.Model):
|
|||||||
d.pop("module", None)
|
d.pop("module", None)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def can_edit_evaluation(self, user) -> bool:
|
||||||
|
"""True if this user can create, delete or edit and evaluation in this modimpl
|
||||||
|
(nb: n'implique pas le droit de saisir ou modifier des notes)
|
||||||
|
"""
|
||||||
|
# acces pour resp. moduleimpl et resp. form semestre (dir etud)
|
||||||
|
if (
|
||||||
|
user.has_permission(Permission.ScoEditAllEvals)
|
||||||
|
or user.id == self.responsable_id
|
||||||
|
or user.id in (r.id for r in self.formsemestre.responsables)
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
elif self.formsemestre.ens_can_edit_eval:
|
||||||
|
if user.id in (e.id for e in self.enseignants):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_edit_notes(self, user: "User", allow_ens=True) -> bool:
|
||||||
|
"""True if authuser can enter or edit notes in this module.
|
||||||
|
If allow_ens, grant access to all ens in this module
|
||||||
|
|
||||||
|
Si des décisions de jury ont déjà été saisies dans ce semestre,
|
||||||
|
seul le directeur des études peut saisir des notes (et il ne devrait pas).
|
||||||
|
"""
|
||||||
|
# was sco_permissions_check.can_edit_notes
|
||||||
|
from app.scodoc import sco_cursus_dut
|
||||||
|
|
||||||
|
if not self.formsemestre.etat:
|
||||||
|
return False # semestre verrouillé
|
||||||
|
is_dir_etud = user.id in (u.id for u in self.formsemestre.responsables)
|
||||||
|
can_edit_all_notes = user.has_permission(Permission.ScoEditAllNotes)
|
||||||
|
if sco_cursus_dut.formsemestre_has_decisions(self.formsemestre_id):
|
||||||
|
# il y a des décisions de jury dans ce semestre !
|
||||||
|
return can_edit_all_notes or is_dir_etud
|
||||||
|
if (
|
||||||
|
not can_edit_all_notes
|
||||||
|
and user.id != self.responsable_id
|
||||||
|
and not is_dir_etud
|
||||||
|
):
|
||||||
|
# enseignant (chargé de TD) ?
|
||||||
|
return allow_ens and user.id in (ens.id for ens in self.enseignants)
|
||||||
|
return True
|
||||||
|
|
||||||
def can_change_ens_by(self, user: User, raise_exc=False) -> bool:
|
def can_change_ens_by(self, user: User, raise_exc=False) -> bool:
|
||||||
"""Check if user can modify module resp.
|
"""Check if user can modify module resp.
|
||||||
If raise_exc, raises exception (AccessDenied or ScoLockedSemError) if not.
|
If raise_exc, raises exception (AccessDenied or ScoLockedSemError) if not.
|
||||||
|
@ -153,6 +153,10 @@ class Module(db.Model):
|
|||||||
"""
|
"""
|
||||||
return scu.ModuleType.get_abbrev(self.module_type)
|
return scu.ModuleType.get_abbrev(self.module_type)
|
||||||
|
|
||||||
|
def titre_str(self) -> str:
|
||||||
|
"Identifiant du module à afficher : abbrev ou titre ou code"
|
||||||
|
return self.abbrev or self.titre or self.code
|
||||||
|
|
||||||
def sort_key_apc(self) -> tuple:
|
def sort_key_apc(self) -> tuple:
|
||||||
"""Clé de tri pour avoir
|
"""Clé de tri pour avoir
|
||||||
présentation par type (res, sae), parcours, type, numéro
|
présentation par type (res, sae), parcours, type, numéro
|
||||||
|
@ -57,8 +57,10 @@ def _pe_view_sem_recap_form(formsemestre_id):
|
|||||||
poursuites d'études.
|
poursuites d'études.
|
||||||
<br>
|
<br>
|
||||||
De nombreux aspects sont paramétrables:
|
De nombreux aspects sont paramétrables:
|
||||||
<a href="https://scodoc.org/AvisPoursuiteEtudes" target="_blank" rel="noopener">
|
<a href="https://scodoc.org/AvisPoursuiteEtudes"
|
||||||
voir la documentation</a>.
|
target="_blank" rel="noopener noreferrer">
|
||||||
|
voir la documentation
|
||||||
|
</a>.
|
||||||
</p>
|
</p>
|
||||||
<form method="post" action="pe_view_sem_recap" id="pe_view_sem_recap_form"
|
<form method="post" action="pe_view_sem_recap" id="pe_view_sem_recap_form"
|
||||||
enctype="multipart/form-data">
|
enctype="multipart/form-data">
|
||||||
|
@ -152,8 +152,10 @@ def sidebar(etudid: int = None):
|
|||||||
# Logo
|
# Logo
|
||||||
H.append(
|
H.append(
|
||||||
f"""<div class="logo-insidebar">
|
f"""<div class="logo-insidebar">
|
||||||
<div class="sidebar-bottom"><a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br>
|
<div class="sidebar-bottom"><a href="{
|
||||||
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
|
url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept )
|
||||||
|
}" class="sidebar">À propos</a><br>
|
||||||
|
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" rel="noopener" class="sidebar">Aide</a>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="logo-logo">
|
<div class="logo-logo">
|
||||||
<a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }">
|
<a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }">
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import datetime
|
||||||
import html
|
import html
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from flask import g, current_app, abort
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import psycopg2.pool
|
import psycopg2.pool
|
||||||
import psycopg2.extras
|
import psycopg2.extras
|
||||||
|
|
||||||
from flask import g, current_app, abort
|
|
||||||
|
|
||||||
import app
|
|
||||||
import app.scodoc.sco_utils as scu
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_exceptions import ScoException, ScoValueError, NoteProcessError
|
from app.scodoc.sco_exceptions import ScoException, ScoValueError, NoteProcessError
|
||||||
import datetime
|
|
||||||
|
|
||||||
quote_html = html.escape
|
quote_html = html.escape
|
||||||
|
|
||||||
@ -459,8 +457,10 @@ def dictfilter(d, fields, filter_nulls=True):
|
|||||||
# --- Misc Tools
|
# --- Misc Tools
|
||||||
|
|
||||||
|
|
||||||
def DateDMYtoISO(dmy: str, null_is_empty=False) -> str:
|
def DateDMYtoISO(dmy: str, null_is_empty=False) -> str: # XXX deprecated
|
||||||
"convert date string from french format to ISO"
|
"""Convert date string from french format to ISO.
|
||||||
|
If null_is_empty (default false), returns "" if no input.
|
||||||
|
"""
|
||||||
if not dmy:
|
if not dmy:
|
||||||
if null_is_empty:
|
if null_is_empty:
|
||||||
return ""
|
return ""
|
||||||
@ -506,7 +506,7 @@ def DateISOtoDMY(isodate):
|
|||||||
return "%02d/%02d/%04d" % (day, month, year)
|
return "%02d/%02d/%04d" % (day, month, year)
|
||||||
|
|
||||||
|
|
||||||
def TimetoISO8601(t, null_is_empty=False):
|
def TimetoISO8601(t, null_is_empty=False) -> str:
|
||||||
"convert time string to ISO 8601 (allow 16:03, 16h03, 16)"
|
"convert time string to ISO 8601 (allow 16:03, 16h03, 16)"
|
||||||
if isinstance(t, datetime.time):
|
if isinstance(t, datetime.time):
|
||||||
return t.isoformat()
|
return t.isoformat()
|
||||||
@ -518,7 +518,7 @@ def TimetoISO8601(t, null_is_empty=False):
|
|||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
def TimefromISO8601(t):
|
def TimefromISO8601(t) -> str:
|
||||||
"convert time string from ISO 8601 to our display format"
|
"convert time string from ISO 8601 to our display format"
|
||||||
if not t:
|
if not t:
|
||||||
return t
|
return t
|
||||||
@ -532,19 +532,6 @@ def TimefromISO8601(t):
|
|||||||
return fs[0] + "h" + fs[1] # discard seconds
|
return fs[0] + "h" + fs[1] # discard seconds
|
||||||
|
|
||||||
|
|
||||||
def TimeDuration(heure_debut, heure_fin):
|
|
||||||
"""duree (nb entier de minutes) entre deux heures a notre format
|
|
||||||
ie 12h23
|
|
||||||
"""
|
|
||||||
if heure_debut and heure_fin:
|
|
||||||
h0, m0 = [int(x) for x in heure_debut.split("h")]
|
|
||||||
h1, m1 = [int(x) for x in heure_fin.split("h")]
|
|
||||||
d = (h1 - h0) * 60 + (m1 - m0)
|
|
||||||
return d
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def float_null_is_zero(x):
|
def float_null_is_zero(x):
|
||||||
if x is None or x == "":
|
if x is None or x == "":
|
||||||
return 0.0
|
return 0.0
|
||||||
|
@ -318,7 +318,7 @@ def list_abs_in_range(
|
|||||||
Returns:
|
Returns:
|
||||||
List of absences
|
List of absences
|
||||||
"""
|
"""
|
||||||
if matin != None:
|
if matin is not None:
|
||||||
matin = _toboolean(matin)
|
matin = _toboolean(matin)
|
||||||
ismatin = " AND A.MATIN = %(matin)s "
|
ismatin = " AND A.MATIN = %(matin)s "
|
||||||
else:
|
else:
|
||||||
@ -387,7 +387,7 @@ def count_abs_just(etudid, debut, fin, matin=None, moduleimpl_id=None) -> int:
|
|||||||
Returns:
|
Returns:
|
||||||
An integer.
|
An integer.
|
||||||
"""
|
"""
|
||||||
if matin != None:
|
if matin is not None:
|
||||||
matin = _toboolean(matin)
|
matin = _toboolean(matin)
|
||||||
ismatin = " AND A.MATIN = %(matin)s "
|
ismatin = " AND A.MATIN = %(matin)s "
|
||||||
else:
|
else:
|
||||||
@ -482,7 +482,9 @@ def _get_abs_description(a, cursor=None):
|
|||||||
else:
|
else:
|
||||||
a["matin"] = False
|
a["matin"] = False
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""select * from absences where etudid=%(etudid)s and jour=%(jour)s and matin=%(matin)s order by entry_date desc""",
|
"""SELECT * FROM absences
|
||||||
|
WHERE etudid=%(etudid)s AND jour=%(jour)s AND matin=%(matin)s
|
||||||
|
ORDER BY entry_date desc""",
|
||||||
a,
|
a,
|
||||||
)
|
)
|
||||||
A = cursor.dictfetchall()
|
A = cursor.dictfetchall()
|
||||||
@ -507,7 +509,7 @@ def _get_abs_description(a, cursor=None):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def list_abs_jour(date, am=True, pm=True, is_abs=True, is_just=None):
|
def list_abs_jour(date, am=True, pm=True, is_abs=True, is_just=None) -> list[dict]:
|
||||||
"""Liste des absences et/ou justificatifs ce jour.
|
"""Liste des absences et/ou justificatifs ce jour.
|
||||||
is_abs: None (peu importe), True, False
|
is_abs: None (peu importe), True, False
|
||||||
is_just: idem
|
is_just: idem
|
||||||
@ -517,9 +519,9 @@ def list_abs_jour(date, am=True, pm=True, is_abs=True, is_just=None):
|
|||||||
req = """SELECT DISTINCT etudid, jour, matin FROM ABSENCES A
|
req = """SELECT DISTINCT etudid, jour, matin FROM ABSENCES A
|
||||||
WHERE A.jour = %(date)s
|
WHERE A.jour = %(date)s
|
||||||
"""
|
"""
|
||||||
if is_abs != None:
|
if is_abs is not None:
|
||||||
req += " AND A.estabs = %(is_abs)s"
|
req += " AND A.estabs = %(is_abs)s"
|
||||||
if is_just != None:
|
if is_just is not None:
|
||||||
req += " AND A.estjust = %(is_just)s"
|
req += " AND A.estjust = %(is_just)s"
|
||||||
if not am:
|
if not am:
|
||||||
req += " AND NOT matin "
|
req += " AND NOT matin "
|
||||||
@ -533,7 +535,7 @@ WHERE A.jour = %(date)s
|
|||||||
return A
|
return A
|
||||||
|
|
||||||
|
|
||||||
def list_abs_non_just_jour(date, am=True, pm=True):
|
def list_abs_non_just_jour(date, am=True, pm=True) -> list[dict]:
|
||||||
"Liste des absences non justifiees ce jour"
|
"Liste des absences non justifiees ce jour"
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
@ -883,7 +885,7 @@ def MonthTableBody(
|
|||||||
descr = ev[4]
|
descr = ev[4]
|
||||||
#
|
#
|
||||||
cc = []
|
cc = []
|
||||||
if color != None:
|
if color is not None:
|
||||||
cc.append('<td bgcolor="%s" class="calcell">' % color)
|
cc.append('<td bgcolor="%s" class="calcell">' % color)
|
||||||
else:
|
else:
|
||||||
cc.append('<td class="calcell">')
|
cc.append('<td class="calcell">')
|
||||||
@ -896,7 +898,7 @@ def MonthTableBody(
|
|||||||
cc.append("<a %s %s>" % (href, descr))
|
cc.append("<a %s %s>" % (href, descr))
|
||||||
|
|
||||||
if legend or d == 1:
|
if legend or d == 1:
|
||||||
if pad_width != None:
|
if pad_width is not None:
|
||||||
n = pad_width - len(legend) # pad to 8 cars
|
n = pad_width - len(legend) # pad to 8 cars
|
||||||
if n > 0:
|
if n > 0:
|
||||||
legend = (
|
legend = (
|
||||||
@ -959,7 +961,7 @@ def MonthTableBody(
|
|||||||
ev_year = int(ev[0][:4])
|
ev_year = int(ev[0][:4])
|
||||||
ev_month = int(ev[0][5:7])
|
ev_month = int(ev[0][5:7])
|
||||||
ev_day = int(ev[0][8:10])
|
ev_day = int(ev[0][8:10])
|
||||||
if ev[4] != None:
|
if ev[4] is not None:
|
||||||
ev_half = int(ev[4])
|
ev_half = int(ev[4])
|
||||||
else:
|
else:
|
||||||
ev_half = 0
|
ev_half = 0
|
||||||
@ -978,7 +980,7 @@ def MonthTableBody(
|
|||||||
if len(ev) > 5 and ev[5]:
|
if len(ev) > 5 and ev[5]:
|
||||||
descr = ev[5]
|
descr = ev[5]
|
||||||
#
|
#
|
||||||
if color != None:
|
if color is not None:
|
||||||
cc.append('<td bgcolor="%s" class="calcell">' % (color))
|
cc.append('<td bgcolor="%s" class="calcell">' % (color))
|
||||||
else:
|
else:
|
||||||
cc.append('<td class="calcell">')
|
cc.append('<td class="calcell">')
|
||||||
@ -1072,7 +1074,8 @@ def invalidate_abs_count_sem(sem):
|
|||||||
|
|
||||||
|
|
||||||
def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate
|
def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate
|
||||||
"""Doit etre appelé à chaque modification des absences pour cet étudiant et cette date.
|
"""Doit etre appelé à chaque modification des absences
|
||||||
|
pour cet étudiant et cette date.
|
||||||
Invalide cache absence et caches semestre
|
Invalide cache absence et caches semestre
|
||||||
date: date au format ISO
|
date: date au format ISO
|
||||||
"""
|
"""
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -321,9 +321,10 @@ def filter_by_formsemestre(
|
|||||||
def justifies(justi: Justificatif, obj: bool = False) -> list[int] or Query:
|
def justifies(justi: Justificatif, obj: bool = False) -> list[int] or Query:
|
||||||
"""
|
"""
|
||||||
Retourne la liste des assiduite_id qui sont justifié par la justification
|
Retourne la liste des assiduite_id qui sont justifié par la justification
|
||||||
Une assiduité est justifiée si elle est COMPLETEMENT ou PARTIELLEMENT comprise dans la plage du justificatif
|
Une assiduité est justifiée si elle est COMPLETEMENT ou PARTIELLEMENT
|
||||||
et que l'état du justificatif est "valide"
|
comprise dans la plage du justificatif
|
||||||
renvoie des id si obj == False, sinon les Assiduités
|
et que l'état du justificatif est "valide".
|
||||||
|
Renvoie des id si obj == False, sinon les Assiduités
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if justi.etat != scu.EtatJustificatif.VALIDE:
|
if justi.etat != scu.EtatJustificatif.VALIDE:
|
||||||
@ -341,7 +342,10 @@ def justifies(justi: Justificatif, obj: bool = False) -> list[int] or Query:
|
|||||||
|
|
||||||
|
|
||||||
def get_all_justified(
|
def get_all_justified(
|
||||||
etudid: int, date_deb: datetime = None, date_fin: datetime = None
|
etudid: int,
|
||||||
|
date_deb: datetime = None,
|
||||||
|
date_fin: datetime = None,
|
||||||
|
moduleimpl_id: int = False,
|
||||||
) -> Query:
|
) -> Query:
|
||||||
"""Retourne toutes les assiduités justifiées sur une période"""
|
"""Retourne toutes les assiduités justifiées sur une période"""
|
||||||
|
|
||||||
@ -359,11 +363,14 @@ def get_all_justified(
|
|||||||
date_deb,
|
date_deb,
|
||||||
date_fin,
|
date_fin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if moduleimpl_id is not False:
|
||||||
|
after = after.filter_by(moduleimpl_id=moduleimpl_id)
|
||||||
return after
|
return after
|
||||||
|
|
||||||
|
|
||||||
# Gestion du cache
|
# Gestion du cache
|
||||||
def get_assiduites_count(etudid, sem):
|
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]:
|
||||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
tuple (nb abs non justifiées, nb abs justifiées)
|
tuple (nb abs non justifiées, nb abs justifiées)
|
||||||
Utilise un cache.
|
Utilise un cache.
|
||||||
@ -377,27 +384,58 @@ def get_assiduites_count(etudid, sem):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def formsemestre_get_assiduites_count(
|
||||||
|
etudid: int,
|
||||||
|
formsemestre: FormSemestre,
|
||||||
|
moduleimpl_id: int = False,
|
||||||
|
cache: bool = True,
|
||||||
|
) -> tuple[int, int]:
|
||||||
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
|
tuple (nb abs non justifiées, nb abs justifiées)
|
||||||
|
Utilise par défaut un cache.
|
||||||
|
"""
|
||||||
|
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
|
||||||
|
return get_assiduites_count_in_interval(
|
||||||
|
etudid,
|
||||||
|
date_debut_iso=formsemestre.date_debut.isoformat(),
|
||||||
|
date_fin_iso=formsemestre.date_fin.isoformat(),
|
||||||
|
metrique=scu.translate_assiduites_metric(metrique),
|
||||||
|
cache=cache,
|
||||||
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_assiduites_count_in_interval(
|
def get_assiduites_count_in_interval(
|
||||||
etudid, date_debut_iso, date_fin_iso, metrique="demi"
|
etudid,
|
||||||
|
date_debut_iso: str = "",
|
||||||
|
date_fin_iso: str = "",
|
||||||
|
metrique="demi",
|
||||||
|
date_debut: datetime = None,
|
||||||
|
date_fin: datetime = None,
|
||||||
|
cache: bool = True,
|
||||||
|
moduleimpl_id: int = False,
|
||||||
):
|
):
|
||||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||||
tuple (nb abs, nb abs justifiées)
|
tuple (nb abs, nb abs justifiées)
|
||||||
Utilise un cache.
|
On peut spécifier les dates comme datetime ou iso.
|
||||||
|
Utilise par défaut un cache.
|
||||||
"""
|
"""
|
||||||
key = (
|
date_debut_iso = date_debut_iso or date_debut.isoformat()
|
||||||
str(etudid)
|
date_fin_iso = date_fin_iso or date_fin.isoformat()
|
||||||
+ "_"
|
key = f"{etudid}_{date_debut_iso}_{date_fin_iso}{metrique}_assiduites"
|
||||||
+ date_debut_iso
|
if moduleimpl_id is not False:
|
||||||
+ "_"
|
key += f"_module_{moduleimpl_id}"
|
||||||
+ date_fin_iso
|
|
||||||
+ f"{metrique}_assiduites"
|
|
||||||
)
|
|
||||||
r = sco_cache.AbsSemEtudCache.get(key)
|
|
||||||
if not r:
|
|
||||||
date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True)
|
|
||||||
date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True)
|
|
||||||
|
|
||||||
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
r = False if cache is False else sco_cache.AbsSemEtudCache.get(key)
|
||||||
|
if not r:
|
||||||
|
date_debut: datetime = date_debut or datetime.fromisoformat(date_debut_iso)
|
||||||
|
date_fin: datetime = date_fin or datetime.fromisoformat(date_fin_iso)
|
||||||
|
|
||||||
|
assiduites: Assiduite = (
|
||||||
|
Assiduite.query.filter_by(etudid=etudid)
|
||||||
|
if moduleimpl_id is False
|
||||||
|
else Assiduite.query.filter_by(etudid=etudid, moduleimpl_id=moduleimpl_id)
|
||||||
|
)
|
||||||
assiduites = assiduites.filter(Assiduite.etat == scu.EtatAssiduite.ABSENT)
|
assiduites = assiduites.filter(Assiduite.etat == scu.EtatAssiduite.ABSENT)
|
||||||
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
|
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
|
||||||
|
|
||||||
@ -410,13 +448,17 @@ def get_assiduites_count_in_interval(
|
|||||||
calculator.compute_assiduites(assiduites)
|
calculator.compute_assiduites(assiduites)
|
||||||
nb_abs: dict = calculator.to_dict()[metrique]
|
nb_abs: dict = calculator.to_dict()[metrique]
|
||||||
|
|
||||||
abs_just: list[Assiduite] = get_all_justified(etudid, date_debut, date_fin)
|
abs_just: list[Assiduite] = get_all_justified(
|
||||||
|
etudid, date_debut, date_fin, moduleimpl_id=moduleimpl_id
|
||||||
|
)
|
||||||
|
|
||||||
calculator.reset()
|
calculator.reset()
|
||||||
calculator.compute_assiduites(abs_just)
|
calculator.compute_assiduites(abs_just)
|
||||||
nb_abs_just: dict = calculator.to_dict()[metrique]
|
nb_abs_just: dict = calculator.to_dict()[metrique]
|
||||||
|
|
||||||
r = (nb_abs, nb_abs_just)
|
r = (nb_abs, nb_abs_just)
|
||||||
|
|
||||||
|
if cache:
|
||||||
ans = sco_cache.AbsSemEtudCache.set(key, r)
|
ans = sco_cache.AbsSemEtudCache.set(key, r)
|
||||||
if not ans:
|
if not ans:
|
||||||
log("warning: get_assiduites_count failed to cache")
|
log("warning: get_assiduites_count failed to cache")
|
||||||
@ -427,7 +469,7 @@ def invalidate_assiduites_count(etudid, sem):
|
|||||||
"""Invalidate (clear) cached counts"""
|
"""Invalidate (clear) cached counts"""
|
||||||
date_debut = sem["date_debut_iso"]
|
date_debut = sem["date_debut_iso"]
|
||||||
date_fin = sem["date_fin_iso"]
|
date_fin = sem["date_fin_iso"]
|
||||||
for met in [string.lower() for string in scu.AssiduitesMetricShort.all()]:
|
for met in scu.AssiduitesMetrics.TAG:
|
||||||
key = str(etudid) + "_" + date_debut + "_" + date_fin + f"{met}_assiduites"
|
key = str(etudid) + "_" + date_debut + "_" + date_fin + f"{met}_assiduites"
|
||||||
sco_cache.AbsSemEtudCache.delete(key)
|
sco_cache.AbsSemEtudCache.delete(key)
|
||||||
|
|
||||||
@ -444,9 +486,9 @@ def invalidate_assiduites_count_sem(sem):
|
|||||||
|
|
||||||
|
|
||||||
def invalidate_assiduites_etud_date(etudid, date: datetime):
|
def invalidate_assiduites_etud_date(etudid, date: datetime):
|
||||||
"""Doit etre appelé à chaque modification des assiduites pour cet étudiant et cette date.
|
"""Doit etre appelé à chaque modification des assiduites
|
||||||
|
pour cet étudiant et cette date.
|
||||||
Invalide cache absence et caches semestre
|
Invalide cache absence et caches semestre
|
||||||
date: date au format ISO
|
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_compute_moy
|
from app.scodoc import sco_compute_moy
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ from app.comp.res_but import ResultatsSemestreBUT
|
|||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import (
|
from app.models import (
|
||||||
ApcParcours,
|
ApcParcours,
|
||||||
|
Evaluation,
|
||||||
Formation,
|
Formation,
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
Identite,
|
Identite,
|
||||||
@ -57,14 +58,12 @@ from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
|||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
from app.scodoc import sco_assiduites
|
from app.scodoc import sco_assiduites
|
||||||
from app.scodoc import sco_abs_views
|
|
||||||
from app.scodoc import sco_bulletins_generator
|
from app.scodoc import sco_bulletins_generator
|
||||||
from app.scodoc import sco_bulletins_json
|
from app.scodoc import sco_bulletins_json
|
||||||
from app.scodoc import sco_bulletins_pdf
|
from app.scodoc import sco_bulletins_pdf
|
||||||
from app.scodoc import sco_bulletins_xml
|
from app.scodoc import sco_bulletins_xml
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
@ -482,6 +481,7 @@ def _ue_mod_bulletin(
|
|||||||
mods = [] # result
|
mods = [] # result
|
||||||
ue_attente = False # true si une eval en attente dans cette UE
|
ue_attente = False # true si une eval en attente dans cette UE
|
||||||
for modimpl in ue_modimpls:
|
for modimpl in ue_modimpls:
|
||||||
|
modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"])
|
||||||
mod_attente = False
|
mod_attente = False
|
||||||
mod = modimpl.copy()
|
mod = modimpl.copy()
|
||||||
mod_moy = nt.get_etud_mod_moy(
|
mod_moy = nt.get_etud_mod_moy(
|
||||||
@ -531,10 +531,13 @@ def _ue_mod_bulletin(
|
|||||||
scu.fmt_coef(modimpl["module"]["coefficient"]),
|
scu.fmt_coef(modimpl["module"]["coefficient"]),
|
||||||
sco_users.user_info(modimpl["responsable_id"])["nomcomplet"],
|
sco_users.user_info(modimpl["responsable_id"])["nomcomplet"],
|
||||||
)
|
)
|
||||||
link_mod = (
|
link_mod = f"""<a class="bull_link" href="{
|
||||||
'<a class="bull_link" href="moduleimpl_status?moduleimpl_id=%s" title="%s">'
|
url_for("notes.moduleimpl_status",
|
||||||
% (modimpl["moduleimpl_id"], mod["mod_descr_txt"])
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=modimpl["moduleimpl_id"]
|
||||||
)
|
)
|
||||||
|
}" title="{mod["mod_descr_txt"]}">"""
|
||||||
|
|
||||||
if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id):
|
if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id):
|
||||||
mod["code"] = modimpl["module"]["code"]
|
mod["code"] = modimpl["module"]["code"]
|
||||||
mod["code_html"] = link_mod + (mod["code"] or "") + "</a>"
|
mod["code_html"] = link_mod + (mod["code"] or "") + "</a>"
|
||||||
@ -561,91 +564,88 @@ def _ue_mod_bulletin(
|
|||||||
mod["code_txt"] = ""
|
mod["code_txt"] = ""
|
||||||
mod["code_html"] = ""
|
mod["code_html"] = ""
|
||||||
# Evaluations: notes de chaque eval
|
# Evaluations: notes de chaque eval
|
||||||
evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"])
|
evaluations_completes = nt.get_modimpl_evaluations_completes(
|
||||||
|
modimpl["moduleimpl_id"]
|
||||||
|
)
|
||||||
|
# On liste séparément les éval. complètes ou non
|
||||||
mod["evaluations"] = []
|
mod["evaluations"] = []
|
||||||
for e in evals:
|
mod["evaluations_incompletes"] = []
|
||||||
e = e.copy()
|
complete_eval_ids = {e.id for e in evaluations_completes}
|
||||||
if e["visibulletin"] or version == "long":
|
all_evals: list[Evaluation] = Evaluation.query.filter_by(
|
||||||
# affiche "bonus" quand les points de malus sont négatifs
|
moduleimpl_id=modimpl["moduleimpl_id"]
|
||||||
|
).order_by(Evaluation.numero, Evaluation.date_debut)
|
||||||
|
# (plus ancienne d'abord)
|
||||||
|
for e in all_evals:
|
||||||
|
if not e.visibulletin and version != "long":
|
||||||
|
continue
|
||||||
|
is_complete = e.id in complete_eval_ids
|
||||||
|
e_dict = e.to_dict_bul()
|
||||||
|
# Note à l'évaluation:
|
||||||
|
val = modimpl_results.evals_notes[e.id].get(etudid, "NP")
|
||||||
|
# Affiche "bonus" quand les points de malus sont négatifs
|
||||||
if is_malus:
|
if is_malus:
|
||||||
val = e["notes"].get(etudid, {"value": "NP"})[
|
if val == "NP":
|
||||||
"value"
|
e_dict["name"] = "Points de bonus/malus sur cette UE"
|
||||||
] # NA si etud demissionnaire
|
elif val > 0:
|
||||||
if val == "NP" or val > 0:
|
e_dict["name"] = "Points de malus sur cette UE"
|
||||||
e["name"] = "Points de malus sur cette UE"
|
|
||||||
else:
|
else:
|
||||||
e["name"] = "Points de bonus sur cette UE"
|
e_dict["name"] = "Points de bonus sur cette UE"
|
||||||
else:
|
else:
|
||||||
e["name"] = e["description"] or f"le {e['jour']}"
|
e_dict[
|
||||||
e["target_html"] = url_for(
|
"name"
|
||||||
|
] = f"""{e.description or ""} {
|
||||||
|
e.descr_date()
|
||||||
|
if e.date_debut and not is_complete
|
||||||
|
else ""}"""
|
||||||
|
e_dict["target_html"] = url_for(
|
||||||
"notes.evaluation_listenotes",
|
"notes.evaluation_listenotes",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
evaluation_id=e["evaluation_id"],
|
evaluation_id=e.id,
|
||||||
format="html",
|
format="html",
|
||||||
tf_submitted=1,
|
tf_submitted=1,
|
||||||
)
|
)
|
||||||
e[
|
e_dict[
|
||||||
"name_html"
|
"name_html"
|
||||||
] = f"""<a class="bull_link" href="{
|
] = f"""<a class="bull_link" href="{
|
||||||
e['target_html']}">{e['name']}</a>"""
|
e_dict['target_html']}">{e_dict['name']}</a>"""
|
||||||
val = e["notes"].get(etudid, {"value": "NP"})["value"]
|
if is_complete: # évaluation complète
|
||||||
# val est NP si etud demissionnaire
|
# val est NP si etud demissionnaire
|
||||||
if val == "NP":
|
if val == "NP":
|
||||||
e["note_txt"] = "nd"
|
e_dict["note_txt"] = "nd"
|
||||||
e["note_html"] = '<span class="note_nd">nd</span>'
|
e_dict["note_html"] = '<span class="note_nd">nd</span>'
|
||||||
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
|
e_dict["coef_txt"] = scu.fmt_coef(e["coefficient"])
|
||||||
else:
|
else:
|
||||||
# (-0.15) s'affiche "bonus de 0.15"
|
# (-0.15) s'affiche "bonus de 0.15"
|
||||||
if is_malus:
|
if is_malus:
|
||||||
val = abs(val)
|
val = abs(val)
|
||||||
e["note_txt"] = scu.fmt_note(val, note_max=e["note_max"])
|
e_dict["note_txt"] = e_dict["note_html"] = scu.fmt_note(
|
||||||
e["note_html"] = e["note_txt"]
|
val, note_max=e.note_max
|
||||||
if is_malus:
|
|
||||||
e["coef_txt"] = ""
|
|
||||||
else:
|
|
||||||
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
|
|
||||||
if e["evaluation_type"] == scu.EVALUATION_RATTRAPAGE:
|
|
||||||
e["coef_txt"] = "rat."
|
|
||||||
elif e["evaluation_type"] == scu.EVALUATION_SESSION2:
|
|
||||||
e["coef_txt"] = "Ses. 2"
|
|
||||||
if e["etat"]["evalattente"]:
|
|
||||||
mod_attente = True # une eval en attente dans ce module
|
|
||||||
if ((not is_malus) or (val != "NP")) and (
|
|
||||||
(
|
|
||||||
e["evaluation_type"] == scu.EVALUATION_NORMALE
|
|
||||||
or not np.isnan(val)
|
|
||||||
)
|
)
|
||||||
|
else: # évaluation incomplète: pas de note
|
||||||
|
e_dict["note_txt"] = e_dict["note_html"] = ""
|
||||||
|
|
||||||
|
if is_malus:
|
||||||
|
e_dict["coef_txt"] = ""
|
||||||
|
else:
|
||||||
|
e_dict["coef_txt"] = scu.fmt_coef(e.coefficient)
|
||||||
|
if e.evaluation_type == scu.EVALUATION_RATTRAPAGE:
|
||||||
|
e_dict["coef_txt"] = "rat."
|
||||||
|
elif e.evaluation_type == scu.EVALUATION_SESSION2:
|
||||||
|
e_dict["coef_txt"] = "Ses. 2"
|
||||||
|
|
||||||
|
if modimpl_results.evaluations_etat[e.id].nb_attente:
|
||||||
|
mod_attente = True # une eval en attente dans ce module
|
||||||
|
|
||||||
|
if ((not is_malus) or (val != "NP")) and (
|
||||||
|
(e.evaluation_type == scu.EVALUATION_NORMALE or not np.isnan(val))
|
||||||
):
|
):
|
||||||
# ne liste pas les eval malus sans notes
|
# ne liste pas les eval malus sans notes
|
||||||
# ni les rattrapages et sessions 2 si pas de note
|
# ni les rattrapages et sessions 2 si pas de note
|
||||||
mod["evaluations"].append(e)
|
if e.id in complete_eval_ids:
|
||||||
|
mod["evaluations"].append(e_dict)
|
||||||
|
else:
|
||||||
|
mod["evaluations_incompletes"].append(e_dict)
|
||||||
|
|
||||||
# Evaluations incomplètes ou futures:
|
|
||||||
mod["evaluations_incompletes"] = []
|
|
||||||
if sco_preferences.get_preference("bul_show_all_evals", formsemestre_id):
|
|
||||||
complete_eval_ids = set([e["evaluation_id"] for e in evals])
|
|
||||||
all_evals = sco_evaluation_db.do_evaluation_list(
|
|
||||||
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
|
|
||||||
)
|
|
||||||
all_evals.reverse() # plus ancienne d'abord
|
|
||||||
for e in all_evals:
|
|
||||||
if e["evaluation_id"] not in complete_eval_ids:
|
|
||||||
e = e.copy()
|
|
||||||
mod["evaluations_incompletes"].append(e)
|
|
||||||
e["name"] = (e["description"] or "") + " (%s)" % e["jour"]
|
|
||||||
e["target_html"] = url_for(
|
|
||||||
"notes.evaluation_listenotes",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
evaluation_id=e["evaluation_id"],
|
|
||||||
tf_submitted=1,
|
|
||||||
format="html",
|
|
||||||
)
|
|
||||||
e["name_html"] = '<a class="bull_link" href="%s">%s</a>' % (
|
|
||||||
e["target_html"],
|
|
||||||
e["name"],
|
|
||||||
)
|
|
||||||
e["note_txt"] = e["note_html"] = ""
|
|
||||||
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
|
|
||||||
# Classement
|
# Classement
|
||||||
if (
|
if (
|
||||||
bul_show_mod_rangs
|
bul_show_mod_rangs
|
||||||
@ -1114,9 +1114,10 @@ def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
|||||||
hea = ""
|
hea = ""
|
||||||
|
|
||||||
if sco_preferences.get_preference("bul_mail_list_abs"):
|
if sco_preferences.get_preference("bul_mail_list_abs"):
|
||||||
hea += "\n\n" + sco_abs_views.ListeAbsEtud(
|
hea += "\n\n" + "(LISTE D'ABSENCES NON DISPONIBLE)" # XXX TODO-ASSIDUITE
|
||||||
etud["etudid"], with_evals=False, format="text"
|
# sco_abs_views.ListeAbsEtud(
|
||||||
)
|
# etud["etudid"], with_evals=False, format="text"
|
||||||
|
# )
|
||||||
|
|
||||||
subject = f"""Relevé de notes de {etud["nomprenom"]}"""
|
subject = f"""Relevé de notes de {etud["nomprenom"]}"""
|
||||||
recipients = [recipient_addr]
|
recipients = [recipient_addr]
|
||||||
|
@ -37,7 +37,7 @@ from app import db, ScoDocJSONEncoder
|
|||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import but_validations
|
from app.models import but_validations
|
||||||
from app.models import Matiere, ModuleImpl, UniteEns
|
from app.models import Evaluation, Matiere, UniteEns
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
|
||||||
@ -46,7 +46,6 @@ import app.scodoc.notesdb as ndb
|
|||||||
from app.scodoc import sco_assiduites
|
from app.scodoc import sco_assiduites
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
@ -112,7 +111,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
if not etudid in nt.identdict:
|
if etudid not in nt.identdict:
|
||||||
abort(404, "etudiant non inscrit dans ce semestre")
|
abort(404, "etudiant non inscrit dans ce semestre")
|
||||||
d = {"type": "classic", "version": "0"}
|
d = {"type": "classic", "version": "0"}
|
||||||
if (not sem["bul_hide_xml"]) or force_publishing:
|
if (not sem["bul_hide_xml"]) or force_publishing:
|
||||||
@ -324,7 +323,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
def _list_modimpls(
|
def _list_modimpls(
|
||||||
nt: NotesTableCompat,
|
nt: NotesTableCompat,
|
||||||
etudid: int,
|
etudid: int,
|
||||||
modimpls: list[ModuleImpl],
|
modimpls: list[dict],
|
||||||
prefs: SemPreferences,
|
prefs: SemPreferences,
|
||||||
version: str,
|
version: str,
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
@ -333,6 +332,7 @@ def _list_modimpls(
|
|||||||
mod_moy = scu.fmt_note(nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid))
|
mod_moy = scu.fmt_note(nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid))
|
||||||
if mod_moy == "NI": # ne mentionne pas les modules ou n'est pas inscrit
|
if mod_moy == "NI": # ne mentionne pas les modules ou n'est pas inscrit
|
||||||
continue
|
continue
|
||||||
|
modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"])
|
||||||
mod = modimpl["module"]
|
mod = modimpl["module"]
|
||||||
# if mod['ects'] is None:
|
# if mod['ects'] is None:
|
||||||
# ects = ''
|
# ects = ''
|
||||||
@ -363,61 +363,42 @@ def _list_modimpls(
|
|||||||
mod_dict["effectif"] = dict(value=nt.mod_rangs[modimpl["moduleimpl_id"]][1])
|
mod_dict["effectif"] = dict(value=nt.mod_rangs[modimpl["moduleimpl_id"]][1])
|
||||||
|
|
||||||
# --- notes de chaque eval:
|
# --- notes de chaque eval:
|
||||||
evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"])
|
evaluations_completes = nt.get_modimpl_evaluations_completes(
|
||||||
|
modimpl["moduleimpl_id"]
|
||||||
|
)
|
||||||
mod_dict["evaluation"] = []
|
mod_dict["evaluation"] = []
|
||||||
if version != "short":
|
if version != "short":
|
||||||
for e in evals:
|
for e in evaluations_completes:
|
||||||
if e["visibulletin"] or version == "long":
|
if e.visibulletin or version == "long":
|
||||||
val = e["notes"].get(etudid, {"value": "NP"})["value"]
|
# Note à l'évaluation:
|
||||||
|
val = modimpl_results.evals_notes[e.id].get(etudid, "NP")
|
||||||
# nb: val est NA si etud démissionnaire
|
# nb: val est NA si etud démissionnaire
|
||||||
val = scu.fmt_note(val, note_max=e["note_max"])
|
e_dict = e.to_dict_bul()
|
||||||
eval_dict = dict(
|
e_dict["note"] = scu.fmt_note(val, note_max=e.note_max)
|
||||||
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
|
||||||
heure_debut=ndb.TimetoISO8601(
|
|
||||||
e["heure_debut"], null_is_empty=True
|
|
||||||
),
|
|
||||||
heure_fin=ndb.TimetoISO8601(e["heure_fin"], null_is_empty=True),
|
|
||||||
coefficient=e["coefficient"],
|
|
||||||
evaluation_type=e["evaluation_type"],
|
|
||||||
# CM : ajout pour permettre de faire le lien sur
|
|
||||||
# les bulletins en ligne avec l'évaluation:
|
|
||||||
evaluation_id=e["evaluation_id"],
|
|
||||||
description=quote_xml_attr(e["description"]),
|
|
||||||
note=val,
|
|
||||||
)
|
|
||||||
if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]:
|
|
||||||
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
|
||||||
if prefs["bul_show_minmax_eval"]:
|
|
||||||
eval_dict["min"] = etat["mini"] # chaine, sur 20
|
|
||||||
eval_dict["max"] = etat["maxi"]
|
|
||||||
if prefs["bul_show_moypromo"]:
|
|
||||||
eval_dict["moy"] = etat["moy"]
|
|
||||||
|
|
||||||
mod_dict["evaluation"].append(eval_dict)
|
if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]:
|
||||||
|
# XXX à revoir pour utiliser modimplresult
|
||||||
|
etat = sco_evaluations.do_evaluation_etat(e.id)
|
||||||
|
if prefs["bul_show_minmax_eval"]:
|
||||||
|
e_dict["min"] = etat["mini"] # chaine, sur 20
|
||||||
|
e_dict["max"] = etat["maxi"]
|
||||||
|
if prefs["bul_show_moypromo"]:
|
||||||
|
e_dict["moy"] = etat["moy"]
|
||||||
|
|
||||||
|
mod_dict["evaluation"].append(e_dict)
|
||||||
|
|
||||||
# Evaluations incomplètes ou futures:
|
# Evaluations incomplètes ou futures:
|
||||||
complete_eval_ids = set([e["evaluation_id"] for e in evals])
|
complete_eval_ids = {e.id for e in evaluations_completes}
|
||||||
if prefs["bul_show_all_evals"]:
|
if prefs["bul_show_all_evals"]:
|
||||||
all_evals = sco_evaluation_db.do_evaluation_list(
|
evaluations: list[Evaluation] = Evaluation.query.filter_by(
|
||||||
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
|
moduleimpl_id=modimpl["moduleimpl_id"]
|
||||||
)
|
).order_by(Evaluation.date_debut)
|
||||||
all_evals.reverse() # plus ancienne d'abord
|
# plus ancienne d'abord
|
||||||
for e in all_evals:
|
for e in evaluations:
|
||||||
if e["evaluation_id"] not in complete_eval_ids:
|
if e.id not in complete_eval_ids:
|
||||||
mod_dict["evaluation"].append(
|
e_dict = e.to_dict_bul()
|
||||||
dict(
|
e_dict["incomplete"] = 1
|
||||||
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
mod_dict["evaluation"].append(e_dict)
|
||||||
heure_debut=ndb.TimetoISO8601(
|
|
||||||
e["heure_debut"], null_is_empty=True
|
|
||||||
),
|
|
||||||
heure_fin=ndb.TimetoISO8601(
|
|
||||||
e["heure_fin"], null_is_empty=True
|
|
||||||
),
|
|
||||||
coefficient=e["coefficient"],
|
|
||||||
description=quote_xml_attr(e["description"]),
|
|
||||||
incomplete="1",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
modules_dict.append(mod_dict)
|
modules_dict.append(mod_dict)
|
||||||
return modules_dict
|
return modules_dict
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ from app.scodoc import sco_pdf
|
|||||||
from app.scodoc.sco_pdf import SU
|
from app.scodoc.sco_pdf import SU
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
from flask import url_for, g
|
||||||
|
|
||||||
|
|
||||||
# Important: Le nom de la classe ne doit pas changer (bien le choisir), car il sera stocké en base de données (dans les préférences)
|
# Important: Le nom de la classe ne doit pas changer (bien le choisir), car il sera stocké en base de données (dans les préférences)
|
||||||
@ -132,11 +133,14 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
|
|||||||
if sco_preferences.get_preference(
|
if sco_preferences.get_preference(
|
||||||
"bul_show_minmax_mod", formsemestre_id
|
"bul_show_minmax_mod", formsemestre_id
|
||||||
):
|
):
|
||||||
rang_minmax = '%s <span class="bul_minmax" title="[min, max] UE">[%s, %s]</span>' % (
|
rang_minmax = (
|
||||||
|
'%s <span class="bul_minmax" title="[min, max] UE">[%s, %s]</span>'
|
||||||
|
% (
|
||||||
mod["mod_rang_txt"],
|
mod["mod_rang_txt"],
|
||||||
scu.fmt_note(mod["stats"]["min"]),
|
scu.fmt_note(mod["stats"]["min"]),
|
||||||
scu.fmt_note(mod["stats"]["max"]),
|
scu.fmt_note(mod["stats"]["max"]),
|
||||||
)
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
rang_minmax = mod["mod_rang_txt"] # vide si pas option rang
|
rang_minmax = mod["mod_rang_txt"] # vide si pas option rang
|
||||||
H.append(
|
H.append(
|
||||||
@ -302,8 +306,8 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
|
|||||||
H = []
|
H = []
|
||||||
# --- Absences
|
# --- Absences
|
||||||
H.append(
|
H.append(
|
||||||
"""<p>
|
f"""<p>
|
||||||
<a href="../Absences/CalAbs?etudid=%(etudid)s" class="bull_link">
|
<a href="{ url_for('assiduites.calendrier_etud', scodoc_dept=g.scodoc_dept, etudid=self.infos["etudid"]) }" class="bull_link">
|
||||||
<b>Absences :</b> %(nbabs)s demi-journées, dont %(nbabsjust)s justifiées
|
<b>Absences :</b> %(nbabs)s demi-journées, dont %(nbabsjust)s justifiées
|
||||||
(pendant ce semestre).
|
(pendant ce semestre).
|
||||||
</a></p>
|
</a></p>
|
||||||
|
@ -125,8 +125,8 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
story.append(Spacer(1, 2 * mm))
|
story.append(Spacer(1, 2 * mm))
|
||||||
if nbabs:
|
if nbabs:
|
||||||
H.append(
|
H.append(
|
||||||
"""<p class="bul_abs">
|
f"""<p class="bul_abs">
|
||||||
<a href="../Absences/CalAbs?etudid=%(etudid)s" class="bull_link">
|
<a href="{ url_for('assiduites.calendrier_etud', scodoc_dept=g.scodoc_dept, etudid=self.infos["etudid"]) }" class="bull_link">
|
||||||
<b>Absences :</b> %(nbabs)s demi-journées, dont %(nbabsjust)s justifiées
|
<b>Absences :</b> %(nbabs)s demi-journées, dont %(nbabsjust)s justifiées
|
||||||
(pendant ce semestre).
|
(pendant ce semestre).
|
||||||
</a></p>
|
</a></p>
|
||||||
|
@ -50,11 +50,11 @@ import app.scodoc.sco_utils as scu
|
|||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
from app.but.bulletin_but_xml_compat import bulletin_but_xml_compat
|
from app.but.bulletin_but_xml_compat import bulletin_but_xml_compat
|
||||||
|
from app.models.evaluations import Evaluation
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
from app.scodoc import sco_assiduites
|
from app.scodoc import sco_assiduites
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
@ -242,6 +242,7 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
# Liste les modules de l'UE
|
# Liste les modules de l'UE
|
||||||
ue_modimpls = [mod for mod in modimpls if mod["module"]["ue_id"] == ue["ue_id"]]
|
ue_modimpls = [mod for mod in modimpls if mod["module"]["ue_id"] == ue["ue_id"]]
|
||||||
for modimpl in ue_modimpls:
|
for modimpl in ue_modimpls:
|
||||||
|
modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"])
|
||||||
mod_moy = scu.fmt_note(
|
mod_moy = scu.fmt_note(
|
||||||
nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
||||||
)
|
)
|
||||||
@ -290,57 +291,34 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
# --- notes de chaque eval:
|
# --- notes de chaque eval:
|
||||||
evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"])
|
evaluations_completes = nt.get_modimpl_evaluations_completes(
|
||||||
if version != "short":
|
modimpl["moduleimpl_id"]
|
||||||
for e in evals:
|
|
||||||
if e["visibulletin"] or version == "long":
|
|
||||||
x_eval = Element(
|
|
||||||
"evaluation",
|
|
||||||
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
|
||||||
heure_debut=ndb.TimetoISO8601(
|
|
||||||
e["heure_debut"], null_is_empty=True
|
|
||||||
),
|
|
||||||
heure_fin=ndb.TimetoISO8601(
|
|
||||||
e["heure_fin"], null_is_empty=True
|
|
||||||
),
|
|
||||||
coefficient=str(e["coefficient"]),
|
|
||||||
evaluation_type=str(e["evaluation_type"]),
|
|
||||||
description=quote_xml_attr(e["description"]),
|
|
||||||
# notes envoyées sur 20, ceci juste pour garder trace:
|
|
||||||
note_max_origin=str(e["note_max"]),
|
|
||||||
)
|
)
|
||||||
|
if version != "short":
|
||||||
|
for e in evaluations_completes:
|
||||||
|
if e.visibulletin or version == "long":
|
||||||
|
# pour xml, tout convertir en chaines
|
||||||
|
e_dict = {k: str(v) for k, v in e.to_dict_bul().items()}
|
||||||
|
# notes envoyées sur 20, ceci juste pour garder trace:
|
||||||
|
e_dict["note_max_origin"] = str(e.note_max)
|
||||||
|
x_eval = Element("evaluation", **e_dict)
|
||||||
x_mod.append(x_eval)
|
x_mod.append(x_eval)
|
||||||
val = e["notes"].get(etudid, {"value": "NP"})[
|
# Note à l'évaluation:
|
||||||
"value"
|
val = modimpl_results.evals_notes[e.id].get(etudid, "NP")
|
||||||
] # NA si etud demissionnaire
|
val = scu.fmt_note(val, note_max=e.note_max)
|
||||||
val = scu.fmt_note(val, note_max=e["note_max"])
|
|
||||||
x_eval.append(Element("note", value=val))
|
x_eval.append(Element("note", value=val))
|
||||||
# Evaluations incomplètes ou futures:
|
# Evaluations incomplètes ou futures:
|
||||||
complete_eval_ids = set([e["evaluation_id"] for e in evals])
|
complete_eval_ids = {e.id for e in evaluations_completes}
|
||||||
if sco_preferences.get_preference(
|
if sco_preferences.get_preference(
|
||||||
"bul_show_all_evals", formsemestre_id
|
"bul_show_all_evals", formsemestre_id
|
||||||
):
|
):
|
||||||
all_evals = sco_evaluation_db.do_evaluation_list(
|
evaluations = Evaluation.query.filter_by(
|
||||||
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
|
moduleimpl_id=modimpl["moduleimpl_id"]
|
||||||
)
|
).order_by(Evaluation.date_debut)
|
||||||
all_evals.reverse() # plus ancienne d'abord
|
for e in evaluations:
|
||||||
for e in all_evals:
|
if e.id not in complete_eval_ids:
|
||||||
if e["evaluation_id"] not in complete_eval_ids:
|
e_dict = e.to_dict_bul()
|
||||||
x_eval = Element(
|
x_eval = Element("evaluation", **e_dict)
|
||||||
"evaluation",
|
|
||||||
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
|
||||||
heure_debut=ndb.TimetoISO8601(
|
|
||||||
e["heure_debut"], null_is_empty=True
|
|
||||||
),
|
|
||||||
heure_fin=ndb.TimetoISO8601(
|
|
||||||
e["heure_fin"], null_is_empty=True
|
|
||||||
),
|
|
||||||
coefficient=str(e["coefficient"]),
|
|
||||||
description=quote_xml_attr(e["description"]),
|
|
||||||
incomplete="1",
|
|
||||||
# notes envoyées sur 20, ceci juste pour garder trace:
|
|
||||||
note_max_origin=str(e["note_max"] or ""),
|
|
||||||
)
|
|
||||||
x_mod.append(x_eval)
|
x_mod.append(x_eval)
|
||||||
# UE capitalisee (listee seulement si meilleure que l'UE courante)
|
# UE capitalisee (listee seulement si meilleure que l'UE courante)
|
||||||
if ue_status["is_capitalized"]:
|
if ue_status["is_capitalized"]:
|
||||||
|
@ -273,9 +273,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
|||||||
|
|
||||||
if formsemestre_id is None:
|
if formsemestre_id is None:
|
||||||
# clear all caches
|
# clear all caches
|
||||||
log(
|
log(f"--- invalidate_formsemestre: clearing all caches. pdfonly={pdfonly}---")
|
||||||
f"----- invalidate_formsemestre: clearing all caches. pdfonly={pdfonly}-----"
|
|
||||||
)
|
|
||||||
formsemestre_ids = [
|
formsemestre_ids = [
|
||||||
formsemestre.id
|
formsemestre.id
|
||||||
for formsemestre in FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id)
|
for formsemestre in FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
@ -285,7 +283,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
|||||||
formsemestre_id
|
formsemestre_id
|
||||||
] + sco_cursus.list_formsemestre_utilisateurs_uecap(formsemestre_id)
|
] + sco_cursus.list_formsemestre_utilisateurs_uecap(formsemestre_id)
|
||||||
log(
|
log(
|
||||||
f"----- invalidate_formsemestre: clearing {formsemestre_ids}. pdfonly={pdfonly} -----"
|
f"--- invalidate_formsemestre: clearing {formsemestre_ids}. pdfonly={pdfonly} ---"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not pdfonly:
|
if not pdfonly:
|
||||||
|
511
app/scodoc/sco_cal.py
Normal file
511
app/scodoc/sco_cal.py
Normal file
@ -0,0 +1,511 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gestion scolarite IUT
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Génération calendrier (ancienne présentation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import calendar
|
||||||
|
import datetime
|
||||||
|
import html
|
||||||
|
import time
|
||||||
|
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidDateError
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
|
def is_work_saturday():
|
||||||
|
"Vrai si le samedi est travaillé"
|
||||||
|
return int(sco_preferences.get_preference("work_saturday"))
|
||||||
|
|
||||||
|
|
||||||
|
def MonthNbDays(month, year):
|
||||||
|
"returns nb of days in month"
|
||||||
|
if month > 7:
|
||||||
|
month = month + 1
|
||||||
|
if month % 2:
|
||||||
|
return 31
|
||||||
|
elif month == 2:
|
||||||
|
if calendar.isleap(year):
|
||||||
|
return 29
|
||||||
|
else:
|
||||||
|
return 28
|
||||||
|
else:
|
||||||
|
return 30
|
||||||
|
|
||||||
|
|
||||||
|
class ddmmyyyy(object):
|
||||||
|
"""immutable dates"""
|
||||||
|
|
||||||
|
def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False):
|
||||||
|
self.work_saturday = work_saturday
|
||||||
|
if date is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
if fmt == "ddmmyyyy":
|
||||||
|
self.day, self.month, self.year = date.split("/")
|
||||||
|
elif fmt == "iso":
|
||||||
|
self.year, self.month, self.day = date.split("-")
|
||||||
|
else:
|
||||||
|
raise ValueError("invalid format spec. (%s)" % fmt)
|
||||||
|
self.year = int(self.year)
|
||||||
|
self.month = int(self.month)
|
||||||
|
self.day = int(self.day)
|
||||||
|
except ValueError:
|
||||||
|
raise ScoValueError("date invalide: %s" % date)
|
||||||
|
# accept years YYYY or YY, uses 1970 as pivot
|
||||||
|
if self.year < 1970:
|
||||||
|
if self.year > 100:
|
||||||
|
raise ScoInvalidDateError("Année invalide: %s" % self.year)
|
||||||
|
if self.year < 70:
|
||||||
|
self.year = self.year + 2000
|
||||||
|
else:
|
||||||
|
self.year = self.year + 1900
|
||||||
|
if self.month < 1 or self.month > 12:
|
||||||
|
raise ScoInvalidDateError("Mois invalide: %s" % self.month)
|
||||||
|
|
||||||
|
if self.day < 1 or self.day > MonthNbDays(self.month, self.year):
|
||||||
|
raise ScoInvalidDateError("Jour invalide: %s" % self.day)
|
||||||
|
|
||||||
|
# weekday in 0-6, where 0 is monday
|
||||||
|
self.weekday = calendar.weekday(self.year, self.month, self.day)
|
||||||
|
|
||||||
|
self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
def iswork(self):
|
||||||
|
"returns true if workable day"
|
||||||
|
if self.work_saturday:
|
||||||
|
nbdays = 6
|
||||||
|
else:
|
||||||
|
nbdays = 5
|
||||||
|
if (
|
||||||
|
self.weekday >= 0 and self.weekday < nbdays
|
||||||
|
): # monday-friday or monday-saturday
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "'%02d/%02d/%04d'" % (self.day, self.month, self.year)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%02d/%02d/%04d" % (self.day, self.month, self.year)
|
||||||
|
|
||||||
|
def ISO(self):
|
||||||
|
"iso8601 representation of the date"
|
||||||
|
return "%04d-%02d-%02d" % (self.year, self.month, self.day)
|
||||||
|
|
||||||
|
def next_day(self, days=1):
|
||||||
|
"date for the next day (nota: may be a non workable day)"
|
||||||
|
day = self.day + days
|
||||||
|
month = self.month
|
||||||
|
year = self.year
|
||||||
|
|
||||||
|
while day > MonthNbDays(month, year):
|
||||||
|
day = day - MonthNbDays(month, year)
|
||||||
|
month = month + 1
|
||||||
|
if month > 12:
|
||||||
|
month = 1
|
||||||
|
year = year + 1
|
||||||
|
return self.__class__(
|
||||||
|
"%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
|
||||||
|
)
|
||||||
|
|
||||||
|
def prev(self, days=1):
|
||||||
|
"date for previous day"
|
||||||
|
day = self.day - days
|
||||||
|
month = self.month
|
||||||
|
year = self.year
|
||||||
|
while day <= 0:
|
||||||
|
month = month - 1
|
||||||
|
if month == 0:
|
||||||
|
month = 12
|
||||||
|
year = year - 1
|
||||||
|
day = day + MonthNbDays(month, year)
|
||||||
|
|
||||||
|
return self.__class__(
|
||||||
|
"%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
|
||||||
|
)
|
||||||
|
|
||||||
|
def next_monday(self):
|
||||||
|
"date of next monday"
|
||||||
|
return self.next_day((7 - self.weekday) % 7)
|
||||||
|
|
||||||
|
def prev_monday(self):
|
||||||
|
"date of last monday, but on sunday, pick next monday"
|
||||||
|
if self.weekday == 6:
|
||||||
|
return self.next_monday()
|
||||||
|
else:
|
||||||
|
return self.prev(self.weekday)
|
||||||
|
|
||||||
|
def __cmp__(self, other): # #py3 TODO à supprimer
|
||||||
|
"""return a negative integer if self < other,
|
||||||
|
zero if self == other, a positive integer if self > other"""
|
||||||
|
return int(self.time - other.time)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.time == other.time
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.time != other.time
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.time < other.time
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.time <= other.time
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.time > other.time
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.time >= other.time
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
"we are immutable !"
|
||||||
|
return hash(self.time) ^ hash(str(self))
|
||||||
|
|
||||||
|
|
||||||
|
# d = ddmmyyyy( '21/12/99' )
|
||||||
|
def DateRangeISO(date_beg, date_end, workable=1):
|
||||||
|
"""returns list of dates in [date_beg,date_end]
|
||||||
|
workable = 1 => keeps only workable days"""
|
||||||
|
if not date_beg:
|
||||||
|
raise ScoValueError("pas de date spécifiée !")
|
||||||
|
if not date_end:
|
||||||
|
date_end = date_beg
|
||||||
|
r = []
|
||||||
|
work_saturday = is_work_saturday()
|
||||||
|
try:
|
||||||
|
cur = ddmmyyyy(date_beg, work_saturday=work_saturday)
|
||||||
|
end = ddmmyyyy(date_end, work_saturday=work_saturday)
|
||||||
|
except (AttributeError, ValueError) as e:
|
||||||
|
raise ScoValueError("date invalide !") from e
|
||||||
|
while cur <= end:
|
||||||
|
if (not workable) or cur.iswork():
|
||||||
|
r.append(cur)
|
||||||
|
cur = cur.next_day()
|
||||||
|
|
||||||
|
return [x.ISO() for x in r]
|
||||||
|
|
||||||
|
|
||||||
|
def day_names():
|
||||||
|
"""Returns week day names.
|
||||||
|
If work_saturday property is set, include saturday
|
||||||
|
"""
|
||||||
|
if is_work_saturday():
|
||||||
|
return scu.DAY_NAMES[:-1]
|
||||||
|
else:
|
||||||
|
return scu.DAY_NAMES[:-2]
|
||||||
|
|
||||||
|
|
||||||
|
def next_iso_day(date):
|
||||||
|
"return date after date"
|
||||||
|
d = ddmmyyyy(date, fmt="iso", work_saturday=is_work_saturday())
|
||||||
|
return d.next_day().ISO()
|
||||||
|
|
||||||
|
|
||||||
|
def YearTable(
|
||||||
|
year,
|
||||||
|
events=[],
|
||||||
|
firstmonth=9,
|
||||||
|
lastmonth=7,
|
||||||
|
halfday=0,
|
||||||
|
dayattributes="",
|
||||||
|
pad_width=8,
|
||||||
|
):
|
||||||
|
"""Generate a calendar table
|
||||||
|
events = list of tuples (date, text, color, href [,halfday])
|
||||||
|
where date is a string in ISO format (yyyy-mm-dd)
|
||||||
|
halfday is boolean (true: morning, false: afternoon)
|
||||||
|
text = text to put in calendar (must be short, 1-5 cars) (optional)
|
||||||
|
if halfday, generate 2 cells per day (morning, afternoon)
|
||||||
|
"""
|
||||||
|
T = [
|
||||||
|
'<table id="maincalendar" class="maincalendar" border="3" cellpadding="1" cellspacing="1" frame="box">'
|
||||||
|
]
|
||||||
|
T.append("<tr>")
|
||||||
|
month = firstmonth
|
||||||
|
while 1:
|
||||||
|
T.append('<td valign="top">')
|
||||||
|
T.append(MonthTableHead(month))
|
||||||
|
T.append(
|
||||||
|
MonthTableBody(
|
||||||
|
month,
|
||||||
|
year,
|
||||||
|
events,
|
||||||
|
halfday,
|
||||||
|
dayattributes,
|
||||||
|
is_work_saturday(),
|
||||||
|
pad_width=pad_width,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
T.append(MonthTableTail())
|
||||||
|
T.append("</td>")
|
||||||
|
if month == lastmonth:
|
||||||
|
break
|
||||||
|
month = month + 1
|
||||||
|
if month > 12:
|
||||||
|
month = 1
|
||||||
|
year = year + 1
|
||||||
|
T.append("</table>")
|
||||||
|
return "\n".join(T)
|
||||||
|
|
||||||
|
|
||||||
|
# ------ HTML Calendar functions (see YearTable function)
|
||||||
|
|
||||||
|
# MONTH/DAY NAMES:
|
||||||
|
|
||||||
|
MONTHNAMES = (
|
||||||
|
"Janvier",
|
||||||
|
"Février",
|
||||||
|
"Mars",
|
||||||
|
"Avril",
|
||||||
|
"Mai",
|
||||||
|
"Juin",
|
||||||
|
"Juillet",
|
||||||
|
"Aout",
|
||||||
|
"Septembre",
|
||||||
|
"Octobre",
|
||||||
|
"Novembre",
|
||||||
|
"Décembre",
|
||||||
|
)
|
||||||
|
|
||||||
|
MONTHNAMES_ABREV = (
|
||||||
|
"Jan.",
|
||||||
|
"Fév.",
|
||||||
|
"Mars",
|
||||||
|
"Avr.",
|
||||||
|
"Mai ",
|
||||||
|
"Juin",
|
||||||
|
"Juil",
|
||||||
|
"Aout",
|
||||||
|
"Sept",
|
||||||
|
"Oct.",
|
||||||
|
"Nov.",
|
||||||
|
"Déc.",
|
||||||
|
)
|
||||||
|
|
||||||
|
DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
|
||||||
|
|
||||||
|
DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D")
|
||||||
|
|
||||||
|
# COLORS:
|
||||||
|
|
||||||
|
WHITE = "#FFFFFF"
|
||||||
|
GRAY1 = "#EEEEEE"
|
||||||
|
GREEN3 = "#99CC99"
|
||||||
|
WEEKDAYCOLOR = GRAY1
|
||||||
|
WEEKENDCOLOR = GREEN3
|
||||||
|
|
||||||
|
|
||||||
|
def MonthTableHead(month):
|
||||||
|
color = WHITE
|
||||||
|
return """<table class="monthcalendar" border="0" cellpadding="0" cellspacing="0" frame="box">
|
||||||
|
<tr bgcolor="%s"><td class="calcol" colspan="2" align="center">%s</td></tr>\n""" % (
|
||||||
|
color,
|
||||||
|
MONTHNAMES_ABREV[month - 1],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def MonthTableTail():
|
||||||
|
return "</table>\n"
|
||||||
|
|
||||||
|
|
||||||
|
def MonthTableBody(
|
||||||
|
month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8
|
||||||
|
):
|
||||||
|
firstday, nbdays = calendar.monthrange(year, month)
|
||||||
|
localtime = time.localtime()
|
||||||
|
current_weeknum = time.strftime("%U", localtime)
|
||||||
|
current_year = localtime[0]
|
||||||
|
T = []
|
||||||
|
# cherche date du lundi de la 1ere semaine de ce mois
|
||||||
|
monday = ddmmyyyy("1/%d/%d" % (month, year))
|
||||||
|
while monday.weekday != 0:
|
||||||
|
monday = monday.prev()
|
||||||
|
|
||||||
|
if work_saturday:
|
||||||
|
weekend = ("D",)
|
||||||
|
else:
|
||||||
|
weekend = ("S", "D")
|
||||||
|
|
||||||
|
if not halfday:
|
||||||
|
for d in range(1, nbdays + 1):
|
||||||
|
weeknum = time.strftime(
|
||||||
|
"%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
|
||||||
|
)
|
||||||
|
day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
|
||||||
|
if day in weekend:
|
||||||
|
bgcolor = WEEKENDCOLOR
|
||||||
|
weekclass = "wkend"
|
||||||
|
attrs = ""
|
||||||
|
else:
|
||||||
|
bgcolor = WEEKDAYCOLOR
|
||||||
|
weekclass = "wk" + str(monday).replace("/", "_")
|
||||||
|
attrs = trattributes
|
||||||
|
color = None
|
||||||
|
legend = ""
|
||||||
|
href = ""
|
||||||
|
descr = ""
|
||||||
|
# event this day ?
|
||||||
|
# each event is a tuple (date, text, color, href)
|
||||||
|
# where date is a string in ISO format (yyyy-mm-dd)
|
||||||
|
for ev in events:
|
||||||
|
ev_year = int(ev[0][:4])
|
||||||
|
ev_month = int(ev[0][5:7])
|
||||||
|
ev_day = int(ev[0][8:10])
|
||||||
|
if year == ev_year and month == ev_month and ev_day == d:
|
||||||
|
if ev[1]:
|
||||||
|
legend = ev[1]
|
||||||
|
if ev[2]:
|
||||||
|
color = ev[2]
|
||||||
|
if ev[3]:
|
||||||
|
href = ev[3]
|
||||||
|
if len(ev) > 4 and ev[4]:
|
||||||
|
descr = ev[4]
|
||||||
|
#
|
||||||
|
cc = []
|
||||||
|
if color is not None:
|
||||||
|
cc.append('<td bgcolor="%s" class="calcell">' % color)
|
||||||
|
else:
|
||||||
|
cc.append('<td class="calcell">')
|
||||||
|
|
||||||
|
if href:
|
||||||
|
href = 'href="%s"' % href
|
||||||
|
if descr:
|
||||||
|
descr = 'title="%s"' % html.escape(descr, quote=True)
|
||||||
|
if href or descr:
|
||||||
|
cc.append("<a %s %s>" % (href, descr))
|
||||||
|
|
||||||
|
if legend or d == 1:
|
||||||
|
if pad_width is not None:
|
||||||
|
n = pad_width - len(legend) # pad to 8 cars
|
||||||
|
if n > 0:
|
||||||
|
legend = (
|
||||||
|
" " * (n // 2) + legend + " " * ((n + 1) // 2)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
legend = " " # empty cell
|
||||||
|
cc.append(legend)
|
||||||
|
if href or descr:
|
||||||
|
cc.append("</a>")
|
||||||
|
cc.append("</td>")
|
||||||
|
cell = "".join(cc)
|
||||||
|
if day == "D":
|
||||||
|
monday = monday.next_day(7)
|
||||||
|
if (
|
||||||
|
weeknum == current_weeknum
|
||||||
|
and current_year == year
|
||||||
|
and weekclass != "wkend"
|
||||||
|
):
|
||||||
|
weekclass += " currentweek"
|
||||||
|
T.append(
|
||||||
|
'<tr bgcolor="%s" class="%s" %s><td class="calday">%d%s</td>%s</tr>'
|
||||||
|
% (bgcolor, weekclass, attrs, d, day, cell)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Calendar with 2 cells / day
|
||||||
|
for d in range(1, nbdays + 1):
|
||||||
|
weeknum = time.strftime(
|
||||||
|
"%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
|
||||||
|
)
|
||||||
|
day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
|
||||||
|
if day in weekend:
|
||||||
|
bgcolor = WEEKENDCOLOR
|
||||||
|
weekclass = "wkend"
|
||||||
|
attrs = ""
|
||||||
|
else:
|
||||||
|
bgcolor = WEEKDAYCOLOR
|
||||||
|
weekclass = "wk" + str(monday).replace("/", "_")
|
||||||
|
attrs = trattributes
|
||||||
|
if (
|
||||||
|
weeknum == current_weeknum
|
||||||
|
and current_year == year
|
||||||
|
and weekclass != "wkend"
|
||||||
|
):
|
||||||
|
weeknum += " currentweek"
|
||||||
|
|
||||||
|
if day == "D":
|
||||||
|
monday = monday.next_day(7)
|
||||||
|
T.append(
|
||||||
|
'<tr bgcolor="%s" class="wk%s" %s><td class="calday">%d%s</td>'
|
||||||
|
% (bgcolor, weekclass, attrs, d, day)
|
||||||
|
)
|
||||||
|
cc = []
|
||||||
|
for morning in (True, False):
|
||||||
|
color = None
|
||||||
|
legend = ""
|
||||||
|
href = ""
|
||||||
|
descr = ""
|
||||||
|
for ev in events:
|
||||||
|
ev_year = int(ev[0][:4])
|
||||||
|
ev_month = int(ev[0][5:7])
|
||||||
|
ev_day = int(ev[0][8:10])
|
||||||
|
if ev[4] is not None:
|
||||||
|
ev_half = int(ev[4])
|
||||||
|
else:
|
||||||
|
ev_half = 0
|
||||||
|
if (
|
||||||
|
year == ev_year
|
||||||
|
and month == ev_month
|
||||||
|
and ev_day == d
|
||||||
|
and morning == ev_half
|
||||||
|
):
|
||||||
|
if ev[1]:
|
||||||
|
legend = ev[1]
|
||||||
|
if ev[2]:
|
||||||
|
color = ev[2]
|
||||||
|
if ev[3]:
|
||||||
|
href = ev[3]
|
||||||
|
if len(ev) > 5 and ev[5]:
|
||||||
|
descr = ev[5]
|
||||||
|
#
|
||||||
|
if color is not None:
|
||||||
|
cc.append('<td bgcolor="%s" class="calcell">' % (color))
|
||||||
|
else:
|
||||||
|
cc.append('<td class="calcell">')
|
||||||
|
if href:
|
||||||
|
href = 'href="%s"' % href
|
||||||
|
if descr:
|
||||||
|
descr = 'title="%s"' % html.escape(descr, quote=True)
|
||||||
|
if href or descr:
|
||||||
|
cc.append("<a %s %s>" % (href, descr))
|
||||||
|
if legend or d == 1:
|
||||||
|
n = 3 - len(legend) # pad to 3 cars
|
||||||
|
if n > 0:
|
||||||
|
legend = (
|
||||||
|
" " * (n // 2) + legend + " " * ((n + 1) // 2)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
legend = " " # empty cell
|
||||||
|
cc.append(legend)
|
||||||
|
if href or descr:
|
||||||
|
cc.append("</a>")
|
||||||
|
cc.append("</td>\n")
|
||||||
|
T.append("".join(cc) + "</tr>")
|
||||||
|
return "\n".join(T)
|
@ -1389,14 +1389,14 @@ def ue_sharing_code(ue_code: str = "", ue_id: int = None, hide_ue_id: int = None
|
|||||||
# UE du même code, code formation et departement:
|
# UE du même code, code formation et departement:
|
||||||
q_ues = (
|
q_ues = (
|
||||||
UniteEns.query.filter_by(ue_code=ue_code)
|
UniteEns.query.filter_by(ue_code=ue_code)
|
||||||
.join(UniteEns.formation, aliased=True)
|
.join(UniteEns.formation)
|
||||||
.filter_by(dept_id=g.scodoc_dept_id, formation_code=formation_code)
|
.filter_by(dept_id=g.scodoc_dept_id, formation_code=formation_code)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Toutes les UE du departement avec ce code:
|
# Toutes les UE du departement avec ce code:
|
||||||
q_ues = (
|
q_ues = (
|
||||||
UniteEns.query.filter_by(ue_code=ue_code)
|
UniteEns.query.filter_by(ue_code=ue_code)
|
||||||
.join(UniteEns.formation, aliased=True)
|
.join(UniteEns.formation)
|
||||||
.filter_by(dept_id=g.scodoc_dept_id)
|
.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,36 +29,18 @@
|
|||||||
"""
|
"""
|
||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from app.models import Evaluation, FormSemestre, Identite
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_abs
|
|
||||||
from app.scodoc import sco_etud
|
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_moduleimpl
|
|
||||||
|
|
||||||
# matin et/ou après-midi ?
|
|
||||||
def _eval_demijournee(E):
|
|
||||||
"1 si matin, 0 si apres midi, 2 si toute la journee"
|
|
||||||
am, pm = False, False
|
|
||||||
if E["heure_debut"] < "13:00":
|
|
||||||
am = True
|
|
||||||
if E["heure_fin"] > "13:00":
|
|
||||||
pm = True
|
|
||||||
if am and pm:
|
|
||||||
demijournee = 2
|
|
||||||
elif am:
|
|
||||||
demijournee = 1
|
|
||||||
else:
|
|
||||||
demijournee = 0
|
|
||||||
pm = True
|
|
||||||
return am, pm, demijournee
|
|
||||||
|
|
||||||
|
|
||||||
def evaluation_check_absences(evaluation_id):
|
# XXX TODO-ASSIDUITE https://scodoc.org/git/ScoDoc/ScoDoc/issues/685
|
||||||
|
def evaluation_check_absences(evaluation: Evaluation):
|
||||||
"""Vérifie les absences au moment de cette évaluation.
|
"""Vérifie les absences au moment de cette évaluation.
|
||||||
Cas incohérents que l'on peut rencontrer pour chaque étudiant:
|
Cas incohérents que l'on peut rencontrer pour chaque étudiant:
|
||||||
note et absent
|
note et absent
|
||||||
@ -66,51 +48,60 @@ def evaluation_check_absences(evaluation_id):
|
|||||||
ABS et absent justifié
|
ABS et absent justifié
|
||||||
EXC et pas noté absent
|
EXC et pas noté absent
|
||||||
EXC et pas justifie
|
EXC et pas justifie
|
||||||
Ramene 3 listes d'etudid
|
Ramene 5 listes d'etudid
|
||||||
"""
|
"""
|
||||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
raise ScoValueError("Fonction non disponible, patience !") # XXX TODO-ASSIDUITE
|
||||||
if not E["jour"]:
|
|
||||||
|
if not evaluation.date_debut:
|
||||||
return [], [], [], [], [] # evaluation sans date
|
return [], [], [], [], [] # evaluation sans date
|
||||||
|
|
||||||
am, pm, demijournee = _eval_demijournee(E)
|
am, pm = evaluation.is_matin(), evaluation.is_apresmidi()
|
||||||
|
|
||||||
# Liste les absences à ce moment:
|
# Liste les absences à ce moment:
|
||||||
A = sco_abs.list_abs_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
|
absences = sco_abs.list_abs_jour(evaluation.date_debut, am=am, pm=pm)
|
||||||
As = set([x["etudid"] for x in A]) # ensemble des etudiants absents
|
abs_etudids = set([x["etudid"] for x in absences]) # ensemble des etudiants absents
|
||||||
NJ = sco_abs.list_abs_non_just_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
|
abs_non_just = sco_abs.list_abs_non_just_jour(
|
||||||
NJs = set([x["etudid"] for x in NJ]) # ensemble des etudiants absents non justifies
|
evaluation.date_debut.date(), am=am, pm=pm
|
||||||
Just = sco_abs.list_abs_jour(
|
|
||||||
ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm, is_abs=None, is_just=True
|
|
||||||
)
|
)
|
||||||
Justs = set([x["etudid"] for x in Just]) # ensemble des etudiants avec justif
|
abs_nj_etudids = set(
|
||||||
|
[x["etudid"] for x in abs_non_just]
|
||||||
|
) # ensemble des etudiants absents non justifies
|
||||||
|
justifs = sco_abs.list_abs_jour(
|
||||||
|
evaluation.date_debut.date(), am=am, pm=pm, is_abs=None, is_just=True
|
||||||
|
)
|
||||||
|
just_etudids = set(
|
||||||
|
[x["etudid"] for x in justifs]
|
||||||
|
) # ensemble des etudiants avec justif
|
||||||
|
|
||||||
# Les notes:
|
# Les notes:
|
||||||
notes_db = 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
|
ValButAbs = [] # une note mais noté absent
|
||||||
AbsNonSignalee = [] # note ABS mais pas noté absent
|
AbsNonSignalee = [] # note ABS mais pas noté absent
|
||||||
ExcNonSignalee = [] # note EXC mais pas noté absent
|
ExcNonSignalee = [] # note EXC mais pas noté absent
|
||||||
ExcNonJust = [] # note EXC mais absent non justifie
|
ExcNonJust = [] # note EXC mais absent non justifie
|
||||||
AbsButExc = [] # note ABS mais justifié
|
AbsButExc = [] # note ABS mais justifié
|
||||||
for etudid, _ in sco_groups.do_evaluation_listeetuds_groups(
|
for etudid, _ in sco_groups.do_evaluation_listeetuds_groups(
|
||||||
evaluation_id, getallstudents=True
|
evaluation.id, getallstudents=True
|
||||||
):
|
):
|
||||||
if etudid in notes_db:
|
if etudid in notes_db:
|
||||||
val = notes_db[etudid]["value"]
|
val = notes_db[etudid]["value"]
|
||||||
if (
|
if (
|
||||||
val != None and val != scu.NOTES_NEUTRALISE and val != scu.NOTES_ATTENTE
|
val is not None
|
||||||
) and etudid in As:
|
and val != scu.NOTES_NEUTRALISE
|
||||||
|
and val != scu.NOTES_ATTENTE
|
||||||
|
) and etudid in abs_etudids:
|
||||||
# note valide et absent
|
# note valide et absent
|
||||||
ValButAbs.append(etudid)
|
ValButAbs.append(etudid)
|
||||||
if val is None and not etudid in As:
|
if val is None and not etudid in abs_etudids:
|
||||||
# absent mais pas signale comme tel
|
# absent mais pas signale comme tel
|
||||||
AbsNonSignalee.append(etudid)
|
AbsNonSignalee.append(etudid)
|
||||||
if val == scu.NOTES_NEUTRALISE and not etudid in As:
|
if val == scu.NOTES_NEUTRALISE and not etudid in abs_etudids:
|
||||||
# Neutralisé mais pas signale absent
|
# Neutralisé mais pas signale absent
|
||||||
ExcNonSignalee.append(etudid)
|
ExcNonSignalee.append(etudid)
|
||||||
if val == scu.NOTES_NEUTRALISE and etudid in NJs:
|
if val == scu.NOTES_NEUTRALISE and etudid in abs_nj_etudids:
|
||||||
# EXC mais pas justifié
|
# EXC mais pas justifié
|
||||||
ExcNonJust.append(etudid)
|
ExcNonJust.append(etudid)
|
||||||
if val is None and etudid in Justs:
|
if val is None and etudid in just_etudids:
|
||||||
# ABS mais justificatif
|
# ABS mais justificatif
|
||||||
AbsButExc.append(etudid)
|
AbsButExc.append(etudid)
|
||||||
|
|
||||||
@ -119,9 +110,16 @@ def evaluation_check_absences(evaluation_id):
|
|||||||
|
|
||||||
def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
|
def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
|
||||||
"""Affiche état vérification absences d'une évaluation"""
|
"""Affiche état vérification absences d'une évaluation"""
|
||||||
|
evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
|
||||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
am, pm = evaluation.is_matin(), evaluation.is_apresmidi()
|
||||||
am, pm, demijournee = _eval_demijournee(E)
|
# 1 si matin, 0 si apres midi, 2 si toute la journee:
|
||||||
|
match am, pm:
|
||||||
|
case False, True:
|
||||||
|
demijournee = 0
|
||||||
|
case True, False:
|
||||||
|
demijournee = 1
|
||||||
|
case _:
|
||||||
|
demijournee = 2
|
||||||
|
|
||||||
(
|
(
|
||||||
ValButAbs,
|
ValButAbs,
|
||||||
@ -129,19 +127,23 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True
|
|||||||
ExcNonSignalee,
|
ExcNonSignalee,
|
||||||
ExcNonJust,
|
ExcNonJust,
|
||||||
AbsButExc,
|
AbsButExc,
|
||||||
) = evaluation_check_absences(evaluation_id)
|
) = evaluation_check_absences(evaluation)
|
||||||
|
|
||||||
if with_header:
|
if with_header:
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header("Vérification absences à l'évaluation"),
|
html_sco_header.html_sem_header("Vérification absences à l'évaluation"),
|
||||||
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
|
sco_evaluations.evaluation_describe(evaluation_id=evaluation.id),
|
||||||
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.</p>""",
|
"""<p class="help">Vérification de la cohérence entre les notes saisies
|
||||||
|
et les absences signalées.</p>""",
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
# pas de header, mais un titre
|
# pas de header, mais un titre
|
||||||
H = [
|
H = [
|
||||||
"""<h2 class="eval_check_absences">%s du %s """
|
f"""<h2 class="eval_check_absences">{
|
||||||
% (E["description"], E["jour"])
|
evaluation.description or "évaluation"
|
||||||
|
} du {
|
||||||
|
evaluation.date_debut.strftime("%d/%m/%Y") if evaluation.date_debut else ""
|
||||||
|
} """
|
||||||
]
|
]
|
||||||
if (
|
if (
|
||||||
not ValButAbs
|
not ValButAbs
|
||||||
@ -157,26 +159,27 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True
|
|||||||
if not etudids and show_ok:
|
if not etudids and show_ok:
|
||||||
H.append("<li>aucun</li>")
|
H.append("<li>aucun</li>")
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud: Identite = db.session.get(Identite, etudid)
|
||||||
H.append(
|
H.append(
|
||||||
'<li><a class="discretelink" href="%s">'
|
f"""<li><a class="discretelink" href="{
|
||||||
% url_for(
|
url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||||
)
|
)
|
||||||
+ "%(nomprenom)s</a>" % etud
|
}">{etud.nomprenom}</a>"""
|
||||||
)
|
)
|
||||||
if linkabs:
|
if linkabs:
|
||||||
H.append(
|
url = url_for(
|
||||||
f"""<a class="stdlink" href="{url_for(
|
"absences.doSignaleAbsence", # XXX TODO-ASSIDUITE
|
||||||
'absences.doSignaleAbsence',
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud["etudid"],
|
etudid=etudid,
|
||||||
datedebut=E["jour"],
|
# par defaut signale le jour du début de l'éval
|
||||||
datefin=E["jour"],
|
datedebut=evaluation.date_debut.strftime("%d/%m/%Y"),
|
||||||
|
datefin=evaluation.date_debut.strftime("%d/%m/%Y"),
|
||||||
demijournee=demijournee,
|
demijournee=demijournee,
|
||||||
moduleimpl_id=E["moduleimpl_id"],
|
moduleimpl_id=evaluation.moduleimpl_id,
|
||||||
)
|
)
|
||||||
}">signaler cette absence</a>"""
|
H.append(
|
||||||
|
f"""<a class="stdlink" href="{url}">signaler cette absence</a>"""
|
||||||
)
|
)
|
||||||
H.append("</li>")
|
H.append("</li>")
|
||||||
H.append("</ul>")
|
H.append("</ul>")
|
||||||
@ -218,7 +221,9 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True
|
|||||||
|
|
||||||
def formsemestre_check_absences_html(formsemestre_id):
|
def formsemestre_check_absences_html(formsemestre_id):
|
||||||
"""Affiche etat verification absences pour toutes les evaluations du semestre !"""
|
"""Affiche etat verification absences pour toutes les evaluations du semestre !"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||||||
|
dept_id=g.scodoc_dept_id, id=formsemestre_id
|
||||||
|
).first_or_404()
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
"Vérification absences aux évaluations de ce semestre",
|
"Vérification absences aux évaluations de ce semestre",
|
||||||
@ -229,29 +234,27 @@ def formsemestre_check_absences_html(formsemestre_id):
|
|||||||
</p>""",
|
</p>""",
|
||||||
]
|
]
|
||||||
# Modules, dans l'ordre
|
# Modules, dans l'ordre
|
||||||
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
for modimpl in formsemestre.modimpls_sorted:
|
||||||
for M in Mlist:
|
if modimpl.evaluations.count() > 0:
|
||||||
evals = sco_evaluation_db.do_evaluation_list(
|
|
||||||
{"moduleimpl_id": M["moduleimpl_id"]}
|
|
||||||
)
|
|
||||||
if evals:
|
|
||||||
H.append(
|
H.append(
|
||||||
'<div class="module_check_absences"><h2><a href="moduleimpl_status?moduleimpl_id=%s">%s: %s</a></h2>'
|
f"""<div class="module_check_absences">
|
||||||
% (
|
<h2><a href="{
|
||||||
M["moduleimpl_id"],
|
url_for("notes.moduleimpl_status",
|
||||||
M["module"]["code"] or "",
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
||||||
M["module"]["abbrev"] or "",
|
}">{modimpl.module.code or ""}: {modimpl.module.abbrev or ""}</a>
|
||||||
|
</h2>"""
|
||||||
)
|
)
|
||||||
)
|
for evaluation in modimpl.evaluations.order_by(
|
||||||
for E in evals:
|
Evaluation.numero, Evaluation.date_debut
|
||||||
|
):
|
||||||
H.append(
|
H.append(
|
||||||
evaluation_check_absences_html(
|
evaluation_check_absences_html(
|
||||||
E["evaluation_id"],
|
evaluation.id, # XXX TODO-ASSIDUITE remplacer par evaluation ...
|
||||||
with_header=False,
|
with_header=False,
|
||||||
show_ok=False,
|
show_ok=False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if evals:
|
|
||||||
H.append("</div>")
|
H.append("</div>")
|
||||||
|
|
||||||
H.append(html_sco_header.sco_footer())
|
H.append(html_sco_header.sco_footer())
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
@ -28,23 +28,20 @@
|
|||||||
"""Gestion évaluations (ScoDoc7, code en voie de modernisation)
|
"""Gestion évaluations (ScoDoc7, code en voie de modernisation)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pprint
|
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
|
|
||||||
from app.models import Evaluation, ModuleImpl, ScolarNews
|
from app.models import Evaluation
|
||||||
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
|
from app.models.evaluations import check_convert_evaluation_args
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied
|
||||||
|
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_permissions_check
|
|
||||||
|
|
||||||
|
|
||||||
_evaluationEditor = ndb.EditableTable(
|
_evaluationEditor = ndb.EditableTable(
|
||||||
@ -53,9 +50,8 @@ _evaluationEditor = ndb.EditableTable(
|
|||||||
(
|
(
|
||||||
"evaluation_id",
|
"evaluation_id",
|
||||||
"moduleimpl_id",
|
"moduleimpl_id",
|
||||||
"jour",
|
"date_debut",
|
||||||
"heure_debut",
|
"date_fin",
|
||||||
"heure_fin",
|
|
||||||
"description",
|
"description",
|
||||||
"note_max",
|
"note_max",
|
||||||
"coefficient",
|
"coefficient",
|
||||||
@ -64,15 +60,11 @@ _evaluationEditor = ndb.EditableTable(
|
|||||||
"evaluation_type",
|
"evaluation_type",
|
||||||
"numero",
|
"numero",
|
||||||
),
|
),
|
||||||
sortkey="numero desc, jour desc, heure_debut desc", # plus recente d'abord
|
sortkey="numero, date_debut desc", # plus recente d'abord
|
||||||
output_formators={
|
output_formators={
|
||||||
"jour": ndb.DateISOtoDMY,
|
|
||||||
"numero": ndb.int_null_is_zero,
|
"numero": ndb.int_null_is_zero,
|
||||||
},
|
},
|
||||||
input_formators={
|
input_formators={
|
||||||
"jour": ndb.DateDMYtoISO,
|
|
||||||
"heure_debut": ndb.TimetoISO8601, # converti par evaluation_enrich_dict
|
|
||||||
"heure_fin": ndb.TimetoISO8601, # converti par evaluation_enrich_dict
|
|
||||||
"visibulletin": bool,
|
"visibulletin": bool,
|
||||||
"publish_incomplete": bool,
|
"publish_incomplete": bool,
|
||||||
"evaluation_type": int,
|
"evaluation_type": int,
|
||||||
@ -80,8 +72,9 @@ _evaluationEditor = ndb.EditableTable(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_list(args, sortkey=None):
|
def get_evaluation_dict(args: dict) -> list[dict]:
|
||||||
"""List evaluations, sorted by numero (or most recent date first).
|
"""Liste evaluations, triées numero (or most recent date first).
|
||||||
|
Fonction de transition pour ancien code ScoDoc7.
|
||||||
|
|
||||||
Ajoute les champs:
|
Ajoute les champs:
|
||||||
'duree' : '2h30'
|
'duree' : '2h30'
|
||||||
@ -89,14 +82,8 @@ def do_evaluation_list(args, sortkey=None):
|
|||||||
'apresmidi' : 1 (termine après 12:00) ou 0
|
'apresmidi' : 1 (termine après 12:00) ou 0
|
||||||
'descrheure' : ' de 15h00 à 16h30'
|
'descrheure' : ' de 15h00 à 16h30'
|
||||||
"""
|
"""
|
||||||
# Attention: transformation fonction ScoDoc7 en SQLAlchemy
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
|
|
||||||
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
|
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
|
||||||
for e in evals:
|
return [e.to_dict() for e in Evaluation.query.filter_by(**args)]
|
||||||
evaluation_enrich_dict(e)
|
|
||||||
|
|
||||||
return evals
|
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_list_in_formsemestre(formsemestre_id):
|
def do_evaluation_list_in_formsemestre(formsemestre_id):
|
||||||
@ -104,147 +91,29 @@ def do_evaluation_list_in_formsemestre(formsemestre_id):
|
|||||||
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
evals = []
|
evals = []
|
||||||
for modimpl in mods:
|
for modimpl in mods:
|
||||||
evals += do_evaluation_list(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
|
evals += get_evaluation_dict(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
|
||||||
return evals
|
return evals
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_create(
|
|
||||||
moduleimpl_id=None,
|
|
||||||
jour=None,
|
|
||||||
heure_debut=None,
|
|
||||||
heure_fin=None,
|
|
||||||
description=None,
|
|
||||||
note_max=None,
|
|
||||||
coefficient=None,
|
|
||||||
visibulletin=None,
|
|
||||||
publish_incomplete=None,
|
|
||||||
evaluation_type=None,
|
|
||||||
numero=None,
|
|
||||||
**kw, # ceci pour absorber les arguments excedentaires de tf #sco8
|
|
||||||
):
|
|
||||||
"""Create an evaluation"""
|
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
|
||||||
raise AccessDenied(
|
|
||||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
|
||||||
)
|
|
||||||
args = locals()
|
|
||||||
log("do_evaluation_create: args=" + str(args))
|
|
||||||
modimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
|
||||||
if modimpl is None:
|
|
||||||
raise ValueError("module not found")
|
|
||||||
check_evaluation_args(args)
|
|
||||||
# Check numeros
|
|
||||||
moduleimpl_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
|
|
||||||
if not "numero" in args or args["numero"] is None:
|
|
||||||
n = None
|
|
||||||
# determine le numero avec la date
|
|
||||||
# Liste des eval existantes triees par date, la plus ancienne en tete
|
|
||||||
mod_evals = do_evaluation_list(
|
|
||||||
args={"moduleimpl_id": moduleimpl_id},
|
|
||||||
sortkey="jour asc, heure_debut asc",
|
|
||||||
)
|
|
||||||
if args["jour"]:
|
|
||||||
next_eval = None
|
|
||||||
t = (
|
|
||||||
ndb.DateDMYtoISO(args["jour"], null_is_empty=True),
|
|
||||||
ndb.TimetoISO8601(args["heure_debut"], null_is_empty=True),
|
|
||||||
)
|
|
||||||
for e in mod_evals:
|
|
||||||
if (
|
|
||||||
ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
|
||||||
ndb.TimetoISO8601(e["heure_debut"], null_is_empty=True),
|
|
||||||
) > t:
|
|
||||||
next_eval = e
|
|
||||||
break
|
|
||||||
if next_eval:
|
|
||||||
n = moduleimpl_evaluation_insert_before(mod_evals, next_eval)
|
|
||||||
else:
|
|
||||||
n = None # a placer en fin
|
|
||||||
if n is None: # pas de date ou en fin:
|
|
||||||
if mod_evals:
|
|
||||||
log(pprint.pformat(mod_evals[-1]))
|
|
||||||
n = mod_evals[-1]["numero"] + 1
|
|
||||||
else:
|
|
||||||
n = 0 # the only one
|
|
||||||
# log("creating with numero n=%d" % n)
|
|
||||||
args["numero"] = n
|
|
||||||
|
|
||||||
#
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
r = _evaluationEditor.create(cnx, args)
|
|
||||||
|
|
||||||
# news
|
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
|
|
||||||
url = url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
)
|
|
||||||
ScolarNews.add(
|
|
||||||
typ=ScolarNews.NEWS_NOTE,
|
|
||||||
obj=moduleimpl_id,
|
|
||||||
text=f"""Création d'une évaluation dans <a href="{url}">{
|
|
||||||
modimpl.module.titre or '(module sans titre)'}</a>""",
|
|
||||||
url=url,
|
|
||||||
)
|
|
||||||
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_edit(args):
|
def do_evaluation_edit(args):
|
||||||
"edit an evaluation"
|
"edit an evaluation"
|
||||||
evaluation_id = args["evaluation_id"]
|
evaluation_id = args["evaluation_id"]
|
||||||
the_evals = do_evaluation_list({"evaluation_id": evaluation_id})
|
evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
|
||||||
if not the_evals:
|
if evaluation is None:
|
||||||
raise ValueError("evaluation inexistante !")
|
raise ValueError("evaluation inexistante !")
|
||||||
moduleimpl_id = the_evals[0]["moduleimpl_id"]
|
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
||||||
raise AccessDenied(
|
raise AccessDenied(
|
||||||
"Modification évaluation impossible pour %s" % current_user.get_nomplogin()
|
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||||
)
|
)
|
||||||
args["moduleimpl_id"] = moduleimpl_id
|
args["moduleimpl_id"] = evaluation.moduleimpl.id
|
||||||
check_evaluation_args(args)
|
check_convert_evaluation_args(evaluation.moduleimpl, args)
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
_evaluationEditor.edit(cnx, args)
|
_evaluationEditor.edit(cnx, args)
|
||||||
# inval cache pour ce semestre
|
# inval cache pour ce semestre
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
sco_cache.invalidate_formsemestre(
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
|
formsemestre_id=evaluation.moduleimpl.formsemestre_id
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_delete(evaluation_id):
|
|
||||||
"delete evaluation"
|
|
||||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
|
||||||
modimpl: ModuleImpl = evaluation.moduleimpl
|
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=modimpl.id):
|
|
||||||
raise AccessDenied(
|
|
||||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
|
||||||
)
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
db.session.delete(evaluation)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# inval cache pour ce semestre
|
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
|
|
||||||
# news
|
|
||||||
url = url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=modimpl.id,
|
|
||||||
)
|
|
||||||
ScolarNews.add(
|
|
||||||
typ=ScolarNews.NEWS_NOTE,
|
|
||||||
obj=modimpl.id,
|
|
||||||
text=f"""Suppression d'une évaluation dans <a href="{
|
|
||||||
url
|
|
||||||
}">{modimpl.module.titre}</a>""",
|
|
||||||
url=url,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -287,68 +156,6 @@ def do_evaluation_get_all_notes(
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_evaluation_renumber(moduleimpl_id, only_if_unumbered=False, redirect=0):
|
|
||||||
"""Renumber evaluations in this module, according to their date. (numero=0: oldest one)
|
|
||||||
Needed because previous versions of ScoDoc did not have eval numeros
|
|
||||||
Note: existing numeros are ignored
|
|
||||||
"""
|
|
||||||
redirect = int(redirect)
|
|
||||||
# log('moduleimpl_evaluation_renumber( moduleimpl_id=%s )' % moduleimpl_id )
|
|
||||||
# List sorted according to date/heure, ignoring numeros:
|
|
||||||
# (note that we place evaluations with NULL date at the end)
|
|
||||||
mod_evals = do_evaluation_list(
|
|
||||||
args={"moduleimpl_id": moduleimpl_id},
|
|
||||||
sortkey="jour asc, heure_debut asc",
|
|
||||||
)
|
|
||||||
|
|
||||||
all_numbered = False not in [x["numero"] > 0 for x in mod_evals]
|
|
||||||
if all_numbered and only_if_unumbered:
|
|
||||||
return # all ok
|
|
||||||
|
|
||||||
# Reset all numeros:
|
|
||||||
i = 1
|
|
||||||
for e in mod_evals:
|
|
||||||
e["numero"] = i
|
|
||||||
do_evaluation_edit(e)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# If requested, redirect to moduleimpl page:
|
|
||||||
if redirect:
|
|
||||||
return flask.redirect(
|
|
||||||
url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_evaluation_insert_before(mod_evals, next_eval):
|
|
||||||
"""Renumber evals such that an evaluation with can be inserted before next_eval
|
|
||||||
Returns numero suitable for the inserted evaluation
|
|
||||||
"""
|
|
||||||
if next_eval:
|
|
||||||
n = next_eval["numero"]
|
|
||||||
if not n:
|
|
||||||
log("renumbering old evals")
|
|
||||||
moduleimpl_evaluation_renumber(next_eval["moduleimpl_id"])
|
|
||||||
next_eval = do_evaluation_list(
|
|
||||||
args={"evaluation_id": next_eval["evaluation_id"]}
|
|
||||||
)[0]
|
|
||||||
n = next_eval["numero"]
|
|
||||||
else:
|
|
||||||
n = 1
|
|
||||||
# log('inserting at position numero %s' % n )
|
|
||||||
# all numeros >= n are incremented
|
|
||||||
for e in mod_evals:
|
|
||||||
if e["numero"] >= n:
|
|
||||||
e["numero"] += 1
|
|
||||||
# log('incrementing %s to %s' % (e['evaluation_id'], e['numero']))
|
|
||||||
do_evaluation_edit(e)
|
|
||||||
|
|
||||||
return n
|
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
||||||
"""Move before/after previous one (decrement/increment numero)
|
"""Move before/after previous one (decrement/increment numero)
|
||||||
(published)
|
(published)
|
||||||
@ -357,18 +164,19 @@ def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
|||||||
moduleimpl_id = evaluation.moduleimpl_id
|
moduleimpl_id = evaluation.moduleimpl_id
|
||||||
redirect = int(redirect)
|
redirect = int(redirect)
|
||||||
# access: can change eval ?
|
# access: can change eval ?
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
||||||
raise AccessDenied(
|
raise AccessDenied(
|
||||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||||
)
|
)
|
||||||
|
Evaluation.moduleimpl_evaluation_renumber(
|
||||||
moduleimpl_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
|
evaluation.moduleimpl, only_if_unumbered=True
|
||||||
e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
)
|
||||||
|
e = get_evaluation_dict(args={"evaluation_id": evaluation_id})[0]
|
||||||
|
|
||||||
after = int(after) # 0: deplace avant, 1 deplace apres
|
after = int(after) # 0: deplace avant, 1 deplace apres
|
||||||
if after not in (0, 1):
|
if after not in (0, 1):
|
||||||
raise ValueError('invalid value for "after"')
|
raise ValueError('invalid value for "after"')
|
||||||
mod_evals = do_evaluation_list({"moduleimpl_id": e["moduleimpl_id"]})
|
mod_evals = get_evaluation_dict({"moduleimpl_id": e["moduleimpl_id"]})
|
||||||
if len(mod_evals) > 1:
|
if len(mod_evals) > 1:
|
||||||
idx = [p["evaluation_id"] for p in mod_evals].index(evaluation_id)
|
idx = [p["evaluation_id"] for p in mod_evals].index(evaluation_id)
|
||||||
neigh = None # object to swap with
|
neigh = None # object to swap with
|
||||||
@ -379,8 +187,8 @@ def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
|||||||
if neigh: #
|
if neigh: #
|
||||||
if neigh["numero"] == e["numero"]:
|
if neigh["numero"] == e["numero"]:
|
||||||
log("Warning: moduleimpl_evaluation_move: forcing renumber")
|
log("Warning: moduleimpl_evaluation_move: forcing renumber")
|
||||||
moduleimpl_evaluation_renumber(
|
Evaluation.moduleimpl_evaluation_renumber(
|
||||||
e["moduleimpl_id"], only_if_unumbered=False
|
evaluation.moduleimpl, only_if_unumbered=False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# swap numero with neighbor
|
# swap numero with neighbor
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
"""Formulaire ajout/édition d'une évaluation
|
"""Formulaire ajout/édition d'une évaluation
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
@ -38,6 +38,7 @@ from flask import request
|
|||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Evaluation, FormSemestre, ModuleImpl
|
from app.models import Evaluation, FormSemestre, ModuleImpl
|
||||||
|
from app.models.evaluations import heure_to_time
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
@ -47,7 +48,6 @@ from app.scodoc import html_sco_header
|
|||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_permissions_check
|
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ def evaluation_create_form(
|
|||||||
can_edit_poids = not preferences["but_disable_edit_poids_evaluations"]
|
can_edit_poids = not preferences["but_disable_edit_poids_evaluations"]
|
||||||
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
|
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
|
||||||
#
|
#
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
if not modimpl.can_edit_evaluation(current_user):
|
||||||
return f"""
|
return f"""
|
||||||
{html_sco_header.sco_header()}
|
{html_sco_header.sco_header()}
|
||||||
<h2>Opération non autorisée</h2>
|
<h2>Opération non autorisée</h2>
|
||||||
@ -139,15 +139,8 @@ def evaluation_create_form(
|
|||||||
|
|
||||||
heures = [f"{h:02d}h{m:02d}" for h in range(8, 19) for m in (0, 30)]
|
heures = [f"{h:02d}h{m:02d}" for h in range(8, 19) for m in (0, 30)]
|
||||||
#
|
#
|
||||||
initvalues["visibulletin"] = initvalues.get("visibulletin", True)
|
|
||||||
if initvalues["visibulletin"]:
|
|
||||||
initvalues["visibulletinlist"] = ["X"]
|
|
||||||
else:
|
|
||||||
initvalues["visibulletinlist"] = []
|
|
||||||
initvalues["coefficient"] = initvalues.get("coefficient", 1.0)
|
initvalues["coefficient"] = initvalues.get("coefficient", 1.0)
|
||||||
vals = scu.get_request_args()
|
vals = scu.get_request_args()
|
||||||
if vals.get("tf_submitted", False) and "visibulletinlist" not in vals:
|
|
||||||
vals["visibulletinlist"] = []
|
|
||||||
#
|
#
|
||||||
ue_coef_dict = modimpl.module.get_ue_coef_dict()
|
ue_coef_dict = modimpl.module.get_ue_coef_dict()
|
||||||
if is_apc: # BUT: poids vers les UE
|
if is_apc: # BUT: poids vers les UE
|
||||||
@ -236,11 +229,9 @@ def evaluation_create_form(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"visibulletinlist",
|
"visibulletin",
|
||||||
{
|
{
|
||||||
"input_type": "checkbox",
|
"input_type": "boolcheckbox",
|
||||||
"allowed_values": ["X"],
|
|
||||||
"labels": [""],
|
|
||||||
"title": "Visible sur bulletins",
|
"title": "Visible sur bulletins",
|
||||||
"explanation": "(pour les bulletins en version intermédiaire)",
|
"explanation": "(pour les bulletins en version intermédiaire)",
|
||||||
},
|
},
|
||||||
@ -349,15 +340,41 @@ def evaluation_create_form(
|
|||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
else:
|
else:
|
||||||
# form submission
|
# form submission
|
||||||
if tf[2]["visibulletinlist"]:
|
args = tf[2]
|
||||||
tf[2]["visibulletin"] = True
|
# modifie le codage des dates
|
||||||
|
# (nb: ce formulaire ne permet de créer que des évaluation sur la même journée)
|
||||||
|
if args.get("jour"):
|
||||||
|
try:
|
||||||
|
date_debut = datetime.datetime.strptime(args["jour"], "%d/%m/%Y")
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError("Date (j/m/a) invalide") from exc
|
||||||
else:
|
else:
|
||||||
tf[2]["visibulletin"] = False
|
date_debut = None
|
||||||
|
args.pop("jour", None)
|
||||||
|
if date_debut and args.get("heure_debut"):
|
||||||
|
try:
|
||||||
|
heure_debut = heure_to_time(args["heure_debut"])
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError("Heure début invalide") from exc
|
||||||
|
args["date_debut"] = datetime.datetime.combine(date_debut, heure_debut)
|
||||||
|
args.pop("heure_debut", None)
|
||||||
|
# note: ce formulaire ne permet de créer que des évaluation avec debut et fin sur le même jour.
|
||||||
|
if date_debut and args.get("heure_fin"):
|
||||||
|
try:
|
||||||
|
heure_fin = heure_to_time(args["heure_fin"])
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError("Heure fin invalide") from exc
|
||||||
|
args["date_fin"] = datetime.datetime.combine(date_debut, heure_fin)
|
||||||
|
args.pop("heure_fin", None)
|
||||||
|
#
|
||||||
if edit:
|
if edit:
|
||||||
sco_evaluation_db.do_evaluation_edit(tf[2])
|
evaluation.from_dict(args)
|
||||||
else:
|
else:
|
||||||
# création d'une evaluation (via fonction ScoDoc7)
|
# création d'une evaluation
|
||||||
evaluation_id = sco_evaluation_db.do_evaluation_create(**tf[2])
|
evaluation = Evaluation.create(moduleimpl=modimpl, **args)
|
||||||
|
db.session.add(evaluation)
|
||||||
|
db.session.commit()
|
||||||
|
evaluation_id = evaluation.id
|
||||||
if is_apc:
|
if is_apc:
|
||||||
# Set poids
|
# Set poids
|
||||||
evaluation = db.session.get(Evaluation, evaluation_id)
|
evaluation = db.session.get(Evaluation, evaluation_id)
|
||||||
|
@ -126,8 +126,8 @@ def evaluations_recap_table(formsemestre: FormSemestre) -> list[dict]:
|
|||||||
evaluation_id=evaluation_id,
|
evaluation_id=evaluation_id,
|
||||||
),
|
),
|
||||||
"_titre_target_attrs": 'class="discretelink"',
|
"_titre_target_attrs": 'class="discretelink"',
|
||||||
"date": e.jour.strftime("%d/%m/%Y") if e.jour else "",
|
"date": e.date_debut.strftime("%d/%m/%Y") if e.date_debut else "",
|
||||||
"_date_order": e.jour.isoformat() if e.jour else "",
|
"_date_order": e.date_debut.isoformat() if e.date_debut else "",
|
||||||
"complete": "oui" if eval_etat.is_complete else "non",
|
"complete": "oui" if eval_etat.is_complete else "non",
|
||||||
"_complete_target": "#",
|
"_complete_target": "#",
|
||||||
"_complete_target_attrs": 'class="bull_link" title="prise en compte dans les moyennes"'
|
"_complete_target_attrs": 'class="bull_link" title="prise en compte dans les moyennes"'
|
||||||
|
@ -30,24 +30,25 @@
|
|||||||
import collections
|
import collections
|
||||||
import datetime
|
import datetime
|
||||||
import operator
|
import operator
|
||||||
import time
|
|
||||||
|
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from app.auth.models import User
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import FormSemestre
|
from app.models import Evaluation, FormSemestre
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
|
from app.scodoc import sco_cal
|
||||||
from app.scodoc import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc import sco_abs
|
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
@ -101,6 +102,14 @@ def do_evaluation_etat(
|
|||||||
) -> dict:
|
) -> dict:
|
||||||
"""Donne infos sur l'état de l'évaluation.
|
"""Donne infos sur l'état de l'évaluation.
|
||||||
Ancienne fonction, lente: préférer ModuleImplResults pour tout calcul.
|
Ancienne fonction, lente: préférer ModuleImplResults pour tout calcul.
|
||||||
|
XXX utilisée par de très nombreuses fonctions, dont
|
||||||
|
- _eval_etat<do_evaluation_etat_in_sem (en cours de remplacement)
|
||||||
|
|
||||||
|
- _eval_etat<do_evaluation_etat_in_mod<formsemestre_tableau_modules
|
||||||
|
qui a seulement besoin de
|
||||||
|
nb_evals_completes, nb_evals_en_cours, nb_evals_vides, attente
|
||||||
|
|
||||||
|
renvoie:
|
||||||
{
|
{
|
||||||
nb_inscrits : inscrits au module
|
nb_inscrits : inscrits au module
|
||||||
nb_notes
|
nb_notes
|
||||||
@ -124,7 +133,7 @@ def do_evaluation_etat(
|
|||||||
) # { etudid : note }
|
) # { etudid : note }
|
||||||
|
|
||||||
# ---- Liste des groupes complets et incomplets
|
# ---- Liste des groupes complets et incomplets
|
||||||
E = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
E = sco_evaluation_db.get_evaluation_dict(args={"evaluation_id": evaluation_id})[0]
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
is_malus = Mod["module_type"] == ModuleType.MALUS # True si module de malus
|
is_malus = Mod["module_type"] == ModuleType.MALUS # True si module de malus
|
||||||
@ -275,7 +284,8 @@ def do_evaluation_etat(
|
|||||||
|
|
||||||
|
|
||||||
def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
|
def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
|
||||||
"""Liste les evaluations de tous les modules de ce semestre.
|
"""Liste les évaluations de tous les modules de ce semestre.
|
||||||
|
Triée par module, numero desc, date_debut desc
|
||||||
Donne pour chaque eval son état (voir do_evaluation_etat)
|
Donne pour chaque eval son état (voir do_evaluation_etat)
|
||||||
{ evaluation_id,nb_inscrits, nb_notes, nb_abs, nb_neutre, moy, median, last_modif ... }
|
{ evaluation_id,nb_inscrits, nb_notes, nb_abs, nb_neutre, moy, median, last_modif ... }
|
||||||
|
|
||||||
@ -315,7 +325,7 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
|
|||||||
'evaluation_type': 0,
|
'evaluation_type': 0,
|
||||||
'heure_debut': datetime.time(8, 0),
|
'heure_debut': datetime.time(8, 0),
|
||||||
'heure_fin': datetime.time(9, 30),
|
'heure_fin': datetime.time(9, 30),
|
||||||
'jour': datetime.date(2015, 11, 3), // vide => 1/1/1
|
'jour': datetime.date(2015, 11, 3), // vide => 1/1/1900
|
||||||
'moduleimpl_id': 'GEAMIP80490',
|
'moduleimpl_id': 'GEAMIP80490',
|
||||||
'note_max': 20.0,
|
'note_max': 20.0,
|
||||||
'numero': 0,
|
'numero': 0,
|
||||||
@ -327,7 +337,7 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
|
|||||||
FROM notes_evaluation E, notes_moduleimpl MI
|
FROM notes_evaluation E, notes_moduleimpl MI
|
||||||
WHERE MI.formsemestre_id = %(formsemestre_id)s
|
WHERE MI.formsemestre_id = %(formsemestre_id)s
|
||||||
and MI.id = E.moduleimpl_id
|
and MI.id = E.moduleimpl_id
|
||||||
ORDER BY MI.id, numero desc, jour desc, heure_debut DESC
|
ORDER BY MI.id, numero desc, date_debut desc
|
||||||
"""
|
"""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
@ -335,9 +345,9 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
|
|||||||
res = cursor.dictfetchall()
|
res = cursor.dictfetchall()
|
||||||
# etat de chaque evaluation:
|
# etat de chaque evaluation:
|
||||||
for r in res:
|
for r in res:
|
||||||
r["jour"] = r["jour"] or datetime.date(1900, 1, 1) # pour les comparaisons
|
|
||||||
if with_etat:
|
if with_etat:
|
||||||
r["etat"] = do_evaluation_etat(r["evaluation_id"])
|
r["etat"] = do_evaluation_etat(r["evaluation_id"])
|
||||||
|
r["jour"] = r["date_debut"] or datetime.date(1900, 1, 1)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@ -379,7 +389,20 @@ def _eval_etat(evals):
|
|||||||
|
|
||||||
def do_evaluation_etat_in_sem(formsemestre_id):
|
def do_evaluation_etat_in_sem(formsemestre_id):
|
||||||
"""-> nb_eval_completes, nb_evals_en_cours, nb_evals_vides,
|
"""-> nb_eval_completes, nb_evals_en_cours, nb_evals_vides,
|
||||||
date derniere modif, attente"""
|
date derniere modif, attente
|
||||||
|
|
||||||
|
XXX utilisé par
|
||||||
|
- formsemestre_status_head
|
||||||
|
- gen_formsemestre_recapcomplet_xml
|
||||||
|
- gen_formsemestre_recapcomplet_json
|
||||||
|
|
||||||
|
"nb_evals_completes"
|
||||||
|
"nb_evals_en_cours"
|
||||||
|
"nb_evals_vides"
|
||||||
|
"date_derniere_note"
|
||||||
|
"last_modif"
|
||||||
|
"attente"
|
||||||
|
"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
evals = nt.get_evaluations_etats()
|
evals = nt.get_evaluations_etats()
|
||||||
@ -403,88 +426,97 @@ def formsemestre_evaluations_cal(formsemestre_id):
|
|||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
evals = nt.get_evaluations_etats()
|
evaluations = formsemestre.get_evaluations() # TODO
|
||||||
nb_evals = len(evals)
|
nb_evals = len(evaluations)
|
||||||
|
|
||||||
color_incomplete = "#FF6060"
|
color_incomplete = "#FF6060"
|
||||||
color_complete = "#A0FFA0"
|
color_complete = "#A0FFA0"
|
||||||
color_futur = "#70E0FF"
|
color_futur = "#70E0FF"
|
||||||
|
|
||||||
today = time.strftime("%Y-%m-%d")
|
year = formsemestre.annee_scolaire()
|
||||||
|
|
||||||
year = formsemestre.date_debut.year
|
|
||||||
if formsemestre.date_debut.month < 8:
|
|
||||||
year -= 1 # calendrier septembre a septembre
|
|
||||||
events = {} # (day, halfday) : event
|
events = {} # (day, halfday) : event
|
||||||
for e in evals:
|
for e in evaluations:
|
||||||
etat = e["etat"]
|
if e.date_debut is None:
|
||||||
if not e["jour"]:
|
continue # éval. sans date
|
||||||
continue
|
txt = e.moduleimpl.module.code or e.moduleimpl.module.abbrev or "éval."
|
||||||
day = e["jour"].strftime("%Y-%m-%d")
|
if e.date_debut == e.date_fin:
|
||||||
mod = sco_moduleimpl.moduleimpl_withmodule_list(
|
heure_debut_txt, heure_fin_txt = "?", "?"
|
||||||
moduleimpl_id=e["moduleimpl_id"]
|
|
||||||
)[0]
|
|
||||||
txt = mod["module"]["code"] or mod["module"]["abbrev"] or "eval"
|
|
||||||
if e["heure_debut"]:
|
|
||||||
debut = e["heure_debut"].strftime("%Hh%M")
|
|
||||||
else:
|
else:
|
||||||
debut = "?"
|
heure_debut_txt = e.date_debut.strftime("%Hh%M") if e.date_debut else "?"
|
||||||
if e["heure_fin"]:
|
heure_fin_txt = e.date_fin.strftime("%Hh%M") if e.date_fin else "?"
|
||||||
fin = e["heure_fin"].strftime("%Hh%M")
|
|
||||||
else:
|
description = f"""{
|
||||||
fin = "?"
|
e.moduleimpl.module.titre
|
||||||
description = "%s, de %s à %s" % (mod["module"]["titre"], debut, fin)
|
}, de {heure_debut_txt} à {heure_fin_txt}"""
|
||||||
if etat["evalcomplete"]:
|
|
||||||
|
# Etat (notes completes) de l'évaluation:
|
||||||
|
modimpl_result = nt.modimpls_results[e.moduleimpl.id]
|
||||||
|
if modimpl_result.evaluations_etat[e.id].is_complete:
|
||||||
color = color_complete
|
color = color_complete
|
||||||
else:
|
else:
|
||||||
color = color_incomplete
|
color = color_incomplete
|
||||||
if day > today:
|
if e.date_debut > datetime.datetime.now(scu.TIME_ZONE):
|
||||||
color = color_futur
|
color = color_futur
|
||||||
href = "moduleimpl_status?moduleimpl_id=%s" % e["moduleimpl_id"]
|
href = url_for(
|
||||||
# if e['heure_debut'].hour < 12:
|
"notes.moduleimpl_status",
|
||||||
# halfday = True
|
scodoc_dept=g.scodoc_dept,
|
||||||
# else:
|
moduleimpl_id=e.moduleimpl_id,
|
||||||
# halfday = False
|
)
|
||||||
if not day in events:
|
day = e.date_debut.date().isoformat() # yyyy-mm-dd
|
||||||
# events[(day,halfday)] = [day, txt, color, href, halfday, description, mod]
|
event = events.get(day)
|
||||||
events[day] = [day, txt, color, href, description, mod]
|
if not event:
|
||||||
|
events[day] = [day, txt, color, href, description, e.moduleimpl]
|
||||||
else:
|
else:
|
||||||
e = events[day]
|
if event[-1].id != e.moduleimpl.id:
|
||||||
if e[-1]["moduleimpl_id"] != mod["moduleimpl_id"]:
|
|
||||||
# plusieurs evals de modules differents a la meme date
|
# plusieurs evals de modules differents a la meme date
|
||||||
e[1] += ", " + txt
|
event[1] += ", " + txt
|
||||||
e[4] += ", " + description
|
event[4] += ", " + description
|
||||||
if not etat["evalcomplete"]:
|
if color == color_incomplete:
|
||||||
e[2] = color_incomplete
|
event[2] = color_incomplete
|
||||||
if day > today:
|
if color == color_futur:
|
||||||
e[2] = color_futur
|
event[2] = color_futur
|
||||||
|
|
||||||
CalHTML = sco_abs.YearTable(
|
cal_html = sco_cal.YearTable(
|
||||||
year, events=list(events.values()), halfday=False, pad_width=None
|
year, events=list(events.values()), halfday=False, pad_width=None
|
||||||
)
|
)
|
||||||
|
|
||||||
H = [
|
return f"""
|
||||||
|
{
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
"Evaluations du semestre",
|
"Evaluations du semestre",
|
||||||
cssstyles=["css/calabs.css"],
|
cssstyles=["css/calabs.css"],
|
||||||
),
|
)
|
||||||
'<div class="cal_evaluations">',
|
}
|
||||||
CalHTML,
|
<div class="cal_evaluations">
|
||||||
"</div>",
|
{ cal_html }
|
||||||
"<p>soit %s évaluations planifiées;" % nb_evals,
|
</div>
|
||||||
"""<ul><li>en <span style="background-color: %s">rouge</span> les évaluations passées auxquelles il manque des notes</li>
|
<p>soit {nb_evals} évaluations planifiées;
|
||||||
<li>en <span style="background-color: %s">vert</span> les évaluations déjà notées</li>
|
</p>
|
||||||
<li>en <span style="background-color: %s">bleu</span> les évaluations futures</li></ul></p>"""
|
<ul>
|
||||||
% (color_incomplete, color_complete, color_futur),
|
<li>en <span style=
|
||||||
"""<p><a href="formsemestre_evaluations_delai_correction?formsemestre_id=%s" class="stdlink">voir les délais de correction</a></p>
|
"background-color: {color_incomplete}">rouge</span>
|
||||||
|
les évaluations passées auxquelles il manque des notes
|
||||||
|
</li>
|
||||||
|
<li>en <span style=
|
||||||
|
"background-color: {color_complete}">vert</span>
|
||||||
|
les évaluations déjà notées
|
||||||
|
</li>
|
||||||
|
<li>en <span style=
|
||||||
|
"background-color: {color_futur}">bleu</span>
|
||||||
|
les évaluations futures
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="{
|
||||||
|
url_for("notes.formsemestre_evaluations_delai_correction",
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id
|
||||||
|
)
|
||||||
|
}" class="stdlink">voir les délais de correction</a>
|
||||||
|
</p>
|
||||||
|
{ html_sco_header.sco_footer() }
|
||||||
"""
|
"""
|
||||||
% (formsemestre_id,),
|
|
||||||
html_sco_header.sco_footer(),
|
|
||||||
]
|
|
||||||
return "\n".join(H)
|
|
||||||
|
|
||||||
|
|
||||||
def evaluation_date_first_completion(evaluation_id):
|
def evaluation_date_first_completion(evaluation_id) -> datetime.datetime:
|
||||||
"""Première date à laquelle l'évaluation a été complète
|
"""Première date à laquelle l'évaluation a été complète
|
||||||
ou None si actuellement incomplète
|
ou None si actuellement incomplète
|
||||||
"""
|
"""
|
||||||
@ -496,7 +528,7 @@ def evaluation_date_first_completion(evaluation_id):
|
|||||||
# Il faut considerer les inscriptions au semestre
|
# Il faut considerer les inscriptions au semestre
|
||||||
# (pour avoir l'etat et le groupe) et aussi les inscriptions
|
# (pour avoir l'etat et le groupe) et aussi les inscriptions
|
||||||
# au module (pour gerer les modules optionnels correctement)
|
# au module (pour gerer les modules optionnels correctement)
|
||||||
# E = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
# E = get_evaluation_dict({"id":evaluation_id})[0]
|
||||||
# M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
# M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
# formsemestre_id = M["formsemestre_id"]
|
# formsemestre_id = M["formsemestre_id"]
|
||||||
# insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( formsemestre_id)
|
# insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( formsemestre_id)
|
||||||
@ -536,40 +568,44 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
|||||||
N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus.
|
N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus.
|
||||||
"""
|
"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
evaluations = formsemestre.get_evaluations()
|
||||||
|
rows = []
|
||||||
evals = nt.get_evaluations_etats()
|
for e in evaluations:
|
||||||
T = []
|
if (e.evaluation_type != scu.EVALUATION_NORMALE) or (
|
||||||
for e in evals:
|
e.moduleimpl.module.module_type == ModuleType.MALUS
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0]
|
|
||||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
|
||||||
if (e["evaluation_type"] != scu.EVALUATION_NORMALE) or (
|
|
||||||
Mod["module_type"] == ModuleType.MALUS
|
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
e["date_first_complete"] = evaluation_date_first_completion(e["evaluation_id"])
|
date_first_complete = evaluation_date_first_completion(e.id)
|
||||||
if e["date_first_complete"]:
|
if date_first_complete and e.date_fin:
|
||||||
e["delai_correction"] = (e["date_first_complete"].date() - e["jour"]).days
|
delai_correction = (date_first_complete.date() - e.date_fin).days
|
||||||
else:
|
else:
|
||||||
e["delai_correction"] = None
|
delai_correction = None
|
||||||
|
|
||||||
e["module_code"] = Mod["code"]
|
rows.append(
|
||||||
e["_module_code_target"] = url_for(
|
{
|
||||||
"notes.moduleimpl_status",
|
"date_first_complete": date_first_complete,
|
||||||
scodoc_dept=g.scodoc_dept,
|
"delai_correction": delai_correction,
|
||||||
moduleimpl_id=M["moduleimpl_id"],
|
"jour": e.date_debut.strftime("%d/%m/%Y")
|
||||||
)
|
if e.date_debut
|
||||||
e["module_titre"] = Mod["titre"]
|
else "sans date",
|
||||||
e["responsable_id"] = M["responsable_id"]
|
"_jour_target": url_for(
|
||||||
e["responsable_nomplogin"] = sco_users.user_info(M["responsable_id"])[
|
|
||||||
"nomplogin"
|
|
||||||
]
|
|
||||||
e["_jour_target"] = url_for(
|
|
||||||
"notes.evaluation_listenotes",
|
"notes.evaluation_listenotes",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
evaluation_id=e["evaluation_id"],
|
evaluation_id=e["evaluation_id"],
|
||||||
|
),
|
||||||
|
"module_code": e.moduleimpl.module.code,
|
||||||
|
"_module_code_target": url_for(
|
||||||
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=e.moduleimpl.id,
|
||||||
|
),
|
||||||
|
"module_titre": e.moduleimpl.module.abbrev or e.moduleimpl.module.titre,
|
||||||
|
"responsable_id": e.moduleimpl.responsable_id,
|
||||||
|
"responsable_nomplogin": sco_users.user_info(
|
||||||
|
e.moduleimpl.responsable_id
|
||||||
|
)["nomplogin"],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
T.append(e)
|
|
||||||
|
|
||||||
columns_ids = (
|
columns_ids = (
|
||||||
"module_code",
|
"module_code",
|
||||||
@ -592,16 +628,14 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
|||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles=titles,
|
titles=titles,
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
rows=T,
|
rows=rows,
|
||||||
html_class="table_leftalign table_coldate",
|
html_class="table_leftalign table_coldate",
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_title="<h2>Correction des évaluations du semestre</h2>",
|
html_title="<h2>Correction des évaluations du semestre</h2>",
|
||||||
caption="Correction des évaluations du semestre",
|
caption="Correction des évaluations du semestre",
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
|
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
|
||||||
origin="Généré par %s le " % sco_version.SCONAME
|
origin=f"""Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}""",
|
||||||
+ scu.timedate_human_repr()
|
|
||||||
+ "",
|
|
||||||
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
|
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
|
||||||
)
|
)
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(format=format)
|
||||||
@ -612,78 +646,64 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True):
|
|||||||
"""HTML description of evaluation, for page headers
|
"""HTML description of evaluation, for page headers
|
||||||
edit_in_place: allow in-place editing when permitted (not implemented)
|
edit_in_place: allow in-place editing when permitted (not implemented)
|
||||||
"""
|
"""
|
||||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||||
moduleimpl_id = E["moduleimpl_id"]
|
modimpl = evaluation.moduleimpl
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
responsable: User = db.session.get(User, modimpl.responsable_id)
|
||||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
resp_nomprenom = responsable.get_prenomnom()
|
||||||
formsemestre_id = M["formsemestre_id"]
|
resp_nomcomplet = responsable.get_nomcomplet()
|
||||||
u = sco_users.user_info(M["responsable_id"])
|
can_edit = modimpl.can_edit_notes(current_user, allow_ens=False)
|
||||||
resp = u["prenomnom"]
|
|
||||||
nomcomplet = u["nomcomplet"]
|
|
||||||
can_edit = sco_permissions_check.can_edit_notes(
|
|
||||||
current_user, moduleimpl_id, allow_ens=False
|
|
||||||
)
|
|
||||||
|
|
||||||
link = (
|
mod_descr = f"""<a class="stdlink" href="{url_for("notes.moduleimpl_status",
|
||||||
'<span class="evallink"><a class="stdlink" href="evaluation_listenotes?moduleimpl_id=%s">voir toutes les notes du module</a></span>'
|
scodoc_dept=g.scodoc_dept,
|
||||||
% moduleimpl_id
|
moduleimpl_id=modimpl.id,
|
||||||
)
|
)}">{modimpl.module.code or ""} {modimpl.module.abbrev or modimpl.module.titre or "?"}</a>
|
||||||
mod_descr = (
|
<span class="resp">(resp. <a title="{resp_nomcomplet}">{resp_nomprenom}</a>)</span>
|
||||||
'<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s'
|
<span class="evallink"><a class="stdlink"
|
||||||
% (
|
href="{url_for(
|
||||||
moduleimpl_id,
|
"notes.evaluation_listenotes",
|
||||||
Mod["code"] or "",
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
||||||
Mod["titre"] or "?",
|
}">voir toutes les notes du module</a></span>
|
||||||
nomcomplet,
|
"""
|
||||||
resp,
|
|
||||||
link,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
etit = E["description"] or ""
|
eval_titre = f' "{evaluation.description}"' if evaluation.description else ""
|
||||||
if etit:
|
if modimpl.module.module_type == ModuleType.MALUS:
|
||||||
etit = ' "' + etit + '"'
|
eval_titre += ' <span class="eval_malus">(points de malus)</span>'
|
||||||
if Mod["module_type"] == ModuleType.MALUS:
|
|
||||||
etit += ' <span class="eval_malus">(points de malus)</span>'
|
|
||||||
H = [
|
H = [
|
||||||
'<span class="eval_title">Évaluation%s</span><p><b>Module : %s</b></p>'
|
f"""<span class="eval_title">Évaluation{eval_titre}</span>
|
||||||
% (etit, mod_descr)
|
<p><b>Module : {mod_descr}</b>
|
||||||
|
</p>"""
|
||||||
]
|
]
|
||||||
if Mod["module_type"] == ModuleType.MALUS:
|
if modimpl.module.module_type == ModuleType.MALUS:
|
||||||
# Indique l'UE
|
# Indique l'UE
|
||||||
ue = sco_edit_ue.ue_list(args={"ue_id": Mod["ue_id"]})[0]
|
ue = modimpl.module.ue
|
||||||
H.append("<p><b>UE : %(acronyme)s</b></p>" % ue)
|
H.append(f"<p><b>UE : {ue.acronyme}</b></p>")
|
||||||
# store min/max values used by JS client-side checks:
|
# store min/max values used by JS client-side checks:
|
||||||
H.append(
|
H.append(
|
||||||
'<span id="eval_note_min" class="sco-hidden">-20.</span><span id="eval_note_max" class="sco-hidden">20.</span>'
|
"""<span id="eval_note_min" class="sco-hidden">-20.</span>
|
||||||
|
<span id="eval_note_max" class="sco-hidden">20.</span>"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# date et absences (pas pour evals de malus)
|
# date et absences (pas pour evals de malus)
|
||||||
if E["jour"]:
|
if evaluation.date_debut is not None:
|
||||||
jour = E["jour"]
|
H.append(f"<p>Réalisée le <b>{evaluation.descr_date()}</b> ")
|
||||||
H.append("<p>Réalisée le <b>%s</b> " % (jour))
|
group_id = sco_groups.get_default_group(modimpl.formsemestre_id)
|
||||||
if E["heure_debut"] != E["heure_fin"]:
|
|
||||||
H.append("de %s à %s " % (E["heure_debut"], E["heure_fin"]))
|
|
||||||
group_id = sco_groups.get_default_group(formsemestre_id)
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""<span class="noprint"><a href="{url_for(
|
f"""<span class="evallink"><a class="stdlink" href="{url_for(
|
||||||
'assiduites.get_etat_abs_date',
|
'assiduites.etat_abs_date',
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
group_ids=group_id,
|
group_ids=group_id,
|
||||||
desc=E["description"],
|
desc=evaluation.description or "",
|
||||||
jour=E["jour"],
|
date_debut=evaluation.date_debut.isoformat(),
|
||||||
heure_debut=E["heure_debut"],
|
date_fin=evaluation.date_fin.isoformat(),
|
||||||
heure_fin=E["heure_fin"],
|
|
||||||
)
|
)
|
||||||
}">(absences ce jour)</a></span>"""
|
}">absences ce jour</a></span>"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
jour = "<em>pas de date</em>"
|
H.append("<p><em>sans date</em> ")
|
||||||
H.append("<p>Réalisée le <b>%s</b> " % (jour))
|
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
'</p><p>Coefficient dans le module: <b>%s</b>, notes sur <span id="eval_note_max">%g</span> '
|
f"""</p><p>Coefficient dans le module: <b>{evaluation.coefficient or "0"}</b>,
|
||||||
% (E["coefficient"], E["note_max"])
|
notes sur <span id="eval_note_max">{(evaluation.note_max or 0):g}</span> """
|
||||||
)
|
)
|
||||||
H.append('<span id="eval_note_min" class="sco-hidden">0.</span>')
|
H.append('<span id="eval_note_min" class="sco-hidden">0.</span>')
|
||||||
if can_edit:
|
if can_edit:
|
||||||
@ -697,7 +717,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True):
|
|||||||
if link_saisie:
|
if link_saisie:
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<a class="stdlink" href="{url_for(
|
<a style="margin-left: 12px;" class="stdlink" href="{url_for(
|
||||||
"notes.saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id)
|
"notes.saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id)
|
||||||
}">saisie des notes</a>
|
}">saisie des notes</a>
|
||||||
"""
|
"""
|
||||||
|
@ -512,8 +512,7 @@ def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, line
|
|||||||
# description evaluation
|
# description evaluation
|
||||||
ws.append_single_cell_row(scu.unescape_html(description), style_titres)
|
ws.append_single_cell_row(scu.unescape_html(description), style_titres)
|
||||||
ws.append_single_cell_row(
|
ws.append_single_cell_row(
|
||||||
"Evaluation du %s (coef. %g)"
|
f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient or 0.0):g})",
|
||||||
% (evaluation.jour or "sans date", evaluation.coefficient or 0.0),
|
|
||||||
style,
|
style,
|
||||||
)
|
)
|
||||||
# ligne blanche
|
# ligne blanche
|
||||||
|
@ -1245,7 +1245,9 @@ def do_formsemestre_clone(
|
|||||||
moduleimpl_id=mod_orig["moduleimpl_id"]
|
moduleimpl_id=mod_orig["moduleimpl_id"]
|
||||||
):
|
):
|
||||||
# copie en enlevant la date
|
# copie en enlevant la date
|
||||||
new_eval = e.clone(not_copying=("jour", "moduleimpl_id"))
|
new_eval = e.clone(
|
||||||
|
not_copying=("date_debut", "date_fin", "moduleimpl_id")
|
||||||
|
)
|
||||||
new_eval.moduleimpl_id = mid
|
new_eval.moduleimpl_id = mid
|
||||||
# Copie les poids APC de l'évaluation
|
# Copie les poids APC de l'évaluation
|
||||||
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
|
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
|
||||||
@ -1443,7 +1445,7 @@ def do_formsemestre_delete(formsemestre_id):
|
|||||||
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
# evaluations
|
# evaluations
|
||||||
evals = sco_evaluation_db.do_evaluation_list(
|
evals = sco_evaluation_db.get_evaluation_dict(
|
||||||
args={"moduleimpl_id": mod["moduleimpl_id"]}
|
args={"moduleimpl_id": mod["moduleimpl_id"]}
|
||||||
)
|
)
|
||||||
for e in evals:
|
for e in evals:
|
||||||
|
@ -38,7 +38,7 @@ from app.comp import res_sem
|
|||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import Formation, FormSemestre, FormSemestreInscription, Scolog
|
from app.models import Formation, FormSemestre, FormSemestreInscription, Scolog
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.groups import GroupDescr
|
from app.models.groups import Partition, GroupDescr
|
||||||
from app.models.validations import ScolarEvent
|
from app.models.validations import ScolarEvent
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
@ -236,6 +236,10 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
|||||||
sco_moduleimpl.do_moduleimpl_inscription_delete(
|
sco_moduleimpl.do_moduleimpl_inscription_delete(
|
||||||
moduleimpl_inscription_id, formsemestre_id=formsemestre_id
|
moduleimpl_inscription_id, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# -- désincription de tous les groupes des partitions de ce semestre
|
||||||
|
Partition.formsemestre_remove_etud(formsemestre_id, etud)
|
||||||
|
|
||||||
# -- désincription du semestre
|
# -- désincription du semestre
|
||||||
do_formsemestre_inscription_delete(
|
do_formsemestre_inscription_delete(
|
||||||
insem["formsemestre_inscription_id"], formsemestre_id=formsemestre_id
|
insem["formsemestre_inscription_id"], formsemestre_id=formsemestre_id
|
||||||
@ -259,7 +263,7 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
|||||||
cnx,
|
cnx,
|
||||||
method="formsemestre_desinscription",
|
method="formsemestre_desinscription",
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
msg="desinscription semestre %s" % formsemestre_id,
|
msg=f"desinscription semestre {formsemestre_id}",
|
||||||
commit=False,
|
commit=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ from app.models import (
|
|||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
NotesNotes,
|
NotesNotes,
|
||||||
)
|
)
|
||||||
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
@ -60,7 +61,6 @@ from app.scodoc.sco_exceptions import (
|
|||||||
)
|
)
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
from app.scodoc import sco_abs
|
|
||||||
from app.scodoc import sco_archives
|
from app.scodoc import sco_archives
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
@ -498,7 +498,7 @@ def retreive_formsemestre_from_request() -> int:
|
|||||||
modimpl = modimpl[0]
|
modimpl = modimpl[0]
|
||||||
formsemestre_id = modimpl["formsemestre_id"]
|
formsemestre_id = modimpl["formsemestre_id"]
|
||||||
elif "evaluation_id" in args:
|
elif "evaluation_id" in args:
|
||||||
E = sco_evaluation_db.do_evaluation_list(
|
E = sco_evaluation_db.get_evaluation_dict(
|
||||||
{"evaluation_id": args["evaluation_id"]}
|
{"evaluation_id": args["evaluation_id"]}
|
||||||
)
|
)
|
||||||
if not E:
|
if not E:
|
||||||
@ -620,7 +620,7 @@ def formsemestre_description_table(
|
|||||||
columns_ids += ["Inscrits", "Responsable", "Enseignants"]
|
columns_ids += ["Inscrits", "Responsable", "Enseignants"]
|
||||||
if with_evals:
|
if with_evals:
|
||||||
columns_ids += [
|
columns_ids += [
|
||||||
"jour",
|
"date_evaluation",
|
||||||
"description",
|
"description",
|
||||||
"coefficient",
|
"coefficient",
|
||||||
"evalcomplete_str",
|
"evalcomplete_str",
|
||||||
@ -630,7 +630,7 @@ def formsemestre_description_table(
|
|||||||
titles = {title: title for title in columns_ids}
|
titles = {title: title for title in columns_ids}
|
||||||
titles.update({f"ue_{ue.id}": ue.acronyme for ue in ues})
|
titles.update({f"ue_{ue.id}": ue.acronyme for ue in ues})
|
||||||
titles["ects"] = "ECTS"
|
titles["ects"] = "ECTS"
|
||||||
titles["jour"] = "Évaluation"
|
titles["date_evaluation"] = "Évaluation"
|
||||||
titles["description"] = ""
|
titles["description"] = ""
|
||||||
titles["coefficient"] = "Coef. éval."
|
titles["coefficient"] = "Coef. éval."
|
||||||
titles["evalcomplete_str"] = "Complète"
|
titles["evalcomplete_str"] = "Complète"
|
||||||
@ -660,9 +660,11 @@ def formsemestre_description_table(
|
|||||||
"Module": ue.titre,
|
"Module": ue.titre,
|
||||||
"_css_row_class": "table_row_ue",
|
"_css_row_class": "table_row_ue",
|
||||||
}
|
}
|
||||||
if use_ue_coefs:
|
if use_ue_coefs and ue.type != UE_SPORT:
|
||||||
ue_info["Coef."] = ue.coefficient
|
ue_info["Coef."] = ue.coefficient or "0."
|
||||||
ue_info["Coef._class"] = "ue_coef"
|
ue_info["_Coef._class"] = "ue_coef"
|
||||||
|
if not ue.coefficient:
|
||||||
|
ue_info["_Coef._class"] += " ue_coef_nul"
|
||||||
if ue.color:
|
if ue.color:
|
||||||
for k in list(ue_info.keys()):
|
for k in list(ue_info.keys()):
|
||||||
if not k.startswith("_"):
|
if not k.startswith("_"):
|
||||||
@ -738,8 +740,10 @@ def formsemestre_description_table(
|
|||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
evaluation_id=e["evaluation_id"],
|
evaluation_id=e["evaluation_id"],
|
||||||
)
|
)
|
||||||
e["_jour_order"] = e["jour"].isoformat()
|
e["_date_evaluation_order"] = e["jour"].isoformat()
|
||||||
e["jour"] = e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
|
e["date_evaluation"] = (
|
||||||
|
e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
|
||||||
|
)
|
||||||
e["UE"] = row["UE"]
|
e["UE"] = row["UE"]
|
||||||
e["_UE_td_attrs"] = row["_UE_td_attrs"]
|
e["_UE_td_attrs"] = row["_UE_td_attrs"]
|
||||||
e["Code"] = row["Code"]
|
e["Code"] = row["Code"]
|
||||||
@ -884,13 +888,12 @@ def _make_listes_sem(formsemestre: FormSemestre, with_absences=True):
|
|||||||
if n_members == 0:
|
if n_members == 0:
|
||||||
continue # skip empty groups
|
continue # skip empty groups
|
||||||
partition_is_empty = False
|
partition_is_empty = False
|
||||||
group["url_etat"] = url_for(
|
group[
|
||||||
"absences.EtatAbsencesGr",
|
"url_etat"
|
||||||
group_ids=group["group_id"],
|
] = f"""{
|
||||||
debut=formsemestre.date_debut.strftime("%d/%m/%Y"),
|
url_for("assiduites.visu_assi_group", scodoc_dept=g.scodoc_dept)
|
||||||
fin=formsemestre.date_fin.strftime("%d/%m/%Y"),
|
}?group_ids={group["id"]}&date_debut={formsemestre.date_debut.isoformat()}&date_fin={formsemestre.date_fin.isoformat()}"""
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
)
|
|
||||||
if group["group_name"]:
|
if group["group_name"]:
|
||||||
group["label"] = "groupe %(group_name)s" % group
|
group["label"] = "groupe %(group_name)s" % group
|
||||||
else:
|
else:
|
||||||
|
@ -45,7 +45,7 @@ from app import db
|
|||||||
from app.models import FormSemestre
|
from app.models import FormSemestre
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_cal
|
||||||
from app.scodoc import sco_excel
|
from app.scodoc import sco_excel
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
@ -819,9 +819,12 @@ def tab_absences_html(groups_infos, etat=None):
|
|||||||
H = ['<div class="tab-content">']
|
H = ['<div class="tab-content">']
|
||||||
if not groups_infos.members:
|
if not groups_infos.members:
|
||||||
return "".join(H) + "<h3>Aucun étudiant !</h3></div>"
|
return "".join(H) + "<h3>Aucun étudiant !</h3></div>"
|
||||||
|
|
||||||
|
group_ids: str = ",".join(map(str, groups_infos.group_ids))
|
||||||
|
formsemestre: FormSemestre = groups_infos.get_formsemestre()
|
||||||
H.extend(
|
H.extend(
|
||||||
[
|
[
|
||||||
"<h3>Absences</h3>",
|
"<h3>Assiduités</h3>",
|
||||||
'<ul class="ul_abs">',
|
'<ul class="ul_abs">',
|
||||||
"<li>",
|
"<li>",
|
||||||
form_choix_saisie_semaine(groups_infos), # Ajout Le Havre
|
form_choix_saisie_semaine(groups_infos), # Ajout Le Havre
|
||||||
@ -829,12 +832,9 @@ def tab_absences_html(groups_infos, etat=None):
|
|||||||
"<li>",
|
"<li>",
|
||||||
form_choix_jour_saisie_hebdo(groups_infos),
|
form_choix_jour_saisie_hebdo(groups_infos),
|
||||||
"</li>",
|
"</li>",
|
||||||
"""<li><a class="stdlink" href="Absences/EtatAbsencesGr?%s&debut=%s&fin=%s">État des absences du groupe</a></li>"""
|
f"""<li><a href="{
|
||||||
% (
|
url_for("assiduites.visu_assi_group", scodoc_dept=g.scodoc_dept)
|
||||||
groups_infos.groups_query_args,
|
}?group_ids={group_ids}&date_debut={formsemestre.date_debut.isoformat()}&date_fin={formsemestre.date_fin.isoformat()}">État des assiduités du groupe</li>""",
|
||||||
groups_infos.formsemestre["date_debut"],
|
|
||||||
groups_infos.formsemestre["date_fin"],
|
|
||||||
),
|
|
||||||
"</ul>",
|
"</ul>",
|
||||||
"<h3>Feuilles</h3>",
|
"<h3>Feuilles</h3>",
|
||||||
'<ul class="ul_feuilles">',
|
'<ul class="ul_feuilles">',
|
||||||
@ -890,13 +890,18 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None):
|
|||||||
if not authuser.has_permission(Permission.ScoAbsChange):
|
if not authuser.has_permission(Permission.ScoAbsChange):
|
||||||
return ""
|
return ""
|
||||||
sem = groups_infos.formsemestre
|
sem = groups_infos.formsemestre
|
||||||
first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday()
|
first_monday = sco_cal.ddmmyyyy(sem["date_debut"]).prev_monday()
|
||||||
today_idx = datetime.date.today().weekday()
|
today_idx = datetime.date.today().weekday()
|
||||||
|
# TODO-ASSIDUITE
|
||||||
|
# Utilisation d'un formulaire et de la saisie journalière.
|
||||||
|
# Dans le formulaire : choisir le jour (lun/mar/...)
|
||||||
|
|
||||||
FA = [] # formulaire avec menu saisi absences
|
FA = [] # formulaire avec menu saisi absences
|
||||||
FA.append(
|
FA.append(
|
||||||
'<form id="form_choix_jour_saisie_hebdo" action="Absences/SignaleAbsenceGrSemestre" method="get">'
|
# TODO-ASSIDUITE et utiliser url_for... (was Absences/SignaleAbsenceGrSemestre)
|
||||||
|
'<form id="form_choix_jour_saisie_hebdo" action="XXX" method="get">'
|
||||||
)
|
)
|
||||||
|
|
||||||
FA.append('<input type="hidden" name="datefin" value="%(date_fin)s"/>' % sem)
|
FA.append('<input type="hidden" name="datefin" value="%(date_fin)s"/>' % sem)
|
||||||
FA.append(groups_infos.get_form_elem())
|
FA.append(groups_infos.get_form_elem())
|
||||||
if moduleimpl_id:
|
if moduleimpl_id:
|
||||||
@ -906,12 +911,12 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None):
|
|||||||
FA.append('<input type="hidden" name="destination" value=""/>')
|
FA.append('<input type="hidden" name="destination" value=""/>')
|
||||||
|
|
||||||
FA.append(
|
FA.append(
|
||||||
"""<input type="button" onclick="$('#form_choix_jour_saisie_hebdo')[0].destination.value=get_current_url(); $('#form_choix_jour_saisie_hebdo').submit();" value="Saisir absences du "/>"""
|
"""<input type="button" onclick="$('#form_choix_jour_saisie_hebdo')[0].destination.value=get_current_url(); $('#form_choix_jour_saisie_hebdo').submit();" value="Saisir absences du (NON DISPONIBLE) "/>"""
|
||||||
)
|
)
|
||||||
FA.append("""<select name="datedebut">""")
|
FA.append("""<select name="datedebut">""")
|
||||||
date = first_monday
|
date = first_monday
|
||||||
i = 0
|
i = 0
|
||||||
for jour in sco_abs.day_names():
|
for jour in sco_cal.day_names():
|
||||||
if i == today_idx:
|
if i == today_idx:
|
||||||
sel = "selected"
|
sel = "selected"
|
||||||
else:
|
else:
|
||||||
@ -945,14 +950,17 @@ def form_choix_saisie_semaine(groups_infos):
|
|||||||
) # car ici utilisee dans un format string !
|
) # car ici utilisee dans un format string !
|
||||||
|
|
||||||
DateJour = time.strftime("%d/%m/%Y")
|
DateJour = time.strftime("%d/%m/%Y")
|
||||||
datelundi = sco_abs.ddmmyyyy(DateJour).prev_monday()
|
datelundi = sco_cal.ddmmyyyy(DateJour).prev_monday()
|
||||||
FA = [] # formulaire avec menu saisie hebdo des absences
|
FA = [] # formulaire avec menu saisie hebdo des absences
|
||||||
|
# XXX TODO-ASSIDUITE et utiliser un POST
|
||||||
FA.append('<form action="Absences/SignaleAbsenceGrHebdo" method="get">')
|
FA.append('<form action="Absences/SignaleAbsenceGrHebdo" method="get">')
|
||||||
FA.append('<input type="hidden" name="datelundi" value="%s"/>' % datelundi)
|
FA.append('<input type="hidden" name="datelundi" value="%s"/>' % datelundi)
|
||||||
FA.append('<input type="hidden" name="moduleimpl_id" value="%s"/>' % moduleimpl_id)
|
FA.append('<input type="hidden" name="moduleimpl_id" value="%s"/>' % moduleimpl_id)
|
||||||
FA.append('<input type="hidden" name="destination" value="%s"/>' % destination)
|
FA.append('<input type="hidden" name="destination" value="%s"/>' % destination)
|
||||||
FA.append(groups_infos.get_form_elem())
|
FA.append(groups_infos.get_form_elem())
|
||||||
FA.append('<input type="submit" class="button" value="Saisie à la semaine" />')
|
FA.append(
|
||||||
|
'<input type="submit" class="button" value="Saisie à la semaine (NON DISPONIBLE)" />'
|
||||||
|
) # XXX
|
||||||
FA.append("</form>")
|
FA.append("</form>")
|
||||||
return "\n".join(FA)
|
return "\n".join(FA)
|
||||||
|
|
||||||
|
@ -171,12 +171,16 @@ def list_inscrits_date(sem):
|
|||||||
return [x[0] for x in cursor.fetchall()]
|
return [x[0] for x in cursor.fetchall()]
|
||||||
|
|
||||||
|
|
||||||
def do_inscrit(sem, etudids, inscrit_groupes=False):
|
def do_inscrit(sem, etudids, inscrit_groupes=False, inscrit_parcours=False):
|
||||||
"""Inscrit ces etudiants dans ce semestre
|
"""Inscrit ces etudiants dans ce semestre
|
||||||
(la liste doit avoir été vérifiée au préalable)
|
(la liste doit avoir été vérifiée au préalable)
|
||||||
En option: inscrit aux mêmes groupes que dans le semestre origine
|
En option:
|
||||||
|
- Si inscrit_groupes, inscrit aux mêmes groupes que dans le semestre origine
|
||||||
|
(toutes partitions, y compris parcours)
|
||||||
|
- Si inscrit_parcours, inscrit au même groupe de parcours (mais ignore les autres partitions)
|
||||||
|
(si les deux sont vrais, inscrit_parcours n'a pas d'effet)
|
||||||
"""
|
"""
|
||||||
# TODO à ré-écrire pour utiliser le smodèle, notamment GroupDescr
|
# TODO à ré-écrire pour utiliser les modèles, notamment GroupDescr
|
||||||
formsemestre: FormSemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
|
formsemestre: FormSemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
|
||||||
formsemestre.setup_parcours_groups()
|
formsemestre.setup_parcours_groups()
|
||||||
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
|
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
|
||||||
@ -187,7 +191,7 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
|||||||
etat=scu.INSCRIT,
|
etat=scu.INSCRIT,
|
||||||
method="formsemestre_inscr_passage",
|
method="formsemestre_inscr_passage",
|
||||||
)
|
)
|
||||||
if inscrit_groupes:
|
if inscrit_groupes or inscrit_parcours:
|
||||||
# Inscription dans les mêmes groupes que ceux du semestre d'origine,
|
# Inscription dans les mêmes groupes que ceux du semestre d'origine,
|
||||||
# s'ils existent.
|
# s'ils existent.
|
||||||
# (mise en correspondance à partir du nom du groupe, sans tenir compte
|
# (mise en correspondance à partir du nom du groupe, sans tenir compte
|
||||||
@ -223,11 +227,16 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
|||||||
group: GroupDescr = db.session.get(
|
group: GroupDescr = db.session.get(
|
||||||
GroupDescr, partition_group["group_id"]
|
GroupDescr, partition_group["group_id"]
|
||||||
)
|
)
|
||||||
|
if inscrit_groupes or (
|
||||||
|
group.partition.partition_name == scu.PARTITION_PARCOURS
|
||||||
|
and inscrit_parcours
|
||||||
|
):
|
||||||
sco_groups.change_etud_group_in_partition(etudid, group)
|
sco_groups.change_etud_group_in_partition(etudid, group)
|
||||||
|
|
||||||
|
|
||||||
def do_desinscrit(sem, etudids):
|
def do_desinscrit(sem: dict, etudids: list[int]):
|
||||||
log("do_desinscrit: %s" % etudids)
|
"désinscrit les étudiants indiqués du formsemestre"
|
||||||
|
log(f"do_desinscrit: {etudids}")
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
sco_formsemestre_inscriptions.do_formsemestre_desinscription(
|
sco_formsemestre_inscriptions.do_formsemestre_desinscription(
|
||||||
etudid, sem["formsemestre_id"]
|
etudid, sem["formsemestre_id"]
|
||||||
@ -273,6 +282,7 @@ def formsemestre_inscr_passage(
|
|||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etuds=[],
|
etuds=[],
|
||||||
inscrit_groupes=False,
|
inscrit_groupes=False,
|
||||||
|
inscrit_parcours=False,
|
||||||
submitted=False,
|
submitted=False,
|
||||||
dialog_confirmed=False,
|
dialog_confirmed=False,
|
||||||
ignore_jury=False,
|
ignore_jury=False,
|
||||||
@ -291,6 +301,7 @@ def formsemestre_inscr_passage(
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
inscrit_groupes = int(inscrit_groupes)
|
inscrit_groupes = int(inscrit_groupes)
|
||||||
|
inscrit_parcours = int(inscrit_parcours)
|
||||||
ignore_jury = int(ignore_jury)
|
ignore_jury = int(ignore_jury)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
# -- check lock
|
# -- check lock
|
||||||
@ -335,6 +346,7 @@ def formsemestre_inscr_passage(
|
|||||||
candidats_non_inscrits,
|
candidats_non_inscrits,
|
||||||
inscrits_ailleurs,
|
inscrits_ailleurs,
|
||||||
inscrit_groupes=inscrit_groupes,
|
inscrit_groupes=inscrit_groupes,
|
||||||
|
inscrit_parcours=inscrit_parcours,
|
||||||
ignore_jury=ignore_jury,
|
ignore_jury=ignore_jury,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -376,6 +388,7 @@ def formsemestre_inscr_passage(
|
|||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
"etuds": ",".join([str(x) for x in etuds]),
|
"etuds": ",".join([str(x) for x in etuds]),
|
||||||
"inscrit_groupes": inscrit_groupes,
|
"inscrit_groupes": inscrit_groupes,
|
||||||
|
"inscrit_parcours": inscrit_parcours,
|
||||||
"ignore_jury": ignore_jury,
|
"ignore_jury": ignore_jury,
|
||||||
"submitted": 1,
|
"submitted": 1,
|
||||||
},
|
},
|
||||||
@ -388,6 +401,7 @@ def formsemestre_inscr_passage(
|
|||||||
sem,
|
sem,
|
||||||
a_inscrire,
|
a_inscrire,
|
||||||
inscrit_groupes=inscrit_groupes,
|
inscrit_groupes=inscrit_groupes,
|
||||||
|
inscrit_parcours=inscrit_parcours,
|
||||||
)
|
)
|
||||||
# Désinscriptions:
|
# Désinscriptions:
|
||||||
do_desinscrit(sem, a_desinscrire)
|
do_desinscrit(sem, a_desinscrire)
|
||||||
@ -433,15 +447,21 @@ def _build_page(
|
|||||||
candidats_non_inscrits,
|
candidats_non_inscrits,
|
||||||
inscrits_ailleurs,
|
inscrits_ailleurs,
|
||||||
inscrit_groupes=False,
|
inscrit_groupes=False,
|
||||||
|
inscrit_parcours=False,
|
||||||
ignore_jury=False,
|
ignore_jury=False,
|
||||||
):
|
):
|
||||||
formsemestre: FormSemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
|
formsemestre: FormSemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
|
||||||
inscrit_groupes = int(inscrit_groupes)
|
inscrit_groupes = int(inscrit_groupes)
|
||||||
|
inscrit_parcours = int(inscrit_parcours)
|
||||||
ignore_jury = int(ignore_jury)
|
ignore_jury = int(ignore_jury)
|
||||||
if inscrit_groupes:
|
if inscrit_groupes:
|
||||||
inscrit_groupes_checked = " checked"
|
inscrit_groupes_checked = " checked"
|
||||||
else:
|
else:
|
||||||
inscrit_groupes_checked = ""
|
inscrit_groupes_checked = ""
|
||||||
|
if inscrit_parcours:
|
||||||
|
inscrit_parcours_checked = " checked"
|
||||||
|
else:
|
||||||
|
inscrit_parcours_checked = ""
|
||||||
if ignore_jury:
|
if ignore_jury:
|
||||||
ignore_jury_checked = " checked"
|
ignore_jury_checked = " checked"
|
||||||
else:
|
else:
|
||||||
@ -458,17 +478,23 @@ def _build_page(
|
|||||||
<a href="#help">aide</a>
|
<a href="#help">aide</a>
|
||||||
|
|
||||||
<input name="inscrit_groupes" type="checkbox" value="1"
|
<input name="inscrit_groupes" type="checkbox" value="1"
|
||||||
{inscrit_groupes_checked}>inscrire aux mêmes groupes</input>
|
{inscrit_groupes_checked}>inscrire aux mêmes groupes (y compris parcours)</input>
|
||||||
|
|
||||||
|
<input name="inscrit_parcours" type="checkbox" value="1"
|
||||||
|
{inscrit_parcours_checked}>inscrire aux mêmes parcours</input>
|
||||||
|
|
||||||
<input name="ignore_jury" type="checkbox" value="1" onchange="document.f.submit()"
|
<input name="ignore_jury" type="checkbox" value="1" onchange="document.f.submit()"
|
||||||
{ignore_jury_checked}>inclure tous les étudiants (même sans décision de jury)</input>
|
{ignore_jury_checked}>inclure tous les étudiants (même sans décision de jury)</input>
|
||||||
|
|
||||||
<div class="pas_recap">Actuellement <span id="nbinscrits">{len(inscrits)}</span> inscrits
|
<div class="pas_recap">Actuellement <span id="nbinscrits">{len(inscrits)}</span>
|
||||||
et {len(candidats_non_inscrits)} candidats supplémentaires
|
inscrits et {len(candidats_non_inscrits)} candidats supplémentaires.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>{scu.EMO_WARNING} <em>Seuls les semestres dont la date de fin est antérieure à la date de début
|
<div>{scu.EMO_WARNING}
|
||||||
de ce semestre ({formsemestre.date_debut.strftime("%d/%m/%Y")}) sont pris en compte.</em></div>
|
<em>Seuls les semestres dont la date de fin est antérieure à la date de début
|
||||||
|
de ce semestre ({formsemestre.date_debut.strftime("%d/%m/%Y")}) sont pris en
|
||||||
|
compte.</em>
|
||||||
|
</div>
|
||||||
{etuds_select_boxes(auth_etuds_by_sem, inscrits_ailleurs)}
|
{etuds_select_boxes(auth_etuds_by_sem, inscrits_ailleurs)}
|
||||||
|
|
||||||
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
||||||
@ -498,7 +524,8 @@ def _build_page(
|
|||||||
return H
|
return H
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_inscr_passage_help(sem):
|
def formsemestre_inscr_passage_help(sem: dict):
|
||||||
|
"texte d'aide en bas de la page passage des étudiants"
|
||||||
return f"""<div class="pas_help"><h3><a name="help">Explications</a></h3>
|
return f"""<div class="pas_help"><h3><a name="help">Explications</a></h3>
|
||||||
<p>Cette page permet d'inscrire des étudiants dans le semestre destination
|
<p>Cette page permet d'inscrire des étudiants dans le semestre destination
|
||||||
<a class="stdlink"
|
<a class="stdlink"
|
||||||
@ -507,18 +534,34 @@ def formsemestre_inscr_passage_help(sem):
|
|||||||
}">{sem['titreannee']}</a>,
|
}">{sem['titreannee']}</a>,
|
||||||
et d'en désincrire si besoin.
|
et d'en désincrire si besoin.
|
||||||
</p>
|
</p>
|
||||||
<p>Les étudiants sont groupés par semestres d'origines. Ceux qui sont en caractères
|
<p>Les étudiants sont groupés par semestre d'origine. Ceux qui sont en caractères
|
||||||
<span class="inscrit">gras</span> sont déjà inscrits dans le semestre destination.
|
<span class="inscrit">gras</span> sont déjà inscrits dans le semestre destination.
|
||||||
Ceux qui sont en <span class"inscrailleurs">gras et en rouge</span> sont inscrits
|
Ceux qui sont en <span class"inscrailleurs">gras et en rouge</span> sont inscrits
|
||||||
dans un <em>autre</em> semestre.</p>
|
dans un <em>autre</em> semestre.
|
||||||
<p>Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter d'autres
|
</p>
|
||||||
étudiants à inscrire dans le semestre destination.</p>
|
<p>Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter
|
||||||
<p>Si vous dé-selectionnez un étudiant déjà inscrit (en gras), il sera désinscrit.</p>
|
d'autres étudiants à inscrire dans le semestre destination.
|
||||||
<p>Le bouton <em>inscrire aux mêmes groupes</em> ne prend en compte que les groupes qui existent
|
</p>
|
||||||
dans les deux semestres: pensez à créer les partitions et groupes que vous souhaitez conserver
|
|
||||||
<b>avant</b> d'inscrire les étudiants.
|
<p>Si vous dé-selectionnez un étudiant déjà inscrit (en gras), il sera désinscrit.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Le bouton <em>inscrire aux mêmes groupes</em> ne prend en compte que les groupes
|
||||||
|
qui existent dans les deux semestres: pensez à créer les partitions et groupes que
|
||||||
|
vous souhaitez conserver <b>avant</b> d'inscrire les étudiants.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Les parcours de BUT sont gérés comme des groupes de la partition parcours: si on
|
||||||
|
conserve les groupes, on conserve les parcours (là aussi, pensez à les cocher dans
|
||||||
|
<a class="stdlink" href="{
|
||||||
|
url_for("notes.formsemestre_editwithmodules", scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=sem["formsemestre_id"] )
|
||||||
|
}">modifier le semestre</a> avant de faire passer les étudiants).
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p class="help">Aucune action ne sera effectuée si vous n'appuyez pas sur le bouton
|
||||||
|
"Appliquer les modifications" !
|
||||||
</p>
|
</p>
|
||||||
<p class="help">Aucune action ne sera effectuée si vous n'appuyez pas sur le bouton "Appliquer les modifications" !</p>
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -69,38 +69,44 @@ def do_evaluation_listenotes(
|
|||||||
mode = None
|
mode = None
|
||||||
if moduleimpl_id:
|
if moduleimpl_id:
|
||||||
mode = "module"
|
mode = "module"
|
||||||
evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
|
evals = sco_evaluation_db.get_evaluation_dict({"moduleimpl_id": moduleimpl_id})
|
||||||
elif evaluation_id:
|
elif evaluation_id:
|
||||||
mode = "eval"
|
mode = "eval"
|
||||||
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
|
evals = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})
|
||||||
else:
|
else:
|
||||||
raise ValueError("missing argument: evaluation or module")
|
raise ValueError("missing argument: evaluation or module")
|
||||||
if not evals:
|
if not evals:
|
||||||
return "<p>Aucune évaluation !</p>", "ScoDoc"
|
return "<p>Aucune évaluation !</p>", "ScoDoc"
|
||||||
|
|
||||||
E = evals[0] # il y a au moins une evaluation
|
eval_dict = evals[0] # il y a au moins une evaluation
|
||||||
modimpl = db.session.get(ModuleImpl, E["moduleimpl_id"])
|
modimpl = db.session.get(ModuleImpl, eval_dict["moduleimpl_id"])
|
||||||
# description de l'evaluation
|
# description de l'evaluation
|
||||||
if mode == "eval":
|
if mode == "eval":
|
||||||
H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)]
|
H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)]
|
||||||
page_title = f"Notes {E['description'] or modimpl.module.code}"
|
page_title = f"Notes {eval_dict['description'] or modimpl.module.code}"
|
||||||
else:
|
else:
|
||||||
H = []
|
H = []
|
||||||
page_title = f"Notes {modimpl.module.code}"
|
page_title = f"Notes {modimpl.module.code}"
|
||||||
# groupes
|
# groupes
|
||||||
groups = sco_groups.do_evaluation_listegroupes(
|
groups = sco_groups.do_evaluation_listegroupes(
|
||||||
E["evaluation_id"], include_default=True
|
eval_dict["evaluation_id"], include_default=True
|
||||||
)
|
)
|
||||||
grlabs = [g["group_name"] or "tous" for g in groups] # legendes des boutons
|
grlabs = [g["group_name"] or "tous" for g in groups] # legendes des boutons
|
||||||
grnams = [str(g["group_id"]) for g in groups] # noms des checkbox
|
grnams = [str(g["group_id"]) for g in groups] # noms des checkbox
|
||||||
|
|
||||||
if len(evals) > 1:
|
if len(evals) > 1:
|
||||||
descr = [
|
descr = [
|
||||||
("moduleimpl_id", {"default": E["moduleimpl_id"], "input_type": "hidden"})
|
(
|
||||||
|
"moduleimpl_id",
|
||||||
|
{"default": eval_dict["moduleimpl_id"], "input_type": "hidden"},
|
||||||
|
)
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
descr = [
|
descr = [
|
||||||
("evaluation_id", {"default": E["evaluation_id"], "input_type": "hidden"})
|
(
|
||||||
|
"evaluation_id",
|
||||||
|
{"default": eval_dict["evaluation_id"], "input_type": "hidden"},
|
||||||
|
)
|
||||||
]
|
]
|
||||||
if len(grnams) > 1:
|
if len(grnams) > 1:
|
||||||
descr += [
|
descr += [
|
||||||
@ -199,7 +205,7 @@ def do_evaluation_listenotes(
|
|||||||
url_for(
|
url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
moduleimpl_id=E["moduleimpl_id"],
|
moduleimpl_id=eval_dict["moduleimpl_id"],
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"",
|
"",
|
||||||
|
@ -230,7 +230,7 @@ def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None):
|
|||||||
return _moduleimpl_inscriptionEditor.list(cnx, args)
|
return _moduleimpl_inscriptionEditor.list(cnx, args)
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_listeetuds(moduleimpl_id):
|
def moduleimpl_listeetuds(moduleimpl_id): # XXX OBSOLETE
|
||||||
"retourne liste des etudids inscrits a ce module"
|
"retourne liste des etudids inscrits a ce module"
|
||||||
req = """SELECT DISTINCT Im.etudid
|
req = """SELECT DISTINCT Im.etudid
|
||||||
FROM notes_moduleimpl_inscription Im,
|
FROM notes_moduleimpl_inscription Im,
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"""
|
"""
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
|
import datetime
|
||||||
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@ -47,10 +48,9 @@ from app.scodoc.sco_permissions import Permission
|
|||||||
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_cal
|
||||||
from app.scodoc import sco_compute_moy
|
from app.scodoc import sco_compute_moy
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
from app.scodoc import sco_formsemestre_status
|
from app.scodoc import sco_formsemestre_status
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
@ -59,19 +59,15 @@ from app.tables import list_etuds
|
|||||||
|
|
||||||
|
|
||||||
# menu evaluation dans moduleimpl
|
# menu evaluation dans moduleimpl
|
||||||
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
|
||||||
"Menu avec actions sur une evaluation"
|
"Menu avec actions sur une evaluation"
|
||||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
modimpl: ModuleImpl = evaluation.moduleimpl
|
||||||
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
group_id = sco_groups.get_default_group(modimpl.formsemestre_id)
|
||||||
|
evaluation_id = evaluation.id
|
||||||
|
can_edit_notes = modimpl.can_edit_notes(current_user, allow_ens=False)
|
||||||
|
can_edit_notes_ens = modimpl.can_edit_notes(current_user)
|
||||||
|
|
||||||
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"])
|
if can_edit_notes and nbnotes != 0:
|
||||||
|
|
||||||
if (
|
|
||||||
sco_permissions_check.can_edit_notes(
|
|
||||||
current_user, E["moduleimpl_id"], allow_ens=False
|
|
||||||
)
|
|
||||||
and nbnotes != 0
|
|
||||||
):
|
|
||||||
sup_label = "Supprimer évaluation impossible (il y a des notes)"
|
sup_label = "Supprimer évaluation impossible (il y a des notes)"
|
||||||
else:
|
else:
|
||||||
sup_label = "Supprimer évaluation"
|
sup_label = "Supprimer évaluation"
|
||||||
@ -83,9 +79,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
|||||||
"args": {
|
"args": {
|
||||||
"evaluation_id": evaluation_id,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_edit_notes(
|
"enabled": can_edit_notes_ens,
|
||||||
current_user, E["moduleimpl_id"]
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Modifier évaluation",
|
"title": "Modifier évaluation",
|
||||||
@ -93,9 +87,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
|||||||
"args": {
|
"args": {
|
||||||
"evaluation_id": evaluation_id,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_edit_notes(
|
"enabled": can_edit_notes,
|
||||||
current_user, E["moduleimpl_id"], allow_ens=False
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": sup_label,
|
"title": sup_label,
|
||||||
@ -103,10 +95,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
|||||||
"args": {
|
"args": {
|
||||||
"evaluation_id": evaluation_id,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": nbnotes == 0
|
"enabled": nbnotes == 0 and can_edit_notes,
|
||||||
and sco_permissions_check.can_edit_notes(
|
|
||||||
current_user, E["moduleimpl_id"], allow_ens=False
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Supprimer toutes les notes",
|
"title": "Supprimer toutes les notes",
|
||||||
@ -114,9 +103,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
|||||||
"args": {
|
"args": {
|
||||||
"evaluation_id": evaluation_id,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_edit_notes(
|
"enabled": can_edit_notes,
|
||||||
current_user, E["moduleimpl_id"], allow_ens=False
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Afficher les notes",
|
"title": "Afficher les notes",
|
||||||
@ -132,21 +119,18 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
|||||||
"args": {
|
"args": {
|
||||||
"evaluation_id": evaluation_id,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_edit_notes(
|
"enabled": can_edit_notes_ens,
|
||||||
current_user, E["moduleimpl_id"]
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Absences ce jour",
|
"title": "Absences ce jour",
|
||||||
"endpoint": "assiduites.get_etat_abs_date",
|
"endpoint": "assiduites.etat_abs_date",
|
||||||
"args": {
|
"args": {
|
||||||
"group_ids": group_id,
|
"group_ids": group_id,
|
||||||
"desc": E["description"],
|
"desc": evaluation.description or "",
|
||||||
"jour": E["jour"],
|
"date_debut": evaluation.date_debut.isoformat(),
|
||||||
"heure_debut": E["heure_debut"],
|
"date_fin": evaluation.date_fin.isoformat(),
|
||||||
"heure_fin": E["heure_fin"],
|
|
||||||
},
|
},
|
||||||
"enabled": E["jour"],
|
"enabled": evaluation.date_debut is not None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Vérifier notes vs absents",
|
"title": "Vérifier notes vs absents",
|
||||||
@ -154,7 +138,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
|||||||
"args": {
|
"args": {
|
||||||
"evaluation_id": evaluation_id,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": nbnotes > 0 and E["jour"],
|
"enabled": nbnotes > 0 and evaluation.date_debut is not None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -203,11 +187,10 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
# Evaluations, la plus RECENTE en tête
|
# Evaluations, par numéros ou la plus RECENTE en tête
|
||||||
evaluations = modimpl.evaluations.order_by(
|
evaluations = modimpl.evaluations.order_by(
|
||||||
Evaluation.numero.desc(),
|
Evaluation.numero.desc(),
|
||||||
Evaluation.jour.desc(),
|
Evaluation.date_debut.desc(),
|
||||||
Evaluation.heure_debut.desc(),
|
|
||||||
).all()
|
).all()
|
||||||
nb_evaluations = len(evaluations)
|
nb_evaluations = len(evaluations)
|
||||||
max_poids = max(
|
max_poids = max(
|
||||||
@ -333,10 +316,6 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
'<tr><td colspan="4">'
|
'<tr><td colspan="4">'
|
||||||
# <em title="mode de calcul de la moyenne du module">règle de calcul standard</em>'
|
# <em title="mode de calcul de la moyenne du module">règle de calcul standard</em>'
|
||||||
)
|
)
|
||||||
# if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False):
|
|
||||||
# H.append(
|
|
||||||
# f' (<a class="stdlink" href="edit_moduleimpl_expr?moduleimpl_id={moduleimpl_id}">changer</a>)'
|
|
||||||
# )
|
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
H.append(
|
H.append(
|
||||||
f"""<tr><td colspan="4"><span class="moduleimpl_abs_link"><a class="stdlink"
|
f"""<tr><td colspan="4"><span class="moduleimpl_abs_link"><a class="stdlink"
|
||||||
@ -350,15 +329,17 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
current_user.has_permission(Permission.ScoAbsChange)
|
current_user.has_permission(Permission.ScoAbsChange)
|
||||||
and formsemestre.est_courant()
|
and formsemestre.est_courant()
|
||||||
):
|
):
|
||||||
datelundi = sco_abs.ddmmyyyy(time.strftime("%d/%m/%Y")).prev_monday()
|
|
||||||
group_id = sco_groups.get_default_group(formsemestre_id)
|
group_id = sco_groups.get_default_group(formsemestre_id)
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<span class="moduleimpl_abs_link"><a class="stdlink"
|
<span class="moduleimpl_abs_link"><a class="stdlink" href="{
|
||||||
href="{url_for("absences.SignaleAbsenceGrHebdo",
|
url_for("assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept)
|
||||||
scodoc_dept=g.scodoc_dept,formsemestre_id=formsemestre_id,
|
}?group_ids={group_id}&jour={
|
||||||
moduleimpl_id=moduleimpl_id, datelundi=datelundi, group_ids=group_id)}">
|
datetime.date.today().isoformat()
|
||||||
Saisie Absences hebdo.</a></span>
|
}&formsemestre_id={formsemestre.id}
|
||||||
|
&moduleimpl_id={moduleimpl_id}
|
||||||
|
"
|
||||||
|
>Saisie Absences hebdo</a></span>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -435,8 +416,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
top_table_links += f"""
|
top_table_links += f"""
|
||||||
<a class="stdlink" style="margin-left:2em;" href="{
|
<a class="stdlink" style="margin-left:2em;" href="{
|
||||||
url_for("notes.moduleimpl_evaluation_renumber",
|
url_for("notes.moduleimpl_evaluation_renumber",
|
||||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id,
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
||||||
redirect=1)
|
|
||||||
}">Trier par date</a>
|
}">Trier par date</a>
|
||||||
"""
|
"""
|
||||||
if nb_evaluations > 0:
|
if nb_evaluations > 0:
|
||||||
@ -572,10 +552,8 @@ def _ligne_evaluation(
|
|||||||
# visualisation des poids (Hinton map)
|
# visualisation des poids (Hinton map)
|
||||||
H.append(_evaluation_poids_html(evaluation, max_poids))
|
H.append(_evaluation_poids_html(evaluation, max_poids))
|
||||||
H.append("""<div class="evaluation_titre">""")
|
H.append("""<div class="evaluation_titre">""")
|
||||||
if evaluation.jour:
|
if evaluation.date_debut:
|
||||||
H.append(
|
H.append(evaluation.descr_date())
|
||||||
f"""Le {evaluation.jour.strftime("%d/%m/%Y")} {evaluation.descr_heure()}"""
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<a href="{url_for("notes.evaluation_edit",
|
f"""<a href="{url_for("notes.evaluation_edit",
|
||||||
@ -719,7 +697,7 @@ def _ligne_evaluation(
|
|||||||
if can_edit_notes:
|
if can_edit_notes:
|
||||||
H.append(
|
H.append(
|
||||||
moduleimpl_evaluation_menu(
|
moduleimpl_evaluation_menu(
|
||||||
evaluation.id,
|
evaluation,
|
||||||
nbnotes=etat["nb_notes"],
|
nbnotes=etat["nb_notes"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -54,34 +54,6 @@ def can_edit_notes(authuser, moduleimpl_id, allow_ens=True):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def can_edit_evaluation(moduleimpl_id=None):
|
|
||||||
"""Vérifie que l'on a le droit de modifier, créer ou détruire une
|
|
||||||
évaluation dans ce module.
|
|
||||||
Sinon, lance une exception.
|
|
||||||
(nb: n'implique pas le droit de saisir ou modifier des notes)
|
|
||||||
"""
|
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
|
|
||||||
# acces pour resp. moduleimpl et resp. form semestre (dir etud)
|
|
||||||
if moduleimpl_id is None:
|
|
||||||
raise ValueError("no moduleimpl specified") # bug
|
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
|
||||||
|
|
||||||
if (
|
|
||||||
current_user.has_permission(Permission.ScoEditAllEvals)
|
|
||||||
or current_user.id == M["responsable_id"]
|
|
||||||
or current_user.id in sem["responsables"]
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
elif sem["ens_can_edit_eval"]:
|
|
||||||
for ens in M["ens"]:
|
|
||||||
if ens["ens_id"] == current_user.id:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def can_suppress_annotation(annotation_id):
|
def can_suppress_annotation(annotation_id):
|
||||||
"""True if current user can suppress this annotation
|
"""True if current user can suppress this annotation
|
||||||
Seuls l'auteur de l'annotation et le chef de dept peuvent supprimer
|
Seuls l'auteur de l'annotation et le chef de dept peuvent supprimer
|
||||||
|
@ -138,7 +138,7 @@ class PlacementForm(FlaskForm):
|
|||||||
|
|
||||||
def set_evaluation_infos(self, evaluation_id):
|
def set_evaluation_infos(self, evaluation_id):
|
||||||
"""Initialise les données du formulaire avec les données de l'évaluation."""
|
"""Initialise les données du formulaire avec les données de l'évaluation."""
|
||||||
eval_data = sco_evaluation_db.do_evaluation_list(
|
eval_data = sco_evaluation_db.get_evaluation_dict(
|
||||||
{"evaluation_id": evaluation_id}
|
{"evaluation_id": evaluation_id}
|
||||||
)
|
)
|
||||||
if not eval_data:
|
if not eval_data:
|
||||||
@ -239,7 +239,7 @@ class PlacementRunner:
|
|||||||
self.groups_ids = [
|
self.groups_ids = [
|
||||||
gid if gid != TOUS else form.tous_id for gid in form["groups"].data
|
gid if gid != TOUS else form.tous_id for gid in form["groups"].data
|
||||||
]
|
]
|
||||||
self.eval_data = sco_evaluation_db.do_evaluation_list(
|
self.eval_data = sco_evaluation_db.get_evaluation_dict(
|
||||||
{"evaluation_id": self.evaluation_id}
|
{"evaluation_id": self.evaluation_id}
|
||||||
)[0]
|
)[0]
|
||||||
self.groups = sco_groups.listgroups(self.groups_ids)
|
self.groups = sco_groups.listgroups(self.groups_ids)
|
||||||
|
@ -45,7 +45,6 @@ from app.models import (
|
|||||||
FormSemestre,
|
FormSemestre,
|
||||||
Module,
|
Module,
|
||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
NotesNotes,
|
|
||||||
ScolarNews,
|
ScolarNews,
|
||||||
)
|
)
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
@ -54,16 +53,13 @@ from app.scodoc.sco_exceptions import (
|
|||||||
AccessDenied,
|
AccessDenied,
|
||||||
InvalidNoteValue,
|
InvalidNoteValue,
|
||||||
NoteProcessError,
|
NoteProcessError,
|
||||||
ScoBugCatcher,
|
|
||||||
ScoException,
|
ScoException,
|
||||||
ScoInvalidParamError,
|
ScoInvalidParamError,
|
||||||
ScoValueError,
|
ScoValueError,
|
||||||
)
|
)
|
||||||
from app.scodoc import html_sco_header, sco_users
|
from app.scodoc import html_sco_header, sco_users
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
from app.scodoc import sco_abs
|
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_edit_module
|
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
@ -71,7 +67,6 @@ from app.scodoc import sco_excel
|
|||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_groups_view
|
from app.scodoc import sco_groups_view
|
||||||
from app.scodoc import sco_moduleimpl
|
|
||||||
from app.scodoc import sco_permissions_check
|
from app.scodoc import sco_permissions_check
|
||||||
from app.scodoc import sco_undo_notes
|
from app.scodoc import sco_undo_notes
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
@ -502,6 +497,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
|||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
# news
|
# news
|
||||||
|
if nb_suppress:
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_NOTE,
|
typ=ScolarNews.NEWS_NOTE,
|
||||||
obj=evaluation.moduleimpl.id,
|
obj=evaluation.moduleimpl.id,
|
||||||
@ -884,15 +880,15 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
|||||||
modimpl = evaluation.moduleimpl
|
modimpl = evaluation.moduleimpl
|
||||||
formsemestre = modimpl.formsemestre
|
formsemestre = modimpl.formsemestre
|
||||||
mod_responsable = sco_users.user_info(modimpl.responsable_id)
|
mod_responsable = sco_users.user_info(modimpl.responsable_id)
|
||||||
if evaluation.jour:
|
if evaluation.date_debut:
|
||||||
indication_date = evaluation.jour.isoformat()
|
indication_date = evaluation.date_debut.date().isoformat()
|
||||||
else:
|
else:
|
||||||
indication_date = scu.sanitize_filename(evaluation.description or "")[:12]
|
indication_date = scu.sanitize_filename(evaluation.description or "")[:12]
|
||||||
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
|
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
|
||||||
|
|
||||||
date_str = (
|
date_str = (
|
||||||
f"""du {evaluation.jour.strftime("%d/%m/%Y")}"""
|
f"""du {evaluation.date_debut.strftime("%d/%m/%Y")}"""
|
||||||
if evaluation.jour
|
if evaluation.date_debut
|
||||||
else "(sans date)"
|
else "(sans date)"
|
||||||
)
|
)
|
||||||
eval_titre = f"""{evaluation.description if evaluation.description else "évaluation"} {date_str}"""
|
eval_titre = f"""{evaluation.description if evaluation.description else "évaluation"} {date_str}"""
|
||||||
@ -1107,19 +1103,21 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in
|
|||||||
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
|
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
|
||||||
|
|
||||||
# Information sur absence (tenant compte de la demi-journée)
|
# Information sur absence (tenant compte de la demi-journée)
|
||||||
jour_iso = evaluation.jour.isoformat() if evaluation.jour else ""
|
jour_iso = (
|
||||||
|
evaluation.date_debut.date().isoformat() if evaluation.date_debut else ""
|
||||||
|
)
|
||||||
warn_abs_lst = []
|
warn_abs_lst = []
|
||||||
if evaluation.is_matin():
|
if evaluation.is_matin():
|
||||||
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True)
|
nbabs = 0 # TODO-ASSIDUITE sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True)
|
||||||
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=True)
|
nbabsjust = 0 # TODO-ASSIDUITE sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=True)
|
||||||
if nbabs:
|
if nbabs:
|
||||||
if nbabsjust:
|
if nbabsjust:
|
||||||
warn_abs_lst.append("absent justifié le matin !")
|
warn_abs_lst.append("absent justifié le matin !")
|
||||||
else:
|
else:
|
||||||
warn_abs_lst.append("absent le matin !")
|
warn_abs_lst.append("absent le matin !")
|
||||||
if evaluation.is_apresmidi():
|
if evaluation.is_apresmidi():
|
||||||
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0)
|
nbabs = 0 # TODO-ASSIDUITE sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0)
|
||||||
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0)
|
nbabsjust = 0 # TODO-ASSIDUITE sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0)
|
||||||
if nbabs:
|
if nbabs:
|
||||||
if nbabsjust:
|
if nbabsjust:
|
||||||
warn_abs_lst.append("absent justifié l'après-midi !")
|
warn_abs_lst.append("absent justifié l'après-midi !")
|
||||||
|
@ -41,7 +41,7 @@ from reportlab.lib.units import cm
|
|||||||
from reportlab.platypus import KeepInFrame, Paragraph, Table, TableStyle
|
from reportlab.platypus import KeepInFrame, Paragraph, Table, TableStyle
|
||||||
from reportlab.platypus.doctemplate import BaseDocTemplate
|
from reportlab.platypus.doctemplate import BaseDocTemplate
|
||||||
|
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_cal
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc.sco_exceptions import ScoPDFFormatError
|
from app.scodoc.sco_exceptions import ScoPDFFormatError
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
@ -299,9 +299,9 @@ def pdf_feuille_releve_absences(
|
|||||||
NB_CELL_PM = sco_preferences.get_preference("feuille_releve_abs_PM")
|
NB_CELL_PM = sco_preferences.get_preference("feuille_releve_abs_PM")
|
||||||
col_width = 0.85 * cm
|
col_width = 0.85 * cm
|
||||||
if sco_preferences.get_preference("feuille_releve_abs_samedi"):
|
if sco_preferences.get_preference("feuille_releve_abs_samedi"):
|
||||||
days = sco_abs.DAYNAMES[:6] # Lundi, ..., Samedi
|
days = sco_cal.DAYNAMES[:6] # Lundi, ..., Samedi
|
||||||
else:
|
else:
|
||||||
days = sco_abs.DAYNAMES[:5] # Lundi, ..., Vendredi
|
days = sco_cal.DAYNAMES[:5] # Lundi, ..., Vendredi
|
||||||
nb_days = len(days)
|
nb_days = len(days)
|
||||||
|
|
||||||
# Informations sur les groupes à afficher:
|
# Informations sur les groupes à afficher:
|
||||||
|
@ -60,7 +60,7 @@ from app.models.formsemestre import FormSemestre
|
|||||||
|
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import UniteEns
|
from app.models import Evaluation, ModuleImpl, UniteEns
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_edit_matiere
|
from app.scodoc import sco_edit_matiere
|
||||||
@ -154,6 +154,7 @@ def external_ue_inscrit_et_note(
|
|||||||
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
|
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
|
||||||
et enregistre les notes.
|
et enregistre les notes.
|
||||||
"""
|
"""
|
||||||
|
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||||
log(
|
log(
|
||||||
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})"
|
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})"
|
||||||
)
|
)
|
||||||
@ -163,18 +164,14 @@ def external_ue_inscrit_et_note(
|
|||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
list(notes_etuds.keys()),
|
list(notes_etuds.keys()),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Création d'une évaluation si il n'y en a pas déjà:
|
# Création d'une évaluation si il n'y en a pas déjà:
|
||||||
mod_evals = sco_evaluation_db.do_evaluation_list(
|
if moduleimpl.evaluations.count() > 0:
|
||||||
args={"moduleimpl_id": moduleimpl_id}
|
|
||||||
)
|
|
||||||
if len(mod_evals):
|
|
||||||
# met la note dans le première évaluation existante:
|
# met la note dans le première évaluation existante:
|
||||||
evaluation_id = mod_evals[0]["evaluation_id"]
|
evaluation: Evaluation = moduleimpl.evaluations.first()
|
||||||
else:
|
else:
|
||||||
# crée une évaluation:
|
# crée une évaluation:
|
||||||
evaluation_id = sco_evaluation_db.do_evaluation_create(
|
evaluation: Evaluation = Evaluation.create(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl=moduleimpl,
|
||||||
note_max=20.0,
|
note_max=20.0,
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
publish_incomplete=True,
|
publish_incomplete=True,
|
||||||
@ -185,7 +182,7 @@ def external_ue_inscrit_et_note(
|
|||||||
# Saisie des notes
|
# Saisie des notes
|
||||||
_, _, _ = sco_saisie_notes.notes_add(
|
_, _, _ = sco_saisie_notes.notes_add(
|
||||||
current_user,
|
current_user,
|
||||||
evaluation_id,
|
evaluation.id,
|
||||||
list(notes_etuds.items()),
|
list(notes_etuds.items()),
|
||||||
do_it=True,
|
do_it=True,
|
||||||
)
|
)
|
||||||
|
@ -149,7 +149,7 @@ def list_operations(evaluation_id):
|
|||||||
|
|
||||||
def evaluation_list_operations(evaluation_id):
|
def evaluation_list_operations(evaluation_id):
|
||||||
"""Page listing operations on evaluation"""
|
"""Page listing operations on evaluation"""
|
||||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0]
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
|
|
||||||
Ops = list_operations(evaluation_id)
|
Ops = list_operations(evaluation_id)
|
||||||
@ -179,7 +179,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
|||||||
"""
|
"""
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
rows = ndb.SimpleDictFetch(
|
rows = ndb.SimpleDictFetch(
|
||||||
"""SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.jour,
|
"""SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.date_debut,
|
||||||
u.user_name, e.id as evaluation_id
|
u.user_name, e.id as evaluation_id
|
||||||
FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi,
|
FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi,
|
||||||
notes_modules mod, identite i, "user" u
|
notes_modules mod, identite i, "user" u
|
||||||
@ -197,6 +197,12 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
|||||||
keep_numeric = format in scu.FORMATS_NUMERIQUES
|
keep_numeric = format in scu.FORMATS_NUMERIQUES
|
||||||
for row in rows:
|
for row in rows:
|
||||||
row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric)
|
row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric)
|
||||||
|
row["date_evaluation"] = (
|
||||||
|
row["date_debut"].strftime("%d/%m/%Y %H:%M") if row["date_debut"] else ""
|
||||||
|
)
|
||||||
|
row["_date_evaluation_order"] = (
|
||||||
|
row["date_debut"].isoformat() if row["date_debut"] else ""
|
||||||
|
)
|
||||||
columns_ids = (
|
columns_ids = (
|
||||||
"date",
|
"date",
|
||||||
"code_nip",
|
"code_nip",
|
||||||
@ -207,7 +213,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
|||||||
"titre",
|
"titre",
|
||||||
"evaluation_id",
|
"evaluation_id",
|
||||||
"description",
|
"description",
|
||||||
"jour",
|
"date_evaluation",
|
||||||
"comment",
|
"comment",
|
||||||
)
|
)
|
||||||
titles = {
|
titles = {
|
||||||
@ -221,7 +227,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
|||||||
"evaluation_id": "evaluation_id",
|
"evaluation_id": "evaluation_id",
|
||||||
"titre": "Module",
|
"titre": "Module",
|
||||||
"description": "Evaluation",
|
"description": "Evaluation",
|
||||||
"jour": "Date éval.",
|
"date_evaluation": "Date éval.",
|
||||||
}
|
}
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles=titles,
|
titles=titles,
|
||||||
|
@ -68,10 +68,15 @@ from app.scodoc.codes_cursus import NOTES_TOLERANCE, CODES_EXPL
|
|||||||
from app.scodoc import sco_xml
|
from app.scodoc import sco_xml
|
||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
|
# En principe, aucun champ text ne devrait excéder cette taille
|
||||||
|
MAX_TEXT_LEN = 64 * 1024
|
||||||
|
|
||||||
# le répertoire static, lié à chaque release pour éviter les problèmes de caches
|
# le répertoire static, lié à chaque release pour éviter les problèmes de caches
|
||||||
STATIC_DIR = (
|
STATIC_DIR = (
|
||||||
os.environ.get("SCRIPT_NAME", "") + "/ScoDoc/static/links/" + sco_version.SCOVERSION
|
os.environ.get("SCRIPT_NAME", "") + "/ScoDoc/static/links/" + sco_version.SCOVERSION
|
||||||
)
|
)
|
||||||
|
# La time zone du serveur:
|
||||||
|
TIME_ZONE = timezone("/".join(os.path.realpath("/etc/localtime").split("/")[-2:]))
|
||||||
|
|
||||||
# ----- CALCUL ET PRESENTATION DES NOTES
|
# ----- CALCUL ET PRESENTATION DES NOTES
|
||||||
NOTES_PRECISION = 1e-4 # evite eventuelles erreurs d'arrondis
|
NOTES_PRECISION = 1e-4 # evite eventuelles erreurs d'arrondis
|
||||||
@ -260,7 +265,7 @@ class AssiduitesMetrics:
|
|||||||
"""Labels associés au métrique de l'assiduité"""
|
"""Labels associés au métrique de l'assiduité"""
|
||||||
|
|
||||||
SHORT: list[str] = ["1/2 J.", "J.", "H."]
|
SHORT: list[str] = ["1/2 J.", "J.", "H."]
|
||||||
LONG: list[str] = ["Demi-Journée", "Journée", "Heure"]
|
LONG: list[str] = ["Demi-journée", "Journée", "Heure"]
|
||||||
TAG: list[str] = ["demi", "journee", "heure"]
|
TAG: list[str] = ["demi", "journee", "heure"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ div.competence {
|
|||||||
padding-left: calc(var(--arrow-width) + 8px);
|
padding-left: calc(var(--arrow-width) + 8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.niveaux>div:not(:last-child)::after {
|
.niveaux>div:not(:last-child)::before {
|
||||||
content: "";
|
content: "";
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -1315,7 +1315,7 @@ a.smallbutton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
span.evallink {
|
span.evallink {
|
||||||
font-size: 80%;
|
margin-left: 16px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2472,6 +2472,12 @@ span.ue_type {
|
|||||||
margin-right: 1.5em;
|
margin-right: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.formsemestre_description td.ue_coef_nul {
|
||||||
|
background-color: yellow!important;
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
ul.notes_module_list span.ue_coefs_list {
|
ul.notes_module_list span.ue_coefs_list {
|
||||||
color: blue;
|
color: blue;
|
||||||
font-size: 70%;
|
font-size: 70%;
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
|
|
||||||
// JS Ajax code for SignaleAbsenceGrSemestre
|
|
||||||
// Contributed by YLB
|
|
||||||
|
|
||||||
function ajaxFunction(mod, etudid, dat) {
|
|
||||||
var ajaxRequest; // The variable that makes Ajax possible!
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Opera 8.0+, Firefox, Safari
|
|
||||||
ajaxRequest = new XMLHttpRequest();
|
|
||||||
} catch (e) {
|
|
||||||
// Internet Explorer Browsers
|
|
||||||
try {
|
|
||||||
ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");
|
|
||||||
} catch (e) {
|
|
||||||
// Something went wrong
|
|
||||||
alert("Your browser broke!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create a function that will receive data sent from the server
|
|
||||||
ajaxRequest.onreadystatechange = function () {
|
|
||||||
if (ajaxRequest.readyState == 4 && ajaxRequest.status == 200) {
|
|
||||||
document.getElementById("AjaxDiv").innerHTML = ajaxRequest.responseText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ajaxRequest.open("POST", SCO_URL + "/Absences/doSignaleAbsenceGrSemestre", true);
|
|
||||||
ajaxRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
|
||||||
var oSelectOne = $("#abs_form")[0].elements["moduleimpl_id"];
|
|
||||||
var index = oSelectOne.selectedIndex;
|
|
||||||
var modul_id = oSelectOne.options[index].value;
|
|
||||||
if (mod == 'add') {
|
|
||||||
ajaxRequest.send("reply=0&moduleimpl_id=" + modul_id + "&abslist:list=" + etudid + ":" + dat);
|
|
||||||
}
|
|
||||||
if (mod == 'remove') {
|
|
||||||
ajaxRequest.send("reply=0&moduleimpl_id=" + modul_id + "&etudids=" + etudid + "&dates=" + dat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----
|
|
||||||
function change_moduleimpl(url) {
|
|
||||||
document.location = url + '&moduleimpl_id=' + document.getElementById('moduleimpl_id').value;
|
|
||||||
}
|
|
@ -385,7 +385,9 @@ class TableRecap(tb.Table):
|
|||||||
first_eval_of_mod = True
|
first_eval_of_mod = True
|
||||||
for e in evals:
|
for e in evals:
|
||||||
col_id = f"eval_{e.id}"
|
col_id = f"eval_{e.id}"
|
||||||
title = f'{modimpl.module.code} {eval_index} {e.jour.isoformat() if e.jour else ""}'
|
title = f"""{modimpl.module.code} {eval_index} {
|
||||||
|
e.date_debut.strftime("%d/%m/%Y") if e.date_debut else ""
|
||||||
|
}"""
|
||||||
col_classes = []
|
col_classes = []
|
||||||
if first_eval:
|
if first_eval:
|
||||||
col_classes.append("first")
|
col_classes.append("first")
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
|
|
||||||
<p>ScoDoc est un logiciel libre écrit en
|
<p>ScoDoc est un logiciel libre écrit en
|
||||||
<a href="http://www.python.org" target="_blank" rel="noopener noreferrer">Python</a>.
|
<a href="http://www.python.org" target="_blank" rel="noopener noreferrer">Python</a>.
|
||||||
Information et documentation sur <a href="https://scodoc.org" target="_blank">scodoc.org</a>.
|
Information et documentation sur
|
||||||
|
<a href="https://scodoc.org" target="_blank" rel="noopener>scodoc.org</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>Le logiciel est distribué sous
|
<p>Le logiciel est distribué sous
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<h2>Présence lors de l'évaluation {{eval.title}} </h2>
|
<h2>Présence du groupe {{group_title}} le {{date_debut.strftime("%d/%m/%Y")}}
|
||||||
<h3>Réalisé le {{eval.jour}} de {{eval.heure_debut}} à {{eval.heure_fin}}</h3>
|
de {{date_debut.strftime("%H:%M")}} à {{date_fin.strftime("%H:%M")}}
|
||||||
|
</h2>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -7,7 +8,7 @@
|
|||||||
Nom
|
Nom
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Assiduité
|
Présence
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="link">
|
<div class="link">
|
||||||
<a class="stdlink" target="_blank" href="{{
|
<a class="stdlink" target="_blank" rel="noopener noreferrer" href="{{
|
||||||
url_for('notes.refcomp_show',
|
url_for('notes.refcomp_show',
|
||||||
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id )
|
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id )
|
||||||
}}">référentiel de compétences</a>
|
}}">référentiel de compétences</a>
|
||||||
|
@ -195,8 +195,9 @@
|
|||||||
Code postal : {{ entreprise.codepostal }}<br>
|
Code postal : {{ entreprise.codepostal }}<br>
|
||||||
Ville : {{ entreprise.ville }}<br>
|
Ville : {{ entreprise.ville }}<br>
|
||||||
Pays : {{ entreprise.pays }}<br>
|
Pays : {{ entreprise.pays }}<br>
|
||||||
<a href="{{ url_for('entreprises.fiche_entreprise', entreprise_id=entreprise.id) }}" target="_blank">Fiche
|
<a href="{{ url_for('entreprises.fiche_entreprise', entreprise_id=entreprise.id) }}"
|
||||||
entreprise</a>
|
rel="noopener noreferrer" target="_blank"
|
||||||
|
>Fiche entreprise</a>
|
||||||
</div>
|
</div>
|
||||||
{% for site in entreprise.sites %}
|
{% for site in entreprise.sites %}
|
||||||
<div class="site">
|
<div class="site">
|
||||||
@ -221,8 +222,9 @@
|
|||||||
Code postal : {{ site.codepostal }}<br>
|
Code postal : {{ site.codepostal }}<br>
|
||||||
Ville : {{ site.ville }}<br>
|
Ville : {{ site.ville }}<br>
|
||||||
Pays : {{ site.pays }}<br>
|
Pays : {{ site.pays }}<br>
|
||||||
<a href="{{ url_for('entreprises.fiche_entreprise', entreprise_id=site.entreprise_id) }}" target="_blank">Fiche
|
<a href="{{ url_for('entreprises.fiche_entreprise', entreprise_id=site.entreprise_id)
|
||||||
entreprise</a>
|
}}" rel="noopener noreferrer" target="_blank"
|
||||||
|
>Fiche entreprise</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -255,7 +257,7 @@
|
|||||||
Notes : {{ correspondant.notes }}<br>
|
Notes : {{ correspondant.notes }}<br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ url_for('entreprises.fiche_entreprise', entreprise_id=correspondant.site.entreprise.id) }}"
|
<a href="{{ url_for('entreprises.fiche_entreprise', entreprise_id=correspondant.site.entreprise.id) }}"
|
||||||
target="_blank">Fiche entreprise</a>
|
target="_blank" rel="noopener noreferrer">Fiche entreprise</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
type, et de saisir les coefficients pondérant l'influence de la
|
type, et de saisir les coefficients pondérant l'influence de la
|
||||||
ressource ou SAÉ vers les Unités d'Enseignement (UE).
|
ressource ou SAÉ vers les Unités d'Enseignement (UE).
|
||||||
Voir les détails sur
|
Voir les détails sur
|
||||||
<a href="https://scodoc.org/BUT" target="_blank">la documentation</a>.
|
<a href="https://scodoc.org/BUT" target="_blank" rel="noopener">la documentation</a>.
|
||||||
</p>
|
</p>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</div>
|
</div>
|
||||||
|
@ -468,7 +468,8 @@
|
|||||||
if (to[0] != "n") {
|
if (to[0] != "n") {
|
||||||
groupeSelected.closest(".grpPartitions").querySelector(`[value="${to}"]`).click();
|
groupeSelected.closest(".grpPartitions").querySelector(`[value="${to}"]`).click();
|
||||||
} else {
|
} else {
|
||||||
groupeSelected.closest(".grpPartitions").querySelector(`[value="aucun"]`).click();
|
let toNumber = to.split("-")[1];
|
||||||
|
groupeSelected.closest(".grpPartitions").querySelector(`[data-idpartition="${toNumber}"] [value="aucun"]`).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -86,7 +86,8 @@
|
|||||||
<div class="sidebar-bottom"><a href="{{ url_for( 'scodoc.about',
|
<div class="sidebar-bottom"><a href="{{ url_for( 'scodoc.about',
|
||||||
scodoc_dept=g.scodoc_dept ) }}" class="sidebar">À propos</a>
|
scodoc_dept=g.scodoc_dept ) }}" class="sidebar">À propos</a>
|
||||||
<br />
|
<br />
|
||||||
<a href="{{ scu.SCO_USER_MANUAL }}" target="_blank" class="sidebar">Aide</a>
|
<a href="{{ scu.SCO_USER_MANUAL }}"
|
||||||
|
target="_blank" rel="noopener" class="sidebar">Aide</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="logo-logo">
|
<div class="logo-logo">
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from flask import g, request, render_template
|
from flask import g, request, render_template
|
||||||
|
|
||||||
from flask import abort, url_for
|
from flask import abort, url_for
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
@ -25,14 +25,16 @@ from app.views import ScoData
|
|||||||
# ---------------
|
# ---------------
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
|
from app.scodoc import safehtml
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_groups_view
|
from app.scodoc import sco_groups_view
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_find_etud
|
from app.scodoc import sco_find_etud
|
||||||
from flask_login import current_user
|
|
||||||
from app.scodoc import sco_utils as scu
|
|
||||||
from app.scodoc import sco_assiduites as scass
|
from app.scodoc import sco_assiduites as scass
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
from app.tables.visu_assiduites import TableAssi, etuds_sorted_from_ids
|
from app.tables.visu_assiduites import TableAssi, etuds_sorted_from_ids
|
||||||
|
|
||||||
@ -547,7 +549,6 @@ def signal_assiduites_group():
|
|||||||
+ [
|
+ [
|
||||||
# Voir fonctionnement JS
|
# Voir fonctionnement JS
|
||||||
"js/etud_info.js",
|
"js/etud_info.js",
|
||||||
"js/abs_ajax.js",
|
|
||||||
"js/groups_view.js",
|
"js/groups_view.js",
|
||||||
"js/assiduites.js",
|
"js/assiduites.js",
|
||||||
"libjs/moment.new.min.js",
|
"libjs/moment.new.min.js",
|
||||||
@ -732,20 +733,23 @@ def visu_assiduites_group():
|
|||||||
).build()
|
).build()
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/EtatAbsencesDate")
|
@bp.route("/etat_abs_date")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def get_etat_abs_date():
|
def etat_abs_date():
|
||||||
evaluation = {
|
"""date_debut, date_fin en ISO"""
|
||||||
"jour": request.args.get("jour"),
|
date_debut_str = request.args.get("date_debut")
|
||||||
"heure_debut": request.args.get("heure_debut"),
|
date_fin_str = request.args.get("date_fin")
|
||||||
"heure_fin": request.args.get("heure_fin"),
|
title = request.args.get("desc")
|
||||||
"title": request.args.get("desc"),
|
|
||||||
}
|
|
||||||
date: str = evaluation["jour"]
|
|
||||||
group_ids: list[int] = request.args.get("group_ids", None)
|
group_ids: list[int] = request.args.get("group_ids", None)
|
||||||
etudiants: list[dict] = []
|
try:
|
||||||
|
date_debut = datetime.datetime.fromisoformat(date_debut_str)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError("date_debut invalide") from exc
|
||||||
|
try:
|
||||||
|
date_fin = datetime.datetime.fromisoformat(date_fin_str)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError("date_fin invalide") from exc
|
||||||
if group_ids is None:
|
if group_ids is None:
|
||||||
group_ids = []
|
group_ids = []
|
||||||
else:
|
else:
|
||||||
@ -758,14 +762,6 @@ def get_etat_abs_date():
|
|||||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||||
for m in groups_infos.members
|
for m in groups_infos.members
|
||||||
]
|
]
|
||||||
|
|
||||||
date_debut = scu.is_iso_formated(
|
|
||||||
f"{evaluation['jour']}T{evaluation['heure_debut'].replace('h',':')}", True
|
|
||||||
)
|
|
||||||
date_fin = scu.is_iso_formated(
|
|
||||||
f"{evaluation['jour']}T{evaluation['heure_fin'].replace('h',':')}", True
|
|
||||||
)
|
|
||||||
|
|
||||||
assiduites: Assiduite = Assiduite.query.filter(
|
assiduites: Assiduite = Assiduite.query.filter(
|
||||||
Assiduite.etudid.in_([e["etudid"] for e in etuds])
|
Assiduite.etudid.in_([e["etudid"] for e in etuds])
|
||||||
)
|
)
|
||||||
@ -773,15 +769,20 @@ def get_etat_abs_date():
|
|||||||
assiduites, Assiduite, date_debut, date_fin, False
|
assiduites, Assiduite, date_debut, date_fin, False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
etudiants: list[dict] = []
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
assi = assiduites.filter_by(etudid=etud["etudid"]).first()
|
assi = assiduites.filter_by(etudid=etud["etudid"]).first()
|
||||||
|
|
||||||
etat = ""
|
etat = ""
|
||||||
if assi != None and assi.etat != 0:
|
if assi is not None and assi.etat != 0:
|
||||||
etat = scu.EtatAssiduite.inverse().get(assi.etat).name
|
etat = scu.EtatAssiduite.inverse().get(assi.etat).name
|
||||||
|
|
||||||
etudiant = {
|
etudiant = {
|
||||||
"nom": f'<a href="{url_for("assiduites.calendrier_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"])}"><font color="#A00000">{etud["nomprenom"]}</font></a>',
|
"nom": f"""<a href="{url_for(
|
||||||
|
"assiduites.calendrier_etud",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
etudid=etud["etudid"])
|
||||||
|
}"><font color="#A00000">{etud["nomprenom"]}</font></a>""",
|
||||||
"etat": etat,
|
"etat": etat,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -790,7 +791,7 @@ def get_etat_abs_date():
|
|||||||
etudiants = list(sorted(etudiants, key=lambda x: x["nom"]))
|
etudiants = list(sorted(etudiants, key=lambda x: x["nom"]))
|
||||||
|
|
||||||
header: str = html_sco_header.sco_header(
|
header: str = html_sco_header.sco_header(
|
||||||
page_title=evaluation["title"],
|
page_title=safehtml.html_to_safe_html(title),
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -799,7 +800,9 @@ def get_etat_abs_date():
|
|||||||
render_template(
|
render_template(
|
||||||
"assiduites/pages/etat_absence_date.j2",
|
"assiduites/pages/etat_absence_date.j2",
|
||||||
etudiants=etudiants,
|
etudiants=etudiants,
|
||||||
eval=evaluation,
|
group_title=groups_infos.groups_titles,
|
||||||
|
date_debut=date_debut,
|
||||||
|
date_fin=date_fin,
|
||||||
),
|
),
|
||||||
html_sco_header.sco_footer(),
|
html_sco_header.sco_footer(),
|
||||||
).build()
|
).build()
|
||||||
@ -1019,7 +1022,7 @@ def _module_selector(
|
|||||||
for ue in ues:
|
for ue in ues:
|
||||||
modimpls_list += ntc.get_modimpls_dict(ue_id=ue["ue_id"])
|
modimpls_list += ntc.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||||
|
|
||||||
selected = moduleimpl_id is not None
|
selected = "" if moduleimpl_id is not None else "selected"
|
||||||
|
|
||||||
modules = []
|
modules = []
|
||||||
|
|
||||||
@ -1032,7 +1035,10 @@ def _module_selector(
|
|||||||
modules.append({"moduleimpl_id": modimpl["moduleimpl_id"], "name": modname})
|
modules.append({"moduleimpl_id": modimpl["moduleimpl_id"], "name": modname})
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"assiduites/widgets/moduleimpl_selector.j2", selected=selected, modules=modules
|
"assiduites/widgets/moduleimpl_selector.j2",
|
||||||
|
selected=selected,
|
||||||
|
modules=modules,
|
||||||
|
moduleimpl_id=moduleimpl_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,8 +57,8 @@ from app.but.forms import jury_but_forms
|
|||||||
from app.comp import jury, res_sem
|
from app.comp import jury, res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import (
|
from app.models import (
|
||||||
|
Evaluation,
|
||||||
Formation,
|
Formation,
|
||||||
ScolarFormSemestreValidation,
|
|
||||||
ScolarAutorisationInscription,
|
ScolarAutorisationInscription,
|
||||||
ScolarNews,
|
ScolarNews,
|
||||||
Scolog,
|
Scolog,
|
||||||
@ -97,9 +97,9 @@ from app.scodoc.sco_exceptions import (
|
|||||||
)
|
)
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.pe import pe_view
|
from app.pe import pe_view
|
||||||
from app.scodoc import sco_abs
|
|
||||||
from app.scodoc import sco_apogee_compare
|
from app.scodoc import sco_apogee_compare
|
||||||
from app.scodoc import sco_archives
|
from app.scodoc import sco_archives
|
||||||
|
from app.scodoc import sco_assiduites
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
from app.scodoc import sco_bulletins_pdf
|
from app.scodoc import sco_bulletins_pdf
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
@ -134,6 +134,7 @@ from app.scodoc import sco_lycee
|
|||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_moduleimpl_inscriptions
|
from app.scodoc import sco_moduleimpl_inscriptions
|
||||||
from app.scodoc import sco_moduleimpl_status
|
from app.scodoc import sco_moduleimpl_status
|
||||||
|
from app.scodoc import sco_permissions_check
|
||||||
from app.scodoc import sco_placement
|
from app.scodoc import sco_placement
|
||||||
from app.scodoc import sco_poursuite_dut
|
from app.scodoc import sco_poursuite_dut
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
@ -378,11 +379,40 @@ sco_publish(
|
|||||||
sco_evaluations.formsemestre_evaluations_delai_correction,
|
sco_evaluations.formsemestre_evaluations_delai_correction,
|
||||||
Permission.ScoView,
|
Permission.ScoView,
|
||||||
)
|
)
|
||||||
sco_publish(
|
|
||||||
"/moduleimpl_evaluation_renumber",
|
|
||||||
sco_evaluation_db.moduleimpl_evaluation_renumber,
|
@bp.route("/moduleimpl_evaluation_renumber", methods=["GET", "POST"])
|
||||||
Permission.ScoView,
|
@scodoc
|
||||||
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
|
@scodoc7func
|
||||||
|
def moduleimpl_evaluation_renumber(moduleimpl_id):
|
||||||
|
"Renumérote les évaluations, triant par date"
|
||||||
|
modimpl: ModuleImpl = (
|
||||||
|
ModuleImpl.query.filter_by(id=moduleimpl_id)
|
||||||
|
.join(FormSemestre)
|
||||||
|
.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
.first_or_404()
|
||||||
)
|
)
|
||||||
|
if not modimpl.can_edit_evaluation(current_user):
|
||||||
|
raise ScoPermissionDenied(
|
||||||
|
dest_url=url_for(
|
||||||
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=modimpl.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Evaluation.moduleimpl_evaluation_renumber(modimpl)
|
||||||
|
# redirect to moduleimpl page:
|
||||||
|
if redirect:
|
||||||
|
return flask.redirect(
|
||||||
|
url_for(
|
||||||
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/moduleimpl_evaluation_move",
|
"/moduleimpl_evaluation_move",
|
||||||
sco_evaluation_db.moduleimpl_evaluation_move,
|
sco_evaluation_db.moduleimpl_evaluation_move,
|
||||||
@ -1122,175 +1152,51 @@ def edit_moduleimpl_resp(moduleimpl_id: int):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_EXPR_HELP = """<p class="help">Expérimental: formule de calcul de la moyenne %(target)s</p>
|
|
||||||
<p class="help">Attention: l'utilisation de formules ralentit considérablement
|
|
||||||
les traitements. A utiliser uniquement dans les cas ne pouvant pas être traités autrement.</p>
|
|
||||||
<p class="help">Dans la formule, les variables suivantes sont définies:</p>
|
|
||||||
<ul class="help">
|
|
||||||
<li><tt>moy</tt> la moyenne, calculée selon la règle standard (moyenne pondérée)</li>
|
|
||||||
<li><tt>moy_is_valid</tt> vrai si la moyenne est valide (numérique)</li>
|
|
||||||
<li><tt>moy_val</tt> la valeur de la moyenne (nombre, valant 0 si invalide)</li>
|
|
||||||
<li><tt>notes</tt> vecteur des notes (/20) aux %(objs)s</li>
|
|
||||||
<li><tt>coefs</tt> vecteur des coefficients des %(objs)s, les coefs des %(objs)s sans notes (ATT, EXC) étant mis à zéro</li>
|
|
||||||
<li><tt>cmask</tt> vecteur de 0/1, 0 si le coef correspondant a été annulé</li>
|
|
||||||
<li>Nombre d'absences: <tt>nb_abs</tt>, <tt>nb_abs_just</tt>, <tt>nb_abs_nojust</tt> (en demi-journées)</li>
|
|
||||||
</ul>
|
|
||||||
<p class="help">Les éléments des vecteurs sont ordonnés dans l'ordre des %(objs)s%(ordre)s.</p>
|
|
||||||
<p class="help">Les fonctions suivantes sont utilisables: <tt>abs, cmp, dot, len, map, max, min, pow, reduce, round, sum, ifelse</tt>.</p>
|
|
||||||
<p class="help">La notation <tt>V(1,2,3)</tt> représente un vecteur <tt>(1,2,3)</tt>.</p>
|
|
||||||
<p class="help"></p>Pour indiquer que la note calculée n'existe pas, utiliser la chaîne <tt>'NA'</tt>.</p>
|
|
||||||
<p class="help">Vous pouvez désactiver la formule (et revenir au mode de calcul "classique")
|
|
||||||
en supprimant le texte ou en faisant précéder la première ligne par <tt>#</tt></p>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/edit_moduleimpl_expr", methods=["GET", "POST"])
|
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
@scodoc7func
|
|
||||||
def edit_moduleimpl_expr(moduleimpl_id):
|
|
||||||
"""Edition formule calcul moyenne module
|
|
||||||
Accessible par Admin, dir des etud et responsable module
|
|
||||||
|
|
||||||
Inutilisé en ScoDoc 9.
|
|
||||||
"""
|
|
||||||
M, sem = sco_moduleimpl.can_change_ens(moduleimpl_id)
|
|
||||||
H = [
|
|
||||||
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"]),
|
|
||||||
),
|
|
||||||
_EXPR_HELP
|
|
||||||
% {
|
|
||||||
"target": "du module",
|
|
||||||
"objs": "évaluations",
|
|
||||||
"ordre": " (le premier élément est la plus ancienne évaluation)",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
initvalues = M
|
|
||||||
form = [
|
|
||||||
("moduleimpl_id", {"input_type": "hidden"}),
|
|
||||||
(
|
|
||||||
"computation_expr",
|
|
||||||
{
|
|
||||||
"title": "Formule de calcul",
|
|
||||||
"input_type": "textarea",
|
|
||||||
"rows": 4,
|
|
||||||
"cols": 60,
|
|
||||||
"explanation": "formule de calcul (expérimental)",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
tf = TrivialFormulator(
|
|
||||||
request.base_url,
|
|
||||||
scu.get_request_args(),
|
|
||||||
form,
|
|
||||||
submitlabel="Modifier formule de calcul",
|
|
||||||
cancelbutton="Annuler",
|
|
||||||
initvalues=initvalues,
|
|
||||||
)
|
|
||||||
if tf[0] == 0:
|
|
||||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
|
||||||
elif tf[0] == -1:
|
|
||||||
return flask.redirect(
|
|
||||||
url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
sco_moduleimpl.do_moduleimpl_edit(
|
|
||||||
{
|
|
||||||
"moduleimpl_id": moduleimpl_id,
|
|
||||||
"computation_expr": tf[2]["computation_expr"],
|
|
||||||
},
|
|
||||||
formsemestre_id=sem["formsemestre_id"],
|
|
||||||
)
|
|
||||||
sco_cache.invalidate_formsemestre(
|
|
||||||
formsemestre_id=sem["formsemestre_id"]
|
|
||||||
) # > modif regle calcul
|
|
||||||
flash("règle de calcul modifiée")
|
|
||||||
return flask.redirect(
|
|
||||||
url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/delete_moduleimpl_expr", methods=["GET", "POST"])
|
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
@scodoc7func
|
|
||||||
def delete_moduleimpl_expr(moduleimpl_id):
|
|
||||||
"""Suppression formule calcul moyenne module
|
|
||||||
Accessible par Admin, dir des etud et responsable module
|
|
||||||
"""
|
|
||||||
modimpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
|
||||||
sco_moduleimpl.can_change_ens(moduleimpl_id)
|
|
||||||
modimpl.computation_expr = None
|
|
||||||
db.session.add(modimpl)
|
|
||||||
db.session.commit()
|
|
||||||
flash("Ancienne formule supprimée")
|
|
||||||
return flask.redirect(
|
|
||||||
url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/view_module_abs")
|
@bp.route("/view_module_abs")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def view_module_abs(moduleimpl_id, format="html"):
|
def view_module_abs(moduleimpl_id, fmt="html"):
|
||||||
"""Visualisation des absences a un module"""
|
"""Visualisation des absences a un module"""
|
||||||
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
modimpl: ModuleImpl = (
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
ModuleImpl.query.filter_by(id=moduleimpl_id)
|
||||||
debut_sem = ndb.DateDMYtoISO(sem["date_debut"])
|
.join(FormSemestre)
|
||||||
fin_sem = ndb.DateDMYtoISO(sem["date_fin"])
|
.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
list_insc = sco_moduleimpl.moduleimpl_listeetuds(moduleimpl_id)
|
).first_or_404()
|
||||||
|
|
||||||
T = []
|
debut_sem = modimpl.formsemestre.date_debut
|
||||||
for etudid in list_insc:
|
fin_sem = modimpl.formsemestre.date_fin
|
||||||
nb_abs = sco_abs.count_abs(
|
inscrits: list[Identite] = sorted(
|
||||||
etudid=etudid,
|
[i.etud for i in modimpl.inscriptions], key=lambda e: e.sort_key
|
||||||
debut=debut_sem,
|
|
||||||
fin=fin_sem,
|
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
)
|
)
|
||||||
if nb_abs:
|
|
||||||
nb_abs_just = sco_abs.count_abs_just(
|
rows = []
|
||||||
etudid=etudid,
|
for etud in inscrits:
|
||||||
debut=debut_sem,
|
nb_abs, nb_abs_just = sco_assiduites.formsemestre_get_assiduites_count(
|
||||||
fin=fin_sem,
|
etud.id, modimpl.formsemestre, cache=False, moduleimpl_id=modimpl.id
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
)
|
)
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
rows.append(
|
||||||
T.append(
|
|
||||||
{
|
{
|
||||||
"nomprenom": etud["nomprenom"],
|
"nomprenom": etud.nomprenom,
|
||||||
"just": nb_abs_just,
|
"just": nb_abs_just,
|
||||||
"nojust": nb_abs - nb_abs_just,
|
"nojust": nb_abs - nb_abs_just,
|
||||||
"total": nb_abs,
|
"total": nb_abs,
|
||||||
"_nomprenom_target": url_for(
|
"_nomprenom_target": url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
'Absences du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
|
f"""Absences du <a href="{
|
||||||
% (moduleimpl_id, M["module"]["titre"]),
|
url_for("notes.moduleimpl_status",
|
||||||
page_title="Absences du module %s" % (M["module"]["titre"]),
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id
|
||||||
|
)}">module {modimpl.module.titre_str()}</a>""",
|
||||||
|
page_title=f"Absences du module {modimpl.module.titre_str()}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
if not T and format == "html":
|
if not rows and fmt == "html":
|
||||||
return (
|
return (
|
||||||
"\n".join(H)
|
"\n".join(H)
|
||||||
+ "<p>Aucune absence signalée</p>"
|
+ "<p>Aucune absence signalée</p>"
|
||||||
@ -1305,16 +1211,16 @@ def view_module_abs(moduleimpl_id, format="html"):
|
|||||||
"total": "Total",
|
"total": "Total",
|
||||||
},
|
},
|
||||||
columns_ids=("nomprenom", "just", "nojust", "total"),
|
columns_ids=("nomprenom", "just", "nojust", "total"),
|
||||||
rows=T,
|
rows=rows,
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
base_url="%s?moduleimpl_id=%s" % (request.base_url, moduleimpl_id),
|
base_url=f"{request.base_url}?moduleimpl_id={moduleimpl_id}",
|
||||||
filename="absmodule_" + scu.make_filename(M["module"]["titre"]),
|
filename="absmodule_" + scu.make_filename(modimpl.module.titre_str()),
|
||||||
caption="Absences dans le module %s" % M["module"]["titre"],
|
caption=f"Absences dans le module {modimpl.module.titre_str()}",
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(format=fmt)
|
||||||
|
|
||||||
return "\n".join(H) + tab.html() + html_sco_header.sco_footer()
|
return "\n".join(H) + tab.html() + html_sco_header.sco_footer()
|
||||||
|
|
||||||
@ -1735,30 +1641,37 @@ sco_publish(
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def evaluation_delete(evaluation_id):
|
def evaluation_delete(evaluation_id):
|
||||||
"""Form delete evaluation"""
|
"""Form delete evaluation"""
|
||||||
El = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})
|
evaluation: Evaluation = (
|
||||||
if not El:
|
Evaluation.query.filter_by(id=evaluation_id)
|
||||||
raise ScoValueError("Evaluation inexistante ! (%s)" % evaluation_id)
|
.join(ModuleImpl)
|
||||||
E = El[0]
|
.join(FormSemestre)
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
.first_or_404()
|
||||||
tit = "Suppression de l'évaluation %(description)s (%(jour)s)" % E
|
)
|
||||||
etat = sco_evaluations.do_evaluation_etat(evaluation_id)
|
|
||||||
|
tit = f"""Suppression de l'évaluation {evaluation.description or ""} ({evaluation.descr_date()})"""
|
||||||
|
etat = sco_evaluations.do_evaluation_etat(evaluation.id)
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(tit, with_h2=False),
|
f"""
|
||||||
"""<h2 class="formsemestre">Module <tt>%(code)s</tt> %(titre)s</h2>""" % Mod,
|
{html_sco_header.html_sem_header(tit, with_h2=False)}
|
||||||
"""<h3>%s</h3>""" % tit,
|
<h2 class="formsemestre">Module <tt>{evaluation.moduleimpl.module.code}</tt>
|
||||||
"""<p class="help">Opération <span class="redboldtext">irréversible</span>. Si vous supprimez l'évaluation, vous ne pourrez pas retrouver les notes associées.</p>""",
|
{evaluation.moduleimpl.module.titre_str()}</h2>
|
||||||
|
<h3>{tit}</h3>
|
||||||
|
<p class="help">Opération <span class="redboldtext">irréversible</span>.
|
||||||
|
Si vous supprimez l'évaluation, vous ne pourrez pas retrouver les notes associées.
|
||||||
|
</p>
|
||||||
|
""",
|
||||||
]
|
]
|
||||||
warning = False
|
warning = False
|
||||||
if etat["nb_notes_total"]:
|
if etat["nb_notes_total"]:
|
||||||
warning = True
|
warning = True
|
||||||
nb_desinscrits = etat["nb_notes_total"] - etat["nb_notes"]
|
nb_desinscrits = etat["nb_notes_total"] - etat["nb_notes"]
|
||||||
H.append(
|
H.append(
|
||||||
"""<div class="ue_warning"><span>Il y a %s notes""" % etat["nb_notes_total"]
|
f"""<div class="ue_warning"><span>Il y a {etat["nb_notes_total"]} notes"""
|
||||||
)
|
)
|
||||||
if nb_desinscrits:
|
if nb_desinscrits:
|
||||||
H.append(
|
H.append(
|
||||||
""" (dont %s d'étudiants qui ne sont plus inscrits)""" % nb_desinscrits
|
""" (dont {nb_desinscrits} d'étudiants qui ne sont plus inscrits)"""
|
||||||
)
|
)
|
||||||
H.append(""" dans l'évaluation</span>""")
|
H.append(""" dans l'évaluation</span>""")
|
||||||
if etat["nb_notes"] == 0:
|
if etat["nb_notes"] == 0:
|
||||||
@ -1768,8 +1681,13 @@ def evaluation_delete(evaluation_id):
|
|||||||
|
|
||||||
if etat["nb_notes"]:
|
if etat["nb_notes"]:
|
||||||
H.append(
|
H.append(
|
||||||
"""<p>Suppression impossible (effacer les notes d'abord)</p><p><a class="stdlink" href="moduleimpl_status?moduleimpl_id=%s">retour au tableau de bord du module</a></p></div>"""
|
f"""<p>Suppression impossible (effacer les notes d'abord)</p>
|
||||||
% E["moduleimpl_id"]
|
<p><a class="stdlink" href="{
|
||||||
|
url_for("notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=evaluation.moduleimpl_id)
|
||||||
|
}">retour au tableau de bord du module</a>
|
||||||
|
</p>
|
||||||
|
</div>"""
|
||||||
)
|
)
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
if warning:
|
if warning:
|
||||||
@ -1779,7 +1697,7 @@ def evaluation_delete(evaluation_id):
|
|||||||
request.base_url,
|
request.base_url,
|
||||||
scu.get_request_args(),
|
scu.get_request_args(),
|
||||||
(("evaluation_id", {"input_type": "hidden"}),),
|
(("evaluation_id", {"input_type": "hidden"}),),
|
||||||
initvalues=E,
|
initvalues={"evaluation_id": evaluation.id},
|
||||||
submitlabel="Confirmer la suppression",
|
submitlabel="Confirmer la suppression",
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
)
|
)
|
||||||
@ -1790,17 +1708,17 @@ def evaluation_delete(evaluation_id):
|
|||||||
url_for(
|
url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
moduleimpl_id=E["moduleimpl_id"],
|
moduleimpl_id=evaluation.moduleimpl_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sco_evaluation_db.do_evaluation_delete(E["evaluation_id"])
|
evaluation.delete()
|
||||||
return (
|
return (
|
||||||
"\n".join(H)
|
"\n".join(H)
|
||||||
+ f"""<p>OK, évaluation supprimée.</p>
|
+ f"""<p>OK, évaluation supprimée.</p>
|
||||||
<p><a class="stdlink" href="{
|
<p><a class="stdlink" href="{
|
||||||
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
|
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
|
||||||
moduleimpl_id=E["moduleimpl_id"])
|
moduleimpl_id=evaluation.moduleimpl_id)
|
||||||
}">Continuer</a></p>"""
|
}">Continuer</a></p>"""
|
||||||
+ html_sco_header.sco_footer()
|
+ html_sco_header.sco_footer()
|
||||||
)
|
)
|
||||||
|
58
migrations/versions/5c44d0d215ca_evaluation_date.py
Normal file
58
migrations/versions/5c44d0d215ca_evaluation_date.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"""evaluation date: modifie le codage des dates d'évaluations
|
||||||
|
|
||||||
|
Revision ID: 5c44d0d215ca
|
||||||
|
Revises: 45e0a855b8eb
|
||||||
|
Create Date: 2023-08-22 14:39:23.831483
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "5c44d0d215ca"
|
||||||
|
down_revision = "45e0a855b8eb"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
"modifie les colonnes codant les dates d'évaluations"
|
||||||
|
with op.batch_alter_table("notes_evaluation", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column("date_debut", sa.DateTime(timezone=True), nullable=True)
|
||||||
|
)
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column("date_fin", sa.DateTime(timezone=True), nullable=True)
|
||||||
|
)
|
||||||
|
# recode les dates existantes
|
||||||
|
op.execute("UPDATE notes_evaluation SET date_debut = jour+heure_debut;")
|
||||||
|
op.execute("UPDATE notes_evaluation SET date_fin = jour+heure_fin;")
|
||||||
|
with op.batch_alter_table("notes_evaluation", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("jour")
|
||||||
|
batch_op.drop_column("heure_fin")
|
||||||
|
batch_op.drop_column("heure_debut")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
"modifie les colonnes codant les dates d'évaluations"
|
||||||
|
with op.batch_alter_table("notes_evaluation", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column(
|
||||||
|
"heure_debut", postgresql.TIME(), autoincrement=False, nullable=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column(
|
||||||
|
"heure_fin", postgresql.TIME(), autoincrement=False, nullable=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column("jour", sa.DATE(), autoincrement=False, nullable=True)
|
||||||
|
)
|
||||||
|
op.execute("UPDATE notes_evaluation SET jour = DATE(date_debut);")
|
||||||
|
op.execute("UPDATE notes_evaluation SET heure_debut = date_debut::time;")
|
||||||
|
op.execute("UPDATE notes_evaluation SET heure_fin = date_fin::time;")
|
||||||
|
with op.batch_alter_table("notes_evaluation", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("date_fin")
|
||||||
|
batch_op.drop_column("date_debut")
|
23
pylintrc
23
pylintrc
@ -1,23 +0,0 @@
|
|||||||
[MASTER]
|
|
||||||
|
|
||||||
# List of plugins (as comma separated values of python module names) to load,
|
|
||||||
# usually to register additional checkers.
|
|
||||||
load-plugins=pylint_flask_sqlalchemy, pylint_flask
|
|
||||||
|
|
||||||
[TYPECHECK]
|
|
||||||
# List of class names for which member attributes should not be checked (useful
|
|
||||||
# for classes with dynamically set attributes). This supports the use of
|
|
||||||
# qualified names.
|
|
||||||
ignored-classes=Permission,
|
|
||||||
SQLObject,
|
|
||||||
Registrant,
|
|
||||||
scoped_session,
|
|
||||||
func
|
|
||||||
|
|
||||||
# List of module names for which member attributes should not be checked
|
|
||||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
|
||||||
# and thus existing member attributes cannot be deduced by static analysis). It
|
|
||||||
# supports qualified module names, as well as Unix pattern matching.
|
|
||||||
ignored-modules=entreprises
|
|
||||||
|
|
||||||
good-names=d,e,f,i,j,k,t,u,v,x,y,z,H,F,ue
|
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.9"
|
SCOVERSION = "9.6.16"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
from pprint import pprint as pp
|
from pprint import pprint as pp
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@ -82,6 +82,7 @@ def make_shell_context():
|
|||||||
"ctx": app.test_request_context(),
|
"ctx": app.test_request_context(),
|
||||||
"current_app": flask.current_app,
|
"current_app": flask.current_app,
|
||||||
"current_user": current_user,
|
"current_user": current_user,
|
||||||
|
"datetime": datetime,
|
||||||
"Departement": Departement,
|
"Departement": Departement,
|
||||||
"db": db,
|
"db": db,
|
||||||
"Evaluation": Evaluation,
|
"Evaluation": Evaluation,
|
||||||
|
@ -315,13 +315,12 @@ pp(GET(f"/formsemestre/880/resultats", headers=HEADERS)[0])
|
|||||||
# jour = sem["date_fin"]
|
# jour = sem["date_fin"]
|
||||||
# evaluation_id = POST(
|
# evaluation_id = POST(
|
||||||
# s,
|
# s,
|
||||||
# "/Notes/do_evaluation_create",
|
# f"/moduleimpl/{mod['moduleimpl_id']}/evaluation/create",
|
||||||
# data={
|
# data={
|
||||||
# "moduleimpl_id": mod["moduleimpl_id"],
|
|
||||||
# "coefficient": 1,
|
# "coefficient": 1,
|
||||||
# "jour": jour, # "5/9/2019",
|
# "jour": jour, # "2023-08-23",
|
||||||
# "heure_debut": "9h00",
|
# "heure_debut": "9:00",
|
||||||
# "heure_fin": "10h00",
|
# "heure_fin": "10:00",
|
||||||
# "note_max": 20, # notes sur 20
|
# "note_max": 20, # notes sur 20
|
||||||
# "description": "essai",
|
# "description": "essai",
|
||||||
# },
|
# },
|
||||||
|
@ -165,37 +165,3 @@ assert isinstance(json.loads(r.text)[0]["billet_id"], int)
|
|||||||
# print(f"{len(inscrits)} inscrits dans ce module")
|
# print(f"{len(inscrits)} inscrits dans ce module")
|
||||||
# # prend le premier inscrit, au hasard:
|
# # prend le premier inscrit, au hasard:
|
||||||
# etudid = inscrits[0]["etudid"]
|
# etudid = inscrits[0]["etudid"]
|
||||||
|
|
||||||
# # ---- Création d'une evaluation le dernier jour du semestre
|
|
||||||
# jour = sem["date_fin"]
|
|
||||||
# evaluation_id = POST(
|
|
||||||
# "/Notes/do_evaluation_create",
|
|
||||||
# data={
|
|
||||||
# "moduleimpl_id": mod["moduleimpl_id"],
|
|
||||||
# "coefficient": 1,
|
|
||||||
# "jour": jour, # "5/9/2019",
|
|
||||||
# "heure_debut": "9h00",
|
|
||||||
# "heure_fin": "10h00",
|
|
||||||
# "note_max": 20, # notes sur 20
|
|
||||||
# "description": "essai",
|
|
||||||
# },
|
|
||||||
# errmsg="échec création évaluation",
|
|
||||||
# )
|
|
||||||
|
|
||||||
# print(
|
|
||||||
# f"Evaluation créée dans le module {mod['moduleimpl_id']}, evaluation_id={evaluation_id}"
|
|
||||||
# )
|
|
||||||
# print(
|
|
||||||
# f"Pour vérifier, aller sur: {DEPT_URL}/Notes/moduleimpl_status?moduleimpl_id={mod['moduleimpl_id']}",
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # ---- Saisie d'une note
|
|
||||||
# junk = POST(
|
|
||||||
# "/Notes/save_note",
|
|
||||||
# data={
|
|
||||||
# "etudid": etudid,
|
|
||||||
# "evaluation_id": evaluation_id,
|
|
||||||
# "value": 16.66, # la note !
|
|
||||||
# "comment": "test API",
|
|
||||||
# },
|
|
||||||
# )
|
|
||||||
|
@ -5,40 +5,51 @@
|
|||||||
"""Construction des fichiers exemples pour la documentation.
|
"""Construction des fichiers exemples pour la documentation.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
cd /opt/scodoc/tests/api
|
python tests/api/make_samples.py [entry_names]
|
||||||
python make_samples.py [entry_names]
|
python tests/api/make_samples.py -i <filepath> [entrynames]
|
||||||
python make_samples.py -i <filepath> [entrynames]
|
|
||||||
|
|
||||||
si entry_names est spécifié, la génération est restreints aux exemples cités. expl: `python make_samples departements departement-formsemestres`
|
Si entry_names est spécifié, la génération est restreinte aux exemples cités.
|
||||||
doit être exécutée immédiatement apres une initialisation de la base pour test API! (car dépendant des identifiants générés lors de la création des objets)
|
Exemple:
|
||||||
cd /opt/scodoc/tests/api
|
python make_samples departements departement-formsemestres
|
||||||
tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database
|
|
||||||
|
|
||||||
Créer éventuellement un fichier `.env` dans /opt/scodoc/tests/api
|
Doit être exécutée immédiatement apres une initialisation de la base pour test API!
|
||||||
avec la config du client API:
|
(car dépendant des identifiants générés lors de la création des objets)
|
||||||
```
|
|
||||||
SCODOC_URL = "http://localhost:5000/"
|
Modifer le /opt/scodoc/.env pour pointer sur la base test
|
||||||
|
SCODOC_DATABASE_URI="postgresql:///SCODOC_TEST_API"
|
||||||
|
|
||||||
|
puis re-créer cette base
|
||||||
|
tools/create_database.sh --drop SCODOC_TEST_API
|
||||||
|
flask db upgrade
|
||||||
|
flask sco-db-init --erase
|
||||||
|
flask init-test-database
|
||||||
|
|
||||||
|
et lancer le serveur test:
|
||||||
|
flask run --debug
|
||||||
```
|
```
|
||||||
|
|
||||||
Cet utilitaire prend en donnée le fichier de nom `samples.csv` contenant la description des exemples (séparés par une tabulation (\t), une ligne par exemple)
|
Cet utilitaire prend en argument le fichier de nom `samples.csv` contenant la description
|
||||||
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md). plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra chacun des exemples
|
des exemples (séparés par une tabulation (\t), une ligne par exemple)
|
||||||
|
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md).
|
||||||
|
Plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra
|
||||||
|
chacun des exemples
|
||||||
* l'url utilisée
|
* l'url utilisée
|
||||||
* la permission nécessaire (par défaut ScoView)
|
* la permission nécessaire (par défaut ScoView)
|
||||||
* la méthode GET,POST à utiliser (si commence par #, la ligne est ignorée)
|
* la méthode GET,POST à utiliser (si commence par #, la ligne est ignorée)
|
||||||
* les arguments éventuel (en cas de POST): une chaîne de caractère selon json
|
* les arguments éventuel (en cas de POST): une chaîne de caractère selon json
|
||||||
|
|
||||||
Implémentation:
|
Implémentation:
|
||||||
Le code complète une structure de données (Samples) qui est un dictionnaire de set (indicé par le nom des exemple.
|
Le code complète une structure de données (Samples) qui est un dictionnaire de set
|
||||||
|
(indicé par le nom des exemples).
|
||||||
Chacun des éléments du set est un exemple (Sample)
|
Chacun des éléments du set est un exemple (Sample)
|
||||||
Quand la structure est complète, on génére tous les fichiers textes
|
Quand la structure est complète, on génére tous les fichiers textes
|
||||||
- nom de l exemple
|
- nom de l'exemple
|
||||||
- un ou plusieurs exemples avec pour chaucn
|
- un ou plusieurs exemples avec pour chacun
|
||||||
- l url utilisée
|
- l'url utilisée
|
||||||
- les arguments éventuels
|
- les arguments éventuels
|
||||||
- le résultat
|
- le résultat
|
||||||
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples) qui est créé ou écrasé si déjà existant
|
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples)
|
||||||
|
qui est créé ou écrasé si déjà existant.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@ -50,7 +61,7 @@ from pprint import pprint as pp
|
|||||||
import urllib3
|
import urllib3
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from pandas import read_csv
|
import pandas as pd
|
||||||
|
|
||||||
from setup_test_api import (
|
from setup_test_api import (
|
||||||
API_PASSWORD,
|
API_PASSWORD,
|
||||||
@ -68,6 +79,10 @@ DATA_DIR = "/tmp/samples/"
|
|||||||
SAMPLES_FILENAME = "tests/ressources/samples/samples.csv"
|
SAMPLES_FILENAME = "tests/ressources/samples/samples.csv"
|
||||||
|
|
||||||
|
|
||||||
|
class SampleException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Sample:
|
class Sample:
|
||||||
def __init__(self, url, method="GET", permission="ScoView", content=None):
|
def __init__(self, url, method="GET", permission="ScoView", content=None):
|
||||||
self.content = content
|
self.content = content
|
||||||
@ -83,7 +98,7 @@ class Sample:
|
|||||||
elif permission == "ScoUsersAdmin":
|
elif permission == "ScoUsersAdmin":
|
||||||
HEADERS = get_auth_headers("admin_api", "admin_api")
|
HEADERS = get_auth_headers("admin_api", "admin_api")
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Bad permission : {permission}")
|
raise SampleException(f"Bad permission : {permission}")
|
||||||
if self.method == "GET":
|
if self.method == "GET":
|
||||||
self.result = GET(self.url, HEADERS)
|
self.result = GET(self.url, HEADERS)
|
||||||
elif self.method == "POST":
|
elif self.method == "POST":
|
||||||
@ -94,20 +109,19 @@ class Sample:
|
|||||||
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
|
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
|
||||||
elif self.method[0] != "#":
|
elif self.method[0] != "#":
|
||||||
error = f'Bad method : "{self.method}"'
|
error = f'Bad method : "{self.method}"'
|
||||||
raise Exception(error)
|
raise SampleException(error)
|
||||||
self.shorten()
|
self.shorten()
|
||||||
file = open(f"sample_TEST.json.md", "tw")
|
with open("sample_TEST.json.md", "tw", encoding="utf-8") as f:
|
||||||
self.dump(file)
|
self.dump(f)
|
||||||
file.close()
|
|
||||||
|
|
||||||
def _shorten(
|
def _shorten(self, item):
|
||||||
self, item
|
"Abrège les longues listes: limite à 2 éléments et affiche '...' etc. à la place"
|
||||||
): # abrege les longues listes (limite à 2 éléments et affiche "... etc. à la place"
|
|
||||||
if isinstance(item, list):
|
if isinstance(item, list):
|
||||||
return [self._shorten(child) for child in item[:2]] + ["... etc."]
|
return [self._shorten(child) for child in item[:2] + ["..."]]
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def shorten(self):
|
def shorten(self):
|
||||||
|
"Abrège le résultat"
|
||||||
self.result = self._shorten(self.result)
|
self.result = self._shorten(self.result)
|
||||||
|
|
||||||
def pp(self):
|
def pp(self):
|
||||||
@ -122,8 +136,8 @@ class Sample:
|
|||||||
|
|
||||||
file.write(f"#### {self.method} {self.url}\n")
|
file.write(f"#### {self.method} {self.url}\n")
|
||||||
if len(self.content) > 0:
|
if len(self.content) > 0:
|
||||||
file.write(f"> `Content-Type: application/json`\n")
|
file.write("> `Content-Type: application/json`\n")
|
||||||
file.write(f"> \n")
|
file.write("> \n")
|
||||||
file.write(f"> `{self.content}`\n\n")
|
file.write(f"> `{self.content}`\n\n")
|
||||||
|
|
||||||
file.write("```json\n")
|
file.write("```json\n")
|
||||||
@ -143,7 +157,7 @@ class Samples:
|
|||||||
"""Entry_names: la liste des entrées à reconstruire.
|
"""Entry_names: la liste des entrées à reconstruire.
|
||||||
si None, la totalité des lignes de samples.csv est prise en compte
|
si None, la totalité des lignes de samples.csv est prise en compte
|
||||||
"""
|
"""
|
||||||
self.entries = defaultdict(lambda: set())
|
self.entries = defaultdict(set)
|
||||||
self.entry_names = entry_names
|
self.entry_names = entry_names
|
||||||
|
|
||||||
def add_sample(self, line):
|
def add_sample(self, line):
|
||||||
@ -171,26 +185,25 @@ class Samples:
|
|||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
for entry, samples in self.entries.items():
|
for entry, samples in self.entries.items():
|
||||||
file = open(f"{DATA_DIR}sample_{entry}.json.md", "tw")
|
with open(f"{DATA_DIR}sample_{entry}.json.md", "tw", encoding="utf-8") as f:
|
||||||
file.write(f"### {entry}\n\n")
|
f.write(f"### {entry}\n\n")
|
||||||
for sample in sorted(
|
# Trié de façon à rendre le fichier indépendant de l'ordre des résultats
|
||||||
samples, key=lambda s: s.url
|
for sample in sorted(samples, key=lambda s: s.url):
|
||||||
): # sorted de façon à rendre le fichier résultat déterministe (i.e. indépendant de l ordre d arrivée des résultats)
|
sample.dump(f)
|
||||||
sample.dump(file)
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
|
|
||||||
def make_samples(samples_filename):
|
def make_samples(samples_filename):
|
||||||
if len(sys.argv) == 1:
|
"Génère les samples"
|
||||||
entry_names = None
|
entry_names = None
|
||||||
elif len(sys.argv) >= 3 and sys.argv[1] == "-i":
|
if len(sys.argv) >= 3 and sys.argv[1] == "-i":
|
||||||
samples_filename = sys.argv[2]
|
samples_filename = sys.argv[2]
|
||||||
entry_names = sys.argv[3:] if len(sys.argv) > 3 else None
|
entry_names = sys.argv[3:] if len(sys.argv) > 3 else None
|
||||||
|
else:
|
||||||
|
entry_names = sys.argv[1:]
|
||||||
|
|
||||||
if os.path.exists(DATA_DIR):
|
if os.path.exists(DATA_DIR):
|
||||||
if not os.path.isdir(DATA_DIR):
|
if not os.path.isdir(DATA_DIR):
|
||||||
raise f"{DATA_DIR} existe déjà et n'est pas un répertoire"
|
raise SampleException(f"{DATA_DIR} existe déjà et n'est pas un répertoire")
|
||||||
else:
|
|
||||||
# DATA_DIR existe déjà - effacer et recréer
|
# DATA_DIR existe déjà - effacer et recréer
|
||||||
shutil.rmtree(DATA_DIR)
|
shutil.rmtree(DATA_DIR)
|
||||||
os.mkdir(DATA_DIR)
|
os.mkdir(DATA_DIR)
|
||||||
@ -198,8 +211,9 @@ def make_samples(samples_filename):
|
|||||||
os.mkdir(DATA_DIR)
|
os.mkdir(DATA_DIR)
|
||||||
|
|
||||||
samples = Samples(entry_names)
|
samples = Samples(entry_names)
|
||||||
df = read_csv(
|
df = pd.read_csv(
|
||||||
samples_filename,
|
samples_filename,
|
||||||
|
comment="#",
|
||||||
sep=";",
|
sep=";",
|
||||||
quotechar='"',
|
quotechar='"',
|
||||||
dtype={
|
dtype={
|
||||||
@ -212,11 +226,12 @@ def make_samples(samples_filename):
|
|||||||
keep_default_na=False,
|
keep_default_na=False,
|
||||||
)
|
)
|
||||||
df = df.reset_index()
|
df = df.reset_index()
|
||||||
df.apply(lambda line: samples.add_sample(line), axis=1)
|
df.apply(samples.add_sample, axis=1)
|
||||||
samples.dump()
|
samples.dump()
|
||||||
return samples
|
return samples
|
||||||
|
|
||||||
|
|
||||||
if not CHECK_CERTIFICATE:
|
if not CHECK_CERTIFICATE:
|
||||||
urllib3.disable_warnings()
|
urllib3.disable_warnings()
|
||||||
|
|
||||||
make_samples(SAMPLES_FILENAME)
|
make_samples(SAMPLES_FILENAME)
|
||||||
|
@ -116,3 +116,73 @@ def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None, dep
|
|||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise APIError(errmsg or f"erreur status={r.status_code} !", r.json())
|
raise APIError(errmsg or f"erreur status={r.status_code} !", r.json())
|
||||||
return r.json() # decode la reponse JSON
|
return r.json() # decode la reponse JSON
|
||||||
|
|
||||||
|
|
||||||
|
def check_fields(data: dict, fields: dict = None):
|
||||||
|
"""
|
||||||
|
Vérifie que le dictionnaire data contient les bonnes clés
|
||||||
|
et les bons types de valeurs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (dict): un dictionnaire (json de retour de l'api)
|
||||||
|
fields (dict, optional): Un dictionnaire représentant les clés et les types d'une réponse.
|
||||||
|
"""
|
||||||
|
assert set(data.keys()) == set(fields.keys())
|
||||||
|
for key in data:
|
||||||
|
if key in ("moduleimpl_id", "desc", "user_id", "external_data"):
|
||||||
|
assert (
|
||||||
|
isinstance(data[key], fields[key]) or data[key] is None
|
||||||
|
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
|
||||||
|
else:
|
||||||
|
assert isinstance(
|
||||||
|
data[key], fields[key]
|
||||||
|
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
|
||||||
|
|
||||||
|
|
||||||
|
def check_failure_get(path: str, headers: dict, err: str = None):
|
||||||
|
"""
|
||||||
|
Vérifie que la requête GET renvoie bien un 404
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): la route de l'api
|
||||||
|
headers (dict): le token d'auth de l'api
|
||||||
|
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
GET(path=path, headers=headers)
|
||||||
|
# ^ Renvoi un 404
|
||||||
|
except APIError as api_err:
|
||||||
|
if err is not None:
|
||||||
|
assert api_err.payload["message"] == err
|
||||||
|
else:
|
||||||
|
raise APIError("Le GET n'aurait pas du fonctionner")
|
||||||
|
|
||||||
|
|
||||||
|
def check_failure_post(path: str, headers: dict, data: dict, err: str = None):
|
||||||
|
"""
|
||||||
|
Vérifie que la requête POST renvoie bien un 404
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): la route de l'api
|
||||||
|
headers (dict): le token d'auth
|
||||||
|
data (dict): un dictionnaire (json) à envoyer
|
||||||
|
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = POST_JSON(path=path, headers=headers, data=data)
|
||||||
|
# ^ Renvoie un 404
|
||||||
|
except APIError as api_err:
|
||||||
|
if err is not None:
|
||||||
|
assert (
|
||||||
|
api_err.payload["message"] == err
|
||||||
|
), f"received: {api_err.payload['message']}"
|
||||||
|
else:
|
||||||
|
raise APIError("Le GET n'aurait pas du fonctionner")
|
||||||
|
@ -1,214 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""Test Logos
|
|
||||||
|
|
||||||
Utilisation :
|
|
||||||
créer les variables d'environnement: (indiquer les valeurs
|
|
||||||
pour le serveur ScoDoc que vous voulez interroger)
|
|
||||||
|
|
||||||
export SCODOC_URL="https://scodoc.xxx.net/"
|
|
||||||
export SCODOC_USER="xxx"
|
|
||||||
export SCODOC_PASSWD="xxx"
|
|
||||||
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
|
|
||||||
|
|
||||||
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
|
|
||||||
|
|
||||||
Lancer :
|
|
||||||
pytest tests/api/test_api_absences.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from app.scodoc import sco_utils as scu
|
|
||||||
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
|
|
||||||
|
|
||||||
# Etudiant pour les tests
|
|
||||||
from tests.api.tools_test_api import (
|
|
||||||
verify_fields,
|
|
||||||
ABSENCES_FIELDS,
|
|
||||||
ABSENCES_GROUP_ETAT_FIELDS,
|
|
||||||
)
|
|
||||||
|
|
||||||
ETUDID = 1
|
|
||||||
|
|
||||||
|
|
||||||
# absences
|
|
||||||
def test_absences(api_headers):
|
|
||||||
"""
|
|
||||||
Test 'absences'
|
|
||||||
|
|
||||||
Route :
|
|
||||||
- /absences/etudid/<int:etudid>
|
|
||||||
"""
|
|
||||||
r = requests.get(
|
|
||||||
f"{API_URL}/absences/etudid/{ETUDID}",
|
|
||||||
headers=api_headers,
|
|
||||||
verify=CHECK_CERTIFICATE,
|
|
||||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
|
||||||
)
|
|
||||||
assert r.status_code == 200
|
|
||||||
absences = r.json()
|
|
||||||
|
|
||||||
assert isinstance(absences, list)
|
|
||||||
for abs in absences:
|
|
||||||
assert verify_fields(abs, ABSENCES_FIELDS) is True
|
|
||||||
assert isinstance(abs["jour"], str)
|
|
||||||
assert isinstance(abs["matin"], bool)
|
|
||||||
assert isinstance(abs["estabs"], bool)
|
|
||||||
assert isinstance(abs["estjust"], bool)
|
|
||||||
assert isinstance(abs["description"], str)
|
|
||||||
assert isinstance(abs["begin"], str)
|
|
||||||
assert isinstance(abs["end"], str)
|
|
||||||
|
|
||||||
assert abs["begin"] < abs["end"]
|
|
||||||
|
|
||||||
|
|
||||||
# absences_justify
|
|
||||||
def test_absences_justify(api_headers):
|
|
||||||
"""
|
|
||||||
Test 'absences_just'
|
|
||||||
|
|
||||||
Route :
|
|
||||||
- /absences/etudid/<int:etudid>/just
|
|
||||||
"""
|
|
||||||
r = requests.get(
|
|
||||||
f"{API_URL}/absences/etudid/{ETUDID}/just",
|
|
||||||
headers=api_headers,
|
|
||||||
verify=CHECK_CERTIFICATE,
|
|
||||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
|
||||||
)
|
|
||||||
assert r.status_code == 200
|
|
||||||
absences = r.json()
|
|
||||||
|
|
||||||
assert isinstance(absences, list)
|
|
||||||
for abs in absences:
|
|
||||||
assert verify_fields(abs, ABSENCES_FIELDS) is True
|
|
||||||
assert isinstance(abs["jour"], str)
|
|
||||||
assert isinstance(abs["matin"], bool)
|
|
||||||
assert isinstance(abs["estabs"], bool)
|
|
||||||
assert isinstance(abs["estjust"], bool)
|
|
||||||
assert isinstance(abs["description"], str)
|
|
||||||
assert isinstance(abs["begin"], str)
|
|
||||||
assert isinstance(abs["end"], str)
|
|
||||||
|
|
||||||
assert abs["begin"] < abs["end"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_abs_groupe_etat(api_headers):
|
|
||||||
"""
|
|
||||||
Test 'abs_groupe_etat'
|
|
||||||
|
|
||||||
Routes :
|
|
||||||
- /absences/abs_group_etat/<int:group_id>
|
|
||||||
- /absences/abs_group_etat/group_id/<int:group_id>/date_debut/<string:date_debut>/date_fin/<string:date_fin>
|
|
||||||
"""
|
|
||||||
group_id = 1
|
|
||||||
r = requests.get(
|
|
||||||
f"{API_URL}/absences/abs_group_etat/{group_id}",
|
|
||||||
headers=api_headers,
|
|
||||||
verify=CHECK_CERTIFICATE,
|
|
||||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
|
||||||
)
|
|
||||||
assert r.status_code == 200
|
|
||||||
|
|
||||||
list_absences = r.json()
|
|
||||||
|
|
||||||
assert isinstance(list_absences, list)
|
|
||||||
list_id_etu = []
|
|
||||||
for etu in list_absences:
|
|
||||||
list_id_etu.append(etu["etudid"])
|
|
||||||
assert verify_fields(etu, ABSENCES_GROUP_ETAT_FIELDS) is True
|
|
||||||
assert isinstance(etu["etudid"], int)
|
|
||||||
assert isinstance(etu["list_abs"], list)
|
|
||||||
|
|
||||||
list_abs = etu["list_abs"]
|
|
||||||
for abs in list_abs:
|
|
||||||
assert verify_fields(abs, ABSENCES_FIELDS) is True
|
|
||||||
assert isinstance(abs["jour"], str)
|
|
||||||
assert isinstance(abs["matin"], bool)
|
|
||||||
assert isinstance(abs["estabs"], bool)
|
|
||||||
assert isinstance(abs["estjust"], bool)
|
|
||||||
assert isinstance(abs["description"], str)
|
|
||||||
assert isinstance(abs["begin"], str)
|
|
||||||
assert isinstance(abs["end"], str)
|
|
||||||
|
|
||||||
assert abs["begin"] < abs["end"]
|
|
||||||
|
|
||||||
# vérifie que chaque étudiant n'apparait qu'une seule fois
|
|
||||||
assert len(set(list_id_etu)) == len(list_id_etu)
|
|
||||||
|
|
||||||
date_debut = "Fri, 15 Apr 2021 00:00:00 GMT"
|
|
||||||
date_fin = "Fri, 18 Apr 2022 00:00:00 GMT"
|
|
||||||
|
|
||||||
r1 = requests.get(
|
|
||||||
f"{API_URL}/absences/abs_group_etat/group_id/{group_id}/date_debut/{date_debut}/date_fin/{date_fin}",
|
|
||||||
headers=api_headers,
|
|
||||||
verify=CHECK_CERTIFICATE,
|
|
||||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
|
||||||
)
|
|
||||||
assert r1.status_code == 200
|
|
||||||
|
|
||||||
list_absences1 = r.json()
|
|
||||||
|
|
||||||
assert isinstance(list_absences1, list)
|
|
||||||
list_id_etu1 = []
|
|
||||||
for etu in list_absences1:
|
|
||||||
list_id_etu1.append(etu["etudid"])
|
|
||||||
assert verify_fields(etu, ABSENCES_GROUP_ETAT_FIELDS) is True
|
|
||||||
assert isinstance(etu["etudid"], int)
|
|
||||||
assert isinstance(etu["list_abs"], list)
|
|
||||||
|
|
||||||
list_abs1 = etu["list_abs"]
|
|
||||||
for abs in list_abs1:
|
|
||||||
assert verify_fields(abs, ABSENCES_FIELDS) is True
|
|
||||||
assert isinstance(abs["jour"], str)
|
|
||||||
assert isinstance(abs["matin"], bool)
|
|
||||||
assert isinstance(abs["estabs"], bool)
|
|
||||||
assert isinstance(abs["estjust"], bool)
|
|
||||||
assert isinstance(abs["description"], str)
|
|
||||||
assert isinstance(abs["begin"], str)
|
|
||||||
assert isinstance(abs["end"], str)
|
|
||||||
|
|
||||||
assert abs["begin"] < abs["end"]
|
|
||||||
|
|
||||||
all_unique1 = True
|
|
||||||
for id in list_id_etu1:
|
|
||||||
if list_id_etu1.count(id) > 1:
|
|
||||||
all_unique1 = False
|
|
||||||
assert all_unique1 is True
|
|
||||||
|
|
||||||
|
|
||||||
# XXX TODO
|
|
||||||
# def reset_etud_abs(api_headers):
|
|
||||||
# """
|
|
||||||
# Test 'reset_etud_abs'
|
|
||||||
#
|
|
||||||
# Routes :
|
|
||||||
# - /absences/etudid/<int:etudid>/list_abs/<string:list_abs>/reset_etud_abs
|
|
||||||
# - /absences/etudid/<int:etudid>/list_abs/<string:list_abs>/reset_etud_abs/only_not_just
|
|
||||||
# - /absences/etudid/<int:etudid>/list_abs/<string:list_abs>/reset_etud_abs/only_just
|
|
||||||
# """
|
|
||||||
# list_abs = []
|
|
||||||
# r = requests.get(
|
|
||||||
# f"{API_URL}/absences/etudid/{ETUDID}/list_abs/{list_abs}/reset_etud_abs",
|
|
||||||
# headers=api_headers,
|
|
||||||
# verify=CHECK_CERTIFICATE,
|
|
||||||
# timeout=scu.SCO_TEST_API_TIMEOUT,
|
|
||||||
# )
|
|
||||||
# assert r.status_code == 200
|
|
||||||
#
|
|
||||||
# r_only_not_just = requests.get(
|
|
||||||
# f"{API_URL}/absences/etudid/{ETUDID}/list_abs/{list_abs}/reset_etud_abs/only_not_just",
|
|
||||||
# headers=api_headers,
|
|
||||||
# verify=CHECK_CERTIFICATE,
|
|
||||||
# timeout=scu.SCO_TEST_API_TIMEOUT,
|
|
||||||
# )
|
|
||||||
# assert r.status_code == 200
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# r_only_just = requests.get(
|
|
||||||
# f"{API_URL}/absences/etudid/{ETUDID}/list_abs/{list_abs}/reset_etud_abs/only_just",
|
|
||||||
# headers=api_headers,
|
|
||||||
# verify=CHECK_CERTIFICATE,
|
|
||||||
# timeout=scu.SCO_TEST_API_TIMEOUT,
|
|
||||||
# )
|
|
||||||
# assert r.status_code == 200
|
|
@ -13,6 +13,9 @@ from tests.api.setup_test_api import (
|
|||||||
APIError,
|
APIError,
|
||||||
api_headers,
|
api_headers,
|
||||||
api_admin_headers,
|
api_admin_headers,
|
||||||
|
check_failure_get,
|
||||||
|
check_failure_post,
|
||||||
|
check_fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
ETUDID = 1
|
ETUDID = 1
|
||||||
@ -44,76 +47,6 @@ COUNT_FIELDS = {"compte": int, "journee": int, "demi": int, "heure": float}
|
|||||||
TO_REMOVE = []
|
TO_REMOVE = []
|
||||||
|
|
||||||
|
|
||||||
def check_fields(data: dict, fields: dict = None):
|
|
||||||
"""
|
|
||||||
Cette fonction permet de vérifier que le dictionnaire data
|
|
||||||
contient les bonnes clés et les bons types de valeurs.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data (dict): un dictionnaire (json de retour de l'api)
|
|
||||||
fields (dict, optional): Un dictionnaire représentant les clés et les types d'une réponse.
|
|
||||||
"""
|
|
||||||
if fields is None:
|
|
||||||
fields = ASSIDUITES_FIELDS
|
|
||||||
assert set(data.keys()) == set(fields.keys())
|
|
||||||
for key in data:
|
|
||||||
if key in ("moduleimpl_id", "desc", "user_id", "external_data"):
|
|
||||||
assert (
|
|
||||||
isinstance(data[key], fields[key]) or data[key] is None
|
|
||||||
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
|
|
||||||
else:
|
|
||||||
assert isinstance(
|
|
||||||
data[key], fields[key]
|
|
||||||
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
|
|
||||||
|
|
||||||
|
|
||||||
def check_failure_get(path: str, headers: dict, err: str = None):
|
|
||||||
"""
|
|
||||||
Cette fonction vérifiée que la requête GET renvoie bien un 404
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path (str): la route de l'api
|
|
||||||
headers (dict): le token d'auth de l'api
|
|
||||||
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
GET(path=path, headers=headers)
|
|
||||||
# ^ Renvoi un 404
|
|
||||||
except APIError as api_err:
|
|
||||||
if err is not None:
|
|
||||||
assert api_err.payload["message"] == err
|
|
||||||
else:
|
|
||||||
raise APIError("Le GET n'aurait pas du fonctionner")
|
|
||||||
|
|
||||||
|
|
||||||
def check_failure_post(path: str, headers: dict, data: dict, err: str = None):
|
|
||||||
"""
|
|
||||||
Cette fonction vérifiée que la requête POST renvoie bien un 404
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path (str): la route de l'api
|
|
||||||
headers (dict): le token d'auth
|
|
||||||
data (dict): un dictionnaire (json) à envoyer
|
|
||||||
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = POST_JSON(path=path, headers=headers, data=data)
|
|
||||||
# ^ Renvoi un 404
|
|
||||||
except APIError as api_err:
|
|
||||||
if err is not None:
|
|
||||||
assert api_err.payload["message"] == err
|
|
||||||
else:
|
|
||||||
raise APIError("Le GET n'aurait pas du fonctionner")
|
|
||||||
|
|
||||||
|
|
||||||
def create_data(etat: str, day: str, module: int = None, desc: str = None):
|
def create_data(etat: str, day: str, module: int = None, desc: str = None):
|
||||||
"""
|
"""
|
||||||
Permet de créer un dictionnaire assiduité
|
Permet de créer un dictionnaire assiduité
|
||||||
@ -146,7 +79,7 @@ def test_route_assiduite(api_headers):
|
|||||||
|
|
||||||
# Bon fonctionnement == id connu
|
# Bon fonctionnement == id connu
|
||||||
data = GET(path="/assiduite/1", headers=api_headers)
|
data = GET(path="/assiduite/1", headers=api_headers)
|
||||||
check_fields(data)
|
check_fields(data, fields=ASSIDUITES_FIELDS)
|
||||||
|
|
||||||
# Mauvais Fonctionnement == id inconnu
|
# Mauvais Fonctionnement == id inconnu
|
||||||
|
|
||||||
@ -273,7 +206,7 @@ def test_route_create(api_admin_headers):
|
|||||||
path=f'/assiduite/{res["success"][0]["message"]["assiduite_id"]}',
|
path=f'/assiduite/{res["success"][0]["message"]["assiduite_id"]}',
|
||||||
headers=api_admin_headers,
|
headers=api_admin_headers,
|
||||||
)
|
)
|
||||||
check_fields(data)
|
check_fields(data, fields=ASSIDUITES_FIELDS)
|
||||||
|
|
||||||
data2 = create_data("absent", "02", MODULE, "desc")
|
data2 = create_data("absent", "02", MODULE, "desc")
|
||||||
res = POST_JSON(f"/assiduite/{ETUDID}/create", [data2], api_admin_headers)
|
res = POST_JSON(f"/assiduite/{ETUDID}/create", [data2], api_admin_headers)
|
||||||
|
@ -18,6 +18,7 @@ Utilisation :
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from types import NoneType
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@ -513,13 +514,16 @@ def test_etudiant_bulletin_semestre(api_headers):
|
|||||||
assert evaluation["description"] is None or isinstance(
|
assert evaluation["description"] is None or isinstance(
|
||||||
evaluation["description"], str
|
evaluation["description"], str
|
||||||
)
|
)
|
||||||
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
|
|
||||||
assert isinstance(evaluation["heure_debut"], str)
|
|
||||||
assert isinstance(evaluation["heure_fin"], str)
|
|
||||||
assert isinstance(evaluation["coef"], str)
|
assert isinstance(evaluation["coef"], str)
|
||||||
assert isinstance(evaluation["poids"], dict)
|
assert isinstance(evaluation["poids"], dict)
|
||||||
assert isinstance(evaluation["note"], dict)
|
assert isinstance(evaluation["note"], dict)
|
||||||
assert isinstance(evaluation["url"], str)
|
assert isinstance(evaluation["url"], str)
|
||||||
|
assert isinstance(evaluation["date_debut"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["date_fin"], (str, NoneType))
|
||||||
|
# Deprecated (supprimer avant #sco9.7):
|
||||||
|
assert isinstance(evaluation["date"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["heure_debut"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["heure_fin"], (str, NoneType))
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
verify_fields(
|
verify_fields(
|
||||||
@ -567,13 +571,16 @@ def test_etudiant_bulletin_semestre(api_headers):
|
|||||||
assert evaluation["description"] is None or isinstance(
|
assert evaluation["description"] is None or isinstance(
|
||||||
evaluation["description"], str
|
evaluation["description"], str
|
||||||
)
|
)
|
||||||
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
|
|
||||||
assert isinstance(evaluation["heure_debut"], str)
|
|
||||||
assert isinstance(evaluation["heure_fin"], str)
|
|
||||||
assert isinstance(evaluation["coef"], str)
|
assert isinstance(evaluation["coef"], str)
|
||||||
assert isinstance(evaluation["poids"], dict)
|
assert isinstance(evaluation["poids"], dict)
|
||||||
assert isinstance(evaluation["note"], dict)
|
assert isinstance(evaluation["note"], dict)
|
||||||
assert isinstance(evaluation["url"], str)
|
assert isinstance(evaluation["url"], str)
|
||||||
|
assert isinstance(evaluation["date_fin"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["date_debut"], (str, NoneType))
|
||||||
|
# Deprecated fields (supprimer avant #sco9.7)
|
||||||
|
assert isinstance(evaluation["date"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["heure_debut"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["heure_fin"], (str, NoneType))
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
verify_fields(
|
verify_fields(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""Test Logos
|
"""Test API evaluations
|
||||||
|
|
||||||
Utilisation :
|
Utilisation :
|
||||||
créer les variables d'environnement: (indiquer les valeurs
|
créer les variables d'environnement: (indiquer les valeurs
|
||||||
@ -18,9 +18,18 @@ Utilisation :
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from types import NoneType
|
||||||
|
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
|
from tests.api.setup_test_api import (
|
||||||
|
API_URL,
|
||||||
|
CHECK_CERTIFICATE,
|
||||||
|
GET,
|
||||||
|
POST_JSON,
|
||||||
|
api_admin_headers,
|
||||||
|
api_headers,
|
||||||
|
check_failure_post,
|
||||||
|
)
|
||||||
from tests.api.tools_test_api import (
|
from tests.api.tools_test_api import (
|
||||||
verify_fields,
|
verify_fields,
|
||||||
EVALUATIONS_FIELDS,
|
EVALUATIONS_FIELDS,
|
||||||
@ -43,25 +52,25 @@ def test_evaluations(api_headers):
|
|||||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
list_eval = r.json()
|
evaluations = r.json()
|
||||||
assert list_eval
|
assert evaluations
|
||||||
assert isinstance(list_eval, list)
|
assert isinstance(evaluations, list)
|
||||||
for eval in list_eval:
|
for e in evaluations:
|
||||||
assert verify_fields(eval, EVALUATIONS_FIELDS) is True
|
assert verify_fields(e, EVALUATIONS_FIELDS)
|
||||||
assert isinstance(eval["id"], int)
|
assert isinstance(e["date_debut"], (str, NoneType))
|
||||||
assert isinstance(eval["note_max"], float)
|
assert isinstance(e["date_fin"], (str, NoneType))
|
||||||
assert isinstance(eval["visi_bulletin"], bool)
|
assert isinstance(e["id"], int)
|
||||||
assert isinstance(eval["evaluation_type"], int)
|
assert isinstance(e["note_max"], float)
|
||||||
assert isinstance(eval["moduleimpl_id"], int)
|
assert isinstance(e["visibulletin"], bool)
|
||||||
assert eval["description"] is None or isinstance(eval["description"], str)
|
assert isinstance(e["evaluation_type"], int)
|
||||||
assert isinstance(eval["coefficient"], float)
|
assert isinstance(e["moduleimpl_id"], int)
|
||||||
assert isinstance(eval["publish_incomplete"], bool)
|
assert e["description"] is None or isinstance(e["description"], str)
|
||||||
assert isinstance(eval["numero"], int)
|
assert isinstance(e["coefficient"], float)
|
||||||
assert eval["date_debut"] is None or isinstance(eval["date_debut"], str)
|
assert isinstance(e["publish_incomplete"], bool)
|
||||||
assert eval["date_fin"] is None or isinstance(eval["date_fin"], str)
|
assert isinstance(e["numero"], int)
|
||||||
assert isinstance(eval["poids"], dict)
|
assert isinstance(e["poids"], dict)
|
||||||
|
|
||||||
assert eval["moduleimpl_id"] == moduleimpl_id
|
assert e["moduleimpl_id"] == moduleimpl_id
|
||||||
|
|
||||||
|
|
||||||
def test_evaluation_notes(api_headers):
|
def test_evaluation_notes(api_headers):
|
||||||
@ -92,3 +101,133 @@ def test_evaluation_notes(api_headers):
|
|||||||
assert isinstance(note["uid"], int)
|
assert isinstance(note["uid"], int)
|
||||||
|
|
||||||
assert eval_id == note["evaluation_id"]
|
assert eval_id == note["evaluation_id"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_evaluation_create(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Test /moduleimpl/<int:moduleimpl_id>/evaluation/create
|
||||||
|
"""
|
||||||
|
moduleimpl_id = 20
|
||||||
|
# Nombre d'évaluations initial
|
||||||
|
evaluations = GET(
|
||||||
|
f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers
|
||||||
|
)
|
||||||
|
nb_evals = len(evaluations)
|
||||||
|
#
|
||||||
|
e = POST_JSON(
|
||||||
|
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||||
|
{"description": "eval test"},
|
||||||
|
api_admin_headers,
|
||||||
|
)
|
||||||
|
assert isinstance(e, dict)
|
||||||
|
assert verify_fields(e, EVALUATIONS_FIELDS)
|
||||||
|
# Check default values
|
||||||
|
assert e["note_max"] == 20.0
|
||||||
|
assert e["evaluation_type"] == 0
|
||||||
|
assert not e["date_debut"]
|
||||||
|
assert not e["date_fin"]
|
||||||
|
assert e["visibulletin"] is True
|
||||||
|
assert e["publish_incomplete"] is False
|
||||||
|
assert e["coefficient"] == 1.0
|
||||||
|
new_nb_evals = len(
|
||||||
|
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||||
|
)
|
||||||
|
assert new_nb_evals == nb_evals + 1
|
||||||
|
nb_evals = new_nb_evals
|
||||||
|
|
||||||
|
# Avec une erreur
|
||||||
|
check_failure_post(
|
||||||
|
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||||
|
api_admin_headers,
|
||||||
|
{"evaluation_type": 666},
|
||||||
|
err="paramètre de type incorrect (invalid evaluation_type value)",
|
||||||
|
)
|
||||||
|
new_nb_evals = len(
|
||||||
|
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||||
|
)
|
||||||
|
assert new_nb_evals == nb_evals # inchangé
|
||||||
|
# Avec plein de valeurs
|
||||||
|
data = {
|
||||||
|
"coefficient": 12.0,
|
||||||
|
"date_debut": "2021-10-15T08:30:00+02:00",
|
||||||
|
"date_fin": "2021-10-15T10:30:00+02:00",
|
||||||
|
"description": "eval test2",
|
||||||
|
"evaluation_type": 1,
|
||||||
|
"visibulletin": False,
|
||||||
|
"publish_incomplete": True,
|
||||||
|
"note_max": 100.0,
|
||||||
|
}
|
||||||
|
e = POST_JSON(
|
||||||
|
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||||
|
data,
|
||||||
|
api_admin_headers,
|
||||||
|
)
|
||||||
|
e_ret = GET(f"/evaluation/{e['id']}", headers=api_admin_headers)
|
||||||
|
for k, v in data.items():
|
||||||
|
assert e_ret[k] == v, f"received '{e_ret[k]}'"
|
||||||
|
|
||||||
|
# Avec des poids APC
|
||||||
|
nb_evals = len(
|
||||||
|
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||||
|
)
|
||||||
|
data.update(
|
||||||
|
{
|
||||||
|
"description": "eval test apc erreur",
|
||||||
|
"poids": {"666": 666.0}, # poids erroné: UE inexistante
|
||||||
|
}
|
||||||
|
)
|
||||||
|
check_failure_post(
|
||||||
|
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||||
|
api_admin_headers,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
new_nb_evals = len(
|
||||||
|
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||||
|
)
|
||||||
|
assert new_nb_evals == nb_evals # inchangé
|
||||||
|
# Avec des poids absurdes
|
||||||
|
data.update({"description": "eval test apc erreur 2", "poids": "nimporte quoi"})
|
||||||
|
check_failure_post(
|
||||||
|
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||||
|
api_admin_headers,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
new_nb_evals = len(
|
||||||
|
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||||
|
)
|
||||||
|
assert new_nb_evals == nb_evals # inchangé
|
||||||
|
# Avec de bons poids
|
||||||
|
# pour cela il nous faut les UEs de ce formsemestre
|
||||||
|
# sachant que l'on a moduleimpl
|
||||||
|
modimpl = GET(f"/moduleimpl/{moduleimpl_id}", headers=api_admin_headers)
|
||||||
|
formation = GET(
|
||||||
|
f"/formsemestre/{modimpl['formsemestre_id']}/programme",
|
||||||
|
headers=api_admin_headers,
|
||||||
|
)
|
||||||
|
ues = formation["ues"]
|
||||||
|
assert len(ues)
|
||||||
|
ue_ids = [ue["id"] for ue in ues]
|
||||||
|
poids = {ue_id: float(i) + 0.5 for i, ue_id in enumerate(ue_ids)}
|
||||||
|
data.update({"description": "eval avec poids", "poids": poids})
|
||||||
|
e = POST_JSON(
|
||||||
|
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||||
|
data,
|
||||||
|
api_admin_headers,
|
||||||
|
)
|
||||||
|
assert e["poids"]
|
||||||
|
e_ret = GET(f"/evaluation/{e['id']}", headers=api_admin_headers)
|
||||||
|
assert e_ret["poids"] == e["poids"]
|
||||||
|
new_nb_evals = len(
|
||||||
|
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||||
|
)
|
||||||
|
assert new_nb_evals == nb_evals + 1
|
||||||
|
nb_evals = new_nb_evals
|
||||||
|
# Delete
|
||||||
|
ans = POST_JSON(
|
||||||
|
f"/evaluation/{e_ret['id']}/delete",
|
||||||
|
headers=api_admin_headers,
|
||||||
|
)
|
||||||
|
assert ans == "ok"
|
||||||
|
assert nb_evals - 1 == len(
|
||||||
|
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||||
|
)
|
||||||
|
@ -18,6 +18,7 @@ Utilisation :
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
|
from types import NoneType
|
||||||
|
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
@ -301,14 +302,18 @@ def test_bulletins(api_headers):
|
|||||||
assert evaluation["description"] is None or isinstance(
|
assert evaluation["description"] is None or isinstance(
|
||||||
evaluation["description"], str
|
evaluation["description"], str
|
||||||
)
|
)
|
||||||
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
|
assert isinstance(evaluation["date_debut"], (str, NoneType))
|
||||||
assert isinstance(evaluation["heure_debut"], str)
|
assert isinstance(evaluation["date_fin"], (str, NoneType))
|
||||||
assert isinstance(evaluation["heure_fin"], str)
|
assert isinstance(evaluation["coef"], (str, NoneType))
|
||||||
assert isinstance(evaluation["coef"], str)
|
|
||||||
assert isinstance(evaluation["poids"], dict)
|
assert isinstance(evaluation["poids"], dict)
|
||||||
assert isinstance(evaluation["note"], dict)
|
assert isinstance(evaluation["note"], dict)
|
||||||
assert isinstance(evaluation["url"], str)
|
assert isinstance(evaluation["url"], str)
|
||||||
|
|
||||||
|
# Deprecated (supprimer avant #sco9.7):
|
||||||
|
assert isinstance(evaluation["date"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["heure_debut"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["heure_fin"], (str, NoneType))
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
verify_fields(
|
verify_fields(
|
||||||
evaluation["poids"],
|
evaluation["poids"],
|
||||||
@ -354,14 +359,18 @@ def test_bulletins(api_headers):
|
|||||||
assert evaluation["description"] is None or isinstance(
|
assert evaluation["description"] is None or isinstance(
|
||||||
evaluation["description"], str
|
evaluation["description"], str
|
||||||
)
|
)
|
||||||
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
|
assert isinstance(evaluation["date_debut"], (str, NoneType))
|
||||||
assert isinstance(evaluation["heure_debut"], str)
|
assert isinstance(evaluation["date_fin"], (str, NoneType))
|
||||||
assert isinstance(evaluation["heure_fin"], str)
|
|
||||||
assert isinstance(evaluation["coef"], str)
|
assert isinstance(evaluation["coef"], str)
|
||||||
assert isinstance(evaluation["poids"], dict)
|
assert isinstance(evaluation["poids"], dict)
|
||||||
assert isinstance(evaluation["note"], dict)
|
assert isinstance(evaluation["note"], dict)
|
||||||
assert isinstance(evaluation["url"], str)
|
assert isinstance(evaluation["url"], str)
|
||||||
|
|
||||||
|
# Deprecated (supprimer avant #sco9.7):
|
||||||
|
assert isinstance(evaluation["date"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["heure_debut"], (str, NoneType))
|
||||||
|
assert isinstance(evaluation["heure_fin"], (str, NoneType))
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
verify_fields(
|
verify_fields(
|
||||||
evaluation["poids"],
|
evaluation["poids"],
|
||||||
|
@ -491,7 +491,7 @@ EVAL_FIELDS = {
|
|||||||
"numero",
|
"numero",
|
||||||
"poids",
|
"poids",
|
||||||
"publish_incomplete",
|
"publish_incomplete",
|
||||||
"visi_bulletin",
|
"visibulletin",
|
||||||
"etat",
|
"etat",
|
||||||
"nb_inscrits",
|
"nb_inscrits",
|
||||||
"nb_notes_manquantes",
|
"nb_notes_manquantes",
|
||||||
@ -565,7 +565,7 @@ EVALUATIONS_FIELDS = {
|
|||||||
"numero",
|
"numero",
|
||||||
"poids",
|
"poids",
|
||||||
"publish_incomplete",
|
"publish_incomplete",
|
||||||
"visi_bulletin",
|
"visibulletin",
|
||||||
}
|
}
|
||||||
|
|
||||||
NOTES_FIELDS = {
|
NOTES_FIELDS = {
|
||||||
|
@ -1,119 +1,120 @@
|
|||||||
"entry_name";"url";"permission";"method";"content"
|
"entry_name";"url";"permission";"method";"content"
|
||||||
|
"assiduite_create";"/assiduite/1/create";"ScoView";"POST";"{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""}"
|
||||||
|
"assiduite_create";"/assiduite/1/create/batch";"ScoView";"POST";"{""batch"":[{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""},{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""retard""},{""date_debut"": ""2022-10-27T11:00"",""date_fin"": ""2022-10-27T13:00"",""etat"": ""present""}]}"
|
||||||
|
"assiduite_delete";"/assiduite/delete";"ScoView";"POST";"{""assiduite_id"": 1}"
|
||||||
|
"assiduite_delete";"/assiduite/delete/batch";"ScoView";"POST";"{""batch"":[2,2,3]}"
|
||||||
|
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
|
||||||
|
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"":""absent""}"
|
||||||
|
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""moduleimpl_id"":2}"
|
||||||
"assiduite";"/assiduite/1";"ScoView";"GET";
|
"assiduite";"/assiduite/1";"ScoView";"GET";
|
||||||
"assiduites";"/assiduites/1";"ScoView";"GET";
|
|
||||||
"assiduites";"/assiduites/1/query?etat=retard";"ScoView";"GET";
|
|
||||||
"assiduites";"/assiduites/1/query?moduleimpl_id=1";"ScoView";"GET";
|
|
||||||
"assiduites_count";"/assiduites/1/count";"ScoView";"GET";
|
"assiduites_count";"/assiduites/1/count";"ScoView";"GET";
|
||||||
"assiduites_count";"/assiduites/1/count/query?etat=retard";"ScoView";"GET";
|
|
||||||
"assiduites_count";"/assiduites/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
|
"assiduites_count";"/assiduites/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
|
||||||
|
"assiduites_count";"/assiduites/1/count/query?etat=retard";"ScoView";"GET";
|
||||||
|
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count";"ScoView";"GET";
|
||||||
|
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
|
||||||
|
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=retard";"ScoView";"GET";
|
||||||
"assiduites_formsemestre";"/assiduites/formsemestre/1";"ScoView";"GET";
|
"assiduites_formsemestre";"/assiduites/formsemestre/1";"ScoView";"GET";
|
||||||
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?etat=retard";"ScoView";"GET";
|
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?etat=retard";"ScoView";"GET";
|
||||||
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?moduleimpl_id=1";"ScoView";"GET";
|
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?moduleimpl_id=1";"ScoView";"GET";
|
||||||
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count";"ScoView";"GET";
|
"assiduites";"/assiduites/1";"ScoView";"GET";
|
||||||
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=retard";"ScoView";"GET";
|
"assiduites";"/assiduites/1/query?etat=retard";"ScoView";"GET";
|
||||||
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
|
"assiduites";"/assiduites/1/query?moduleimpl_id=1";"ScoView";"GET";
|
||||||
"assiduite_create";"/assiduite/1/create";"ScoView";"POST";"{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""}"
|
|
||||||
"assiduite_create";"/assiduite/1/create/batch";"ScoView";"POST";"{""batch"":[{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""},{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""retard""},{""date_debut"": ""2022-10-27T11:00"",""date_fin"": ""2022-10-27T13:00"",""etat"": ""present""}]}"
|
|
||||||
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"":""absent""}"
|
|
||||||
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""moduleimpl_id"":2}"
|
|
||||||
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
|
|
||||||
"assiduite_delete";"/assiduite/delete";"ScoView";"POST";"{""assiduite_id"": 1}"
|
|
||||||
"assiduite_delete";"/assiduite/delete/batch";"ScoView";"POST";"{""batch"":[2,2,3]}"
|
|
||||||
"departements";"/departements";"ScoView";"GET";
|
|
||||||
"departements-ids";"/departements_ids";"ScoView";"GET";
|
|
||||||
"departement";"/departement/TAPI";"ScoView";"GET";
|
|
||||||
"departement";"/departement/id/1";"ScoView";"GET";
|
|
||||||
"departement-etudiants";"/departement/TAPI/etudiants";"ScoView";"GET";
|
|
||||||
"departement-etudiants";"/departement/id/1/etudiants";"ScoView";"GET";
|
|
||||||
"departement-formsemestres_ids";"/departement/TAPI/formsemestres_ids";"ScoView";"GET";
|
|
||||||
"departement-formsemestres_ids";"/departement/id/1/formsemestres_ids";"ScoView";"GET";
|
|
||||||
"departement-formsemestres-courants";"/departement/TAPI/formsemestres_courants";"ScoView";"GET";
|
|
||||||
"departement-formsemestres-courants";"/departement/id/1/formsemestres_courants";"ScoView";"GET";
|
|
||||||
"departement-create";"/departement/create";"ScoSuperAdmin";"POST";"{""acronym"": ""NEWONE"" , ""visible"": true}"
|
"departement-create";"/departement/create";"ScoSuperAdmin";"POST";"{""acronym"": ""NEWONE"" , ""visible"": true}"
|
||||||
"departement-edit";"/departement/NEWONE/edit";"ScoSuperAdmin";"POST";"{""visible"": false}"
|
|
||||||
"departement-delete";"/departement/NEWONE/delete";"ScoSuperAdmin";"POST";
|
"departement-delete";"/departement/NEWONE/delete";"ScoSuperAdmin";"POST";
|
||||||
"etudiants-courants";"/etudiants/courants?date_courante=2022-07-20";"ScoView";"GET";
|
"departement-edit";"/departement/NEWONE/edit";"ScoSuperAdmin";"POST";"{""visible"": false}"
|
||||||
"etudiants-courants";"/etudiants/courants/long?date_courante=2022-07-20";"ScoView";"GET";
|
"departement-etudiants";"/departement/id/1/etudiants";"ScoView";"GET";
|
||||||
|
"departement-etudiants";"/departement/TAPI/etudiants";"ScoView";"GET";
|
||||||
|
"departement-formsemestres_ids";"/departement/id/1/formsemestres_ids";"ScoView";"GET";
|
||||||
|
"departement-formsemestres_ids";"/departement/TAPI/formsemestres_ids";"ScoView";"GET";
|
||||||
|
"departement-formsemestres-courants";"/departement/id/1/formsemestres_courants";"ScoView";"GET";
|
||||||
|
"departement-formsemestres-courants";"/departement/TAPI/formsemestres_courants";"ScoView";"GET";
|
||||||
|
"departement-logo";"/departement/id/1/logo/D";"ScoSuperAdmin";"GET";
|
||||||
|
"departement-logo";"/departement/TAPI/logo/D";"ScoSuperAdmin";"GET";
|
||||||
|
"departement-logos";"/departement/id/1/logos";"ScoSuperAdmin";"GET";
|
||||||
|
"departement-logos";"/departement/TAPI/logos";"ScoSuperAdmin";"GET";
|
||||||
|
"departement";"/departement/id/1";"ScoView";"GET";
|
||||||
|
"departement";"/departement/TAPI";"ScoView";"GET";
|
||||||
|
"departements-ids";"/departements_ids";"ScoView";"GET";
|
||||||
|
"departements";"/departements";"ScoView";"GET";
|
||||||
|
"etudiant_formsemestres";"/etudiant/nip/11/formsemestres";"ScoView";"GET";
|
||||||
|
"etudiant-formsemestre-bulletin";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||||
|
#"etudiant-formsemestre-bulletin";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||||
|
#"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||||
|
#"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
||||||
|
"etudiant-formsemestre-groups";"/etudiant/etudid/11/formsemestre/1/groups";"ScoView";"GET";
|
||||||
|
"etudiant-formsemestres";"/etudiant/etudid/11/formsemestres";"ScoView";"GET";
|
||||||
|
"etudiant-formsemestres";"/etudiant/ine/INE11/formsemestres";"ScoView";"GET";
|
||||||
"etudiant";"/etudiant/etudid/11";"ScoView";"GET";
|
"etudiant";"/etudiant/etudid/11";"ScoView";"GET";
|
||||||
"etudiant";"/etudiant/nip/11";"ScoView";"GET";
|
|
||||||
"etudiant";"/etudiant/ine/INE11";"ScoView";"GET";
|
"etudiant";"/etudiant/ine/INE11";"ScoView";"GET";
|
||||||
|
"etudiant";"/etudiant/nip/11";"ScoView";"GET";
|
||||||
"etudiants-clef";"/etudiants/etudid/11";"ScoView";"GET";
|
"etudiants-clef";"/etudiants/etudid/11";"ScoView";"GET";
|
||||||
"etudiants-clef";"/etudiants/ine/INE11";"ScoView";"GET";
|
"etudiants-clef";"/etudiants/ine/INE11";"ScoView";"GET";
|
||||||
"etudiants-clef";"/etudiants/nip/11";"ScoView";"GET";
|
"etudiants-clef";"/etudiants/nip/11";"ScoView";"GET";
|
||||||
"etudiant-formsemestres";"/etudiant/etudid/11/formsemestres";"ScoView";"GET";
|
"etudiants-courants";"/etudiants/courants?date_courante=2022-07-20";"ScoView";"GET";
|
||||||
"etudiant-formsemestres";"/etudiant/ine/INE11/formsemestres";"ScoView";"GET";
|
"etudiants-courants";"/etudiants/courants/long?date_courante=2022-07-20";"ScoView";"GET";
|
||||||
"etudiant_formsemestres";"/etudiant/nip/11/formsemestres";"ScoView";"GET";
|
"evaluation";"/evaluation/1";"ScoView";"GET";
|
||||||
"etudiant-formsemestre-bulletin";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
|
"evaluation-notes";"/evaluation/1/notes";"ScoView";"GET";
|
||||||
"etudiant-formsemestre-bulletin";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
|
|
||||||
"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
|
|
||||||
"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
|
||||||
"etudiant-formsemestre-groups";"/etudiant/etudid/11/formsemestre/1/groups";"ScoView";"GET";
|
|
||||||
"formations";"/formations";"ScoView";"GET";
|
|
||||||
"formations_ids";"/formations_ids";"ScoView";"GET";
|
|
||||||
"formation";"/formation/1";"ScoView";"GET";
|
|
||||||
"formation-export";"/formation/1/export";"ScoView";"GET";
|
|
||||||
"formation-export";"/formation/1/export_with_ids";"ScoView";"GET";
|
"formation-export";"/formation/1/export_with_ids";"ScoView";"GET";
|
||||||
|
"formation-export";"/formation/1/export";"ScoView";"GET";
|
||||||
"formation-referentiel_competences";"/formation/1/referentiel_competences";"ScoView";"GET";
|
"formation-referentiel_competences";"/formation/1/referentiel_competences";"ScoView";"GET";
|
||||||
"moduleimpl";"/moduleimpl/1";"ScoView";"GET";
|
"formation";"/formation/1";"ScoView";"GET";
|
||||||
|
"formations_ids";"/formations_ids";"ScoView";"GET";
|
||||||
|
"formations";"/formations";"ScoView";"GET";
|
||||||
|
"formsemestre-bulletins";"/formsemestre/1/bulletins";"ScoView";"GET";
|
||||||
|
"formsemestre-decisions_jury";"/formsemestre/1/decisions_jury";"ScoView";"GET";
|
||||||
|
"formsemestre-etat_evals";"/formsemestre/1/etat_evals";"ScoView";"GET";
|
||||||
|
"formsemestre-etudiants-query";"/formsemestre/1/etudiants/query?etat=D";"ScoView";"GET";
|
||||||
|
"formsemestre-etudiants";"/formsemestre/1/etudiants";"ScoView";"GET";
|
||||||
|
"formsemestre-etudiants";"/formsemestre/1/etudiants/long";"ScoView";"GET";
|
||||||
|
"formsemestre-partition-create";"/formsemestre/1/partition/create";"ScoSuperAdmin";"POST";"{""partition_name"": ""PART""} "
|
||||||
|
"formsemestre-partitions-order";"/formsemestre/1/partitions/order";"ScoSuperAdmin";"POST";"[ 1 ]"
|
||||||
|
"formsemestre-partitions";"/formsemestre/1/partitions";"ScoView";"GET";
|
||||||
|
"formsemestre-programme";"/formsemestre/1/programme";"ScoView";"GET";
|
||||||
|
"formsemestre-resultats";"/formsemestre/1/resultats";"ScoView";"GET";
|
||||||
"formsemestre";"/formsemestre/1";"ScoView";"GET";
|
"formsemestre";"/formsemestre/1";"ScoView";"GET";
|
||||||
"formsemestres-query";"/formsemestres/query?annee_scolaire=2022&etape_apo=A2";"ScoView";"GET";
|
"formsemestres-query";"/formsemestres/query?annee_scolaire=2022&etape_apo=A2";"ScoView";"GET";
|
||||||
"formsemestres-query";"/formsemestres/query?nip=11";"ScoView";"GET";
|
"formsemestres-query";"/formsemestres/query?nip=11";"ScoView";"GET";
|
||||||
"formsemestre-bulletins";"/formsemestre/1/bulletins";"ScoView";"GET";
|
|
||||||
"formsemestre-programme";"/formsemestre/1/programme";"ScoView";"GET";
|
|
||||||
"formsemestre-etudiants";"/formsemestre/1/etudiants";"ScoView";"GET";
|
|
||||||
"formsemestre-etudiants";"/formsemestre/1/etudiants/long";"ScoView";"GET";
|
|
||||||
"formsemestre-etudiants-query";"/formsemestre/1/etudiants/query?etat=D";"ScoView";"GET";
|
|
||||||
"formsemestre-etat_evals";"/formsemestre/1/etat_evals";"ScoView";"GET";
|
|
||||||
"formsemestre-resultats";"/formsemestre/1/resultats";"ScoView";"GET";
|
|
||||||
"formsemestre-decisions_jury";"/formsemestre/1/decisions_jury";"ScoView";"GET";
|
|
||||||
"formsemestre-partitions";"/formsemestre/1/partitions";"ScoView";"GET";
|
|
||||||
"partition";"/partition/1";"ScoView";"GET";
|
|
||||||
"group-etudiants";"/group/1/etudiants";"ScoView";"GET";
|
|
||||||
"group-etudiants-query";"/group/1/etudiants/query?etat=D";"ScoView";"GET";
|
|
||||||
"moduleimpl-evaluations";"/moduleimpl/1/evaluations";"ScoView";"GET";
|
|
||||||
"evaluation-notes";"/evaluation/1/notes";"ScoView";"GET";
|
|
||||||
"user";"/user/1";"ScoView";"GET";
|
|
||||||
"users-query";"/users/query?starts_with=u_";"ScoView";"GET";
|
|
||||||
"permissions";"/permissions";"ScoView";"GET";
|
|
||||||
"roles";"/roles";"ScoView";"GET";
|
|
||||||
"role";"/role/Observateur";"ScoView";"GET";
|
|
||||||
"group-set_etudiant";"/group/1/set_etudiant/10";"ScoSuperAdmin";"POST";
|
|
||||||
"group-remove_etudiant";"/group/1/remove_etudiant/10";"ScoSuperAdmin";"POST";
|
|
||||||
"partition-group-create";"/partition/1/group/create";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP""}"
|
|
||||||
"group-edit";"/group/2/edit";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP2""}"
|
|
||||||
"group-delete";"/group/2/delete";"ScoSuperAdmin";"POST";
|
"group-delete";"/group/2/delete";"ScoSuperAdmin";"POST";
|
||||||
"formsemestre-partition-create";"/formsemestre/1/partition/create";"ScoSuperAdmin";"POST";"{""partition_name"": ""PART""} "
|
"group-edit";"/group/2/edit";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP2""}"
|
||||||
"formsemestre-partitions-order";"/formsemestre/1/partitions/order";"ScoSuperAdmin";"POST";"[ 1 ]"
|
"group-etudiants-query";"/group/1/etudiants/query?etat=D";"ScoView";"GET";
|
||||||
"partition-edit";"/partition/1/edit";"ScoSuperAdmin";"POST";"{""partition_name"":""P2BIS"", ""numero"":3,""bul_show_rank"":true,""show_in_lists"":false, ""groups_editable"":true}"
|
"group-etudiants";"/group/1/etudiants";"ScoView";"GET";
|
||||||
"partition-remove_etudiant";"/partition/2/remove_etudiant/10";"ScoSuperAdmin";"POST";
|
"group-remove_etudiant";"/group/1/remove_etudiant/10";"ScoSuperAdmin";"POST";
|
||||||
"partition-groups-order";"/partition/1/groups/order";"ScoSuperAdmin";"POST";"[ 1 ]"
|
"group-set_etudiant";"/group/1/set_etudiant/10";"ScoSuperAdmin";"POST";
|
||||||
|
"logo";"/logo/B";"ScoSuperAdmin";"GET";
|
||||||
|
"logos";"/logos";"ScoSuperAdmin";"GET";
|
||||||
|
"moduleimpl-evaluations";"/moduleimpl/1/evaluations";"ScoView";"GET";
|
||||||
|
"moduleimpl";"/moduleimpl/1";"ScoView";"GET";
|
||||||
"partition-delete";"/partition/2/delete";"ScoSuperAdmin";"POST";
|
"partition-delete";"/partition/2/delete";"ScoSuperAdmin";"POST";
|
||||||
|
"partition-edit";"/partition/1/edit";"ScoSuperAdmin";"POST";"{""partition_name"":""P2BIS"", ""numero"":3,""bul_show_rank"":true,""show_in_lists"":false, ""groups_editable"":true}"
|
||||||
|
"partition-group-create";"/partition/1/group/create";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP""}"
|
||||||
|
"partition-groups-order";"/partition/1/groups/order";"ScoSuperAdmin";"POST";"[ 1 ]"
|
||||||
|
"partition-remove_etudiant";"/partition/2/remove_etudiant/10";"ScoSuperAdmin";"POST";
|
||||||
|
"partition";"/partition/1";"ScoView";"GET";
|
||||||
|
"permissions";"/permissions";"ScoView";"GET";
|
||||||
|
"role-add_permission";"/role/customRole/add_permission/ScoUsersView";"ScoSuperAdmin";"POST";
|
||||||
|
"role-create";"/role/create/customRole";"ScoSuperAdmin";"POST";"{""permissions"": [""ScoView"", ""ScoUsersView""]}"
|
||||||
|
"role-delete";"/role/customRole/delete";"ScoSuperAdmin";"POST";
|
||||||
|
"role-edit";"/role/customRole/edit";"ScoSuperAdmin";"POST";"{ ""name"" : ""LaveurDeVitres"", ""permissions"" : [ ""ScoView"" ] }"
|
||||||
|
"role-remove_permission";"/role/customRole/remove_permission/ScoUsersView";"ScoSuperAdmin";"POST";
|
||||||
|
"role";"/role/Observateur";"ScoView";"GET";
|
||||||
|
"roles";"/roles";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short";"ScoView";"GET";
|
||||||
|
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
||||||
"user-create";"/user/create";"ScoSuperAdmin";"POST";"{""user_name"": ""alain"", ""dept"": null, ""nom"": ""alain"", ""prenom"": ""bruno"", ""active"": true }"
|
"user-create";"/user/create";"ScoSuperAdmin";"POST";"{""user_name"": ""alain"", ""dept"": null, ""nom"": ""alain"", ""prenom"": ""bruno"", ""active"": true }"
|
||||||
"user-edit";"/user/10/edit";"ScoSuperAdmin";"POST";"{ ""dept"": ""TAPI"", ""nom"": ""alain2"", ""prenom"": ""bruno2"", ""active"": false }"
|
"user-edit";"/user/10/edit";"ScoSuperAdmin";"POST";"{ ""dept"": ""TAPI"", ""nom"": ""alain2"", ""prenom"": ""bruno2"", ""active"": false }"
|
||||||
"user-password";"/user/3/password";"ScoSuperAdmin";"POST";"{ ""password"": ""rePlaCemeNT456averylongandcomplicated"" }"
|
"user-password";"/user/3/password";"ScoSuperAdmin";"POST";"{ ""password"": ""rePlaCemeNT456averylongandcomplicated"" }"
|
||||||
"user-password";"/user/3/password";"ScoSuperAdmin";"POST";"{ ""password"": ""too_simple"" }"
|
"user-password";"/user/3/password";"ScoSuperAdmin";"POST";"{ ""password"": ""too_simple"" }"
|
||||||
"user-role-add";"/user/10/role/Observateur/add";"ScoSuperAdmin";"POST";
|
"user-role-add";"/user/10/role/Observateur/add";"ScoSuperAdmin";"POST";
|
||||||
"user-role-remove";"/user/10/role/Observateur/remove";"ScoSuperAdmin";"POST";
|
"user-role-remove";"/user/10/role/Observateur/remove";"ScoSuperAdmin";"POST";
|
||||||
"role-create";"/role/create/customRole";"ScoSuperAdmin";"POST";"{""permissions"": [""ScoView"", ""ScoUsersView""]}"
|
"user";"/user/1";"ScoView";"GET";
|
||||||
"role-remove_permission";"/role/customRole/remove_permission/ScoUsersView";"ScoSuperAdmin";"POST";
|
"users-query";"/users/query?starts_with=u_";"ScoView";"GET";
|
||||||
"role-add_permission";"/role/customRole/add_permission/ScoUsersView";"ScoSuperAdmin";"POST";
|
|
||||||
"role-edit";"/role/customRole/edit";"ScoSuperAdmin";"POST";"{ ""name"" : ""LaveurDeVitres"", ""permissions"" : [ ""ScoView"" ] }"
|
|
||||||
"role-delete";"/role/customRole/delete";"ScoSuperAdmin";"POST";
|
|
||||||
"logos";"/logos";"ScoSuperAdmin";"GET";
|
|
||||||
"logo";"/logo/B";"ScoSuperAdmin";"GET";
|
|
||||||
"departement-logos";"/departement/TAPI/logos";"ScoSuperAdmin";"GET";
|
|
||||||
"departement-logos";"/departement/id/1/logos";"ScoSuperAdmin";"GET";
|
|
||||||
"departement-logo";"/departement/TAPI/logo/D";"ScoSuperAdmin";"GET";
|
|
||||||
"departement-logo";"/departement/id/1/logo/D";"ScoSuperAdmin";"GET";
|
|
||||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
|
|
||||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
|
|
||||||
|
Can't render this file because it contains an unexpected character in line 41 and column 2.
|
@ -7,7 +7,7 @@ A utiliser avec debug.py (côté serveur).
|
|||||||
La classe ScoFake offre un ensemble de raccourcis permettant d'écrire
|
La classe ScoFake offre un ensemble de raccourcis permettant d'écrire
|
||||||
facilement des tests ou de reproduire des bugs.
|
facilement des tests ou de reproduire des bugs.
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import random
|
import random
|
||||||
import sys
|
import sys
|
||||||
@ -16,7 +16,14 @@ import typing
|
|||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models import Departement, Formation, FormationModalite, Matiere
|
from app.models import (
|
||||||
|
Departement,
|
||||||
|
Evaluation,
|
||||||
|
Formation,
|
||||||
|
FormationModalite,
|
||||||
|
Matiere,
|
||||||
|
ModuleImpl,
|
||||||
|
)
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_edit_matiere
|
from app.scodoc import sco_edit_matiere
|
||||||
@ -297,9 +304,8 @@ class ScoFake(object):
|
|||||||
def create_evaluation(
|
def create_evaluation(
|
||||||
self,
|
self,
|
||||||
moduleimpl_id=None,
|
moduleimpl_id=None,
|
||||||
jour=None,
|
date_debut: datetime.datetime = None,
|
||||||
heure_debut="8h00",
|
date_fin: datetime.datetime = None,
|
||||||
heure_fin="9h00",
|
|
||||||
description=None,
|
description=None,
|
||||||
note_max=20,
|
note_max=20,
|
||||||
coefficient=None,
|
coefficient=None,
|
||||||
@ -307,14 +313,17 @@ class ScoFake(object):
|
|||||||
publish_incomplete=None,
|
publish_incomplete=None,
|
||||||
evaluation_type=None,
|
evaluation_type=None,
|
||||||
numero=None,
|
numero=None,
|
||||||
):
|
) -> dict:
|
||||||
args = locals()
|
args = locals()
|
||||||
del args["self"]
|
del args["self"]
|
||||||
oid = sco_evaluation_db.do_evaluation_create(**args)
|
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||||
oids = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": oid})
|
assert moduleimpl
|
||||||
if not oids:
|
evaluation: Evaluation = Evaluation.create(moduleimpl=moduleimpl, **args)
|
||||||
raise ScoValueError("evaluation not created !")
|
db.session.add(evaluation)
|
||||||
return oids[0]
|
db.session.commit()
|
||||||
|
eval_dict = evaluation.to_dict()
|
||||||
|
eval_dict["id"] = evaluation.id
|
||||||
|
return eval_dict
|
||||||
|
|
||||||
@logging_meth
|
@logging_meth
|
||||||
def create_note(
|
def create_note(
|
||||||
@ -406,7 +415,7 @@ class ScoFake(object):
|
|||||||
for e_idx in range(1, nb_evaluations_per_module + 1):
|
for e_idx in range(1, nb_evaluations_per_module + 1):
|
||||||
e = self.create_evaluation(
|
e = self.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
jour=date_debut,
|
date_debut=datetime.datetime.strptime(date_debut, "%d/%m/%Y"),
|
||||||
description="evaluation test %s" % e_idx,
|
description="evaluation test %s" % e_idx,
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
)
|
)
|
||||||
@ -427,7 +436,7 @@ class ScoFake(object):
|
|||||||
for e in eval_list:
|
for e in eval_list:
|
||||||
for idx, etud in enumerate(etuds):
|
for idx, etud in enumerate(etuds):
|
||||||
self.create_note(
|
self.create_note(
|
||||||
evaluation_id=e["id"],
|
evaluation_id=e["evaluation_id"],
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
note=notes[idx % len(notes)],
|
note=notes[idx % len(notes)],
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Quelques fonctions d'initialisation pour tests unitaires
|
Quelques fonctions d'initialisation pour tests unitaires
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
from app import db, models
|
from app import db, models
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ def build_modules_with_evaluations(
|
|||||||
for _ in range(nb_evals_per_modimpl):
|
for _ in range(nb_evals_per_modimpl):
|
||||||
e = G.create_evaluation(
|
e = G.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
jour="01/01/2021",
|
date_debut=datetime.datetime(2021, 1, 1),
|
||||||
description="evaluation 1",
|
description="evaluation 1",
|
||||||
coefficient=0,
|
coefficient=0,
|
||||||
)
|
)
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
# -*- mode: python -*-
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Comptage des absences
|
|
||||||
"""
|
|
||||||
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
|
|
||||||
|
|
||||||
from tests.unit import sco_fake_gen
|
|
||||||
|
|
||||||
from app.scodoc import sco_abs, sco_formsemestre
|
|
||||||
from app.scodoc import sco_abs_views
|
|
||||||
|
|
||||||
|
|
||||||
def test_abs_counts(test_client):
|
|
||||||
"""Comptage des absences"""
|
|
||||||
G = sco_fake_gen.ScoFake(verbose=False)
|
|
||||||
|
|
||||||
# --- Création d'étudiants
|
|
||||||
etud = G.create_etud(code_nip=None)
|
|
||||||
|
|
||||||
# --- Création d'une formation
|
|
||||||
formation_id = G.create_formation(acronyme="")
|
|
||||||
ue_id = G.create_ue(formation_id=formation_id, acronyme="TST1", titre="ue test")
|
|
||||||
matiere_id = G.create_matiere(ue_id=ue_id, titre="matière test")
|
|
||||||
module_id = G.create_module(
|
|
||||||
matiere_id=matiere_id,
|
|
||||||
code="TSM1",
|
|
||||||
coefficient=1.0,
|
|
||||||
titre="module test",
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Mise place d'un semestre
|
|
||||||
formsemestre_id = G.create_formsemestre(
|
|
||||||
formation_id=formation_id,
|
|
||||||
semestre_id=1,
|
|
||||||
date_debut="01/01/2021",
|
|
||||||
date_fin="30/06/2021",
|
|
||||||
)
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
_ = G.create_moduleimpl(
|
|
||||||
module_id=module_id,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Inscription des étudiants
|
|
||||||
G.inscrit_etudiant(formsemestre_id, etud)
|
|
||||||
|
|
||||||
# --- Saisie absences
|
|
||||||
etudid = etud["etudid"]
|
|
||||||
|
|
||||||
for debut, fin, demijournee in [
|
|
||||||
("01/01/2020", "31/01/2020", 2), # hors semestre
|
|
||||||
("15/01/2021", "15/01/2021", 1),
|
|
||||||
("18/01/2021", "18/01/2021", 0),
|
|
||||||
("19/01/2021", "19/01/2021", 2),
|
|
||||||
("22/01/2021", "22/01/2021", 1),
|
|
||||||
("30/06/2021", "30/06/2021", 2), # dernier jour
|
|
||||||
]:
|
|
||||||
sco_abs_views.doSignaleAbsence(
|
|
||||||
datedebut=debut,
|
|
||||||
datefin=fin,
|
|
||||||
demijournee=demijournee,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Justification de certaines absences
|
|
||||||
|
|
||||||
for debut, fin, demijournee in [
|
|
||||||
("15/01/2021", "15/01/2021", 1),
|
|
||||||
("18/01/2021", "18/01/2021", 0),
|
|
||||||
("19/01/2021", "19/01/2021", 2),
|
|
||||||
]:
|
|
||||||
sco_abs_views.doJustifAbsence(
|
|
||||||
datedebut=debut,
|
|
||||||
datefin=fin,
|
|
||||||
demijournee=demijournee,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Utilisation de get_abs_count() de sco_abs
|
|
||||||
|
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
|
||||||
|
|
||||||
# --- Utilisation de sco_abs.count_abs()
|
|
||||||
|
|
||||||
nb_abs2 = sco_abs.count_abs(etudid=etudid, debut="2021-01-01", fin="2021-06-30")
|
|
||||||
nb_absj2 = sco_abs.count_abs_just(
|
|
||||||
etudid=etudid, debut="2021-01-01", fin="2021-06-30"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert nbabs == nb_abs2 == 7
|
|
||||||
assert nbabsjust == nb_absj2 == 4
|
|
||||||
|
|
||||||
# --- Nombre de justificatifs:
|
|
||||||
justifs = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30")
|
|
||||||
assert len(justifs) == 4
|
|
||||||
|
|
||||||
# --- Suppression d'absence
|
|
||||||
_ = sco_abs_views.doAnnuleAbsence("19/01/2021", "19/01/2021", 2, etudid=etudid)
|
|
||||||
|
|
||||||
# --- Vérification
|
|
||||||
justifs_2 = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30")
|
|
||||||
assert len(justifs_2) == len(justifs)
|
|
||||||
new_nbabs, _ = sco_abs.get_abs_count(etudid, sem) # version cachée
|
|
||||||
new_nbabs2 = sco_abs.count_abs(etudid=etudid, debut="2021-01-01", fin="2021-06-30")
|
|
||||||
|
|
||||||
assert new_nbabs == new_nbabs2
|
|
||||||
assert new_nbabs == (nbabs - 2) # on a supprimé deux absences
|
|
||||||
|
|
||||||
# --- annulation absence sans supprimer le justificatif
|
|
||||||
sco_abs_views.AnnuleAbsencesDatesNoJust(etudid, ["2021-01-15"])
|
|
||||||
nbabs_3, nbjust_3 = sco_abs.get_abs_count(etudid, sem)
|
|
||||||
assert nbabs_3 == new_nbabs
|
|
||||||
justifs_3 = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30")
|
|
||||||
assert len(justifs_3) == len(justifs_2)
|
|
||||||
# XXX à continuer
|
|
@ -1,344 +0,0 @@
|
|||||||
# -*- mode: python -*-
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Créer et justifier des absences en utilisant le parametre demijournee
|
|
||||||
"""
|
|
||||||
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from tests.unit import sco_fake_gen
|
|
||||||
from app import db
|
|
||||||
from app.models import Module
|
|
||||||
from app.scodoc import sco_abs
|
|
||||||
from app.scodoc import sco_abs_views
|
|
||||||
from app.scodoc import sco_groups
|
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
from app.scodoc import sco_preferences
|
|
||||||
from app.views import absences
|
|
||||||
|
|
||||||
|
|
||||||
def test_abs_demijournee(test_client):
|
|
||||||
"""Opération élémentaires sur les absences, tests demi-journées
|
|
||||||
Travaille dans base TEST00 (defaut)
|
|
||||||
"""
|
|
||||||
G = sco_fake_gen.ScoFake(verbose=False)
|
|
||||||
|
|
||||||
# --- Création d'étudiants
|
|
||||||
etud = G.create_etud(code_nip=None)
|
|
||||||
|
|
||||||
# --- Création d'une formation
|
|
||||||
formation_id = G.create_formation(acronyme="")
|
|
||||||
ue_id = G.create_ue(formation_id=formation_id, acronyme="TST1", titre="ue test")
|
|
||||||
matiere_id = G.create_matiere(ue_id=ue_id, titre="matière test")
|
|
||||||
module_id = G.create_module(
|
|
||||||
matiere_id=matiere_id,
|
|
||||||
code="TSM1",
|
|
||||||
coefficient=1.0,
|
|
||||||
titre="module test",
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Mise place d'un semestre
|
|
||||||
formsemestre_id = G.create_formsemestre(
|
|
||||||
formation_id=formation_id,
|
|
||||||
semestre_id=1,
|
|
||||||
date_debut="01/01/2021",
|
|
||||||
date_fin="30/06/2021",
|
|
||||||
)
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
|
|
||||||
_ = G.create_moduleimpl(
|
|
||||||
module_id=module_id,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Inscription des étudiants
|
|
||||||
G.inscrit_etudiant(formsemestre_id, etud)
|
|
||||||
|
|
||||||
# --- Saisie absences
|
|
||||||
etudid = etud["etudid"]
|
|
||||||
|
|
||||||
_ = sco_abs_views.doSignaleAbsence(
|
|
||||||
"15/01/2021",
|
|
||||||
"15/01/2021",
|
|
||||||
demijournee=2,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = sco_abs_views.doSignaleAbsence(
|
|
||||||
"18/01/2021",
|
|
||||||
"18/01/2021",
|
|
||||||
demijournee=1,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = sco_abs_views.doSignaleAbsence(
|
|
||||||
"19/01/2021",
|
|
||||||
"19/01/2021",
|
|
||||||
demijournee=0,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Justification de certaines absences
|
|
||||||
|
|
||||||
_ = sco_abs_views.doJustifAbsence(
|
|
||||||
"18/01/2021",
|
|
||||||
"18/01/2021",
|
|
||||||
demijournee=1,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = sco_abs_views.doJustifAbsence(
|
|
||||||
"19/01/2021",
|
|
||||||
"19/01/2021",
|
|
||||||
demijournee=2,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
# NE JUSTIFIE QUE LE MATIN MALGRES LE PARAMETRE demijournee = 2
|
|
||||||
|
|
||||||
# --- Test
|
|
||||||
|
|
||||||
nbabs, nbabs_just = sco_abs.get_abs_count(etudid, sem)
|
|
||||||
assert (
|
|
||||||
nbabs == 4
|
|
||||||
) # l'étudiant a été absent le 15 journée compléte (2 abs : 1 matin, 1 apres midi) et le 18 (1 matin), et le 19 (1 apres midi).
|
|
||||||
assert nbabs_just == 2 # Justifie abs du matin + abs après midi
|
|
||||||
|
|
||||||
|
|
||||||
def test_abs_basic(test_client):
|
|
||||||
"""creation de 10 étudiants, formation, semestre, ue, module, absences le matin, l'apres midi, la journée compléte
|
|
||||||
et justification d'absences, supression d'absences, création d'une liste etat absences, creation d'un groupe afin
|
|
||||||
de tester la fonction EtatAbsencesGroupes
|
|
||||||
|
|
||||||
Fonctions de l'API utilisé :
|
|
||||||
- doSignaleAbsence
|
|
||||||
- doAnnuleAbsence
|
|
||||||
- doJustifAbsence
|
|
||||||
- get_partition_groups
|
|
||||||
- get_partitions_list
|
|
||||||
- sco_abs.get_abs_count(etudid, sem)
|
|
||||||
- ListeAbsEtud
|
|
||||||
- partition_create
|
|
||||||
- create_group
|
|
||||||
- set_group
|
|
||||||
- EtatAbsencesGr
|
|
||||||
- AddBilletAbsence
|
|
||||||
- billets_etud
|
|
||||||
"""
|
|
||||||
G = sco_fake_gen.ScoFake(verbose=False)
|
|
||||||
|
|
||||||
# --- Création d'étudiants
|
|
||||||
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
|
|
||||||
|
|
||||||
# --- Création d'une formation
|
|
||||||
formation_id = G.create_formation(acronyme="")
|
|
||||||
ue_id = G.create_ue(formation_id=formation_id, acronyme="TST1", titre="ue test")
|
|
||||||
matiere_id = G.create_matiere(ue_id=ue_id, titre="matière test")
|
|
||||||
module_id = G.create_module(
|
|
||||||
matiere_id=matiere_id,
|
|
||||||
code="TSM1",
|
|
||||||
coefficient=1.0,
|
|
||||||
titre="module test",
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Mise place d'un semestre
|
|
||||||
formsemestre_id = G.create_formsemestre(
|
|
||||||
formation_id=formation_id,
|
|
||||||
semestre_id=1,
|
|
||||||
date_debut="01/01/2021",
|
|
||||||
date_fin="30/06/2021",
|
|
||||||
)
|
|
||||||
|
|
||||||
moduleimpl_id = G.create_moduleimpl(
|
|
||||||
module_id=module_id,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Inscription des étudiants
|
|
||||||
for etud in etuds:
|
|
||||||
G.inscrit_etudiant(formsemestre_id, etud)
|
|
||||||
|
|
||||||
# --- Création d'une évaluation
|
|
||||||
e = G.create_evaluation(
|
|
||||||
moduleimpl_id=moduleimpl_id,
|
|
||||||
jour="22/01/2021",
|
|
||||||
description="evaluation test",
|
|
||||||
coefficient=1.0,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Saisie absences
|
|
||||||
etudid = etuds[0]["etudid"]
|
|
||||||
|
|
||||||
_ = sco_abs_views.doSignaleAbsence(
|
|
||||||
"15/01/2021",
|
|
||||||
"15/01/2021",
|
|
||||||
demijournee=1,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = sco_abs_views.doSignaleAbsence(
|
|
||||||
"18/01/2021",
|
|
||||||
"18/01/2021",
|
|
||||||
demijournee=0,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = sco_abs_views.doSignaleAbsence(
|
|
||||||
"19/01/2021",
|
|
||||||
"19/01/2021",
|
|
||||||
demijournee=2,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = sco_abs_views.doSignaleAbsence(
|
|
||||||
"22/01/2021",
|
|
||||||
"22/01/2021",
|
|
||||||
demijournee=1,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Justification de certaines absences
|
|
||||||
|
|
||||||
_ = sco_abs_views.doJustifAbsence(
|
|
||||||
"15/01/2021",
|
|
||||||
"15/01/2021",
|
|
||||||
demijournee=1,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = sco_abs_views.doJustifAbsence(
|
|
||||||
"18/01/2021",
|
|
||||||
"18/01/2021",
|
|
||||||
demijournee=0,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = sco_abs_views.doJustifAbsence(
|
|
||||||
"19/01/2021",
|
|
||||||
"19/01/2021",
|
|
||||||
demijournee=2,
|
|
||||||
etudid=etudid,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Test
|
|
||||||
|
|
||||||
b = sco_abs.is_work_saturday()
|
|
||||||
assert b == 0 # samedi ne sont pas compris
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
|
||||||
# l'étudiant a été absent le 15 (apres midi) , (16 et 17 we),
|
|
||||||
# 18 (matin) et 19 janvier (matin et apres midi), et 22 (matin)
|
|
||||||
assert nbabs == 5
|
|
||||||
# l'étudiant justifie ses abs du 15, 18 et 19
|
|
||||||
assert nbabsjust == 4
|
|
||||||
|
|
||||||
# --- Suppression d'une absence et d'une justification
|
|
||||||
|
|
||||||
_ = sco_abs_views.doAnnuleAbsence("19/01/2021", "19/01/2021", 2, etudid=etudid)
|
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
|
||||||
assert nbabs == 3
|
|
||||||
assert nbabsjust == 2
|
|
||||||
|
|
||||||
# --- suppression d'une justification pas encore disponible à l'aide de python.
|
|
||||||
|
|
||||||
# --- Création d'une liste d'abs
|
|
||||||
|
|
||||||
liste_abs = sco_abs_views.ListeAbsEtud(
|
|
||||||
etudid, format="json", absjust_only=1, sco_year="2020"
|
|
||||||
).get_data(as_text=True)
|
|
||||||
liste_abs2 = sco_abs_views.ListeAbsEtud(
|
|
||||||
etudid, format="json", sco_year="2020"
|
|
||||||
).get_data(as_text=True)
|
|
||||||
|
|
||||||
load_liste_abs = json.loads(liste_abs)
|
|
||||||
load_liste_abs2 = json.loads(liste_abs2)
|
|
||||||
|
|
||||||
assert len(load_liste_abs2) == 1
|
|
||||||
assert len(load_liste_abs) == 2
|
|
||||||
assert load_liste_abs2[0]["ampm"] == 1
|
|
||||||
assert load_liste_abs2[0]["datedmy"] == "22/01/2021"
|
|
||||||
mod = db.session.get(Module, module_id)
|
|
||||||
assert load_liste_abs2[0]["exams"] == mod.code
|
|
||||||
# absjust_only -> seulement les abs justifiés
|
|
||||||
|
|
||||||
# --- Création d'un groupe
|
|
||||||
|
|
||||||
_ = sco_groups.partition_create(
|
|
||||||
formsemestre_id=sem["formsemestre_id"],
|
|
||||||
partition_name="Eleve",
|
|
||||||
)
|
|
||||||
li1 = sco_groups.get_partitions_list(sem["formsemestre_id"])
|
|
||||||
_ = sco_groups.create_group(li1[0]["partition_id"], "Groupe 1")
|
|
||||||
|
|
||||||
# --- Affectation des élèves dans des groupes
|
|
||||||
|
|
||||||
li_grp1 = sco_groups.get_partition_groups(li1[0])
|
|
||||||
for etud in etuds:
|
|
||||||
sco_groups.set_group(etud["etudid"], li_grp1[0]["group_id"])
|
|
||||||
|
|
||||||
# --- Test de EtatAbsencesGroupes
|
|
||||||
|
|
||||||
grp1_abs = absences.EtatAbsencesGr(
|
|
||||||
group_ids=[li_grp1[0]["group_id"]],
|
|
||||||
debut="01/01/2021",
|
|
||||||
fin="30/06/2021",
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
# grp1_abs est une Response car on a appelé une vue (1er appel)
|
|
||||||
load_grp1_abs = json.loads(grp1_abs.get_data(as_text=True))
|
|
||||||
|
|
||||||
assert len(load_grp1_abs) == 10
|
|
||||||
|
|
||||||
tab_id = [] # tab des id present dans load_grp1_abs
|
|
||||||
for un_etud in load_grp1_abs:
|
|
||||||
tab_id.append(un_etud["etudid"])
|
|
||||||
|
|
||||||
for (
|
|
||||||
etud
|
|
||||||
) in (
|
|
||||||
etuds
|
|
||||||
): # verification si tous les etudiants sont present dans la liste du groupe d'absence
|
|
||||||
assert etud["etudid"] in tab_id
|
|
||||||
|
|
||||||
for un_etud in load_grp1_abs:
|
|
||||||
if un_etud["etudid"] == etudid:
|
|
||||||
assert un_etud["nbabs"] == 3
|
|
||||||
assert un_etud["nbjustifs_noabs"] == 2
|
|
||||||
assert un_etud["nbabsjust"] == 2
|
|
||||||
assert un_etud["nbabsnonjust"] == 1
|
|
||||||
assert un_etud["nomprenom"] == etuds[0]["nomprenom"]
|
|
||||||
|
|
||||||
# --- Création de billets
|
|
||||||
# Active la gestion de billets:
|
|
||||||
sco_preferences.get_base_preferences().set(None, "handle_billets_abs", 1)
|
|
||||||
|
|
||||||
b1 = absences.AddBilletAbsence(
|
|
||||||
begin="2021-01-22 00:00",
|
|
||||||
end="2021-01-22 23:59",
|
|
||||||
etudid=etudid,
|
|
||||||
description="abs du 22",
|
|
||||||
justified=False,
|
|
||||||
code_nip=etuds[0]["code_nip"],
|
|
||||||
code_ine=etuds[0]["code_ine"],
|
|
||||||
)
|
|
||||||
|
|
||||||
b2 = absences.AddBilletAbsence(
|
|
||||||
begin="2021-01-15 00:00",
|
|
||||||
end="2021-01-15 23:59",
|
|
||||||
etudid=etudid,
|
|
||||||
description="abs du 15",
|
|
||||||
code_nip=etuds[0]["code_nip"],
|
|
||||||
code_ine=etuds[0]["code_ine"],
|
|
||||||
)
|
|
||||||
|
|
||||||
li_bi = absences.billets_etud(etudid=etudid, format="json").get_data(as_text=True)
|
|
||||||
assert isinstance(li_bi, str)
|
|
||||||
load_li_bi = json.loads(li_bi)
|
|
||||||
|
|
||||||
assert len(load_li_bi) == 2
|
|
||||||
assert (
|
|
||||||
load_li_bi[1]["description"] == "abs du 15"
|
|
||||||
or load_li_bi[1]["description"] == "abs du 22"
|
|
||||||
)
|
|
@ -7,12 +7,15 @@ ses fonctions liées
|
|||||||
|
|
||||||
Ecrit par HARTMANN Matthias (en s'inspirant de tests.unit.test_abs_count.py par Fares Amer )
|
Ecrit par HARTMANN Matthias (en s'inspirant de tests.unit.test_abs_count.py par Fares Amer )
|
||||||
"""
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
import app.scodoc.sco_assiduites as scass
|
import app.scodoc.sco_assiduites as scass
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Assiduite, FormSemestre, Identite, Justificatif, ModuleImpl
|
from app.models import Assiduite, FormSemestre, Identite, Justificatif, ModuleImpl
|
||||||
from app.scodoc import sco_abs_views, sco_formsemestre
|
|
||||||
|
# from app.scodoc import sco_abs_views, sco_formsemestre TODO-ASSIDUITE
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from tests.unit import sco_fake_gen
|
from tests.unit import sco_fake_gen
|
||||||
from tools import downgrade_module, migrate_abs_to_assiduites
|
from tools import downgrade_module, migrate_abs_to_assiduites
|
||||||
@ -36,6 +39,7 @@ def test_bi_directional_enum(test_client):
|
|||||||
assert BiInt.inverse()[1] == BiInt.A and BiInt.inverse()[2] == BiInt.B
|
assert BiInt.inverse()[1] == BiInt.A and BiInt.inverse()[2] == BiInt.B
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip # XXX TODO-ASSIDUITE (issue #690)
|
||||||
def test_general(test_client):
|
def test_general(test_client):
|
||||||
"""tests général du modèle assiduite"""
|
"""tests général du modèle assiduite"""
|
||||||
|
|
||||||
@ -76,7 +80,9 @@ def test_general(test_client):
|
|||||||
date_fin="31/07/2024",
|
date_fin="31/07/2024",
|
||||||
)
|
)
|
||||||
|
|
||||||
formsemestre_1 = sco_formsemestre.get_formsemestre(formsemestre_id_1)
|
formsemestre_1 = sco_formsemestre.get_formsemestre(
|
||||||
|
formsemestre_id_1
|
||||||
|
) # Utiliser plutot FormSemestre de nos jours TODO-ASSIDUITE
|
||||||
formsemestre_2 = sco_formsemestre.get_formsemestre(formsemestre_id_2)
|
formsemestre_2 = sco_formsemestre.get_formsemestre(formsemestre_id_2)
|
||||||
formsemestre_3 = sco_formsemestre.get_formsemestre(formsemestre_id_3)
|
formsemestre_3 = sco_formsemestre.get_formsemestre(formsemestre_id_3)
|
||||||
|
|
||||||
@ -143,6 +149,7 @@ def test_general(test_client):
|
|||||||
editer_supprimer_justificatif(etuds[0])
|
editer_supprimer_justificatif(etuds[0])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip # XXX TODO-ASSIDUITE (issue #696)
|
||||||
def verif_migration_abs_assiduites():
|
def verif_migration_abs_assiduites():
|
||||||
"""Vérification que le script de migration fonctionne correctement"""
|
"""Vérification que le script de migration fonctionne correctement"""
|
||||||
downgrade_module(assiduites=True, justificatifs=True)
|
downgrade_module(assiduites=True, justificatifs=True)
|
||||||
@ -301,7 +308,7 @@ def verif_migration_abs_assiduites():
|
|||||||
False,
|
False,
|
||||||
), # 3 assi 22-23-24/02/2023 08h > 13h (3dj) JUSTI(ext)
|
), # 3 assi 22-23-24/02/2023 08h > 13h (3dj) JUSTI(ext)
|
||||||
]:
|
]:
|
||||||
sco_abs_views.doSignaleAbsence(
|
sco_abs_views.doSignaleAbsence( # TODO-ASSIDUITE
|
||||||
datedebut=debut,
|
datedebut=debut,
|
||||||
datefin=fin,
|
datefin=fin,
|
||||||
demijournee=demijournee,
|
demijournee=demijournee,
|
||||||
|
@ -22,7 +22,7 @@ from tests.unit import test_sco_basic
|
|||||||
DEPT = TestConfig.DEPT_TEST
|
DEPT = TestConfig.DEPT_TEST
|
||||||
|
|
||||||
|
|
||||||
def test_bulletin(test_client):
|
def test_bulletin_data_classic(test_client):
|
||||||
"""Vérifications sur les bulletins de notes"""
|
"""Vérifications sur les bulletins de notes"""
|
||||||
G = sco_fake_gen.ScoFake(verbose=False)
|
G = sco_fake_gen.ScoFake(verbose=False)
|
||||||
app.set_sco_dept(DEPT)
|
app.set_sco_dept(DEPT)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
Test modèles évaluations avec poids BUT
|
Test modèles évaluations avec poids BUT
|
||||||
et calcul moyennes modules
|
et calcul moyennes modules
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ def test_evaluation_poids(test_client):
|
|||||||
)
|
)
|
||||||
_e1 = G.create_evaluation(
|
_e1 = G.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
jour="01/01/2021",
|
date_debut=datetime.datetime(2021, 1, 1),
|
||||||
description="evaluation 1",
|
description="evaluation 1",
|
||||||
coefficient=0,
|
coefficient=0,
|
||||||
)
|
)
|
||||||
@ -218,7 +219,7 @@ def test_module_moy(test_client):
|
|||||||
# Crée une deuxième évaluation dans le même moduleimpl:
|
# Crée une deuxième évaluation dans le même moduleimpl:
|
||||||
evaluation2_id = G.create_evaluation(
|
evaluation2_id = G.create_evaluation(
|
||||||
moduleimpl_id=evaluation1.moduleimpl_id,
|
moduleimpl_id=evaluation1.moduleimpl_id,
|
||||||
jour="02/01/2021",
|
date_debut=datetime.datetime(2021, 1, 2),
|
||||||
description="evaluation 2",
|
description="evaluation 2",
|
||||||
coefficient=coef_e2,
|
coefficient=coef_e2,
|
||||||
)["evaluation_id"]
|
)["evaluation_id"]
|
||||||
|
@ -15,12 +15,11 @@ import app
|
|||||||
from app import db
|
from app import db
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import FormSemestre
|
from app.models import Evaluation, FormSemestre, ModuleImpl
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import notesdb as ndb
|
|
||||||
from config import TestConfig
|
from config import TestConfig
|
||||||
from tests.unit.test_sco_basic import run_sco_basic
|
from tests.unit.test_sco_basic import run_sco_basic
|
||||||
|
|
||||||
@ -58,23 +57,24 @@ def test_cache_evaluations(test_client):
|
|||||||
# prépare le département avec quelques semestres:
|
# prépare le département avec quelques semestres:
|
||||||
run_sco_basic()
|
run_sco_basic()
|
||||||
#
|
#
|
||||||
sems = sco_formsemestre.do_formsemestre_list()
|
formsemestres = FormSemestre.query
|
||||||
assert len(sems)
|
assert formsemestres.count()
|
||||||
sem_evals = []
|
evaluation = None
|
||||||
for sem in sems:
|
for formsemestre in formsemestres:
|
||||||
sem_evals = sco_evaluations.do_evaluation_list_in_sem(
|
evaluation: Evaluation = (
|
||||||
sem["formsemestre_id"], with_etat=False
|
Evaluation.query.join(ModuleImpl)
|
||||||
|
.filter_by(formsemestre_id=formsemestre.id)
|
||||||
|
.first()
|
||||||
)
|
)
|
||||||
if sem_evals:
|
if evaluation is not None:
|
||||||
break
|
break
|
||||||
if not sem_evals:
|
if evaluation is None:
|
||||||
raise Exception("no evaluations")
|
raise Exception("no evaluations")
|
||||||
#
|
#
|
||||||
evaluation_id = sem_evals[0]["evaluation_id"]
|
eval_notes = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
||||||
eval_notes = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
|
||||||
# should have been be cached, except if empty
|
# should have been be cached, except if empty
|
||||||
if eval_notes:
|
if eval_notes:
|
||||||
assert sco_cache.EvaluationCache.get(evaluation_id)
|
assert sco_cache.EvaluationCache.get(evaluation.id)
|
||||||
sco_cache.invalidate_formsemestre(sem["formsemestre_id"])
|
sco_cache.invalidate_formsemestre(evaluation.moduleimpl.formsemestre.id)
|
||||||
# should have been erased from cache:
|
# should have been erased from cache:
|
||||||
assert not sco_cache.EvaluationCache.get(evaluation_id)
|
assert not sco_cache.EvaluationCache.get(evaluation.id)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
Vérif moyennes de modules des bulletins
|
Vérif moyennes de modules des bulletins
|
||||||
et aussi moyennes modules et UE internes (via nt)
|
et aussi moyennes modules et UE internes (via nt)
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from flask import g
|
from flask import g
|
||||||
from config import TestConfig
|
from config import TestConfig
|
||||||
@ -93,13 +94,13 @@ def test_notes_modules(test_client):
|
|||||||
coef_2 = 2.0
|
coef_2 = 2.0
|
||||||
e1 = G.create_evaluation(
|
e1 = G.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
jour="01/01/2020",
|
date_debut=datetime.datetime(2020, 1, 1),
|
||||||
description="evaluation 1",
|
description="evaluation 1",
|
||||||
coefficient=coef_1,
|
coefficient=coef_1,
|
||||||
)
|
)
|
||||||
e2 = G.create_evaluation(
|
e2 = G.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
jour="01/01/2020",
|
date_debut=datetime.datetime(2020, 1, 1),
|
||||||
description="evaluation 2",
|
description="evaluation 2",
|
||||||
coefficient=coef_2,
|
coefficient=coef_2,
|
||||||
)
|
)
|
||||||
@ -107,16 +108,16 @@ def test_notes_modules(test_client):
|
|||||||
note_1 = 12.0
|
note_1 = 12.0
|
||||||
note_2 = 13.0
|
note_2 = 13.0
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e1["id"], etudid=etuds[0]["etudid"], note=note_1
|
evaluation_id=e1["evaluation_id"], etudid=etuds[0]["etudid"], note=note_1
|
||||||
)
|
)
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e2["id"], etudid=etuds[0]["etudid"], note=note_2
|
evaluation_id=e2["evaluation_id"], etudid=etuds[0]["etudid"], note=note_2
|
||||||
)
|
)
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=note_1 / 2
|
evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=note_1 / 2
|
||||||
)
|
)
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e2["id"], etudid=etuds[1]["etudid"], note=note_2 / 3
|
evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=note_2 / 3
|
||||||
)
|
)
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
sem["formsemestre_id"], etud["etudid"]
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
@ -138,16 +139,24 @@ def test_notes_modules(test_client):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Absence à une évaluation
|
# Absence à une évaluation
|
||||||
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs
|
_, _, _ = G.create_note(
|
||||||
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=note_2)
|
evaluation_id=e1["evaluation_id"], etudid=etudid, note=None
|
||||||
|
) # abs
|
||||||
|
_, _, _ = G.create_note(
|
||||||
|
evaluation_id=e2["evaluation_id"], etudid=etudid, note=note_2
|
||||||
|
)
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
sem["formsemestre_id"], etud["etudid"]
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
)
|
)
|
||||||
note_th = (coef_1 * 0.0 + coef_2 * note_2) / (coef_1 + coef_2)
|
note_th = (coef_1 * 0.0 + coef_2 * note_2) / (coef_1 + coef_2)
|
||||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th)
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th)
|
||||||
# Absences aux deux évaluations
|
# Absences aux deux évaluations
|
||||||
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs
|
_, _, _ = G.create_note(
|
||||||
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=None) # abs
|
evaluation_id=e1["evaluation_id"], etudid=etudid, note=None
|
||||||
|
) # abs
|
||||||
|
_, _, _ = G.create_note(
|
||||||
|
evaluation_id=e2["evaluation_id"], etudid=etudid, note=None
|
||||||
|
) # abs
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
sem["formsemestre_id"], etud["etudid"]
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
)
|
)
|
||||||
@ -162,9 +171,11 @@ def test_notes_modules(test_client):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Note excusée EXC <-> scu.NOTES_NEUTRALISE
|
# Note excusée EXC <-> scu.NOTES_NEUTRALISE
|
||||||
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1)
|
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1
|
||||||
|
)
|
||||||
|
_, _, _ = G.create_note(
|
||||||
|
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||||
) # EXC
|
) # EXC
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
sem["formsemestre_id"], etud["etudid"]
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
@ -179,9 +190,11 @@ def test_notes_modules(test_client):
|
|||||||
expected_moy_ue=note_1,
|
expected_moy_ue=note_1,
|
||||||
)
|
)
|
||||||
# Note en attente ATT <-> scu.NOTES_ATTENTE
|
# Note en attente ATT <-> scu.NOTES_ATTENTE
|
||||||
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1)
|
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_ATTENTE
|
evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1
|
||||||
|
)
|
||||||
|
_, _, _ = G.create_note(
|
||||||
|
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
|
||||||
) # ATT
|
) # ATT
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
sem["formsemestre_id"], etud["etudid"]
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
@ -197,10 +210,10 @@ def test_notes_modules(test_client):
|
|||||||
)
|
)
|
||||||
# Neutralisation (EXC) des 2 évals
|
# Neutralisation (EXC) des 2 évals
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||||
) # EXC
|
) # EXC
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||||
) # EXC
|
) # EXC
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
sem["formsemestre_id"], etud["etudid"]
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
@ -216,10 +229,10 @@ def test_notes_modules(test_client):
|
|||||||
)
|
)
|
||||||
# Attente (ATT) sur les 2 evals
|
# Attente (ATT) sur les 2 evals
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_ATTENTE
|
evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
|
||||||
) # ATT
|
) # ATT
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_ATTENTE
|
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
|
||||||
) # ATT
|
) # ATT
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
sem["formsemestre_id"], etud["etudid"]
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
@ -277,7 +290,7 @@ def test_notes_modules(test_client):
|
|||||||
{"etudid": etudid, "moduleimpl_id": moduleimpl_id},
|
{"etudid": etudid, "moduleimpl_id": moduleimpl_id},
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
)
|
)
|
||||||
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=12.5)
|
_, _, _ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=12.5)
|
||||||
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
mod_stats = nt.get_mod_stats(moduleimpl_id)
|
mod_stats = nt.get_mod_stats(moduleimpl_id)
|
||||||
@ -301,11 +314,13 @@ def test_notes_modules(test_client):
|
|||||||
# Note dans module 2:
|
# Note dans module 2:
|
||||||
e_m2 = G.create_evaluation(
|
e_m2 = G.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id2,
|
moduleimpl_id=moduleimpl_id2,
|
||||||
jour="01/01/2020",
|
date_debut=datetime.datetime(2020, 1, 1),
|
||||||
description="evaluation mod 2",
|
description="evaluation mod 2",
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
)
|
)
|
||||||
_, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etudid, note=19.5)
|
_, _, _ = G.create_note(
|
||||||
|
evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=19.5
|
||||||
|
)
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
@ -314,20 +329,22 @@ def test_notes_modules(test_client):
|
|||||||
# 2 modules, notes EXC dans le premier, note valide n dans le second
|
# 2 modules, notes EXC dans le premier, note valide n dans le second
|
||||||
# la moyenne de l'UE doit être n
|
# la moyenne de l'UE doit être n
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||||
) # EXC
|
) # EXC
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||||
) # EXC
|
) # EXC
|
||||||
_, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etudid, note=12.5)
|
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=11.0
|
evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=12.5
|
||||||
)
|
)
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e2["id"], etudid=etuds[1]["etudid"], note=11.0
|
evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
|
||||||
)
|
)
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e_m2["id"], etudid=etuds[1]["etudid"], note=11.0
|
evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
|
||||||
|
)
|
||||||
|
_, _, _ = G.create_note(
|
||||||
|
evaluation_id=e_m2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
|
||||||
)
|
)
|
||||||
|
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
@ -385,16 +402,20 @@ def test_notes_modules_att_dem(test_client):
|
|||||||
coef_1 = 1.0
|
coef_1 = 1.0
|
||||||
e1 = G.create_evaluation(
|
e1 = G.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
jour="01/01/2020",
|
date_debut=datetime.datetime(2020, 1, 1),
|
||||||
description="evaluation 1",
|
description="evaluation 1",
|
||||||
coefficient=coef_1,
|
coefficient=coef_1,
|
||||||
)
|
)
|
||||||
# Attente (ATT) sur les 2 evals
|
# Attente (ATT) sur les 2 evals
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e1["id"], etudid=etuds[0]["etudid"], note=scu.NOTES_ATTENTE
|
evaluation_id=e1["evaluation_id"],
|
||||||
|
etudid=etuds[0]["etudid"],
|
||||||
|
note=scu.NOTES_ATTENTE,
|
||||||
) # ATT
|
) # ATT
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=scu.NOTES_ATTENTE
|
evaluation_id=e1["evaluation_id"],
|
||||||
|
etudid=etuds[1]["etudid"],
|
||||||
|
note=scu.NOTES_ATTENTE,
|
||||||
) # ATT
|
) # ATT
|
||||||
# Démission du premier étudiant
|
# Démission du premier étudiant
|
||||||
sco_formsemestre_inscriptions.do_formsemestre_demission(
|
sco_formsemestre_inscriptions.do_formsemestre_demission(
|
||||||
@ -435,7 +456,7 @@ def test_notes_modules_att_dem(test_client):
|
|||||||
|
|
||||||
# Saisie note ABS pour le deuxième etud
|
# Saisie note ABS pour le deuxième etud
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=None
|
evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=None
|
||||||
)
|
)
|
||||||
nt = check_nt(
|
nt = check_nt(
|
||||||
etuds[1]["etudid"],
|
etuds[1]["etudid"],
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"""Test calculs rattrapages
|
"""Test calculs rattrapages
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app import db
|
from app import db
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.models import FormSemestre, ModuleImpl
|
from app.models import Evaluation, FormSemestre, ModuleImpl
|
||||||
from app.scodoc import (
|
from app.scodoc import (
|
||||||
sco_bulletins,
|
sco_bulletins,
|
||||||
sco_evaluation_db,
|
|
||||||
sco_formsemestre,
|
sco_formsemestre,
|
||||||
sco_saisie_notes,
|
sco_saisie_notes,
|
||||||
)
|
)
|
||||||
@ -58,14 +58,14 @@ def test_notes_rattrapage(test_client):
|
|||||||
# --- Creation évaluation
|
# --- Creation évaluation
|
||||||
e = G.create_evaluation(
|
e = G.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
jour="01/01/2020",
|
date_debut=datetime.datetime(2020, 1, 1),
|
||||||
description="evaluation test",
|
description="evaluation test",
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
)
|
)
|
||||||
# --- Création d'une évaluation "de rattrapage"
|
# --- Création d'une évaluation "de rattrapage"
|
||||||
e_rat = G.create_evaluation(
|
e_rat = G.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
jour="02/01/2020",
|
date_debut=datetime.datetime(2020, 1, 2),
|
||||||
description="evaluation rattrapage",
|
description="evaluation rattrapage",
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
evaluation_type=scu.EVALUATION_RATTRAPAGE,
|
evaluation_type=scu.EVALUATION_RATTRAPAGE,
|
||||||
@ -130,7 +130,9 @@ def test_notes_rattrapage(test_client):
|
|||||||
# Note moyenne: reviens à 10/20
|
# Note moyenne: reviens à 10/20
|
||||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
||||||
# Supprime l'évaluation de rattrapage:
|
# Supprime l'évaluation de rattrapage:
|
||||||
sco_evaluation_db.do_evaluation_delete(e_rat["id"])
|
evaluation = db.session.get(Evaluation, e_rat["id"])
|
||||||
|
assert evaluation
|
||||||
|
evaluation.delete()
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
sem["formsemestre_id"], etud["etudid"]
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
)
|
)
|
||||||
@ -139,7 +141,7 @@ def test_notes_rattrapage(test_client):
|
|||||||
# Création évaluation session 2:
|
# Création évaluation session 2:
|
||||||
e_session2 = G.create_evaluation(
|
e_session2 = G.create_evaluation(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
jour="02/01/2020",
|
date_debut=datetime.datetime(2020, 1, 2),
|
||||||
description="evaluation session 2",
|
description="evaluation session 2",
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
evaluation_type=scu.EVALUATION_SESSION2,
|
evaluation_type=scu.EVALUATION_SESSION2,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user