Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
18 changed files with 120 additions and 50 deletions
Showing only changes of commit 1298c6d4db - Show all commits

View File

@ -148,7 +148,7 @@ def handle_invalid_usage(error):
# JSON ENCODING # JSON ENCODING
# used by some internal finctions # used by some internal finctions
# the API is now using flask_son, NOT THIS ENCODER # the API is now using flask_json, NOT THIS ENCODER
class ScoDocJSONEncoder(json.JSONEncoder): class ScoDocJSONEncoder(json.JSONEncoder):
def default(self, o): # pylint: disable=E0202 def default(self, o): # pylint: disable=E0202
if isinstance(o, (datetime.date, datetime.datetime)): if isinstance(o, (datetime.date, datetime.datetime)):
@ -260,7 +260,13 @@ def create_app(config_class=DevConfig):
CAS(app, url_prefix="/cas", configuration_function=cas.set_cas_configuration) CAS(app, url_prefix="/cas", configuration_function=cas.set_cas_configuration)
app.wsgi_app = ReverseProxied(app.wsgi_app) app.wsgi_app = ReverseProxied(app.wsgi_app)
FlaskJSON(app) app_json = FlaskJSON(app)
@app_json.encoder
def scodoc_json_encoder(o):
"Overide default date encoding (RFC 822) and use ISO"
if isinstance(o, (datetime.date, datetime.datetime)):
return o.isoformat()
# Pour conserver l'ordre des objets dans les JSON: # Pour conserver l'ordre des objets dans les JSON:
# e.g. l'ordre des UE dans les bulletins # e.g. l'ordre des UE dans les bulletins

View File

@ -25,6 +25,7 @@ from app.models import (
Scolog, Scolog,
Justificatif, Justificatif,
) )
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
from app.scodoc.sco_utils import json_error from app.scodoc.sco_utils import json_error
@ -77,7 +78,7 @@ def assiduite_justificatifs(assiduite_id: int = None, long: bool = False):
] ]
""" """
return _get_assiduites_justif(assiduite_id, True) return get_assiduites_justif(assiduite_id, True)
# etudid # etudid
@ -371,7 +372,7 @@ 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, 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)
@ -420,7 +421,9 @@ def count_assiduites_formsemestre(
etuds_id = [etud.id for etud in etuds] etuds_id = [etud.id for etud in etuds]
assiduites_query = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id)) assiduites_query = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id))
assiduites_query = scass.filter_by_formsemestre(assiduites_query, formsemestre) assiduites_query = scass.filter_by_formsemestre(
assiduites_query, Assiduite, formsemestre
)
metric: str = "all" metric: str = "all"
filtered: dict = {} filtered: dict = {}
if with_query: if with_query:
@ -1000,7 +1003,9 @@ def _filter_manager(requested, assiduites_query: Assiduite):
formsemestre: FormSemestre = None formsemestre: FormSemestre = None
formsemestre_id = int(formsemestre_id) formsemestre_id = int(formsemestre_id)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
assiduites_query = scass.filter_by_formsemestre(assiduites_query, formsemestre) assiduites_query = scass.filter_by_formsemestre(
assiduites_query, Assiduite, formsemestre
)
# cas 6 : est_just # cas 6 : est_just
@ -1027,20 +1032,8 @@ def _filter_manager(requested, assiduites_query: Assiduite):
return assiduites_query return assiduites_query
def _get_assiduites_justif(assiduite_id: int, long: bool):
assi: Assiduite = Assiduite.query.get_or_404(assiduite_id)
justifs: Justificatif = Justificatif.query.filter(
Justificatif.etudid == assi.etudid,
Justificatif.date_debut <= assi.date_debut,
Justificatif.date_fin >= assi.date_fin,
)
return [j.justif_id if not long else j.to_dict(True) for j in justifs]
def _with_justifs(assi): def _with_justifs(assi):
if request.args.get("with_justifs") is None: if request.args.get("with_justifs") is None:
return assi return assi
assi["justificatifs"] = _get_assiduites_justif(assi["assiduite_id"], True) assi["justificatifs"] = get_assiduites_justif(assi["assiduite_id"], True)
return assi return assi

View File

