forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
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)
|
|
@ -14,6 +14,7 @@ from flask_login import current_user, login_required
|
|||||||
from app import db, log
|
from app import db, log
|
||||||
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.scodoc import sco_preferences
|
||||||
from app.api import api_bp as bp
|
from app.api import api_bp as bp
|
||||||
from app.api import api_web_bp, get_model_api_object, tools
|
from app.api import api_web_bp, get_model_api_object, tools
|
||||||
from app.decorators import permission_required, scodoc
|
from app.decorators import permission_required, scodoc
|
||||||
@ -25,6 +26,7 @@ from app.models import (
|
|||||||
Scolog,
|
Scolog,
|
||||||
Justificatif,
|
Justificatif,
|
||||||
)
|
)
|
||||||
|
from flask_sqlalchemy.query import Query
|
||||||
from app.models.assiduites import get_assiduites_justif
|
from app.models.assiduites import get_assiduites_justif
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
@ -256,7 +258,7 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False)
|
|||||||
404,
|
404,
|
||||||
message="étudiant inconnu",
|
message="étudiant inconnu",
|
||||||
)
|
)
|
||||||
assiduites_query = etud.assiduites
|
assiduites_query: Query = etud.assiduites
|
||||||
|
|
||||||
if with_query:
|
if with_query:
|
||||||
assiduites_query = _filter_manager(request, assiduites_query)
|
assiduites_query = _filter_manager(request, assiduites_query)
|
||||||
@ -372,7 +374,9 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
|||||||
if formsemestre is None:
|
if formsemestre is None:
|
||||||
return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
|
return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
|
||||||
|
|
||||||
assiduites_query = scass.filter_by_formsemestre(Assiduite.query,Assiduite, formsemestre)
|
assiduites_query = scass.filter_by_formsemestre(
|
||||||
|
Assiduite.query, Assiduite, formsemestre
|
||||||
|
)
|
||||||
|
|
||||||
if with_query:
|
if with_query:
|
||||||
assiduites_query = _filter_manager(request, assiduites_query)
|
assiduites_query = _filter_manager(request, assiduites_query)
|
||||||
@ -597,8 +601,8 @@ def _create_singular(
|
|||||||
|
|
||||||
desc: str = data.get("desc", None)
|
desc: str = data.get("desc", None)
|
||||||
|
|
||||||
external_data = data.get("external_data", False)
|
external_data = data.get("external_data", None)
|
||||||
if external_data is not False:
|
if external_data is not None:
|
||||||
if not isinstance(external_data, dict):
|
if not isinstance(external_data, dict):
|
||||||
errors.append("param 'external_data' : n'est pas un objet JSON")
|
errors.append("param 'external_data' : n'est pas un objet JSON")
|
||||||
|
|
||||||
@ -959,7 +963,7 @@ def _count_manager(requested) -> tuple[str, dict]:
|
|||||||
return (metric, filtered)
|
return (metric, filtered)
|
||||||
|
|
||||||
|
|
||||||
def _filter_manager(requested, assiduites_query: Assiduite):
|
def _filter_manager(requested, assiduites_query: Query) -> Query:
|
||||||
"""
|
"""
|
||||||
Retourne les assiduites entrées filtrées en fonction de la request
|
Retourne les assiduites entrées filtrées en fonction de la request
|
||||||
"""
|
"""
|
||||||
@ -977,7 +981,7 @@ def _filter_manager(requested, assiduites_query: Assiduite):
|
|||||||
fin = scu.is_iso_formated(fin, True)
|
fin = scu.is_iso_formated(fin, True)
|
||||||
|
|
||||||
if (deb, fin) != (None, None):
|
if (deb, fin) != (None, None):
|
||||||
assiduites_query: Assiduite = scass.filter_by_date(
|
assiduites_query: Query = scass.filter_by_date(
|
||||||
assiduites_query, Assiduite, deb, fin
|
assiduites_query, Assiduite, deb, fin
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1015,11 +1019,11 @@ def _filter_manager(requested, assiduites_query: Assiduite):
|
|||||||
falses: tuple[str] = ("f", "faux", "false")
|
falses: tuple[str] = ("f", "faux", "false")
|
||||||
|
|
||||||
if est_just.lower() in trues:
|
if est_just.lower() in trues:
|
||||||
assiduites_query: Assiduite = scass.filter_assiduites_by_est_just(
|
assiduites_query: Query = scass.filter_assiduites_by_est_just(
|
||||||
assiduites_query, True
|
assiduites_query, True
|
||||||
)
|
)
|
||||||
elif est_just.lower() in falses:
|
elif est_just.lower() in falses:
|
||||||
assiduites_query: Assiduite = scass.filter_assiduites_by_est_just(
|
assiduites_query: Query = scass.filter_assiduites_by_est_just(
|
||||||
assiduites_query, False
|
assiduites_query, False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1027,7 +1031,7 @@ def _filter_manager(requested, assiduites_query: Assiduite):
|
|||||||
|
|
||||||
user_id = requested.args.get("user_id", False)
|
user_id = requested.args.get("user_id", False)
|
||||||
if user_id is not False:
|
if user_id is not False:
|
||||||
assiduites_query: Assiduite = scass.filter_by_user_id(assiduites_query, user_id)
|
assiduites_query: Query = scass.filter_by_user_id(assiduites_query, user_id)
|
||||||
|
|
||||||
return assiduites_query
|
return assiduites_query
|
||||||
|
|
||||||
|
@ -281,7 +281,15 @@ def dept_formsemestres_courants(acronym: str):
|
|||||||
FormSemestre.date_debut <= test_date,
|
FormSemestre.date_debut <= test_date,
|
||||||
FormSemestre.date_fin >= test_date,
|
FormSemestre.date_fin >= test_date,
|
||||||
)
|
)
|
||||||
return [d.to_dict_api() for d in formsemestres]
|
return [
|
||||||
|
d.to_dict_api()
|
||||||
|
for d in formsemestres.order_by(
|
||||||
|
FormSemestre.date_debut.desc(),
|
||||||
|
FormSemestre.modalite,
|
||||||
|
FormSemestre.semestre_id,
|
||||||
|
FormSemestre.titre,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/id/<int:dept_id>/formsemestres_courants")
|
@bp.route("/departement/id/<int:dept_id>/formsemestres_courants")
|
||||||
|
@ -154,8 +154,6 @@ def get_photo_image(etudid: int = None, nip: str = None, ine: str = None):
|
|||||||
etudid : l'etudid de l'étudiant
|
etudid : l'etudid de l'étudiant
|
||||||
nip : le code nip de l'étudiant
|
nip : le code nip de l'étudiant
|
||||||
ine : le code ine de l'étudiant
|
ine : le code ine de l'étudiant
|
||||||
|
|
||||||
Attention : Ne peut être qu'utilisée en tant que route de département
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
etud = tools.get_etud(etudid, nip, ine)
|
etud = tools.get_etud(etudid, nip, ine)
|
||||||
@ -176,6 +174,44 @@ def get_photo_image(etudid: int = None, nip: str = None, ine: str = None):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/etudiant/etudid/<int:etudid>/photo", methods=["POST"])
|
||||||
|
@api_web_bp.route("/etudiant/etudid/<int:etudid>/photo", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeAdr)
|
||||||
|
@as_json
|
||||||
|
def set_photo_image(etudid: int = None):
|
||||||
|
"""Enregistre la photo de l'étudiant."""
|
||||||
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoEtudChangeAdr)
|
||||||
|
query = Identite.query.filter_by(id=etudid)
|
||||||
|
if not None in allowed_depts:
|
||||||
|
# restreint aux départements autorisés:
|
||||||
|
query = query.join(Departement).filter(
|
||||||
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||||
|
)
|
||||||
|
if g.scodoc_dept is not None:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
etud: Identite = query.first()
|
||||||
|
if etud is None:
|
||||||
|
return json_error(404, message="etudiant inexistant")
|
||||||
|
# Récupère l'image
|
||||||
|
if len(request.files) == 0:
|
||||||
|
return json_error(404, "Il n'y a pas de fichier joint")
|
||||||
|
|
||||||
|
file = list(request.files.values())[0]
|
||||||
|
if not file.filename:
|
||||||
|
return json_error(404, "Il n'y a pas de fichier joint")
|
||||||
|
data = file.stream.read()
|
||||||
|
|
||||||
|
status, err_msg = sco_photos.store_photo(etud, data, file.filename)
|
||||||
|
if status:
|
||||||
|
return {"etudid": etud.id, "message": "recorded photo"}
|
||||||
|
return json_error(
|
||||||
|
404,
|
||||||
|
message=f"Erreur: {err_msg}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudiants/etudid/<int:etudid>", methods=["GET"])
|
@bp.route("/etudiants/etudid/<int:etudid>", methods=["GET"])
|
||||||
@bp.route("/etudiants/nip/<string:nip>", methods=["GET"])
|
@bp.route("/etudiants/nip/<string:nip>", methods=["GET"])
|
||||||
@bp.route("/etudiants/ine/<string:ine>", methods=["GET"])
|
@bp.route("/etudiants/ine/<string:ine>", methods=["GET"])
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
sco_evaluation_db.do_evaluation_delete(evaluation_id)
|
||||||
|
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
|
||||||
|
@ -99,18 +99,20 @@ def formsemestre_infos(formsemestre_id: int):
|
|||||||
def formsemestres_query():
|
def formsemestres_query():
|
||||||
"""
|
"""
|
||||||
Retourne les formsemestres filtrés par
|
Retourne les formsemestres filtrés par
|
||||||
étape Apogée ou année scolaire ou département (acronyme ou id)
|
étape Apogée ou année scolaire ou département (acronyme ou id) ou état ou code étudiant
|
||||||
|
|
||||||
etape_apo : un code étape apogée
|
etape_apo : un code étape apogée
|
||||||
annee_scolaire : année de début de l'année scolaire
|
annee_scolaire : année de début de l'année scolaire
|
||||||
dept_acronym : acronyme du département (eg "RT")
|
dept_acronym : acronyme du département (eg "RT")
|
||||||
dept_id : id du département
|
dept_id : id du département
|
||||||
ine ou nip: code d'un étudiant: ramène alors tous les semestres auxquels il est inscrit.
|
ine ou nip: code d'un étudiant: ramène alors tous les semestres auxquels il est inscrit.
|
||||||
|
etat: 0 si verrouillé, 1 sinon
|
||||||
"""
|
"""
|
||||||
etape_apo = request.args.get("etape_apo")
|
etape_apo = request.args.get("etape_apo")
|
||||||
annee_scolaire = request.args.get("annee_scolaire")
|
annee_scolaire = request.args.get("annee_scolaire")
|
||||||
dept_acronym = request.args.get("dept_acronym")
|
dept_acronym = request.args.get("dept_acronym")
|
||||||
dept_id = request.args.get("dept_id")
|
dept_id = request.args.get("dept_id")
|
||||||
|
etat = request.args.get("etat")
|
||||||
nip = request.args.get("nip")
|
nip = request.args.get("nip")
|
||||||
ine = request.args.get("ine")
|
ine = request.args.get("ine")
|
||||||
formsemestres = FormSemestre.query
|
formsemestres = FormSemestre.query
|
||||||
@ -126,6 +128,12 @@ def formsemestres_query():
|
|||||||
formsemestres = formsemestres.filter(
|
formsemestres = formsemestres.filter(
|
||||||
FormSemestre.date_fin >= debut_annee, FormSemestre.date_debut <= fin_annee
|
FormSemestre.date_fin >= debut_annee, FormSemestre.date_debut <= fin_annee
|
||||||
)
|
)
|
||||||
|
if etat is not None:
|
||||||
|
try:
|
||||||
|
etat = bool(int(etat))
|
||||||
|
except ValueError:
|
||||||
|
return json_error(404, "invalid etat: integer expected")
|
||||||
|
formsemestres = formsemestres.filter_by(etat=etat)
|
||||||
if dept_acronym is not None:
|
if dept_acronym is not None:
|
||||||
formsemestres = formsemestres.join(Departement).filter_by(acronym=dept_acronym)
|
formsemestres = formsemestres.join(Departement).filter_by(acronym=dept_acronym)
|
||||||
if dept_id is not None:
|
if dept_id is not None:
|
||||||
@ -151,7 +159,15 @@ def formsemestres_query():
|
|||||||
formsemestres = formsemestres.join(FormSemestreInscription).join(Identite)
|
formsemestres = formsemestres.join(FormSemestreInscription).join(Identite)
|
||||||
formsemestres = formsemestres.filter_by(code_ine=ine)
|
formsemestres = formsemestres.filter_by(code_ine=ine)
|
||||||
|
|
||||||
return [formsemestre.to_dict_api() for formsemestre in formsemestres]
|
return [
|
||||||
|
formsemestre.to_dict_api()
|
||||||
|
for formsemestre in formsemestres.order_by(
|
||||||
|
FormSemestre.date_debut.desc(),
|
||||||
|
FormSemestre.modalite,
|
||||||
|
FormSemestre.semestre_id,
|
||||||
|
FormSemestre.titre,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
|
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
|
||||||
@ -196,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
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
|||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_utils import json_error
|
from app.scodoc.sco_utils import json_error
|
||||||
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
|
|
||||||
# Partie Modèle
|
# Partie Modèle
|
||||||
@ -261,7 +262,7 @@ def _create_singular(
|
|||||||
# TOUT EST OK
|
# TOUT EST OK
|
||||||
|
|
||||||
try:
|
try:
|
||||||
nouv_justificatif: Justificatif = Justificatif.create_justificatif(
|
nouv_justificatif: Query = Justificatif.create_justificatif(
|
||||||
date_debut=deb,
|
date_debut=deb,
|
||||||
date_fin=fin,
|
date_fin=fin,
|
||||||
etat=etat,
|
etat=etat,
|
||||||
@ -307,7 +308,7 @@ def justif_edit(justif_id: int):
|
|||||||
"date_fin"?: str
|
"date_fin"?: str
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
justificatif_unique: Justificatif = Justificatif.query.filter_by(
|
justificatif_unique: Query = Justificatif.query.filter_by(
|
||||||
id=justif_id
|
id=justif_id
|
||||||
).first_or_404()
|
).first_or_404()
|
||||||
|
|
||||||
@ -426,9 +427,7 @@ def justif_delete():
|
|||||||
|
|
||||||
|
|
||||||
def _delete_singular(justif_id: int, database):
|
def _delete_singular(justif_id: int, database):
|
||||||
justificatif_unique: Justificatif = Justificatif.query.filter_by(
|
justificatif_unique: Query = Justificatif.query.filter_by(id=justif_id).first()
|
||||||
id=justif_id
|
|
||||||
).first()
|
|
||||||
if justificatif_unique is None:
|
if justificatif_unique is None:
|
||||||
return (404, "Justificatif non existant")
|
return (404, "Justificatif non existant")
|
||||||
|
|
||||||
@ -470,7 +469,7 @@ def justif_import(justif_id: int = None):
|
|||||||
if file.filename == "":
|
if file.filename == "":
|
||||||
return json_error(404, "Il n'y a pas de fichier joint")
|
return json_error(404, "Il n'y a pas de fichier joint")
|
||||||
|
|
||||||
query = Justificatif.query.filter_by(id=justif_id)
|
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
@ -509,11 +508,11 @@ def justif_export(justif_id: int = None, filename: str = None):
|
|||||||
Retourne un fichier d'une archive d'un justificatif
|
Retourne un fichier d'une archive d'un justificatif
|
||||||
"""
|
"""
|
||||||
|
|
||||||
query = Justificatif.query.filter_by(id=justif_id)
|
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
justificatif_unique: Justificatif = query.first_or_404()
|
justificatif_unique: Justificaitf = query.first_or_404()
|
||||||
|
|
||||||
archive_name: str = justificatif_unique.fichier
|
archive_name: str = justificatif_unique.fichier
|
||||||
if archive_name is None:
|
if archive_name is None:
|
||||||
@ -551,7 +550,7 @@ def justif_remove(justif_id: int = None):
|
|||||||
|
|
||||||
data: dict = request.get_json(force=True)
|
data: dict = request.get_json(force=True)
|
||||||
|
|
||||||
query = Justificatif.query.filter_by(id=justif_id)
|
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
@ -604,7 +603,7 @@ def justif_list(justif_id: int = None):
|
|||||||
Liste les fichiers du justificatif
|
Liste les fichiers du justificatif
|
||||||
"""
|
"""
|
||||||
|
|
||||||
query = Justificatif.query.filter_by(id=justif_id)
|
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
@ -642,7 +641,7 @@ def justif_justifies(justif_id: int = None):
|
|||||||
Liste assiduite_id justifiées par le justificatif
|
Liste assiduite_id justifiées par le justificatif
|
||||||
"""
|
"""
|
||||||
|
|
||||||
query = Justificatif.query.filter_by(id=justif_id)
|
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
@ -676,13 +675,13 @@ def _filter_manager(requested, justificatifs_query):
|
|||||||
fin = scu.is_iso_formated(fin, True)
|
fin = scu.is_iso_formated(fin, True)
|
||||||
|
|
||||||
if (deb, fin) != (None, None):
|
if (deb, fin) != (None, None):
|
||||||
justificatifs_query: Justificatif = scass.filter_by_date(
|
justificatifs_query: Query = scass.filter_by_date(
|
||||||
justificatifs_query, Justificatif, deb, fin
|
justificatifs_query, Justificatif, deb, fin
|
||||||
)
|
)
|
||||||
|
|
||||||
user_id = requested.args.get("user_id", False)
|
user_id = requested.args.get("user_id", False)
|
||||||
if user_id is not False:
|
if user_id is not False:
|
||||||
justificatifs_query: Justificatif = scass.filter_by_user_id(
|
justificatifs_query: Query = scass.filter_by_user_id(
|
||||||
justificatifs_query, user_id
|
justificatifs_query, user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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)
|
@ -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`.
|
||||||
"""
|
"""
|
||||||
|
@ -14,6 +14,8 @@ from app.scodoc.sco_utils import (
|
|||||||
localize_datetime,
|
localize_datetime,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
|
|
||||||
class Assiduite(db.Model):
|
class Assiduite(db.Model):
|
||||||
"""
|
"""
|
||||||
@ -124,7 +126,7 @@ class Assiduite(db.Model):
|
|||||||
) -> object or int:
|
) -> object or int:
|
||||||
"""Créer une nouvelle assiduité pour l'étudiant"""
|
"""Créer une nouvelle assiduité pour l'étudiant"""
|
||||||
# Vérification de non duplication des périodes
|
# Vérification de non duplication des périodes
|
||||||
assiduites: list[Assiduite] = etud.assiduites
|
assiduites: Query = etud.assiduites
|
||||||
if is_period_conflicting(date_debut, date_fin, assiduites, Assiduite):
|
if is_period_conflicting(date_debut, date_fin, assiduites, Assiduite):
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)"
|
"Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)"
|
||||||
@ -307,7 +309,7 @@ class Justificatif(db.Model):
|
|||||||
def is_period_conflicting(
|
def is_period_conflicting(
|
||||||
date_debut: datetime,
|
date_debut: datetime,
|
||||||
date_fin: datetime,
|
date_fin: datetime,
|
||||||
collection: list[Assiduite or Justificatif],
|
collection: Query,
|
||||||
collection_cls: Assiduite or Justificatif,
|
collection_cls: Assiduite or Justificatif,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
|
@ -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,108 @@ 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 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 +171,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 +314,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 +359,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 +444,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)
|
||||||
|
@ -101,6 +101,23 @@ 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_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 ) }">
|
||||||
|
@ -6,7 +6,7 @@ from PIL import Image as PILImage
|
|||||||
|
|
||||||
def ImageScale(img_file, maxx, maxy):
|
def ImageScale(img_file, maxx, maxy):
|
||||||
im = PILImage.open(img_file)
|
im = PILImage.open(img_file)
|
||||||
im.thumbnail((maxx, maxy), PILImage.ANTIALIAS)
|
im.thumbnail((maxx, maxy), PILImage.LANCZOS)
|
||||||
out_file_str = io.BytesIO()
|
out_file_str = io.BytesIO()
|
||||||
im.save(out_file_str, im.format)
|
im.save(out_file_str, im.format)
|
||||||
out_file_str.seek(0)
|
out_file_str.seek(0)
|
||||||
@ -20,7 +20,7 @@ def ImageScaleH(img_file, W=None, H=90):
|
|||||||
if W is None:
|
if W is None:
|
||||||
# keep aspect
|
# keep aspect
|
||||||
W = int((im.size[0] * H) / float(im.size[1]))
|
W = int((im.size[0] * H) / float(im.size[1]))
|
||||||
im.thumbnail((W, H), PILImage.ANTIALIAS)
|
im.thumbnail((W, H), PILImage.LANCZOS)
|
||||||
out_file_str = io.BytesIO()
|
out_file_str = io.BytesIO()
|
||||||
im.save(out_file_str, im.format)
|
im.save(out_file_str, im.format)
|
||||||
out_file_str.seek(0)
|
out_file_str.seek(0)
|
||||||
|
@ -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
@ -34,6 +34,7 @@ from flask import flash, render_template, url_for
|
|||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from app.models import Identite
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_import_etuds
|
from app.scodoc import sco_import_etuds
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
@ -351,10 +352,8 @@ def etudarchive_import_files(
|
|||||||
):
|
):
|
||||||
"Importe des fichiers"
|
"Importe des fichiers"
|
||||||
|
|
||||||
def callback(etud, data, filename):
|
def callback(etud: Identite, data, filename):
|
||||||
return _store_etud_file_to_new_archive(
|
return _store_etud_file_to_new_archive(etud.id, data, filename, description)
|
||||||
etud["etudid"], data, filename, description
|
|
||||||
)
|
|
||||||
|
|
||||||
# Utilise la fontion developpée au depart pour les photos
|
# Utilise la fontion developpée au depart pour les photos
|
||||||
(
|
(
|
||||||
|
@ -13,6 +13,7 @@ from app.scodoc import sco_formsemestre_inscriptions
|
|||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
|
|
||||||
class CountCalculator:
|
class CountCalculator:
|
||||||
@ -167,7 +168,7 @@ class CountCalculator:
|
|||||||
|
|
||||||
self.hours += delta.total_seconds() / 3600
|
self.hours += delta.total_seconds() / 3600
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, object]:
|
def to_dict(self) -> dict[str, int or float]:
|
||||||
"""Retourne les métriques sous la forme d'un dictionnaire"""
|
"""Retourne les métriques sous la forme d'un dictionnaire"""
|
||||||
return {
|
return {
|
||||||
"compte": self.count,
|
"compte": self.count,
|
||||||
@ -178,8 +179,8 @@ class CountCalculator:
|
|||||||
|
|
||||||
|
|
||||||
def get_assiduites_stats(
|
def get_assiduites_stats(
|
||||||
assiduites: Assiduite, metric: str = "all", filtered: dict[str, object] = None
|
assiduites: Query, metric: str = "all", filtered: dict[str, object] = None
|
||||||
) -> Assiduite:
|
) -> dict[str, int or float]:
|
||||||
"""Compte les assiduités en fonction des filtres"""
|
"""Compte les assiduités en fonction des filtres"""
|
||||||
|
|
||||||
if filtered is not None:
|
if filtered is not None:
|
||||||
@ -218,7 +219,7 @@ def get_assiduites_stats(
|
|||||||
return output if output else count
|
return output if output else count
|
||||||
|
|
||||||
|
|
||||||
def filter_assiduites_by_etat(assiduites: Assiduite, etat: str) -> Assiduite:
|
def filter_assiduites_by_etat(assiduites: Assiduite, etat: str) -> Query:
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection d'assiduites en fonction de leur état
|
Filtrage d'une collection d'assiduites en fonction de leur état
|
||||||
"""
|
"""
|
||||||
@ -227,9 +228,7 @@ def filter_assiduites_by_etat(assiduites: Assiduite, etat: str) -> Assiduite:
|
|||||||
return assiduites.filter(Assiduite.etat.in_(etats))
|
return assiduites.filter(Assiduite.etat.in_(etats))
|
||||||
|
|
||||||
|
|
||||||
def filter_assiduites_by_est_just(
|
def filter_assiduites_by_est_just(assiduites: Assiduite, est_just: bool) -> Query:
|
||||||
assiduites: Assiduite, est_just: bool
|
|
||||||
) -> Justificatif:
|
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection d'assiduites en fonction de s'ils sont justifiés
|
Filtrage d'une collection d'assiduites en fonction de s'ils sont justifiés
|
||||||
"""
|
"""
|
||||||
@ -239,7 +238,7 @@ def filter_assiduites_by_est_just(
|
|||||||
def filter_by_user_id(
|
def filter_by_user_id(
|
||||||
collection: Assiduite or Justificatif,
|
collection: Assiduite or Justificatif,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
) -> Justificatif:
|
) -> Query:
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection en fonction de l'user_id
|
Filtrage d'une collection en fonction de l'user_id
|
||||||
"""
|
"""
|
||||||
@ -252,7 +251,7 @@ def filter_by_date(
|
|||||||
date_deb: datetime = None,
|
date_deb: datetime = None,
|
||||||
date_fin: datetime = None,
|
date_fin: datetime = None,
|
||||||
strict: bool = False,
|
strict: bool = False,
|
||||||
):
|
) -> Query:
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection d'assiduites en fonction d'une date
|
Filtrage d'une collection d'assiduites en fonction d'une date
|
||||||
"""
|
"""
|
||||||
@ -272,9 +271,7 @@ def filter_by_date(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def filter_justificatifs_by_etat(
|
def filter_justificatifs_by_etat(justificatifs: Justificatif, etat: str) -> Query:
|
||||||
justificatifs: Justificatif, etat: str
|
|
||||||
) -> Justificatif:
|
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection de justificatifs en fonction de leur état
|
Filtrage d'une collection de justificatifs en fonction de leur état
|
||||||
"""
|
"""
|
||||||
@ -283,9 +280,7 @@ def filter_justificatifs_by_etat(
|
|||||||
return justificatifs.filter(Justificatif.etat.in_(etats))
|
return justificatifs.filter(Justificatif.etat.in_(etats))
|
||||||
|
|
||||||
|
|
||||||
def filter_by_module_impl(
|
def filter_by_module_impl(assiduites: Assiduite, module_impl_id: int or None) -> Query:
|
||||||
assiduites: Assiduite, module_impl_id: int or None
|
|
||||||
) -> Assiduite:
|
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection d'assiduites en fonction de l'ID du module_impl
|
Filtrage d'une collection d'assiduites en fonction de l'ID du module_impl
|
||||||
"""
|
"""
|
||||||
@ -296,7 +291,7 @@ def filter_by_formsemestre(
|
|||||||
collection_query: Assiduite or Justificatif,
|
collection_query: Assiduite or Justificatif,
|
||||||
collection_class: Assiduite or Justificatif,
|
collection_class: Assiduite or Justificatif,
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
):
|
) -> Query:
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection en fonction d'un formsemestre
|
Filtrage d'une collection en fonction d'un formsemestre
|
||||||
"""
|
"""
|
||||||
@ -323,12 +318,13 @@ def filter_by_formsemestre(
|
|||||||
return collection_result.filter(collection_class.date_fin <= form_date_fin)
|
return collection_result.filter(collection_class.date_fin <= form_date_fin)
|
||||||
|
|
||||||
|
|
||||||
def justifies(justi: Justificatif, obj: bool = False) -> list[int]:
|
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:
|
||||||
@ -347,7 +343,7 @@ def justifies(justi: Justificatif, obj: bool = False) -> list[int]:
|
|||||||
|
|
||||||
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
|
||||||
) -> list[Assiduite]:
|
) -> Query:
|
||||||
"""Retourne toutes les assiduités justifiées sur une période"""
|
"""Retourne toutes les assiduités justifiées sur une période"""
|
||||||
|
|
||||||
if date_deb is None:
|
if date_deb is None:
|
||||||
@ -368,7 +364,7 @@ def get_all_justified(
|
|||||||
|
|
||||||
|
|
||||||
# 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.
|
||||||
@ -382,25 +378,43 @@ def get_assiduites_count(etudid, sem):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def formsemestre_get_assiduites_count(
|
||||||
|
etudid: int, formsemestre: FormSemestre
|
||||||
|
) -> tuple[int, int]:
|
||||||
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
|
tuple (nb abs non justifiées, nb abs justifiées)
|
||||||
|
Utilise un cache.
|
||||||
|
"""
|
||||||
|
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
|
||||||
|
return get_assiduites_count_in_interval(
|
||||||
|
etudid,
|
||||||
|
date_debut=formsemestre.date_debut,
|
||||||
|
date_fin=formsemestre.date_fin,
|
||||||
|
metrique=scu.translate_assiduites_metric(metrique),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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,
|
||||||
):
|
):
|
||||||
"""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)
|
||||||
|
On peut spécifier les dates comme datetime ou iso.
|
||||||
Utilise un cache.
|
Utilise 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
|
|
||||||
+ "_"
|
|
||||||
+ date_fin_iso
|
|
||||||
+ f"{metrique}_assiduites"
|
|
||||||
)
|
|
||||||
r = sco_cache.AbsSemEtudCache.get(key)
|
r = sco_cache.AbsSemEtudCache.get(key)
|
||||||
if not r:
|
if not r:
|
||||||
date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True)
|
date_debut: datetime = date_debut or datetime.fromisoformat(date_debut_iso)
|
||||||
date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True)
|
date_fin: datetime = date_fin or datetime.fromisoformat(date_fin_iso)
|
||||||
|
|
||||||
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
||||||
assiduites = assiduites.filter(Assiduite.etat == scu.EtatAssiduite.ABSENT)
|
assiduites = assiduites.filter(Assiduite.etat == scu.EtatAssiduite.ABSENT)
|
||||||
@ -432,7 +446,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 ["demi", "journee", "compte", "heure"]:
|
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)
|
||||||
|
|
||||||
@ -449,9 +463,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
|
||||||
|
|
||||||
|
@ -132,11 +132,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(
|
||||||
@ -301,9 +304,11 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
|
|||||||
authuser = self.authuser
|
authuser = self.authuser
|
||||||
H = []
|
H = []
|
||||||
# --- Absences
|
# --- Absences
|
||||||
|
# XXX TODO-ASSIDUITE
|
||||||
|
# au passage, utiliser url_for...
|
||||||
H.append(
|
H.append(
|
||||||
"""<p>
|
"""<p>
|
||||||
<a href="../Absences/CalAbs?etudid=%(etudid)s" class="bull_link">
|
XXX <a href="../Absences/CalAbs?etudid=%(etudid)s" 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>
|
||||||
|
@ -124,9 +124,12 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
nbabs = self.infos["nbabs"]
|
nbabs = self.infos["nbabs"]
|
||||||
story.append(Spacer(1, 2 * mm))
|
story.append(Spacer(1, 2 * mm))
|
||||||
if nbabs:
|
if nbabs:
|
||||||
|
# XXX TODO-ASSIDUITE
|
||||||
|
# et utiliser url_for...
|
||||||
H.append(
|
H.append(
|
||||||
"""<p class="bul_abs">
|
"""<p class="bul_abs">
|
||||||
<a href="../Absences/CalAbs?etudid=%(etudid)s" class="bull_link">
|
<a href="../Absences/CalAbs?etudid=%(etudid)s" class="bull_link">
|
||||||
|
XXX
|
||||||
<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)
|
||||||
|
@ -37,14 +37,13 @@ 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, ModuleImpl, ScolarNews
|
||||||
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, ScoValueError
|
||||||
|
|
||||||
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 +52,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 +62,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 +74,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 +84,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,119 +93,37 @@ 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):
|
def do_evaluation_delete(evaluation_id):
|
||||||
"delete evaluation"
|
"delete evaluation"
|
||||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||||
modimpl: ModuleImpl = evaluation.moduleimpl
|
modimpl: ModuleImpl = evaluation.moduleimpl
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=modimpl.id):
|
if not modimpl.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()}"
|
||||||
)
|
)
|
||||||
@ -226,7 +133,7 @@ def do_evaluation_delete(evaluation_id):
|
|||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Impossible de supprimer cette évaluation: il reste des notes"
|
"Impossible de supprimer cette évaluation: il reste des notes"
|
||||||
)
|
)
|
||||||
|
log(f"deleting evaluation {evaluation}")
|
||||||
db.session.delete(evaluation)
|
db.session.delete(evaluation)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@ -287,68 +194,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 +202,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 +225,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"'
|
||||||
|
@ -46,8 +46,8 @@ 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 +101,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 +132,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 +283,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 +324,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 +336,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 +344,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 +388,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 +425,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 +527,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 +567,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 +627,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,7 +645,7 @@ 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]
|
E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0]
|
||||||
moduleimpl_id = E["moduleimpl_id"]
|
moduleimpl_id = E["moduleimpl_id"]
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=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]
|
||||||
|
@ -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:
|
||||||
|
@ -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"]
|
||||||
@ -847,10 +851,14 @@ def _make_listes_sem(formsemestre: FormSemestre, with_absences=True):
|
|||||||
form_abs_tmpl += f"""
|
form_abs_tmpl += f"""
|
||||||
<a class="btn" href="{
|
<a class="btn" href="{
|
||||||
url_for("assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept)
|
url_for("assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept)
|
||||||
}?group_ids=%(group_id)s&jour={datetime.date.today().isoformat()}&formsemestre_id={formsemestre.id}"><button>Saisie Journalière</button></a>
|
}?group_ids=%(group_id)s&jour={
|
||||||
|
datetime.date.today().isoformat()
|
||||||
|
}&formsemestre_id={formsemestre.id}"><button>Saisie journalière</button></a>
|
||||||
<a class="btn" href="{
|
<a class="btn" href="{
|
||||||
url_for("assiduites.signal_assiduites_diff", scodoc_dept=g.scodoc_dept)
|
url_for("assiduites.signal_assiduites_diff", scodoc_dept=g.scodoc_dept)
|
||||||
}?group_ids=%(group_id)s&formsemestre_id={formsemestre.formsemestre_id}"><button>Saisie Différée</button></a>
|
}?group_ids=%(group_id)s&formsemestre_id={
|
||||||
|
formsemestre.formsemestre_id
|
||||||
|
}"><button>Saisie différée</button></a>
|
||||||
</td>
|
</td>
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
@ -880,13 +888,14 @@ 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(
|
# XXX TODO-ASSIDUITE
|
||||||
"absences.EtatAbsencesGr",
|
group["url_etat"] = "non disponible" # url_for(
|
||||||
group_ids=group["group_id"],
|
# "absences.EtatAbsencesGr",
|
||||||
debut=formsemestre.date_debut.strftime("%d/%m/%Y"),
|
# group_ids=group["group_id"],
|
||||||
fin=formsemestre.date_fin.strftime("%d/%m/%Y"),
|
# debut=formsemestre.date_debut.strftime("%d/%m/%Y"),
|
||||||
scodoc_dept=g.scodoc_dept,
|
# fin=formsemestre.date_fin.strftime("%d/%m/%Y"),
|
||||||
)
|
# 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
|
||||||
@ -829,7 +829,8 @@ 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>"""
|
# XXX TODO-ASSIDUITE
|
||||||
|
"""<li><a class="stdlink" href="Absences/EtatAbsencesGr?%s&debut=%s&fin=%s">XXX État des absences du groupe</a></li>"""
|
||||||
% (
|
% (
|
||||||
groups_infos.groups_query_args,
|
groups_infos.groups_query_args,
|
||||||
groups_infos.formsemestre["date_debut"],
|
groups_infos.formsemestre["date_debut"],
|
||||||
@ -890,12 +891,13 @@ 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()
|
||||||
|
|
||||||
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())
|
||||||
@ -906,12 +908,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 +947,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)
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -47,7 +47,7 @@ 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_evaluation_db
|
||||||
@ -61,7 +61,7 @@ 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_id, nbnotes=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]
|
E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0]
|
||||||
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
|
|
||||||
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"])
|
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"])
|
||||||
@ -203,11 +203,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 +332,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,17 +345,20 @@ 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()
|
datelundi = sco_cal.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="XXX"
|
||||||
href="{url_for("absences.SignaleAbsenceGrHebdo",
|
>Saisie Absences hebdo. (INDISPONIBLE)</a></span>
|
||||||
scodoc_dept=g.scodoc_dept,formsemestre_id=formsemestre_id,
|
|
||||||
moduleimpl_id=moduleimpl_id, datelundi=datelundi, group_ids=group_id)}">
|
|
||||||
Saisie Absences hebdo.</a></span>
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
# TODO-ASSIDUITE
|
||||||
|
# href="{
|
||||||
|
# url_for("absences.SignaleAbsenceGrHebdo",
|
||||||
|
# scodoc_dept=g.scodoc_dept,formsemestre_id=formsemestre_id,
|
||||||
|
# moduleimpl_id=moduleimpl_id, datelundi=datelundi, group_ids=group_id)
|
||||||
|
# }"
|
||||||
|
|
||||||
H.append("</td></tr></table>")
|
H.append("</td></tr></table>")
|
||||||
#
|
#
|
||||||
@ -435,8 +433,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 +569,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",
|
||||||
|
@ -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
|
||||||
|
@ -59,7 +59,7 @@ from flask.helpers import make_response, url_for
|
|||||||
|
|
||||||
from app import log
|
from app import log
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Identite
|
from app.models import Identite, Scolog
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_portal_apogee
|
from app.scodoc import sco_portal_apogee
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
@ -86,12 +86,12 @@ def unknown_image_url() -> str:
|
|||||||
return url_for("scolar.get_photo_image", scodoc_dept=g.scodoc_dept, etudid="")
|
return url_for("scolar.get_photo_image", scodoc_dept=g.scodoc_dept, etudid="")
|
||||||
|
|
||||||
|
|
||||||
def photo_portal_url(etud):
|
def photo_portal_url(code_nip: str):
|
||||||
"""Returns external URL to retreive photo on portal,
|
"""Returns external URL to retreive photo on portal,
|
||||||
or None if no portal configured"""
|
or None if no portal configured"""
|
||||||
photo_url = sco_portal_apogee.get_photo_url()
|
photo_url = sco_portal_apogee.get_photo_url()
|
||||||
if photo_url and etud["code_nip"]:
|
if photo_url and code_nip:
|
||||||
return photo_url + "?nip=" + etud["code_nip"]
|
return photo_url + "?nip=" + code_nip
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -120,13 +120,13 @@ def etud_photo_url(etud: dict, size="small", fast=False) -> str:
|
|||||||
path = photo_pathname(etud["photo_filename"], size=size)
|
path = photo_pathname(etud["photo_filename"], size=size)
|
||||||
if not path:
|
if not path:
|
||||||
# Portail ?
|
# Portail ?
|
||||||
ext_url = photo_portal_url(etud)
|
ext_url = photo_portal_url(etud["code_nip"])
|
||||||
if not ext_url:
|
if not ext_url:
|
||||||
# fallback: Photo "unknown"
|
# fallback: Photo "unknown"
|
||||||
photo_url = unknown_image_url()
|
photo_url = unknown_image_url()
|
||||||
else:
|
else:
|
||||||
# essaie de copier la photo du portail
|
# essaie de copier la photo du portail
|
||||||
new_path, _ = copy_portal_photo_to_fs(etud)
|
new_path, _ = copy_portal_photo_to_fs(etud["etudid"])
|
||||||
if not new_path:
|
if not new_path:
|
||||||
# copy failed, can we use external url ?
|
# copy failed, can we use external url ?
|
||||||
# nb: rarement utile, car le portail est rarement accessible sans authentification
|
# nb: rarement utile, car le portail est rarement accessible sans authentification
|
||||||
@ -185,8 +185,8 @@ def build_image_response(filename):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def etud_photo_is_local(etud: dict, size="small"):
|
def etud_photo_is_local(photo_filename: str, size="small"):
|
||||||
return photo_pathname(etud["photo_filename"], size=size)
|
return photo_pathname(photo_filename, size=size)
|
||||||
|
|
||||||
|
|
||||||
def etud_photo_html(etud: dict = None, etudid=None, title=None, size="small") -> str:
|
def etud_photo_html(etud: dict = None, etudid=None, title=None, size="small") -> str:
|
||||||
@ -205,7 +205,7 @@ def etud_photo_html(etud: dict = None, etudid=None, title=None, size="small") ->
|
|||||||
nom = etud.get("nomprenom", etud["nom_disp"])
|
nom = etud.get("nomprenom", etud["nom_disp"])
|
||||||
if title is None:
|
if title is None:
|
||||||
title = nom
|
title = nom
|
||||||
if not etud_photo_is_local(etud):
|
if not etud_photo_is_local(etud["photo_filename"]):
|
||||||
fallback = (
|
fallback = (
|
||||||
f"""onerror='this.onerror = null; this.src="{unknown_image_url()}"'"""
|
f"""onerror='this.onerror = null; this.src="{unknown_image_url()}"'"""
|
||||||
)
|
)
|
||||||
@ -254,7 +254,7 @@ def photo_pathname(photo_filename: str, size="orig"):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def store_photo(etud: dict, data, filename: str) -> tuple[bool, str]:
|
def store_photo(etud: Identite, data, filename: str) -> tuple[bool, str]:
|
||||||
"""Store image for this etud.
|
"""Store image for this etud.
|
||||||
If there is an existing photo, it is erased and replaced.
|
If there is an existing photo, it is erased and replaced.
|
||||||
data is a bytes string with image raw data.
|
data is a bytes string with image raw data.
|
||||||
@ -268,21 +268,17 @@ def store_photo(etud: dict, data, filename: str) -> tuple[bool, str]:
|
|||||||
if filesize < 10 or filesize > MAX_FILE_SIZE:
|
if filesize < 10 or filesize > MAX_FILE_SIZE:
|
||||||
return False, f"Fichier image '{filename}' de taille invalide ! ({filesize})"
|
return False, f"Fichier image '{filename}' de taille invalide ! ({filesize})"
|
||||||
try:
|
try:
|
||||||
saved_filename = save_image(etud["etudid"], data)
|
saved_filename = save_image(etud, data)
|
||||||
except (OSError, PIL.UnidentifiedImageError) as exc:
|
except (OSError, PIL.UnidentifiedImageError) as exc:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
msg="Fichier d'image '{filename}' invalide ou format non supporté"
|
msg="Fichier d'image '{filename}' invalide ou format non supporté"
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
# update database:
|
# update database:
|
||||||
etud["photo_filename"] = saved_filename
|
etud.photo_filename = saved_filename
|
||||||
etud["foto"] = None
|
db.session.add(etud)
|
||||||
|
Scolog.logdb(method="changePhoto", msg=saved_filename, etudid=etud.id)
|
||||||
cnx = ndb.GetDBConnexion()
|
db.session.commit()
|
||||||
sco_etud.identite_edit_nocheck(cnx, etud)
|
|
||||||
cnx.commit()
|
|
||||||
#
|
|
||||||
logdb(cnx, method="changePhoto", msg=saved_filename, etudid=etud["etudid"])
|
|
||||||
#
|
#
|
||||||
return True, "ok"
|
return True, "ok"
|
||||||
|
|
||||||
@ -313,7 +309,7 @@ def suppress_photo(etud: Identite) -> None:
|
|||||||
# Internal functions
|
# Internal functions
|
||||||
|
|
||||||
|
|
||||||
def save_image(etudid, data):
|
def save_image(etud: Identite, data: bytes):
|
||||||
"""data is a bytes string.
|
"""data is a bytes string.
|
||||||
Save image in JPEG in 2 sizes (original and h90).
|
Save image in JPEG in 2 sizes (original and h90).
|
||||||
Returns filename (relative to PHOTO_DIR), without extension
|
Returns filename (relative to PHOTO_DIR), without extension
|
||||||
@ -322,7 +318,7 @@ def save_image(etudid, data):
|
|||||||
data_file.write(data)
|
data_file.write(data)
|
||||||
data_file.seek(0)
|
data_file.seek(0)
|
||||||
img = PILImage.open(data_file)
|
img = PILImage.open(data_file)
|
||||||
filename = get_new_filename(etudid)
|
filename = get_new_filename(etud)
|
||||||
path = os.path.join(PHOTO_DIR, filename)
|
path = os.path.join(PHOTO_DIR, filename)
|
||||||
log("saving %dx%d jpeg to %s" % (img.size[0], img.size[1], path))
|
log("saving %dx%d jpeg to %s" % (img.size[0], img.size[1], path))
|
||||||
img = img.convert("RGB")
|
img = img.convert("RGB")
|
||||||
@ -338,16 +334,16 @@ def scale_height(img, W=None, H=REDUCED_HEIGHT):
|
|||||||
if W is None:
|
if W is None:
|
||||||
# keep aspect
|
# keep aspect
|
||||||
W = int((img.size[0] * H) / img.size[1])
|
W = int((img.size[0] * H) / img.size[1])
|
||||||
img.thumbnail((W, H), PILImage.ANTIALIAS)
|
img.thumbnail((W, H), PILImage.LANCZOS)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
def get_new_filename(etudid):
|
def get_new_filename(etud: Identite):
|
||||||
"""Constructs a random filename to store a new image.
|
"""Constructs a random filename to store a new image.
|
||||||
The path is constructed as: Fxx/etudid
|
The path is constructed as: Fxx/etudid
|
||||||
"""
|
"""
|
||||||
dept = g.scodoc_dept
|
dept = etud.departement.acronym
|
||||||
return find_new_dir() + dept + "_" + str(etudid)
|
return find_new_dir() + dept + "_" + str(etud.id)
|
||||||
|
|
||||||
|
|
||||||
def find_new_dir():
|
def find_new_dir():
|
||||||
@ -367,15 +363,14 @@ def find_new_dir():
|
|||||||
return d + "/"
|
return d + "/"
|
||||||
|
|
||||||
|
|
||||||
def copy_portal_photo_to_fs(etud: dict):
|
def copy_portal_photo_to_fs(etudid: int):
|
||||||
"""Copy the photo from portal (distant website) to local fs.
|
"""Copy the photo from portal (distant website) to local fs.
|
||||||
Returns rel. path or None if copy failed, with a diagnostic message
|
Returns rel. path or None if copy failed, with a diagnostic message
|
||||||
"""
|
"""
|
||||||
if "nomprenom" not in etud:
|
etud: Identite = Identite.query.get_or_404(etudid)
|
||||||
sco_etud.format_etud_ident(etud)
|
url = photo_portal_url(etud.code_nip)
|
||||||
url = photo_portal_url(etud)
|
|
||||||
if not url:
|
if not url:
|
||||||
return None, f"""{etud['nomprenom']}: pas de code NIP"""
|
return None, f"""{etud.nomprenom}: pas de code NIP"""
|
||||||
portal_timeout = sco_preferences.get_preference("portal_timeout")
|
portal_timeout = sco_preferences.get_preference("portal_timeout")
|
||||||
error_message = None
|
error_message = None
|
||||||
try:
|
try:
|
||||||
@ -394,11 +389,11 @@ def copy_portal_photo_to_fs(etud: dict):
|
|||||||
log(f"copy_portal_photo_to_fs: {error_message}")
|
log(f"copy_portal_photo_to_fs: {error_message}")
|
||||||
return (
|
return (
|
||||||
None,
|
None,
|
||||||
f"""{etud["nomprenom"]}: erreur chargement de {url}\n{error_message}""",
|
f"""{etud.nomprenom}: erreur chargement de {url}\n{error_message}""",
|
||||||
)
|
)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
log(f"copy_portal_photo_to_fs: download failed {r.status_code }")
|
log(f"copy_portal_photo_to_fs: download failed {r.status_code }")
|
||||||
return None, f"""{etud["nomprenom"]}: erreur chargement de {url}"""
|
return None, f"""{etud.nomprenom}: erreur chargement de {url}"""
|
||||||
|
|
||||||
data = r.content # image bytes
|
data = r.content # image bytes
|
||||||
try:
|
try:
|
||||||
@ -410,8 +405,8 @@ def copy_portal_photo_to_fs(etud: dict):
|
|||||||
if status:
|
if status:
|
||||||
log(f"copy_portal_photo_to_fs: copied {url}")
|
log(f"copy_portal_photo_to_fs: copied {url}")
|
||||||
return (
|
return (
|
||||||
photo_pathname(etud["photo_filename"]),
|
photo_pathname(etud.photo_filename),
|
||||||
f"{etud['nomprenom']}: photo chargée",
|
f"{etud.nomprenom}: photo chargée",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return None, f"{etud['nomprenom']}: <b>{error_message}</b>"
|
return None, f"{etud.nomprenom}: <b>{error_message}</b>"
|
||||||
|
@ -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)
|
||||||
|
@ -162,7 +162,7 @@ def _convert_pref_type(p, pref_spec):
|
|||||||
# special case for float values (where NULL means 0)
|
# special case for float values (where NULL means 0)
|
||||||
p["value"] = float(p["value"] or 0)
|
p["value"] = float(p["value"] or 0)
|
||||||
elif typ == "int":
|
elif typ == "int":
|
||||||
p["value"] = int(p["value"] or 0)
|
p["value"] = int(float(p["value"] or 0))
|
||||||
else:
|
else:
|
||||||
raise ValueError("invalid preference type")
|
raise ValueError("invalid preference type")
|
||||||
|
|
||||||
@ -629,6 +629,7 @@ class BasePreferences(object):
|
|||||||
"type": "float",
|
"type": "float",
|
||||||
"category": "assi",
|
"category": "assi",
|
||||||
"only_global": True,
|
"only_global": True,
|
||||||
|
"explanation": "Durée d'un créneau en heure. Utilisé dans les pages de saisie",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -658,10 +659,10 @@ class BasePreferences(object):
|
|||||||
{
|
{
|
||||||
"initvalue": "1/2 J.",
|
"initvalue": "1/2 J.",
|
||||||
"input_type": "menu",
|
"input_type": "menu",
|
||||||
"labels": ["1/2 J.", "J.", "H."],
|
"labels": scu.AssiduitesMetrics.LONG,
|
||||||
"allowed_values": ["1/2 J.", "J.", "H."],
|
"allowed_values": scu.AssiduitesMetrics.SHORT,
|
||||||
"title": "Métrique de l'assiduité",
|
"title": "Métrique de l'assiduité",
|
||||||
"explanation": "Unité utilisée dans la fiche étudiante, le bilan, et dans les calculs (J. = journée, H. = heure)",
|
"explanation": "Unité utilisée dans la fiche étudiante, les bilans et les calculs",
|
||||||
"category": "assi",
|
"category": "assi",
|
||||||
"only_global": True,
|
"only_global": True,
|
||||||
},
|
},
|
||||||
@ -669,10 +670,10 @@ class BasePreferences(object):
|
|||||||
(
|
(
|
||||||
"assi_seuil",
|
"assi_seuil",
|
||||||
{
|
{
|
||||||
"initvalue": 3.0,
|
"initvalue": 3,
|
||||||
"size": 10,
|
"size": 10,
|
||||||
"title": "Seuil d'alerte des absences",
|
"title": "Seuil d'alerte des absences",
|
||||||
"type": "float",
|
"type": "int",
|
||||||
"explanation": "Nombres d'absences limite avant alerte dans le bilan (utilisation de l'unité métrique ↑ )",
|
"explanation": "Nombres d'absences limite avant alerte dans le bilan (utilisation de l'unité métrique ↑ )",
|
||||||
"category": "assi",
|
"category": "assi",
|
||||||
"only_global": True,
|
"only_global": True,
|
||||||
|
@ -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 !")
|
||||||
|
@ -43,7 +43,8 @@ from PIL import Image as PILImage
|
|||||||
import flask
|
import flask
|
||||||
from flask import url_for, g, send_file, request
|
from flask import url_for, g, send_file, request
|
||||||
|
|
||||||
from app import log
|
from app import db, log
|
||||||
|
from app.models import Identite
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
@ -146,7 +147,7 @@ def trombino_html(groups_infos):
|
|||||||
'<span class="trombi_box"><span class="trombi-photo" id="trombi-%s">'
|
'<span class="trombi_box"><span class="trombi-photo" id="trombi-%s">'
|
||||||
% t["etudid"]
|
% t["etudid"]
|
||||||
)
|
)
|
||||||
if sco_photos.etud_photo_is_local(t, size="small"):
|
if sco_photos.etud_photo_is_local(t["photo_filename"], size="small"):
|
||||||
foto = sco_photos.etud_photo_html(t, title="")
|
foto = sco_photos.etud_photo_html(t, title="")
|
||||||
else: # la photo n'est pas immédiatement dispo
|
else: # la photo n'est pas immédiatement dispo
|
||||||
foto = f"""<span class="unloaded_img" id="{t["etudid"]
|
foto = f"""<span class="unloaded_img" id="{t["etudid"]
|
||||||
@ -194,7 +195,7 @@ def check_local_photos_availability(groups_infos, fmt=""):
|
|||||||
nb_missing = 0
|
nb_missing = 0
|
||||||
for t in groups_infos.members:
|
for t in groups_infos.members:
|
||||||
_ = sco_photos.etud_photo_url(t) # -> copy distant files if needed
|
_ = sco_photos.etud_photo_url(t) # -> copy distant files if needed
|
||||||
if not sco_photos.etud_photo_is_local(t):
|
if not sco_photos.etud_photo_is_local(t["photo_filename"]):
|
||||||
nb_missing += 1
|
nb_missing += 1
|
||||||
if nb_missing > 0:
|
if nb_missing > 0:
|
||||||
parameters = {"group_ids": groups_infos.group_ids, "format": fmt}
|
parameters = {"group_ids": groups_infos.group_ids, "format": fmt}
|
||||||
@ -278,7 +279,7 @@ def trombino_copy_photos(group_ids=[], dialog_confirmed=False):
|
|||||||
msg = []
|
msg = []
|
||||||
nok = 0
|
nok = 0
|
||||||
for etud in groups_infos.members:
|
for etud in groups_infos.members:
|
||||||
path, diag = sco_photos.copy_portal_photo_to_fs(etud)
|
path, diag = sco_photos.copy_portal_photo_to_fs(etud["etudid"])
|
||||||
msg.append(diag)
|
msg.append(diag)
|
||||||
if path:
|
if path:
|
||||||
nok += 1
|
nok += 1
|
||||||
@ -539,7 +540,7 @@ def photos_import_files_form(group_ids=()):
|
|||||||
return flask.redirect(back_url)
|
return flask.redirect(back_url)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
def callback(etud, data, filename):
|
def callback(etud: Identite, data, filename):
|
||||||
return sco_photos.store_photo(etud, data, filename)
|
return sco_photos.store_photo(etud, data, filename)
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -640,14 +641,12 @@ def zip_excel_import_files(
|
|||||||
if normname in filename_to_etudid:
|
if normname in filename_to_etudid:
|
||||||
etudid = filename_to_etudid[normname]
|
etudid = filename_to_etudid[normname]
|
||||||
# ok, store photo
|
# ok, store photo
|
||||||
try:
|
etud: Identite = db.session.get(Identite, etudid)
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
if not etud:
|
||||||
del filename_to_etudid[normname]
|
|
||||||
except Exception as exc:
|
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"ID étudiant invalide: {etudid}", dest_url=back_url
|
f"ID étudiant invalide: {etudid}", dest_url=back_url
|
||||||
) from exc
|
)
|
||||||
|
del filename_to_etudid[normname]
|
||||||
status, err_msg = callback(
|
status, err_msg = callback(
|
||||||
etud,
|
etud,
|
||||||
data,
|
data,
|
||||||
|
@ -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
|
||||||
@ -163,6 +168,11 @@ class BiDirectionalEnum(Enum):
|
|||||||
"""Vérifie sur un attribut existe dans l'enum"""
|
"""Vérifie sur un attribut existe dans l'enum"""
|
||||||
return attr.upper() in cls._member_names_
|
return attr.upper() in cls._member_names_
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all(cls, keys=True):
|
||||||
|
"""Retourne toutes les clés de l'enum"""
|
||||||
|
return cls._member_names_ if keys else list(cls._value2member_map_.keys())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, attr: str, default: any = None):
|
def get(cls, attr: str, default: any = None):
|
||||||
"""Récupère une valeur à partir de son attribut"""
|
"""Récupère une valeur à partir de son attribut"""
|
||||||
@ -251,15 +261,54 @@ def is_period_overlapping(
|
|||||||
return p_deb < i_fin and p_fin > i_deb
|
return p_deb < i_fin and p_fin > i_deb
|
||||||
|
|
||||||
|
|
||||||
def translate_assiduites_metric(hr_metric) -> str:
|
class AssiduitesMetrics:
|
||||||
if hr_metric == "1/2 J.":
|
"""Labels associés au métrique de l'assiduité"""
|
||||||
return "demi"
|
|
||||||
if hr_metric == "J.":
|
SHORT: list[str] = ["1/2 J.", "J.", "H."]
|
||||||
return "journee"
|
LONG: list[str] = ["Demi-journée", "Journée", "Heure"]
|
||||||
if hr_metric == "N.":
|
TAG: list[str] = ["demi", "journee", "heure"]
|
||||||
return "compte"
|
|
||||||
if hr_metric == "H.":
|
|
||||||
return "heure"
|
def translate_assiduites_metric(metric, inverse=True, short=True) -> str:
|
||||||
|
"""
|
||||||
|
translate_assiduites_metric
|
||||||
|
|
||||||
|
SHORT[true] : "J." "H." "N." "1/2 J."
|
||||||
|
SHORT[false] : "Journée" "Heure" "Nombre" "Demi-Journée"
|
||||||
|
|
||||||
|
inverse[false] : "demi" -> "1/2 J."
|
||||||
|
inverse[true] : "1/2 J." -> "demi"
|
||||||
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
metric (str): la métrique à traduire
|
||||||
|
inverse (bool, optional). Defaults to True.
|
||||||
|
short (bool, optional). Defaults to True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: la métrique traduite
|
||||||
|
"""
|
||||||
|
index: int = None
|
||||||
|
if not inverse:
|
||||||
|
try:
|
||||||
|
index = AssiduitesMetrics.TAG.index(metric)
|
||||||
|
return (
|
||||||
|
AssiduitesMetrics.SHORT[index]
|
||||||
|
if short
|
||||||
|
else AssiduitesMetrics.LONG[index]
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
index = (
|
||||||
|
AssiduitesMetrics.SHORT.index(metric)
|
||||||
|
if short
|
||||||
|
else AssiduitesMetrics.LONG.index(metric)
|
||||||
|
)
|
||||||
|
return AssiduitesMetrics.TAG[index]
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Types de modules
|
# Types de modules
|
||||||
|
@ -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;
|
||||||
|
@ -1792,6 +1792,10 @@ td.formsemestre_status_inscrits {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.formsemestre_status button {
|
||||||
|
margin-left: 12px;;
|
||||||
|
}
|
||||||
|
|
||||||
td.rcp_titre_sem a.jury_link {
|
td.rcp_titre_sem a.jury_link {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
color: red;
|
color: red;
|
||||||
@ -2468,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;
|
|
||||||
}
|
|
@ -885,7 +885,7 @@ function createAssiduite(etat, etudid) {
|
|||||||
(data, status) => {
|
(data, status) => {
|
||||||
//success
|
//success
|
||||||
if (data.success.length > 0) {
|
if (data.success.length > 0) {
|
||||||
let obj = data.success["0"].assiduite_id;
|
let obj = data.success["0"].message.assiduite_id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(data, status) => {
|
(data, status) => {
|
||||||
@ -910,7 +910,7 @@ function deleteAssiduite(assiduite_id) {
|
|||||||
(data, status) => {
|
(data, status) => {
|
||||||
//success
|
//success
|
||||||
if (data.success.length > 0) {
|
if (data.success.length > 0) {
|
||||||
let obj = data.success["0"].assiduite_id;
|
let obj = data.success["0"].message.assiduite_id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(data, status) => {
|
(data, status) => {
|
||||||
@ -1411,7 +1411,10 @@ function getModuleImplId() {
|
|||||||
function setModuleImplId(assiduite, module = null) {
|
function setModuleImplId(assiduite, module = null) {
|
||||||
const moduleimpl = module == null ? getModuleImplId() : module;
|
const moduleimpl = module == null ? getModuleImplId() : module;
|
||||||
if (moduleimpl === "autre") {
|
if (moduleimpl === "autre") {
|
||||||
if ("external_data" in assiduite && assiduite.external_data != undefined) {
|
if (
|
||||||
|
"external_data" in assiduite &&
|
||||||
|
assiduite.external_data instanceof Object
|
||||||
|
) {
|
||||||
if ("module" in assiduite.external_data) {
|
if ("module" in assiduite.external_data) {
|
||||||
assiduite.external_data.module = "Autre";
|
assiduite.external_data.module = "Autre";
|
||||||
} else {
|
} else {
|
||||||
@ -1423,7 +1426,10 @@ function setModuleImplId(assiduite, module = null) {
|
|||||||
assiduite.moduleimpl_id = null;
|
assiduite.moduleimpl_id = null;
|
||||||
} else {
|
} else {
|
||||||
assiduite["moduleimpl_id"] = moduleimpl;
|
assiduite["moduleimpl_id"] = moduleimpl;
|
||||||
if ("external_data" in assiduite && assiduite.external_data != undefined) {
|
if (
|
||||||
|
"external_data" in assiduite &&
|
||||||
|
assiduite.external_data instanceof Object
|
||||||
|
) {
|
||||||
if ("module" in assiduite.external_data) {
|
if ("module" in assiduite.external_data) {
|
||||||
delete assiduite.external_data.module;
|
delete assiduite.external_data.module;
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -125,11 +125,9 @@ class RowAssi(tb.Row):
|
|||||||
"absent": ["Absences", 0.0, 0.0],
|
"absent": ["Absences", 0.0, 0.0],
|
||||||
}
|
}
|
||||||
|
|
||||||
assi_metric = {
|
assi_metric = scu.translate_assiduites_metric(
|
||||||
"H.": "heure",
|
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
|
||||||
"J.": "journee",
|
)
|
||||||
"1/2 J.": "demi",
|
|
||||||
}.get(sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id))
|
|
||||||
|
|
||||||
for etat, valeur in retour.items():
|
for etat, valeur in retour.items():
|
||||||
compte_etat = scass.get_assiduites_stats(
|
compte_etat = scass.get_assiduites_stats(
|
||||||
|
@ -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
|
||||||
|
@ -164,7 +164,7 @@
|
|||||||
dateType: 'json',
|
dateType: 'json',
|
||||||
contentType: false,
|
contentType: false,
|
||||||
processData: false,
|
processData: false,
|
||||||
success: () => { },
|
success: () => { console.log("done") },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -192,8 +192,8 @@
|
|||||||
errorAlert();
|
errorAlert();
|
||||||
}
|
}
|
||||||
if (Object.keys(data.success).length > 0) {
|
if (Object.keys(data.success).length > 0) {
|
||||||
couverture = data.success[0].couverture
|
couverture = data.success[0].message.couverture
|
||||||
justif_id = data.success[0].justif_id;
|
justif_id = data.success[0].message.justif_id;
|
||||||
importFiles(justif_id);
|
importFiles(justif_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -336,19 +336,21 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
const defAnnee = {{ annee }}
|
const defAnnee = {{ annee }}
|
||||||
|
let annees = {{ annees | safe }}
|
||||||
|
annees = annees.filter((x, i) => annees.indexOf(x) === i)
|
||||||
const etudid = {{ sco.etud.id }};
|
const etudid = {{ sco.etud.id }};
|
||||||
const nonwork = [{{ nonworkdays | safe }}];
|
const nonwork = [{{ nonworkdays | safe }}];
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
const select = document.querySelector('#annee');
|
const select = document.querySelector('#annee');
|
||||||
for (let i = defAnnee + 1; i > defAnnee - 6; i--) {
|
annees.forEach((a) => {
|
||||||
const opt = document.createElement("option");
|
const opt = document.createElement("option");
|
||||||
opt.value = i + "",
|
opt.value = a + "",
|
||||||
opt.textContent = i + "";
|
opt.textContent = `${a} - ${a + 1}`;
|
||||||
if (i === defAnnee) {
|
if (a === defAnnee) {
|
||||||
opt.selected = true;
|
opt.selected = true;
|
||||||
}
|
}
|
||||||
select.appendChild(opt)
|
select.appendChild(opt)
|
||||||
}
|
})
|
||||||
setterAnnee(defAnnee)
|
setterAnnee(defAnnee)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,7 +2,16 @@
|
|||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
<h1>Configuration du Module d'assiduité</h1>
|
<div class="row">
|
||||||
|
|
||||||
|
<h1>Configuration du suivi de l'assiduité</h1>
|
||||||
|
|
||||||
|
<div class="help"> Ces paramètres seront utilisés par tous les départements et
|
||||||
|
affectent notamment les comptages d'absences de tous les bulletins des
|
||||||
|
étudiants : ne changer que lorsque c'est vraiment nécessaire.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
|
@ -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>
|
||||||
|
@ -40,8 +40,9 @@
|
|||||||
|
|
||||||
{% if readonly == "false" %}
|
{% if readonly == "false" %}
|
||||||
<div style="margin: 1vh 0;">
|
<div style="margin: 1vh 0;">
|
||||||
<div id="forcemodule" style="display: none; margin:10px 0px;">Une préférence du semestre vous impose d'indiquer
|
<div id="forcemodule" style="display: none; margin:10px 0px;">
|
||||||
le module !</div>
|
Vous devez spécifier le module ! (voir réglage préférence du semestre)
|
||||||
|
</div>
|
||||||
<div>Module :{{moduleimpl_select|safe}}</div>
|
<div>Module :{{moduleimpl_select|safe}}</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
|
|
||||||
{{tableau | safe}}
|
{{tableau | safe}}
|
||||||
|
|
||||||
|
<div class=""help">
|
||||||
|
Les comptes sont exprimés en {{ assi_metric }}.
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const date_debut = "{{date_debut}}";
|
const date_debut = "{{date_debut}}";
|
||||||
const date_fin = "{{date_fin}}";
|
const date_fin = "{{date_fin}}";
|
||||||
|
@ -680,7 +680,7 @@
|
|||||||
rbtn.parentElement.setAttribute('etat', etat);
|
rbtn.parentElement.setAttribute('etat', etat);
|
||||||
asyncCreateAssiduite(assiduite, (data) => {
|
asyncCreateAssiduite(assiduite, (data) => {
|
||||||
if (Object.keys(data.success).length > 0) {
|
if (Object.keys(data.success).length > 0) {
|
||||||
const assi_id = data.success['0'].assiduite_id;
|
const assi_id = data.success['0'].message.assiduite_id;
|
||||||
etudLine.setAttribute('assiduite_id', assi_id);
|
etudLine.setAttribute('assiduite_id', assi_id);
|
||||||
assiduite["assiduite_id"] = assi_id;
|
assiduite["assiduite_id"] = assi_id;
|
||||||
assiduites[etudid].push(assiduite);
|
assiduites[etudid].push(assiduite);
|
||||||
@ -917,7 +917,7 @@
|
|||||||
).done((c, e) => {
|
).done((c, e) => {
|
||||||
Object.keys(c[0].success).forEach((k) => {
|
Object.keys(c[0].success).forEach((k) => {
|
||||||
const assiduite = createList[Number.parseInt(k)];
|
const assiduite = createList[Number.parseInt(k)];
|
||||||
assiduite["assiduite_id"] = c[0].success[k].assiduite_id;
|
assiduite["assiduite_id"] = c[0].success[k].message.assiduite_id;
|
||||||
assiduites[assiduite.etudid].push(assiduite);
|
assiduites[assiduite.etudid].push(assiduite);
|
||||||
})
|
})
|
||||||
Object.keys(e[0].success).forEach((k) => {
|
Object.keys(e[0].success).forEach((k) => {
|
||||||
|
@ -162,7 +162,11 @@
|
|||||||
userIdDiv.textContent = `saisi le ${formatDateModal(
|
userIdDiv.textContent = `saisi le ${formatDateModal(
|
||||||
assiduite.entry_date,
|
assiduite.entry_date,
|
||||||
"à"
|
"à"
|
||||||
)} \npar ${assiduite.user_id}`;
|
)}`;
|
||||||
|
|
||||||
|
if (assiduite.user_id != null) {
|
||||||
|
userIdDiv.textContent += `\npar ${assiduite.user_id}`
|
||||||
|
}
|
||||||
bubble.appendChild(userIdDiv);
|
bubble.appendChild(userIdDiv);
|
||||||
|
|
||||||
bubble.style.left = `${event.clientX - bubble.offsetWidth / 2}px`;
|
bubble.style.left = `${event.clientX - bubble.offsetWidth / 2}px`;
|
||||||
|
@ -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>
|
||||||
|
@ -57,18 +57,18 @@
|
|||||||
<div class="sco_help">Ces images peuvent être intégrées dans les documents
|
<div class="sco_help">Ces images peuvent être intégrées dans les documents
|
||||||
générés par ScoDoc: bulletins, PV, etc.
|
générés par ScoDoc: bulletins, PV, etc.
|
||||||
</div>
|
</div>
|
||||||
<p><a class="stdlink" href="{{url_for('scodoc.configure_logos')}}">configuration des images et logos</a>
|
<p><a class="stdlink" href="{{url_for('scodoc.configure_logos')}}">Configuration des images et logos</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Exports Apogée</h2>
|
<h2>Exports Apogée</h2>
|
||||||
<p><a class="stdlink" href="{{url_for('scodoc.config_codes_decisions')}}">configuration des codes de décision</a>
|
<p><a class="stdlink" href="{{url_for('scodoc.config_codes_decisions')}}">Configuration des codes de décision</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>Assiduités</h2>
|
<h2>Assiduités</h2>
|
||||||
<p><a class="stdlink" href="{{url_for('scodoc.config_assiduites')}}">configuration du module d'assiduités</a>
|
<p><a class="stdlink" href="{{url_for('scodoc.config_assiduites')}}">Configuration du suivi de l'assiduité</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
<h4>Fichiers chargés:</h4>
|
<h4>Fichiers chargés:</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{% for (etud, name) in stored_etud_filename %}
|
{% for (etud, name) in stored_etud_filename %}
|
||||||
<li>{{etud["nomprenom"]}}: <tt>{{name}}</tt></li>
|
<li>{{etud.nomprenom}}: <tt>{{name}}</tt></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -18,6 +18,6 @@ Importation des photo effectuée
|
|||||||
{% if stored_etud_filename %}
|
{% if stored_etud_filename %}
|
||||||
# Fichiers chargés:
|
# Fichiers chargés:
|
||||||
{% for (etud, name) in stored_etud_filename %}
|
{% for (etud, name) in stored_etud_filename %}
|
||||||
- {{etud["nomprenom"]}}: <tt>{{name}}</tt></li>
|
- {{etud.nomprenom}}: <tt>{{name}}</tt></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -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
@ -327,11 +327,9 @@ def bilan_etud():
|
|||||||
date_debut: str = f"{scu.annee_scolaire()}-09-01"
|
date_debut: str = f"{scu.annee_scolaire()}-09-01"
|
||||||
date_fin: str = f"{scu.annee_scolaire()+1}-06-30"
|
date_fin: str = f"{scu.annee_scolaire()+1}-06-30"
|
||||||
|
|
||||||
assi_metric = {
|
assi_metric = scu.translate_assiduites_metric(
|
||||||
"H.": "heure",
|
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
|
||||||
"J.": "journee",
|
)
|
||||||
"1/2 J.": "demi",
|
|
||||||
}.get(sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id))
|
|
||||||
|
|
||||||
return HTMLBuilder(
|
return HTMLBuilder(
|
||||||
header,
|
header,
|
||||||
@ -419,6 +417,16 @@ def calendrier_etud():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
annees: list[int] = sorted(
|
||||||
|
[ins.formsemestre.date_debut.year for ins in etud.formsemestre_inscriptions],
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
annees_str: str = "["
|
||||||
|
for ann in annees:
|
||||||
|
annees_str += f"{ann},"
|
||||||
|
annees_str += "]"
|
||||||
|
|
||||||
return HTMLBuilder(
|
return HTMLBuilder(
|
||||||
header,
|
header,
|
||||||
render_template(
|
render_template(
|
||||||
@ -427,6 +435,7 @@ def calendrier_etud():
|
|||||||
annee=scu.annee_scolaire(),
|
annee=scu.annee_scolaire(),
|
||||||
nonworkdays=_non_work_days(),
|
nonworkdays=_non_work_days(),
|
||||||
minitimeline=_mini_timeline(),
|
minitimeline=_mini_timeline(),
|
||||||
|
annees=annees_str,
|
||||||
),
|
),
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
@ -538,7 +547,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",
|
||||||
@ -727,16 +735,13 @@ def visu_assiduites_group():
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def get_etat_abs_date():
|
def get_etat_abs_date():
|
||||||
evaluation = {
|
infos_date = {
|
||||||
"jour": request.args.get("jour"),
|
"jour": request.args.get("jour"),
|
||||||
"heure_debut": request.args.get("heure_debut"),
|
"heure_debut": request.args.get("heure_debut"),
|
||||||
"heure_fin": request.args.get("heure_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] = []
|
|
||||||
|
|
||||||
if group_ids is None:
|
if group_ids is None:
|
||||||
group_ids = []
|
group_ids = []
|
||||||
else:
|
else:
|
||||||
@ -751,10 +756,10 @@ def get_etat_abs_date():
|
|||||||
]
|
]
|
||||||
|
|
||||||
date_debut = scu.is_iso_formated(
|
date_debut = scu.is_iso_formated(
|
||||||
f"{evaluation['jour']}T{evaluation['heure_debut'].replace('h',':')}", True
|
f"{infos_date['jour']}T{infos_date['heure_debut'].replace('h',':')}", True
|
||||||
)
|
)
|
||||||
date_fin = scu.is_iso_formated(
|
date_fin = scu.is_iso_formated(
|
||||||
f"{evaluation['jour']}T{evaluation['heure_fin'].replace('h',':')}", True
|
f"{infos_date['jour']}T{infos_date['heure_fin'].replace('h',':')}", True
|
||||||
)
|
)
|
||||||
|
|
||||||
assiduites: Assiduite = Assiduite.query.filter(
|
assiduites: Assiduite = Assiduite.query.filter(
|
||||||
@ -764,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,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=infos_date["title"],
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -790,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()
|
||||||
@ -807,8 +819,6 @@ def visu_assi_group():
|
|||||||
fmt = request.args.get("format", "html")
|
fmt = request.args.get("format", "html")
|
||||||
|
|
||||||
group_ids: list[int] = request.args.get("group_ids", None)
|
group_ids: list[int] = request.args.get("group_ids", None)
|
||||||
etudiants: list[dict] = []
|
|
||||||
|
|
||||||
if group_ids is None:
|
if group_ids is None:
|
||||||
group_ids = []
|
group_ids = []
|
||||||
else:
|
else:
|
||||||
@ -842,16 +852,23 @@ def visu_assi_group():
|
|||||||
grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>"
|
grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>"
|
||||||
)
|
)
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"assiduites/pages/visu_assi.j2",
|
"assiduites/pages/visu_assi.j2",
|
||||||
tableau=table.html(),
|
assi_metric=scu.translate_assiduites_metric(
|
||||||
gr_tit=gr_tit,
|
scu.translate_assiduites_metric(
|
||||||
|
sco_preferences.get_preference(
|
||||||
|
"assi_metrique", dept_id=g.scodoc_dept_id
|
||||||
|
),
|
||||||
|
),
|
||||||
|
inverse=False,
|
||||||
|
short=False,
|
||||||
|
),
|
||||||
date_debut=dates["debut"],
|
date_debut=dates["debut"],
|
||||||
date_fin=dates["fin"],
|
date_fin=dates["fin"],
|
||||||
|
gr_tit=gr_tit,
|
||||||
group_ids=request.args.get("group_ids", None),
|
group_ids=request.args.get("group_ids", None),
|
||||||
sco=ScoData(formsemestre=groups_infos.get_formsemestre()),
|
sco=ScoData(formsemestre=groups_infos.get_formsemestre()),
|
||||||
|
tableau=table.html(),
|
||||||
title=f"Assiduité {grp} {groups_infos.groups_titles}",
|
title=f"Assiduité {grp} {groups_infos.groups_titles}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ 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,
|
||||||
ScolarAutorisationInscription,
|
ScolarAutorisationInscription,
|
||||||
ScolarNews,
|
ScolarNews,
|
||||||
@ -97,9 +98,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 +135,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 +380,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 +1153,64 @@ 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,
|
# TODO-ASSIDUITE ne va pas car ne filtre pas sur le moduleimpl
|
||||||
fin=fin_sem,
|
# nb_abs, nb_abs_just = sco_assiduites.formsemestre_get_assiduites_count(etud.id, modimpl.formsemestre)
|
||||||
moduleimpl_id=moduleimpl_id,
|
nb_abs, nb_abs_just = 0, 0 # XXX TODO-ASSIDUITE
|
||||||
)
|
# nb_abs = sco_abs.count_abs(
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
# etudid=etud.id,
|
||||||
T.append(
|
# debut=debut_sem,
|
||||||
|
# fin=fin_sem,
|
||||||
|
# moduleimpl_id=moduleimpl_id,
|
||||||
|
# )
|
||||||
|
# if nb_abs:
|
||||||
|
# nb_abs_just = sco_abs.count_abs_just(
|
||||||
|
# etudid=etud.id,
|
||||||
|
# debut=debut_sem,
|
||||||
|
# fin=fin_sem,
|
||||||
|
# moduleimpl_id=moduleimpl_id,
|
||||||
|
# )
|
||||||
|
rows.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 +1225,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,7 +1655,7 @@ 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})
|
El = sco_evaluation_db.get_evaluation_dict(args={"evaluation_id": evaluation_id})
|
||||||
if not El:
|
if not El:
|
||||||
raise ScoValueError("Evaluation inexistante ! (%s)" % evaluation_id)
|
raise ScoValueError("Evaluation inexistante ! (%s)" % evaluation_id)
|
||||||
E = El[0]
|
E = El[0]
|
||||||
|
@ -1016,19 +1016,20 @@ def etud_photo_orig_page(etudid=None):
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def form_change_photo(etudid=None):
|
def form_change_photo(etudid=None):
|
||||||
"""Formulaire changement photo étudiant"""
|
"""Formulaire changement photo étudiant"""
|
||||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
etud = Identite.get_etud(etudid)
|
||||||
if sco_photos.etud_photo_is_local(etud):
|
if sco_photos.etud_photo_is_local(etud.photo_filename):
|
||||||
etud["photoloc"] = "dans ScoDoc"
|
photo_loc = "dans ScoDoc"
|
||||||
else:
|
else:
|
||||||
etud["photoloc"] = "externe"
|
photo_loc = "externe"
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Changement de photo"),
|
html_sco_header.sco_header(page_title="Changement de photo"),
|
||||||
"""<h2>Changement de la photo de %(nomprenom)s</h2>
|
f"""<h2>Changement de la photo de {etud.nomprenom}</h2>
|
||||||
<p>Photo actuelle (%(photoloc)s):
|
<p>Photo actuelle ({photo_loc}):
|
||||||
"""
|
{sco_photos.etud_photo_html(etudid=etud.id, title="photo actuelle")}
|
||||||
% etud,
|
</p>
|
||||||
sco_photos.etud_photo_html(etud, title="photo actuelle"),
|
<p>Le fichier ne doit pas dépasser {sco_photos.MAX_FILE_SIZE//1024}Ko
|
||||||
"""</p><p>Le fichier ne doit pas dépasser 500Ko (recadrer l'image, format "portrait" de préférence).</p>
|
(recadrer l'image, format "portrait" de préférence).
|
||||||
|
</p>
|
||||||
<p>L'image sera automagiquement réduite pour obtenir une hauteur de 90 pixels.</p>
|
<p>L'image sera automagiquement réduite pour obtenir une hauteur de 90 pixels.</p>
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
@ -1036,7 +1037,7 @@ def form_change_photo(etudid=None):
|
|||||||
request.base_url,
|
request.base_url,
|
||||||
scu.get_request_args(),
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
("etudid", {"default": etudid, "input_type": "hidden"}),
|
("etudid", {"default": etud.id, "input_type": "hidden"}),
|
||||||
(
|
(
|
||||||
"photofile",
|
"photofile",
|
||||||
{"input_type": "file", "title": "Fichier image", "size": 20},
|
{"input_type": "file", "title": "Fichier image", "size": 20},
|
||||||
@ -1045,16 +1046,18 @@ def form_change_photo(etudid=None):
|
|||||||
submitlabel="Valider",
|
submitlabel="Valider",
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
)
|
)
|
||||||
dest_url = url_for(
|
dest_url = url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
|
||||||
)
|
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
return (
|
return (
|
||||||
"\n".join(H)
|
"\n".join(H)
|
||||||
+ tf[1]
|
+ f"""
|
||||||
+ '<p><a class="stdlink" href="form_suppress_photo?etudid=%s">Supprimer cette photo</a></p>'
|
{tf[1]}
|
||||||
% etudid
|
<p><a class="stdlink" href="{
|
||||||
+ html_sco_header.sco_footer()
|
url_for("scolar.form_suppress_photo",
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||||
|
}">Supprimer cette photo</a></p>
|
||||||
|
{html_sco_header.sco_footer()}
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
|
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.7"
|
SCOVERSION = "9.6.15"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
20
scodoc.py
20
scodoc.py
@ -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,
|
||||||
@ -536,7 +537,7 @@ def photos_import_files(formsemestre_id: int, xlsfile: str, zipfile: str):
|
|||||||
admin_user = get_super_admin()
|
admin_user = get_super_admin()
|
||||||
login_user(admin_user)
|
login_user(admin_user)
|
||||||
|
|
||||||
def callback(etud, data, filename):
|
def callback(etud: Identite, data, filename):
|
||||||
return sco_photos.store_photo(etud, data, filename)
|
return sco_photos.store_photo(etud, data, filename)
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -660,7 +661,12 @@ def profile(host, port, length, profile_dir):
|
|||||||
@click.option(
|
@click.option(
|
||||||
"-n",
|
"-n",
|
||||||
"--noon",
|
"--noon",
|
||||||
help="Spécifie l'heure de fin du matin (et donc début de l'après-midi) format `hh:mm`",
|
help="Spécifie l'heure de fin du matin format `hh:mm`",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-a",
|
||||||
|
"--afternoon",
|
||||||
|
help="Spécifie l'heure de début de l'après-midi format `hh:mm` valeur identique à --noon si non spécifié",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-e",
|
"-e",
|
||||||
@ -669,10 +675,14 @@ def profile(host, port, length, profile_dir):
|
|||||||
)
|
)
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def migrate_abs_to_assiduites(
|
def migrate_abs_to_assiduites(
|
||||||
dept: str = None, morning: str = None, noon: str = None, evening: str = None
|
dept: str = None,
|
||||||
|
morning: str = None,
|
||||||
|
noon: str = None,
|
||||||
|
afternoon: str = None,
|
||||||
|
evening: str = None,
|
||||||
): # migrate-abs-to-assiduites
|
): # migrate-abs-to-assiduites
|
||||||
"""Permet de migrer les absences vers le nouveau module d'assiduités"""
|
"""Permet de migrer les absences vers le nouveau module d'assiduités"""
|
||||||
tools.migrate_abs_to_assiduites(dept, morning, noon, evening)
|
tools.migrate_abs_to_assiduites(dept, morning, noon, afternoon, evening)
|
||||||
# import cProfile
|
# import cProfile
|
||||||
# cProfile.runctx(
|
# cProfile.runctx(
|
||||||
# f"tools.migrate_abs_to_assiduites({dept})",
|
# f"tools.migrate_abs_to_assiduites({dept})",
|
||||||
|
@ -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)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user