@ -137,6 +137,9 @@ def etudiant(etudid: int = None, nip: str = None, ine: str = None):
return etud.to_dict_api() return etud.to_dict_api()
@bp.route("/etudiant/etudid/<int:etudid>/photo")
@bp.route("/etudiant/nip/<string:nip>/photo")
@bp.route("/etudiant/ine/<string:ine>/photo")
@api_web_bp.route("/etudiant/etudid/<int:etudid>/photo") @api_web_bp.route("/etudiant/etudid/<int:etudid>/photo")
@api_web_bp.route("/etudiant/nip/<string:nip>/photo") @api_web_bp.route("/etudiant/nip/<string:nip>/photo")
@api_web_bp.route("/etudiant/ine/<string:ine>/photo") @api_web_bp.route("/etudiant/ine/<string:ine>/photo")

View File

@ -18,7 +18,7 @@ from app.api import api_bp as bp
from app.api import api_web_bp from app.api import api_web_bp
from app.api import get_model_api_object, tools from app.api import get_model_api_object, tools
from app.decorators import permission_required, scodoc from app.decorators import permission_required, scodoc
from app.models import Identite, Justificatif, Departement from app.models import Identite, Justificatif, Departement, FormSemestre
from app.models.assiduites import ( from app.models.assiduites import (
compute_assiduites_justified, compute_assiduites_justified,
) )
@ -682,8 +682,19 @@ def _filter_manager(requested, justificatifs_query):
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:
justificatif_query: Justificatif = scass.filter_by_user_id( justificatifs_query: Justificatif = scass.filter_by_user_id(
justificatif_query, user_id justificatifs_query, user_id
)
# cas 5 : formsemestre_id
formsemestre_id = requested.args.get("formsemestre_id")
if formsemestre_id is not None:
formsemestre: FormSemestre = None
formsemestre_id = int(formsemestre_id)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
justificatifs_query = scass.filter_by_formsemestre(
justificatifs_query, Justificatif, formsemestre
) )
return justificatifs_query return justificatifs_query

View File

@ -129,6 +129,12 @@ class Assiduite(db.Model):
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)"
) )
if not est_just:
est_just = (
len(_get_assiduites_justif(etud.etudid, date_debut, date_fin)) > 0
)
if moduleimpl is not None: if moduleimpl is not None:
# Vérification de l'existence du module pour l'étudiant # Vérification de l'existence du module pour l'étudiant
if moduleimpl.est_inscrit(etud): if moduleimpl.est_inscrit(etud):
@ -359,3 +365,20 @@ def compute_assiduites_justified(
db.session.add(assi) db.session.add(assi)
db.session.commit() db.session.commit()
return assiduites_justifiees return assiduites_justifiees
def get_assiduites_justif(assiduite_id: int, long: bool):
assi: Assiduite = Assiduite.query.get_or_404(assiduite_id)
return _get_assiduites_justif(assi.etudid, assi.date_debut, assi.date_fin, long)
def _get_assiduites_justif(
etudid: int, date_debut: datetime, date_fin: datetime, long: bool = False
):
justifs: Justificatif = Justificatif.query.filter(
Justificatif.etudid == etudid,
Justificatif.date_debut <= date_debut,
Justificatif.date_fin >= date_fin,
)
return [j.justif_id if not long else j.to_dict(True) for j in justifs]

View File

@ -471,9 +471,16 @@ class ApcNiveau(db.Model, XMLModel):
for pn in parcour_niveaux for pn in parcour_niveaux
] ]
else: else:
niveaux: list[ApcNiveau] = competence.niveaux.filter_by( niveaux: list[ApcNiveau] = (
annee=f"BUT{int(annee)}" ApcNiveau.query.filter_by(annee=f"BUT{int(annee)}")
).all() .join(ApcCompetence)
.filter_by(id=competence.id)
.join(ApcParcoursNiveauCompetence)
.filter(ApcParcoursNiveauCompetence.niveau == ApcNiveau.ordre)
.join(ApcAnneeParcours)
.filter_by(parcours_id=parcour.id)
.all()
)
_cache[key] = niveaux _cache[key] = niveaux
return niveaux return niveaux

View File

@ -194,7 +194,9 @@ def get_assiduites_stats(
elif key == "moduleimpl_id": elif key == "moduleimpl_id":
assiduites = filter_by_module_impl(assiduites, filtered[key]) assiduites = filter_by_module_impl(assiduites, filtered[key])
elif key == "formsemestre": elif key == "formsemestre":
assiduites = filter_by_formsemestre(assiduites, filtered[key]) assiduites = filter_by_formsemestre(
assiduites, Assiduite, filtered[key]
)
elif key == "est_just": elif key == "est_just":
assiduites = filter_assiduites_by_est_just(assiduites, filtered[key]) assiduites = filter_assiduites_by_est_just(assiduites, filtered[key])
elif key == "user_id": elif key == "user_id":
@ -290,16 +292,20 @@ def filter_by_module_impl(
return assiduites.filter(Assiduite.moduleimpl_id == module_impl_id) return assiduites.filter(Assiduite.moduleimpl_id == module_impl_id)
def filter_by_formsemestre(assiduites_query: Assiduite, formsemestre: FormSemestre): def filter_by_formsemestre(
collection_query: Assiduite or Justificatif,
collection_class: Assiduite or Justificatif,
formsemestre: FormSemestre,
):
""" """
Filtrage d'une collection d'assiduites en fonction d'un formsemestre Filtrage d'une collection en fonction d'un formsemestre
""" """
if formsemestre is None: if formsemestre is None:
return assiduites_query.filter(False) return collection_query.filter(False)
assiduites_query = ( collection_result = (
assiduites_query.join(Identite, Assiduite.etudid == Identite.id) collection_query.join(Identite, collection_class.etudid == Identite.id)
.join( .join(
FormSemestreInscription, FormSemestreInscription,
Identite.id == FormSemestreInscription.etudid, Identite.id == FormSemestreInscription.etudid,
@ -310,9 +316,11 @@ def filter_by_formsemestre(assiduites_query: Assiduite, formsemestre: FormSemest
form_date_debut = formsemestre.date_debut + timedelta(days=1) form_date_debut = formsemestre.date_debut + timedelta(days=1)
form_date_fin = formsemestre.date_fin + timedelta(days=1) form_date_fin = formsemestre.date_fin + timedelta(days=1)
assiduites_query = assiduites_query.filter(Assiduite.date_debut >= form_date_debut) collection_result = collection_result.filter(
collection_class.date_debut >= form_date_debut
)
return assiduites_query.filter(Assiduite.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]:

View File

@ -1716,3 +1716,13 @@ function getModuleImpl(assiduite) {
return moduleimpls[id]; return moduleimpls[id];
} }
function getUser(obj) {
if ("external_data" in obj && obj.external_data != null) {
if ("enseignant" in obj.external_data) {
return obj.external_data.enseignant;
}
}
return obj.user_id;
}

View File

@ -108,7 +108,7 @@
async_get( async_get(
path, path,
(data) => { (data) => {
const user = data.user_id; const user = getUser(data);
const module = getModuleImpl(data); const module = getModuleImpl(data);
const date_debut = moment.tz(data.date_debut, TIMEZONE).format("DD/MM/YYYY HH:mm"); const date_debut = moment.tz(data.date_debut, TIMEZONE).format("DD/MM/YYYY HH:mm");

View File

@ -246,8 +246,8 @@
} }
if (keyword.indexOf("module") != -1) { if (keyword.indexOf("module") != -1) {
keyValueA = getModuleImpl(keyValueA); keyValueA = getModuleImpl(a);
keyValueB = getModuleImpl(keyValueB); keyValueB = getModuleImpl(b);
} }
let orderDertermined = keyValueA > keyValueB; let orderDertermined = keyValueA > keyValueB;

View File

@ -124,7 +124,7 @@
async_get( async_get(
path, path,
(data) => { (data) => {
const user = data.user_id; const user = getUser(data);
const date_debut = moment.tz(data.date_debut, TIMEZONE).format("DD/MM/YYYY HH:mm"); const date_debut = moment.tz(data.date_debut, TIMEZONE).format("DD/MM/YYYY HH:mm");
const date_fin = moment.tz(data.date_fin, TIMEZONE).format("DD/MM/YYYY HH:mm"); const date_fin = moment.tz(data.date_fin, TIMEZONE).format("DD/MM/YYYY HH:mm");
const entry_date = moment.tz(data.entry_date, TIMEZONE).format("DD/MM/YYYY HH:mm"); const entry_date = moment.tz(data.entry_date, TIMEZONE).format("DD/MM/YYYY HH:mm");
@ -336,7 +336,7 @@
a.textContent = name a.textContent = name
a.classList.add("fich-file") a.classList.add("fich-file")
a.onclick = () => { downloadFile(id, name) }; a.onclick = () => { downloadFile(justif_id, name) };
const input = document.createElement('input') const input = document.createElement('input')
input.type = "checkbox" input.type = "checkbox"

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.6.6" SCOVERSION = "9.6.7"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"

View File

@ -412,6 +412,7 @@ def delete_dept(dept, force=False): # delete-dept
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app.scodoc import sco_dept from app.scodoc import sco_dept
msg = ""
db.reflect() db.reflect()
ndb.open_db_connection() ndb.open_db_connection()
d = models.Departement.query.filter_by(acronym=dept).first() d = models.Departement.query.filter_by(acronym=dept).first()

View File

@ -17,6 +17,7 @@ Utilisation :
pytest tests/api/test_api_etudiants.py pytest tests/api/test_api_etudiants.py
""" """
import re
import requests import requests
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -100,6 +101,7 @@ def test_etudiants_courant(api_headers):
etud = etudiants[-1] etud = etudiants[-1]
assert verify_fields(etud, ETUD_FIELDS) is True assert verify_fields(etud, ETUD_FIELDS) is True
assert re.match(r"^\d{4}-\d\d-\d\d$", etud["date_naissance"])
def test_etudiant(api_headers): def test_etudiant(api_headers):

View File

@ -6,14 +6,10 @@ Comptage des absences
""" """
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021 # test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
import json
from tests.unit import sco_fake_gen from tests.unit import sco_fake_gen
from app.scodoc import sco_abs, sco_formsemestre from app.scodoc import sco_abs, sco_formsemestre
from app.scodoc import sco_abs_views from app.scodoc import sco_abs_views
from app.scodoc import sco_groups
from app.views import absences
def test_abs_counts(test_client): def test_abs_counts(test_client):

View File

@ -926,13 +926,22 @@ def verifier_comptage_et_filtrage_assiduites(
FormSemestre.query.filter_by(id=fms["id"]).first() for fms in formsemestres FormSemestre.query.filter_by(id=fms["id"]).first() for fms in formsemestres
] ]
assert ( assert (
scass.filter_by_formsemestre(etu1.assiduites, formsemestres[0]).count() == 4 scass.filter_by_formsemestre(
etu1.assiduites, Assiduite, formsemestres[0]
).count()
== 4
), "Filtrage 'Formsemestre' mauvais" ), "Filtrage 'Formsemestre' mauvais"
assert ( assert (
scass.filter_by_formsemestre(etu1.assiduites, formsemestres[1]).count() == 3 scass.filter_by_formsemestre(
etu1.assiduites, Assiduite, formsemestres[1]
).count()
== 3
), "Filtrage 'Formsemestre' mauvais" ), "Filtrage 'Formsemestre' mauvais"
assert ( assert (
scass.filter_by_formsemestre(etu1.assiduites, formsemestres[2]).count() == 0 scass.filter_by_formsemestre(
etu1.assiduites, Assiduite, formsemestres[2]
).count()
== 0
), "Filtrage 'Formsemestre' mauvais" ), "Filtrage 'Formsemestre' mauvais"
# Date début # Date début

View File

@ -134,8 +134,9 @@ def associe_modules_et_parcours(formation: Formation, formation_infos: dict):
for module in formation.modules for module in formation.modules
if re.match(code_module, module.code) if re.match(code_module, module.code)
]: ]:
module.parcours.append(parcour) if not parcour in module.parcours:
db.session.add(module) module.parcours.append(parcour)
db.session.add(module)
db.session.commit() db.session.commit()

View File

@ -59,10 +59,10 @@ SCODOC_USER=scodoc
# Tests unitaires lancés dans le répertoire de travail # Tests unitaires lancés dans le répertoire de travail
echo "TESTS UNITAIRES" echo "TESTS UNITAIRES"
(cd "$UNIT_TESTS_DIR"; pytest tests/unit) (cd "$UNIT_TESTS_DIR"; pytest tests/unit) || terminate "Erreur dans tests unitaires"
# Tests API # Tests API
(cd "$UNIT_TESTS_DIR"; tools/test_api.sh) (cd "$UNIT_TESTS_DIR"; tools/test_api.sh) || terminate "Erreur dans tests unitaires API"
# Création répertoire du paquet, et de opt # Création répertoire du paquet, et de opt