Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into dev93

This commit is contained in:
Emmanuel Viennet 2022-04-28 01:22:28 +02:00
commit 7b12552121
51 changed files with 4424 additions and 628 deletions

View File

@ -23,4 +23,13 @@ def requested_format(default_format="json", allowed_formats=None):
from app.api import tokens from app.api import tokens
from app.api import sco_api from app.api import sco_api
from app.api import test_api
from app.api import departements
from app.api import etudiants
from app.api import formations
from app.api import formsemestres
from app.api import partitions
from app.api import evaluations
from app.api import jury
from app.api import absences
from app.api import logos from app.api import logos

150
app/api/absences.py Normal file
View File

@ -0,0 +1,150 @@
#################################################### Absences #########################################################
from flask import jsonify
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.api.tools import get_etu_from_etudid_or_nip_or_ine
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
@bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
@bp.route("/absences/nip/<int:nip>", methods=["GET"])
@bp.route("/absences/ine/<int:ine>", methods=["GET"])
@token_permission_required(Permission.APIView)
def absences(etudid: int = None, nip: int = None, ine: int = None):
"""
Retourne la liste des absences d'un étudiant donné
etudid : l'etudid d'un étudiant
nip: le code nip d'un étudiant
ine : le code ine d'un étudiant
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"
}
]
"""
if etudid is None:
# Récupération de l'étudiant
etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
if etud is None:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel.\n "
"Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
)
etudid = etud.etudid
# Récupération des absences de l'étudiant
ndb.open_db_connection()
absences = sco_abs.list_abs_date(etudid)
for absence in absences:
absence["jour"] = absence["jour"].isoformat()
return jsonify(absences)
@bp.route("/absences/etudid/<int:etudid>/just", methods=["GET"])
@bp.route("/absences/nip/<int:nip>/just", methods=["GET"])
@bp.route("/absences/ine/<int:ine>/just", methods=["GET"])
@token_permission_required(Permission.APIView)
def absences_just(etudid: int = None, nip: int = None, ine: int = None):
"""
Retourne la liste des absences justifiées d'un étudiant donné
etudid : l'etudid d'un étudiant
nip: le code nip d'un étudiant
ine : le code ine d'un étudiant
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": false,
"description": "",
"begin": "2022-04-15 12:00:00",
"end": "2022-04-15 17:59:59"
}
]
"""
if etudid is None:
# Récupération de l'étudiant
try:
etu = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
etudid = etu.etudid
except AttributeError:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel.\n "
"Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
)
# Récupération des absences justifiées de l'étudiant
absences = sco_abs.list_abs_date(etudid)
for absence in [absence for absence in absences if absence["estjust"]]:
absence["jour"] = absence["jour"].isoformat()
return jsonify(absences)
@bp.route(
"/absences/abs_group_etat/?group_id=<int:group_id>&date_debut=date_debut&date_fin=date_fin",
methods=["GET"],
)
@token_permission_required(Permission.APIView)
def abs_groupe_etat( # XXX A REVOIR XXX
group_id: int, date_debut, date_fin, with_boursier=True, format="html"
):
"""
Retoune la liste des absences d'un ou plusieurs groupes entre deux dates
"""
# Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date()
try:
# Utilisation de la fonction get_group_members
members = get_group_members(group_id)
except ValueError:
return error_response(
409, message="La requête ne peut être traitée en létat actuel"
)
data = []
# Filtre entre les deux dates renseignées
for member in members:
abs = sco_abs.list_abs_date(member.id, date_debut, date_fin)
data.append(abs)
# return jsonify(data) # XXX TODO faire en sorte de pouvoir renvoyer sa (ex to_dict() dans absences)
return error_response(501, message="Not implemented")

View File

@ -24,6 +24,10 @@
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from functools import wraps
from flask import abort
from flask import g from flask import g
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
from app.auth.models import User from app.auth.models import User
@ -63,15 +67,17 @@ def get_user_roles(user):
return user.roles return user.roles
# def token_permission_required(permission): def token_permission_required(permission):
# def decorator(f): def decorator(f):
# @wraps(f) @wraps(f)
# def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
# scodoc_dept = getattr(g, "scodoc_dept", None) scodoc_dept = getattr(g, "scodoc_dept", None)
# if not current_user.has_permission(permission, scodoc_dept): if hasattr(g, "current_user") and not g.current_user.has_permission(
# abort(403) permission, scodoc_dept
# return f(*args, **kwargs) ):
abort(403)
return f(*args, **kwargs)
# return login_required(decorated_function) return decorated_function # login_required(decorated_function)
# return decorator return decorator

303
app/api/departements.py Normal file
View File

@ -0,0 +1,303 @@
############################################### Departements ##########################################################
import app
from app import models
from app.api import bp
from app.api.auth import token_permission_required
from app.api.errors import error_response
from app.scodoc.sco_permissions import Permission
from flask import jsonify
@bp.route("/departements", methods=["GET"])
@token_permission_required(Permission.APIView)
def departements():
"""
Retourne la liste des ids de départements visibles
Exemple de résultat :
[
{
"id": 1,
"acronym": "TAPI",
"description": null,
"visible": true,
"date_creation": "Fri, 15 Apr 2022 12:19:28 GMT"
},
{
"id": 2,
"acronym": "MMI",
"description": null,
"visible": false,
"date_creation": "Fri, 18 Apr 2022 11:20:8 GMT"
},
...
]
"""
# Récupération de tous les départements
depts = models.Departement.query.all()
# Mise en place de la liste avec tous les départements
data = [d.to_dict() for d in depts]
return jsonify(data)
@bp.route("/departements/<string:dept>/etudiants/liste", methods=["GET"])
@bp.route(
"/departements/<string:dept>/etudiants/liste/<int:formsemestre_id>", methods=["GET"]
)
@token_permission_required(Permission.APIView)
def liste_etudiants(dept: str, formsemestre_id=None):
"""
Retourne la liste des étudiants d'un département
dept: l'acronym d'un département
formsemestre_id: l'id d'un formesemestre
Exemple de résultat :
[
{
"civilite": "X",
"code_ine": null,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 18,
"nom": "MOREL",
"prenom": "JACQUES"
},
{
"civilite": "X",
"code_ine": null,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 19,
"nom": "FOURNIER",
"prenom": "ANNE"
},
...
]
"""
# Si le formsemestre_id a été renseigné
if formsemestre_id is not None:
# Récupération du formsemestre
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
# Récupération du département
departement = formsemestre.departement
# Si le formsemestre_id n'a pas été renseigné
else:
# Récupération du formsemestre
departement = models.Departement.query.filter_by(acronym=dept).first_or_404()
# Récupération des étudiants
etudiants = departement.etudiants.all()
# Mise en forme des données
list_etu = [etu.to_dict_bul(include_urls=False) for etu in etudiants]
return jsonify(list_etu)
@bp.route("/departements/<string:dept>/semestres_courants", methods=["GET"])
@token_permission_required(Permission.APIView)
def liste_semestres_courant(dept: str):
"""
Liste des semestres actifs d'un départements donné
dept: l'acronym d'un département
Exemple de résultat :
[
{
"titre": "master machine info",
"gestion_semestrielle": false,
"scodoc7_id": null,
"date_debut": "01/09/2021",
"bul_bgcolor": null,
"date_fin": "15/12/2022",
"resp_can_edit": false,
"dept_id": 1,
"etat": true,
"resp_can_change_ens": false,
"id": 1,
"modalite": "FI",
"ens_can_edit_eval": false,
"formation_id": 1,
"gestion_compensation": false,
"elt_sem_apo": null,
"semestre_id": 1,
"bul_hide_xml": false,
"elt_annee_apo": null,
"block_moyennes": false,
"formsemestre_id": 1,
"titre_num": "master machine info semestre 1",
"date_debut_iso": "2021-09-01",
"date_fin_iso": "2022-12-15",
"responsables": [
3,
2
]
},
...
]
"""
# Récupération des départements comportant l'acronym mit en paramètre
dept = models.Departement.query.filter_by(acronym=dept).first_or_404()
# Récupération des semestres suivant id_dept
semestres = models.FormSemestre.query.filter_by(dept_id=dept.id, etat=True)
# Mise en forme des données
data = [d.to_dict() for d in semestres]
return jsonify(data)
@bp.route(
"/departements/<string:dept>/formations/<int:formation_id>/referentiel_competences",
methods=["GET"],
)
@token_permission_required(Permission.APIView)
def referenciel_competences(dept: str, formation_id: int):
"""
Retourne le référentiel de compétences
dept : l'acronym d'un département
formation_id : l'id d'une formation
"""
dept = models.Departement.query.filter_by(acronym=dept).first_or_404()
formation = models.Formation.query.filter_by(
id=formation_id, dept_id=dept.id
).first_or_404()
ref_comp = formation.referentiel_competence_id
if ref_comp is None:
return error_response(
204, message="Pas de référenciel de compétences pour cette formation"
)
else:
return jsonify(ref_comp)
@bp.route(
"/departements/<string:dept>/formsemestre/<string:formsemestre_id>/programme",
methods=["GET"],
)
@token_permission_required(Permission.APIView)
def semestre_index(dept: str, formsemestre_id: int):
"""
Retourne la liste des Ues, ressources et SAE d'un semestre
dept : l'acronym d'un département
formsemestre_id : l'id d'un formesemestre
Exemple de résultat :
{
"ues": [
{
"type": 0,
"formation_id": 1,
"ue_code": "UCOD11",
"id": 1,
"ects": 12.0,
"acronyme": "RT1.1",
"is_external": false,
"numero": 1,
"code_apogee": "",
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
"coefficient": 0.0,
"semestre_idx": 1,
"color": "#B80004",
"ue_id": 1
},
...
],
"ressources": [
{
"titre": "Fondamentaux de la programmation",
"coefficient": 1.0,
"module_type": 2,
"id": 17,
"ects": null,
"abbrev": null,
"ue_id": 3,
"code": "R107",
"formation_id": 1,
"heures_cours": 0.0,
"matiere_id": 3,
"heures_td": 0.0,
"semestre_id": 1,
"heures_tp": 0.0,
"numero": 70,
"code_apogee": "",
"module_id": 17
},
...
],
"saes": [
{
"titre": "Se pr\u00e9senter sur Internet",
"coefficient": 1.0,
"module_type": 3,
"id": 14,
"ects": null,
"abbrev": null,
"ue_id": 3,
"code": "SAE14",
"formation_id": 1,
"heures_cours": 0.0,
"matiere_id": 3,
"heures_td": 0.0,
"semestre_id": 1,
"heures_tp": 0.0,
"numero": 40,
"code_apogee": "",
"module_id": 14
},
...
]
}
"""
app.set_sco_dept(dept)
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
ues = formsemestre.query_ues()
ues_dict = []
ressources = []
saes = []
for ue in ues:
ues_dict.append(ue.to_dict())
ressources = ue.get_ressources()
saes = ue.get_saes()
data_ressources = []
for ressource in ressources:
data_ressources.append(ressource.to_dict())
data_saes = []
for sae in saes:
data_saes.append(sae.to_dict())
data = {
"ues": ues_dict,
"ressources": data_ressources,
"saes": data_saes,
}
return data

432
app/api/etudiants.py Normal file
View File

@ -0,0 +1,432 @@
#################################################### Etudiants ########################################################
from flask import jsonify
import app
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.api.tools import get_etu_from_etudid_or_nip_or_ine
from app.models import FormSemestreInscription, FormSemestre, Identite
from app.scodoc import sco_bulletins
from app.scodoc import sco_groups
from app.scodoc.sco_permissions import Permission
from app.scodoc import notesdb as ndb
@bp.route("/etudiants/courant", methods=["GET"])
@token_permission_required(Permission.APIView)
def etudiants_courant():
"""
Retourne la liste des étudiants courant
Exemple de résultat :
{
{
"civilite": "X",
"code_ine": null,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 18,
"nom": "MOREL",
"prenom": "JACQUES"
},
{
"civilite": "X",
"code_ine": null,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 19,
"nom": "FOURNIER",
"prenom": "ANNE"
},
...
}
"""
# Récupération de tous les étudiants
etuds = Identite.query.filter(
Identite.id == FormSemestreInscription.etudid,
FormSemestreInscription.formsemestre_id == FormSemestre.id,
FormSemestre.date_debut <= app.db.func.now(),
FormSemestre.date_fin >= app.db.func.now(),
)
data = [etu.to_dict_bul(include_urls=False) for etu in etuds]
return jsonify(data)
@bp.route("/etudiant/etudid/<int:etudid>", methods=["GET"])
@bp.route("/etudiant/nip/<int:nip>", methods=["GET"])
@bp.route("/etudiant/ine/<int:ine>", methods=["GET"])
@token_permission_required(Permission.APIView)
def etudiant(etudid: int = None, nip: int = None, ine: int = None):
"""
Retourne les informations de l'étudiant correspondant à l'id passé en paramètres.
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
Exemple de résultat :
{
"civilite": "X",
"code_ine": null,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 18,
"nom": "MOREL",
"prenom": "JACQUES"
}
"""
# Récupération de l'étudiant
etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
# Mise en forme des données
data = etud.to_dict_bul(include_urls=False)
return jsonify(data)
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
@bp.route("/etudiant/nip/<int:nip>/formsemestres")
@bp.route("/etudiant/ine/<int:ine>/formsemestres")
@token_permission_required(Permission.APIView)
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
"""
Retourne la liste des semestres qu'un étudiant a suivis, triés par ordre chronologique.
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
Exemple de résultat :
[
{
"titre": "master machine info",
"gestion_semestrielle": false,
"date_debut": "01/09/2021",
"bul_bgcolor": null,
"date_fin": "15/12/2022",
"resp_can_edit": false,
"dept_id": 1,
"etat": true,
"resp_can_change_ens": false,
"id": 1,
"modalite": "FI",
"ens_can_edit_eval": false,
"formation_id": 1,
"gestion_compensation": false,
"elt_sem_apo": null,
"semestre_id": 1,
"bul_hide_xml": false,
"elt_annee_apo": null,
"block_moyennes": false,
"formsemestre_id": 1,
"titre_num": "master machine info semestre 1",
"date_debut_iso": "2021-09-01",
"date_fin_iso": "2022-12-15",
"responsables": [
3,
2
]
},
...
]
"""
# Récupération de l'étudiant
etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
formsemestres = models.FormSemestre.query.filter(
models.FormSemestreInscription.etudid == etud.id,
models.FormSemestreInscription.formsemestre_id == models.FormSemestre.id,
).order_by(models.FormSemestre.date_debut)
return jsonify([formsemestre.to_dict() for formsemestre in formsemestres])
@bp.route(
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin",
methods=["GET"],
)
@bp.route(
"/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin",
methods=["GET"],
)
@bp.route(
"/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin",
methods=["GET"],
)
@token_permission_required(Permission.APIView)
def etudiant_bulletin_semestre(
formsemestre_id, etudid: int = None, nip: int = None, ine: int = None
):
"""
Retourne le bulletin d'un étudiant en fonction de son id et d'un semestre donné
formsemestre_id : l'id d'un formsemestre
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
Exemple de résultat :
{
"version": "0",
"type": "BUT",
"date": "2022-04-27T07:18:16.450634Z",
"publie": true,
"etudiant": {
"civilite": "X",
"code_ine": "1",
"code_nip": "1",
"date_naissance": "",
"email": "SACHA.COSTA@example.com",
"emailperso": "",
"etudid": 1,
"nom": "COSTA",
"prenom": "SACHA",
"nomprenom": "Sacha COSTA",
"lieu_naissance": "",
"dept_naissance": "",
"nationalite": "",
"boursier": "",
"fiche_url": "/ScoDoc/TAPI/Scolarite/ficheEtud?etudid=1",
"photo_url": "/ScoDoc/TAPI/Scolarite/get_photo_image?etudid=1&size=small",
"id": 1,
"codepostaldomicile": "",
"paysdomicile": "",
"telephonemobile": "",
"typeadresse": "domicile",
"domicile": "",
"villedomicile": "",
"telephone": "",
"fax": "",
"description": ""
},
"formation": {
"id": 1,
"acronyme": "BUT R&amp;T",
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
"titre": "BUT R&amp;T"
},
"formsemestre_id": 1,
"etat_inscription": "I",
"options": {
"show_abs": true,
"show_abs_modules": false,
"show_ects": true,
"show_codemodules": false,
"show_matieres": false,
"show_rangs": true,
"show_ue_rangs": true,
"show_mod_rangs": true,
"show_moypromo": false,
"show_minmax": false,
"show_minmax_mod": false,
"show_minmax_eval": false,
"show_coef": true,
"show_ue_cap_details": false,
"show_ue_cap_current": true,
"show_temporary": true,
"temporary_txt": "Provisoire",
"show_uevalid": true,
"show_date_inscr": true
},
"ressources": {
"R101": {
"id": 1,
"titre": "Initiation aux r\u00e9seaux informatiques",
"code_apogee": null,
"url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=1",
"moyenne": {},
"evaluations": [
{
"id": 1,
"description": "eval1",
"date": "2022-04-20",
"heure_debut": "08:00",
"heure_fin": "09:00",
"coef": "01.00",
"poids": {
"RT1.1": 1.0,
},
"note": {
"value": "12.00",
"min": "00.00",
"max": "18.00",
"moy": "10.88"
},
"url": "/ScoDoc/TAPI/Scolarite/Notes/evaluation_listenotes?evaluation_id=1"
}
]
},
},
"saes": {
"SAE11": {
"id": 2,
"titre": "Se sensibiliser \u00e0 l&apos;hygi\u00e8ne informatique et \u00e0 la cybers\u00e9curit\u00e9",
"code_apogee": null,
"url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=2",
"moyenne": {},
"evaluations": []
},
},
"ues": {
"RT1.1": {
"id": 1,
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
"numero": 1,
"type": 0,
"color": "#B80004",
"competence": null,
"moyenne": {
"value": "08.50",
"min": "06.00",
"max": "16.50",
"moy": "11.31",
"rang": "12",
"total": 16
},
"bonus": "00.00",
"malus": "00.00",
"capitalise": null,
"ressources": {
"R101": {
"id": 1,
"coef": 12.0,
"moyenne": "12.00"
},
},
"saes": {
"SAE11": {
"id": 2,
"coef": 16.0,
"moyenne": "~"
},
},
"ECTS": {
"acquis": 0.0,
"total": 12.0
}
},
"semestre": {
"etapes": [],
"date_debut": "2021-09-01",
"date_fin": "2022-08-31",
"annee_universitaire": "2021 - 2022",
"numero": 1,
"inscription": "",
"groupes": [],
"absences": {
"injustifie": 1,
"total": 2
},
"ECTS": {
"acquis": 0,
"total": 30.0
},
"notes": {
"value": "10.60",
"min": "02.40",
"moy": "11.05",
"max": "17.40"
},
"rang": {
"value": "10",
"total": 16
}
}
}
"""
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
app.set_sco_dept(dept.acronym)
# Récupération de l'étudiant
try:
etu = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
except AttributeError:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel.\n "
"Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
)
return sco_bulletins.get_formsemestre_bulletin_etud_json(formsemestre, etu)
@bp.route(
"/etudiant/etudid/<int:etudid>/semestre/<int:formsemestre_id>/groups",
methods=["GET"],
)
@bp.route(
"/etudiant/nip/<int:nip>/semestre/<int:formsemestre_id>/groups", methods=["GET"]
)
@bp.route(
"/etudiant/ine/<int:ine>/semestre/<int:formsemestre_id>/groups", methods=["GET"]
)
@token_permission_required(Permission.APIView)
def etudiant_groups(
formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None
):
"""
Retourne la liste des groupes auxquels appartient l'étudiant dans le semestre indiqué
formsemestre_id : l'id d'un formsemestre
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
Exemple de résultat :
[
{
"partition_id": 1,
"id": 1,
"formsemestre_id": 1,
"partition_name": null,
"numero": 0,
"bul_show_rank": false,
"show_in_lists": true,
"group_id": 1,
"group_name": null
},
{
"partition_id": 2,
"id": 2,
"formsemestre_id": 1,
"partition_name": "TD",
"numero": 1,
"bul_show_rank": false,
"show_in_lists": true,
"group_id": 2,
"group_name": "A"
}
]
"""
if etudid is None:
etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
if etud is None:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel.\n "
"Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
)
etudid = etud.etudid
# Récupération du formsemestre
sem = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
dept = models.Departement.query.get(sem.dept_id)
app.set_sco_dept(dept.acronym)
data = sco_groups.get_etud_groups(etudid, sem.id)
return jsonify(data)

111
app/api/evaluations.py Normal file
View File

@ -0,0 +1,111 @@
############################################### Evaluations ###########################################################
from flask import jsonify
import app
from app import models
from app.api import bp
from app.api.auth import token_permission_required
from app.api.errors import error_response
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
from app.scodoc.sco_permissions import Permission
@bp.route("/evaluations/<int:moduleimpl_id>", methods=["GET"])
@token_permission_required(Permission.APIView)
def evaluations(moduleimpl_id: int):
"""
Retourne la liste des évaluations à partir de l'id d'un moduleimpl
moduleimpl_id : l'id d'un moduleimpl
Exemple de résultat :
[
{
"moduleimpl_id": 1,
"jour": "20/04/2022",
"heure_debut": "08h00",
"description": "eval1",
"coefficient": 1.0,
"publish_incomplete": false,
"numero": 0,
"id": 1,
"heure_fin": "09h00",
"note_max": 20.0,
"visibulletin": true,
"evaluation_type": 0,
"evaluation_id": 1,
"jouriso": "2022-04-20",
"duree": "1h",
"descrheure": " de 08h00 \u00e0 09h00",
"matin": 1,
"apresmidi": 0
},
...
]
"""
# Récupération de toutes les évaluations
evals = models.Evaluation.query.filter_by(id=moduleimpl_id)
# Mise en forme des données
data = [d.to_dict() for d in evals]
return jsonify(data)
@bp.route("/evaluations/eval_notes/<int:evaluation_id>", methods=["GET"])
@token_permission_required(Permission.APIView)
def evaluation_notes(evaluation_id: int):
"""
Retourne la liste des notes à partir de l'id d'une évaluation donnée
evaluation_id : l'id d'une évaluation
Exemple de résultat :
{
"1": {
"id": 1,
"etudid": 10,
"evaluation_id": 1,
"value": 15.0,
"comment": "",
"date": "Wed, 20 Apr 2022 06:49:05 GMT",
"uid": 2
},
"2": {
"id": 2,
"etudid": 1,
"evaluation_id": 1,
"value": 12.0,
"comment": "",
"date": "Wed, 20 Apr 2022 06:49:06 GMT",
"uid": 2
},
...
}
"""
# Fonction utilisée : app.scodoc.sco_evaluation_db.do_evaluation_get_all_notes()
eval = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404()
moduleimpl = models.ModuleImpl.query.filter_by(id=eval.moduleimpl_id).first_or_404()
formsemestre = models.FormSemestre.query.filter_by(
id=moduleimpl.formsemestre_id
).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
app.set_sco_dept(dept.acronym)
try:
# Utilisation de la fonction do_evaluation_get_all_notes
data = do_evaluation_get_all_notes(evaluation_id)
except AttributeError:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel. \n"
"Veillez vérifier la conformité du 'evaluation_id'",
)
return jsonify(data)

260
app/api/formations.py Normal file
View File

@ -0,0 +1,260 @@
##############################################" Formations ############################################################
from flask import jsonify
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.scodoc.sco_formations import formation_export
from app.scodoc.sco_permissions import Permission
@bp.route("/formations", methods=["GET"])
@token_permission_required(Permission.APIView)
def formations():
"""
Retourne la liste des formations
Exemple de résultat :
[
{
"id": 1,
"acronyme": "BUT R&amp;T",
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
"formation_code": "V1RET",
"code_specialite": null,
"dept_id": 1,
"titre": "BUT R&amp;T",
"version": 1,
"type_parcours": 700,
"referentiel_competence_id": null,
"formation_id": 1
},
...
]
"""
# Récupération de toutes les formations
list_formations = models.Formation.query.all()
# Mise en forme des données
data = [d.to_dict() for d in list_formations]
return jsonify(data)
@bp.route("/formations/<int:formation_id>", methods=["GET"])
@token_permission_required(Permission.APIView)
def formations_by_id(formation_id: int):
"""
Retourne une formation en fonction d'un id donné
formation_id : l'id d'une formation
Exemple de résultat :
{
"id": 1,
"acronyme": "BUT R&amp;T",
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
"formation_code": "V1RET",
"code_specialite": null,
"dept_id": 1,
"titre": "BUT R&amp;T",
"version": 1,
"type_parcours": 700,
"referentiel_competence_id": null,
"formation_id": 1
}
"""
# Récupération de la formation
forma = models.Formation.query.filter_by(id=formation_id).first_or_404()
# Mise en forme des données
data = forma.to_dict()
return jsonify(data)
@bp.route("/formations/formation_export/<int:formation_id>", methods=["GET"])
@token_permission_required(Permission.APIView)
def formation_export_by_formation_id(formation_id: int, export_ids=False):
"""
Retourne la formation, avec UE, matières, modules
formation_id : l'id d'une formation
Exemple de résultat :
{
"id": 1,
"acronyme": "BUT R&amp;T",
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
"formation_code": "V1RET",
"code_specialite": null,
"dept_id": 1,
"titre": "BUT R&amp;T",
"version": 1,
"type_parcours": 700,
"referentiel_competence_id": null,
"formation_id": 1,
"ue": [
{
"acronyme": "RT1.1",
"numero": 1,
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
"type": 0,
"ue_code": "UCOD11",
"ects": 12.0,
"is_external": false,
"code_apogee": "",
"coefficient": 0.0,
"semestre_idx": 1,
"color": "#B80004",
"reference": 1,
"matiere": [
{
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
"numero": 1,
"module": [
{
"titre": "Initiation aux r\u00e9seaux informatiques",
"abbrev": "Init aux r\u00e9seaux informatiques",
"code": "R101",
"heures_cours": 0.0,
"heures_td": 0.0,
"heures_tp": 0.0,
"coefficient": 1.0,
"ects": "",
"semestre_id": 1,
"numero": 10,
"code_apogee": "",
"module_type": 2,
"coefficients": [
{
"ue_reference": "1",
"coef": "12.0"
},
{
"ue_reference": "2",
"coef": "4.0"
},
{
"ue_reference": "3",
"coef": "4.0"
}
]
},
{
"titre": "Se sensibiliser \u00e0 l&apos;hygi\u00e8ne informatique et \u00e0 la cybers\u00e9curit\u00e9",
"abbrev": "Hygi\u00e8ne informatique",
"code": "SAE11",
"heures_cours": 0.0,
"heures_td": 0.0,
"heures_tp": 0.0,
"coefficient": 1.0,
"ects": "",
"semestre_id": 1,
"numero": 10,
"code_apogee": "",
"module_type": 3,
"coefficients": [
{
"ue_reference": "1",
"coef": "16.0"
}
]
},
...
]
},
...
]
},
]
}
"""
# Fonction utilité : app.scodoc.sco_formations.formation_export()
try:
# Utilisation de la fonction formation_export
data = formation_export(formation_id)
except ValueError:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel. \n"
"Veillez vérifier la conformité du 'formation_id'",
)
return jsonify(data)
@bp.route("/formations/moduleimpl/<int:moduleimpl_id>", methods=["GET"])
@token_permission_required(Permission.APIView)
def moduleimpls(moduleimpl_id: int):
"""
Retourne la liste des moduleimpl
moduleimpl_id : l'id d'un moduleimpl
"""
# Récupération des tous les moduleimpl
list_moduleimpls = models.ModuleImpl.query.filter_by(id=moduleimpl_id)
# Mise en forme des données
data = [moduleimpl.to_dict() for moduleimpl in list_moduleimpls]
return jsonify(data)
@bp.route(
"/formations/moduleimpl/formsemestre/<int:formsemestre_id>/liste",
methods=["GET"],
) # XXX TODO penser à changer la route sur la doc
@token_permission_required(Permission.APIView)
def moduleimpls_sem(formsemestre_id: int):
"""
Retourne la liste des moduleimpl d'un semestre
formsemestre_id : l'id d'un formsemestre
Exemple d'utilisation :
[
{
"id": 1,
"formsemestre_id": 1,
"computation_expr": null,
"module_id": 1,
"responsable_id": 2,
"module": {
"heures_tp": 0.0,
"code_apogee": "",
"titre": "Initiation aux r\u00e9seaux informatiques",
"coefficient": 1.0,
"module_type": 2,
"id": 1,
"ects": null,
"abbrev": "Init aux r\u00e9seaux informatiques",
"ue_id": 1,
"code": "R101",
"formation_id": 1,
"heures_cours": 0.0,
"matiere_id": 1,
"heures_td": 0.0,
"semestre_id": 1,
"numero": 10,
"module_id": 1
},
"moduleimpl_id": 1,
"ens": []
},
...
]
"""
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
moduleimpls = formsemestre.modimpls_sorted
data = [moduleimpl.to_dict() for moduleimpl in moduleimpls]
return jsonify(data)

472
app/api/formsemestres.py Normal file
View File

@ -0,0 +1,472 @@
########################################## Formsemestres ##############################################################
from flask import jsonify
import app
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.api.tools import get_etu_from_etudid_or_nip_or_ine
from app.models import FormSemestre, FormSemestreEtape
from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json
from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_pvjury import formsemestre_pvjury
@bp.route("/formsemestre/<int:formsemestre_id>", methods=["GET"])
@token_permission_required(Permission.APIView)
def formsemestre(formsemestre_id: int):
"""
Retourne l'information sur le formsemestre correspondant au formsemestre_id
formsemestre_id : l'id d'un formsemestre
Exemple de résultat :
{
"date_fin": "31/08/2022",
"resp_can_edit": false,
"dept_id": 1,
"etat": true,
"resp_can_change_ens": true,
"id": 1,
"modalite": "FI",
"ens_can_edit_eval": false,
"formation_id": 1,
"gestion_compensation": false,
"elt_sem_apo": null,
"semestre_id": 1,
"bul_hide_xml": false,
"elt_annee_apo": null,
"titre": "Semestre test",
"block_moyennes": false,
"scodoc7_id": null,
"date_debut": "01/09/2021",
"gestion_semestrielle": false,
"bul_bgcolor": "white",
"formsemestre_id": 1,
"titre_num": "Semestre test semestre 1",
"date_debut_iso": "2021-09-01",
"date_fin_iso": "2022-08-31",
"responsables": []
}
"""
# Récupération de tous les formsemestres
formsemetre = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
# Mise en forme des données
data = formsemetre.to_dict()
return jsonify(data)
@bp.route("/formsemestre/apo/<string:etape_apo>", methods=["GET"])
@token_permission_required(Permission.APIView)
def formsemestre_apo(etape_apo: str):
"""
Retourne les informations sur les formsemestres
etape_apo : l'id d'une étape apogée
"""
formsemestres = FormSemestre.query.filter(
FormSemestreEtape.etape_apo == etape_apo,
FormSemestreEtape.formsemestre_id == FormSemestre.id,
)
return jsonify([formsemestre.to_dict() for formsemestre in formsemestres])
@bp.route(
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/etudid/<int:etudid>/bulletin",
methods=["GET"],
)
@bp.route(
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/nip/<int:nip>/bulletin",
methods=["GET"],
)
@bp.route(
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/ine/<int:ine>/bulletin",
methods=["GET"],
)
@token_permission_required(Permission.APIView)
def etudiant_bulletin(
formsemestre_id,
dept,
etudid: int = None,
nip: int = None,
ine: int = None,
):
"""
Retourne le bulletin de note d'un étudiant
formsemestre_id : l'id d'un formsemestre
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
Exemple de résultat :
{
"etudid":1,
"formsemestre_id":1,
"date":"2022-04-27T10:44:47.448094",
"publie":true,
"etapes":[
],
"etudiant":{
"etudid":1,
"code_nip":"1",
"code_ine":"1",
"nom":"COSTA",
"prenom":"Sacha",
"civilite":"",
"photo_url":"/ScoDoc/TAPI/Scolarite/get_photo_image?etudid=1&amp;size=small",
"email":"SACHA.COSTA@example.com",
"emailperso":"",
"sexe":""
},
"note":{
"value":"10.60",
"min":"-",
"max":"-",
"moy":"-"
},
"rang":{
"value":"10",
"ninscrits":16
},
"rang_group":[
{
"group_type":"TD",
"group_name":"",
"value":"",
"ninscrits":""
}
],
"note_max":{
"value":20
},
"bonus_sport_culture":{
"value":0.0
},
"ue":[
{
"id":1,
"numero":"1",
"acronyme":"RT1.1",
"titre":"Administrer les r\u00e9seaux et l\u2019Internet",
"note":{
"value":"08.50",
"min":"06.00",
"max":"16.50",
"moy":"11.31"
},
"rang":"12",
"effectif":16,
"ects":"12",
"code_apogee":"",
"module":[
{
"id":1,
"code":"R101",
"coefficient":1.0,
"numero":10,
"titre":"Initiation aux r\u00e9seaux informatiques",
"abbrev":"Init aux r\u00e9seaux informatiques",
"note":{
"value":"12.00",
"moy":"-",
"max":"-",
"min":"-",
"nb_notes":"-",
"nb_missing":"-",
"nb_valid_evals":"-"
},
"code_apogee":"",
"evaluation":[
{
"jour":"2022-04-20",
"heure_debut":"08:00:00",
"heure_fin":"09:00:00",
"coefficient":1.0,
"evaluation_type":0,
"evaluation_id":1,
"description":"eval1",
"note":"12.00"
}
]
},
...
]
}
],
"ue_capitalisee":[],
"absences":{
"nbabs":2,
"nbabsjust":1
},
"appreciation":[]
}
"""
# Fonction utilisée : app.scodoc.sco_bulletins_json.make_json_formsemestre_bulletinetud()
try:
app.set_sco_dept(dept)
except:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel.\n "
"Veilliez vérifier que le nom de département est valide",
)
if etudid is None:
# Récupération de l'étudiant
try:
etu = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
etudid = etu.etudid
except AttributeError:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel.\n "
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
)
data = make_json_formsemestre_bulletinetud(formsemestre_id, etudid)
return data
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins", methods=["GET"])
@token_permission_required(Permission.APIView)
def bulletins(formsemestre_id: int):
"""
Retourne les bulletins d'un formsemestre donné
formsemestre_id : l'id d'un formesemestre
Exemple de résultat :
[
{
"version": "0",
"type": "BUT",
"date": "2022-04-27T07:18:16.450634Z",
"publie": true,
"etudiant": {
"civilite": "X",
"code_ine": "1",
"code_nip": "1",
"date_naissance": "",
"email": "SACHA.COSTA@example.com",
"emailperso": "",
"etudid": 1,
"nom": "COSTA",
"prenom": "SACHA",
"nomprenom": "Sacha COSTA",
"lieu_naissance": "",
"dept_naissance": "",
"nationalite": "",
"boursier": "",
"fiche_url": "/ScoDoc/TAPI/Scolarite/ficheEtud?etudid=1",
"photo_url": "/ScoDoc/TAPI/Scolarite/get_photo_image?etudid=1&size=small",
"id": 1,
"codepostaldomicile": "",
"paysdomicile": "",
"telephonemobile": "",
"typeadresse": "domicile",
"domicile": "",
"villedomicile": "",
"telephone": "",
"fax": "",
"description": ""
},
"formation": {
"id": 1,
"acronyme": "BUT R&amp;T",
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
"titre": "BUT R&amp;T"
},
"formsemestre_id": 1,
"etat_inscription": "I",
"options": {
"show_abs": true,
"show_abs_modules": false,
"show_ects": true,
"show_codemodules": false,
"show_matieres": false,
"show_rangs": true,
"show_ue_rangs": true,
"show_mod_rangs": true,
"show_moypromo": false,
"show_minmax": false,
"show_minmax_mod": false,
"show_minmax_eval": false,
"show_coef": true,
"show_ue_cap_details": false,
"show_ue_cap_current": true,
"show_temporary": true,
"temporary_txt": "Provisoire",
"show_uevalid": true,
"show_date_inscr": true
},
"ressources": {
"R101": {
"id": 1,
"titre": "Initiation aux r\u00e9seaux informatiques",
"code_apogee": null,
"url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=1",
"moyenne": {},
"evaluations": [
{
"id": 1,
"description": "eval1",
"date": "2022-04-20",
"heure_debut": "08:00",
"heure_fin": "09:00",
"coef": "01.00",
"poids": {
"RT1.1": 1.0,
},
"note": {
"value": "12.00",
"min": "00.00",
"max": "18.00",
"moy": "10.88"
},
"url": "/ScoDoc/TAPI/Scolarite/Notes/evaluation_listenotes?evaluation_id=1"
}
]
},
},
"saes": {
"SAE11": {
"id": 2,
"titre": "Se sensibiliser \u00e0 l&apos;hygi\u00e8ne informatique et \u00e0 la cybers\u00e9curit\u00e9",
"code_apogee": null,
"url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=2",
"moyenne": {},
"evaluations": []
},
},
"ues": {
"RT1.1": {
"id": 1,
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
"numero": 1,
"type": 0,
"color": "#B80004",
"competence": null,
"moyenne": {
"value": "08.50",
"min": "06.00",
"max": "16.50",
"moy": "11.31",
"rang": "12",
"total": 16
},
"bonus": "00.00",
"malus": "00.00",
"capitalise": null,
"ressources": {
"R101": {
"id": 1,
"coef": 12.0,
"moyenne": "12.00"
},
},
"saes": {
"SAE11": {
"id": 2,
"coef": 16.0,
"moyenne": "~"
},
},
"ECTS": {
"acquis": 0.0,
"total": 12.0
}
},
"semestre": {
"etapes": [],
"date_debut": "2021-09-01",
"date_fin": "2022-08-31",
"annee_universitaire": "2021 - 2022",
"numero": 1,
"inscription": "",
"groupes": [],
"absences": {
"injustifie": 1,
"total": 2
},
"ECTS": {
"acquis": 0,
"total": 30.0
},
"notes": {
"value": "10.60",
"min": "02.40",
"moy": "11.05",
"max": "17.40"
},
"rang": {
"value": "10",
"total": 16
}
}
},
...
]
"""
# Fonction utilisée : app.scodoc.sco_bulletins.get_formsemestre_bulletin_etud_json()
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
app.set_sco_dept(dept.acronym)
etuds = formsemestre.etuds
data = []
for etu in etuds:
bul_etu = get_formsemestre_bulletin_etud_json(formsemestre, etu)
data.append(bul_etu.json)
return jsonify(data)
@bp.route("/formsemestre/<int:formsemestre_id>/jury", methods=["GET"])
@token_permission_required(Permission.APIView)
def jury(formsemestre_id: int):
"""
Retourne le récapitulatif des décisions jury
formsemestre_id : l'id d'un formsemestre
Exemple de résultat :
"""
# Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury()
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
app.set_sco_dept(dept.acronym)
data = formsemestre_pvjury(formsemestre_id)
# try:
# # Utilisation de la fonction formsemestre_pvjury
# data = formsemestre_pvjury(formsemestre_id)
# except AttributeError:
# return error_response(
# 409,
# message="La requête ne peut être traitée en létat actuel. \n"
# "Veillez vérifier la conformité du 'formation_id'",
# )
return jsonify(data)

41
app/api/jury.py Normal file
View File

@ -0,0 +1,41 @@
#################################################### Jury #############################################################
from flask import jsonify
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.scodoc.sco_prepajury import feuille_preparation_jury
from app.scodoc.sco_pvjury import formsemestre_pvjury
@bp.route("/jury/formsemestre/<int:formsemestre_id>/preparation_jury", methods=["GET"])
# @token_permission_required(Permission.?)
def jury_preparation(formsemestre_id: int):
"""
Retourne la feuille de préparation du jury
formsemestre_id : l'id d'un formsemestre
"""
# Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury()
# Utilisation de la fonction feuille_preparation_jury
prepa_jury = feuille_preparation_jury(formsemestre_id)
return error_response(501, message="Not implemented")
@bp.route("/jury/formsemestre/<int:formsemestre_id>/decisions_jury", methods=["GET"])
# @token_permission_required(Permission.?)
def jury_decisions(formsemestre_id: int):
"""
Retourne les décisions du jury suivant un formsemestre donné
formsemestre_id : l'id d'un formsemestre
"""
# Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury()
# Utilisation de la fonction formsemestre_pvjury
decision_jury = formsemestre_pvjury(formsemestre_id)
return error_response(501, message="Not implemented")

View File

@ -36,13 +36,15 @@ from app.api import bp
from app.api import requested_format from app.api import requested_format
from app.api.auth import token_auth from app.api.auth import token_auth
from app.api.errors import error_response from app.api.errors import error_response
from app.decorators import permission_required
from app.models import Departement from app.models import Departement
from app.scodoc.sco_logos import list_logos, find_logo from app.scodoc.sco_logos import list_logos, find_logo
from app.api.auth import token_auth, token_permission_required
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
@bp.route("/logos", methods=["GET"]) @bp.route("/logos", methods=["GET"])
@token_auth.login_required @token_permission_required(Permission.APIView)
def api_get_glob_logos(): def api_get_glob_logos():
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None): if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
return error_response(401, message="accès interdit") return error_response(401, message="accès interdit")
@ -54,7 +56,7 @@ def api_get_glob_logos():
@bp.route("/logos/<string:logoname>", methods=["GET"]) @bp.route("/logos/<string:logoname>", methods=["GET"])
@token_auth.login_required @token_permission_required(Permission.APIView)
def api_get_glob_logo(logoname): def api_get_glob_logo(logoname):
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None): if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
return error_response(401, message="accès interdit") return error_response(401, message="accès interdit")
@ -70,7 +72,7 @@ def api_get_glob_logo(logoname):
@bp.route("/departements/<string:departement>/logos", methods=["GET"]) @bp.route("/departements/<string:departement>/logos", methods=["GET"])
@token_auth.login_required @token_permission_required(Permission.APIView)
def api_get_local_logos(departement): def api_get_local_logos(departement):
dept_id = Departement.from_acronym(departement).id dept_id = Departement.from_acronym(departement).id
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement): if not g.current_user.has_permission(Permission.ScoChangePreferences, departement):
@ -80,7 +82,7 @@ def api_get_local_logos(departement):
@bp.route("/departements/<string:departement>/logos/<string:logoname>", methods=["GET"]) @bp.route("/departements/<string:departement>/logos/<string:logoname>", methods=["GET"])
@token_auth.login_required @token_permission_required(Permission.APIView)
def api_get_local_logo(departement, logoname): def api_get_local_logo(departement, logoname):
# format = requested_format("jpg", ['png', 'jpg']) XXX ? # format = requested_format("jpg", ['png', 'jpg']) XXX ?
dept_id = Departement.from_acronym(departement).id dept_id = Departement.from_acronym(departement).id

150
app/api/partitions.py Normal file
View File

@ -0,0 +1,150 @@
############################################### Partitions ############################################################
from flask import jsonify
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.scodoc.sco_groups import get_group_members, setGroups, get_partitions_list
from app.scodoc.sco_permissions import Permission
@bp.route("/partitions/<int:formsemestre_id>", methods=["GET"])
@token_permission_required(Permission.APIView)
def partition(formsemestre_id: int):
"""
Retourne la liste de toutes les partitions d'un formsemestre
formsemestre_id : l'id d'un formsemestre
Exemple de résultat :
[
{
"partition_id": 2,
"id": 2,
"formsemestre_id": 1,
"partition_name": "TD",
"numero": 1,
"bul_show_rank": false,
"show_in_lists": true
},
{
"partition_id": 1,
"id": 1,
"formsemestre_id": 1,
"partition_name": null,
"numero": 0,
"bul_show_rank": false,
"show_in_lists": true
}
]
"""
# # Récupération de toutes les partitions
# partitions = models.Partition.query.filter_by(id=formsemestre_id)
#
# # Mise en forme des données
# data = [partition.to_dict() for partition in partitions]
data = get_partitions_list(formsemestre_id)
return jsonify(data)
@bp.route("/partitions/groups/<int:group_id>", methods=["GET"])
@bp.route("/partitions/groups/<int:group_id>/etat/<string:etat>", methods=["GET"])
@token_permission_required(Permission.APIView)
def etud_in_group(group_id: int, etat=None):
"""
Retourne la liste des étudiants dans un groupe
group_id : l'id d'un groupe
etat : état de l'inscription
Exemple de résultat :
[
{
"etudid": 10,
"id": 10,
"dept_id": 1,
"nom": "BOUTET",
"prenom": "Marguerite",
"nom_usuel": "",
"civilite": "F",
"date_naissance": null,
"lieu_naissance": null,
"dept_naissance": null,
"nationalite": null,
"statut": null,
"boursier": null,
"photo_filename": null,
"code_nip": "10",
"code_ine": "10",
"scodoc7_id": null,
"email": "MARGUERITE.BOUTET@example.com",
"emailperso": null,
"domicile": null,
"codepostaldomicile": null,
"villedomicile": null,
"paysdomicile": null,
"telephone": null,
"telephonemobile": null,
"fax": null,
"typeadresse": "domicile",
"description": null,
"group_id": 1,
"etat": "I",
"civilite_str": "Mme",
"nom_disp": "BOUTET",
"nomprenom": "Mme Marguerite BOUTET",
"ne": "e",
"email_default": "MARGUERITE.BOUTET@example.com"
},
...
]
"""
# Fonction utilisée : app.scodoc.sco_groups.get_group_members()
if etat is None:
data = get_group_members(group_id)
else:
data = get_group_members(group_id, etat)
if len(data) == 0:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel. \n"
"Aucun groupe ne correspond au 'group_id' renseigné",
)
return jsonify(data)
@bp.route(
"/partitions/set_groups?partition_id=<int:partition_id>&groups_lists=<int:groups_lists>&"
"groups_to_create=<int:groups_to_create>&groups_to_delete=<int:groups_to_delete>",
methods=["POST"],
)
@token_permission_required(Permission.APIEtudChangeGroups)
def set_groups(
partition_id: int, groups_lists: int, groups_to_delete: int, groups_to_create: int
):
"""
Set les groups
partition_id : l'id d'une partition
groups_lists : membres de chaque groupe existant
groups_ti_delete : les groupes à supprimer
groups_to_create : les groupes à créer
"""
# Fonction utilisée : app.scodoc.sco_groups.setGroups()
try:
# Utilisation de la fonction setGroups
setGroups(partition_id, groups_lists, groups_to_create, groups_to_delete)
return error_response(200, message="Groups set")
except ValueError:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel. \n"
"Veillez vérifier la conformité des éléments passé en paramètres",
)

253
app/api/remiser.py Normal file
View File

@ -0,0 +1,253 @@
# @bp.route("/etudiants", methods=["GET"])
# @token_permission_required(Permission.APIView)
# def etudiants():
# """
# Retourne la liste de tous les étudiants
#
# Exemple de résultat :
# {
# "civilite": "X",
# "code_ine": null,
# "code_nip": null,
# "date_naissance": null,
# "email": null,
# "emailperso": null,
# "etudid": 18,
# "nom": "MOREL",
# "prenom": "JACQUES"
# },
# {
# "civilite": "X",
# "code_ine": null,
# "code_nip": null,
# "date_naissance": null,
# "email": null,
# "emailperso": null,
# "etudid": 19,
# "nom": "FOURNIER",
# "prenom": "ANNE"
# },
# ...
# """
# # Récupération de tous les étudiants
# etu = models.Identite.query.all()
#
# # Mise en forme des données
# data = [d.to_dict_bul(include_urls=False) for d in etu]
#
# return jsonify(data)
# @bp.route(
# "/evaluations/eval_set_notes?eval_id=<int:eval_id>&etudid=<int:etudid>&note=<float:note>",
# methods=["POST"],
# )
# @bp.route(
# "/evaluations/eval_set_notes?eval_id=<int:eval_id>&nip=<int:nip>&note=<float:note>",
# methods=["POST"],
# )
# @bp.route(
# "/evaluations/eval_set_notes?eval_id=<int:eval_id>&ine=<int:ine>&note=<float:note>",
# methods=["POST"],
# )
# @token_permission_required(Permission.APIEditAllNotes)
# def evaluation_set_notes(
# eval_id: int, note: float, etudid: int = None, nip: int = None, ine: int = None
# ):
# """
# Set les notes d'une évaluation pour un étudiant donnée
#
# eval_id : l'id d'une évaluation
# note : la note à attribuer
# etudid : l'etudid d'un étudiant
# nip : le code nip d'un étudiant
# ine : le code ine d'un étudiant
# """
# # Fonction utilisée : app.scodoc.sco_saisie_notes.notes_add()
#
# # Qu'est ce qu'un user ???
# # notes_add()
# return error_response(501, message="Not implemented")
# ### Inutil en définitif ###
# @bp.route(
# "/absences/abs_signale?etudid=<int:etudid>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
# "&description=<string:description>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_signale?nip=<int:nip>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
# "&description=<string:description>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
# "&description=<string:description>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
# "&description=<string:description>&moduleimpl_id=<int:moduleimpl_id>",
# methods=["POST"],
# )
# @token_permission_required(Permission.APIAbsChange)
# def abs_signale(
# date: datetime,
# matin: bool,
# justif: bool,
# etudid: int = None,
# nip: int = None,
# ine: int = None, ### Inutil en définitif
# description: str = None,
# moduleimpl_id: int = None,
# ):
# """
# Permet d'ajouter une absence en base
#
# date : la date de l'absence
# matin : True ou False
# justif : True ou False
# etudid : l'etudid d'un étudiant
# nip: le code nip d'un étudiant
# ine : le code ine d'un étudiant
# description : description possible à ajouter sur l'absence
# moduleimpl_id : l'id d'un moduleimpl
# """
# # Fonctions utilisées : app.scodoc.sco_abs.add_absence() et app.scodoc.sco_abs.add_justif()
#
# if etudid is None:
# # Récupération de l'étudiant
# try:
# etu = get_etu_from_request(etudid, nip, ine)
# etudid = etu.etudid
# except AttributeError:
# return error_response(
# 409,
# message="La requête ne peut être traitée en létat actuel.\n "
# "Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
# )
# try:
# # Utilisation de la fonction add_absence
# add_absence(etudid, date, matin, justif, description, moduleimpl_id)
# if justif == True:
# # Utilisation de la fonction add_justif
# add_justif(etudid, date, matin, description)
# except ValueError:
# return error_response(
# 409, message="La requête ne peut être traitée en létat actuel"
# )
# @bp.route(
# "/absences/abs_annule_justif?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_annule_justif?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_annule_justif?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>",
# methods=["POST"],
# )
# @token_permission_required(Permission.APIAbsChange)
# def abs_annule_justif(
# jour: datetime, matin: str, etudid: int = None, nip: int = None, ine: int = None
# ):
# """
# Retourne un html
# jour : la date de l'absence a annulé
# matin : True ou False
# etudid : l'etudid d'un étudiant
# nip: le code nip d'un étudiant
# ine : le code ine d'un étudiant
# """
# # Fonction utilisée : app.scodoc.sco_abs.annule_justif()
# if etudid is None:
# # Récupération de l'étudiant
# try:
# etu = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
# etudid = etu.etudid
# except AttributeError:
# return error_response(
# 409,
# message="La requête ne peut être traitée en létat actuel.\n "
# "Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
# )
# try:
# # Utilisation de la fonction annule_justif
# annule_justif(etudid, jour, matin)
# except ValueError:
# return error_response(
# 409,
# message="La requête ne peut être traitée en létat actuel.\n "
# "Veilliez vérifier que le 'jour' et le 'matin' sont valides",
# )
# return error_response(200, message="OK")
# @bp.route(
# "/jury/set_decision/etudid?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
# "&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
# methods=["POST"],
# )
# @bp.route(
# "/jury/set_decision/nip?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
# "&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
# methods=["POST"],
# )
# @bp.route(
# "/jury/set_decision/ine?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
# "&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
# methods=["POST"],
# )
# # @token_permission_required(Permission.)
# def set_decision_jury(
# formsemestre_id: int,
# decision_jury: str,
# devenir_jury: str,
# assiduite: bool,
# etudid: int = None,
# nip: int = None,
# ine: int = None,
# ):
# """
# Attribuer la décision du jury et le devenir à un etudiant
#
# formsemestre_id : l'id d'un formsemestre
# decision_jury : la décision du jury
# devenir_jury : le devenir du jury
# assiduite : True ou False
# etudid : l'etudid d'un étudiant
# nip: le code nip d'un étudiant
# ine : le code ine d'un étudiant
# """
# return error_response(501, message="Not implemented")
#
#
# @bp.route(
# "/jury/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/annule_decision",
# methods=["DELETE"],
# )
# @bp.route(
# "/jury/nip/<int:nip>/formsemestre/<int:formsemestre_id>/annule_decision",
# methods=["DELETE"],
# )
# @bp.route(
# "/jury/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision",
# methods=["DELETE"],
# )
# # @token_permission_required(Permission.)
# def annule_decision_jury(
# formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None
# ):
# """
# Supprime la déciosion du jury pour un étudiant donné
#
# formsemestre_id : l'id d'un formsemestre
# etudid : l'etudid d'un étudiant
# nip: le code nip d'un étudiant
# ine : le code ine d'un étudiant
# """
# return error_response(501, message="Not implemented")

View File

@ -50,442 +50,103 @@ from app.api.errors import error_response
from app import models from app import models
from app.models import FormSemestre, FormSemestreInscription, Identite from app.models import FormSemestre, FormSemestreInscription, Identite
from app.models import ApcReferentielCompetences from app.models import ApcReferentielCompetences
from app.scodoc.sco_abs import (
annule_absence,
annule_justif,
add_absence,
add_justif,
list_abs_date,
)
from app.scodoc.sco_bulletins import formsemestre_bulletinetud_dict
from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
from app.scodoc.sco_formations import formation_export
from app.scodoc.sco_formsemestre_inscriptions import (
do_formsemestre_inscription_listinscrits,
)
from app.scodoc.sco_groups import setGroups, get_etud_groups, get_group_members
from app.scodoc.sco_logos import list_logos, find_logo, _list_dept_logos
from app.scodoc.sco_moduleimpl import moduleimpl_list
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
@bp.route("/list_depts", methods=["GET"]) # ###################################################### Logos ##########################################################
@token_auth.login_required #
def list_depts(): # # XXX TODO voir get_logo déjà existant dans app/views/scodoc.py
depts = models.Departement.query.filter_by(visible=True).all() #
data = [d.to_dict() for d in depts] # @bp.route("/logos", methods=["GET"])
return jsonify(data) # def liste_logos(format="json"):
# """
# Liste des logos définis pour le site scodoc.
@bp.route("/etudiants/courant", methods=["GET"]) # """
@token_auth.login_required # # fonction to use : list_logos()
def etudiants(): # # try:
"""Liste de tous les étudiants actuellement inscrits à un semestre # # res = list_logos()
en cours. # # except ValueError:
""" # # return error_response(409, message="La requête ne peut être traitée en létat actuel")
# Vérification de l'accès: permission Observateur sur tous les départements # #
# (c'est un exemple à compléter) # # if res is None:
if not g.current_user.has_permission(Permission.ScoObservateur, None): # # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
return error_response(401, message="accès interdit") # #
# # return res
query = db.session.query(Identite).filter( #
FormSemestreInscription.formsemestre_id == FormSemestre.id, #
FormSemestreInscription.etudid == Identite.id, #
FormSemestre.date_debut <= func.now(), # @bp.route("/logos/<string:logo_name>", methods=["GET"])
FormSemestre.date_fin >= func.now(), # def recup_logo_global(logo_name: str):
) # """
return jsonify([e.to_dict_bul(include_urls=False) for e in query]) # Retourne l'image au format png ou jpg
#
# logo_name : le nom du logo rechercher
######################## Departements ################################## # """
# # fonction to use find_logo
# # try:
@bp.route("/departements", methods=["GET"]) # # res = find_logo(logo_name)
@token_auth.login_required # # except ValueError:
def departements(): # # return error_response(409, message="La requête ne peut être traitée en létat actuel")
""" # #
Liste des ids de départements # # if res is None:
""" # # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
depts = models.Departement.query.filter_by(visible=True).all() # #
data = [d.id for d in depts] # # return res
return jsonify(data) #
#
# @bp.route("/departements/<string:dept>/logos", methods=["GET"])
@bp.route("/departements/<string:dept>/etudiants/liste/<int:sem_id>", methods=["GET"]) # def logo_dept(dept: str):
@token_auth.login_required # """
def liste_etudiants(dept, *args, sem_id): # XXX TODO A REVOIR # Liste des logos définis pour le département visé.
""" #
Liste des étudiants d'un département # dept : l'id d'un département
""" # """
# Test si le sem_id à été renseigné ou non # # fonction to use: _list_dept_logos
if sem_id is not None: # # dept_id = models.Departement.query.filter_by(acronym=dept).first()
# Récupération du/des depts # # try:
list_depts = models.Departement.query.filter( # # res = _list_dept_logos(dept_id.id)
models.Departement.acronym == dept, # # except ValueError:
models.FormSemestre.semestre_id == sem_id, # # return error_response(409, message="La requête ne peut être traitée en létat actuel")
) # #
list_etuds = [] # # if res is None:
for dept in list_depts: # # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
# Récupération des étudiants d'un département # #
x = models.Identite.query.filter(models.Identite.dept_id == dept.getId()) # # return res
for y in x: #
# Ajout des étudiants dans la liste global #
list_etuds.append(y) # @bp.route("/departement/<string:dept>/logos/<string:logo_name>", methods=["GET"])
else: # def recup_logo_dept_global(dept: str, logo_name: str):
list_depts = models.Departement.query.filter( # """
models.Departement.acronym == dept, # L'image format png ou jpg
models.FormSemestre.semestre_id == models.Departement.formsemestres, #
) # dept : l'id d'un département
list_etuds = [] # logo_name : le nom du logo rechercher
for dept in list_depts: # """
x = models.Identite.query.filter(models.Identite.dept_id == dept.getId()) # # fonction to use find_logo
for y in x: # # dept_id = models.Departement.query.filter_by(acronym=dept).first()
list_etuds.append(y) # # try:
# # res = find_logo(logo_name, dept_id.id)
data = [d.to_dict() for d in list_etuds] # # except ValueError:
# return jsonify(data) # # return error_response(409, message="La requête ne peut être traitée en létat actuel")
return error_response(501, message="Not implemented") # #
# # if res is None:
# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
@bp.route("/departements/<string:dept>/semestres_actifs", methods=["GET"]) # #
@token_auth.login_required # # return res
def liste_semestres_actifs(dept): # TODO : changer nom
"""
Liste des semestres actifs d'un départements donné
"""
# Récupération de l'id du dept
dept_id = models.Departement.query.filter(models.Departement.acronym == dept)
# Puis ici récupération du FormSemestre correspondant
depts_actifs = models.FormSemestre.query.filter_by(
etat=True,
dept_id=dept_id,
)
data = [da.to_dict() for da in depts_actifs]
# return jsonify(data)
return error_response(501, message="Not implemented")
@bp.route("/referentiel_competences/<int:referentiel_competence_id>")
@token_auth.login_required
def referentiel_competences(referentiel_competence_id):
"""
Le référentiel de compétences
"""
ref = ApcReferentielCompetences.query.get_or_404(referentiel_competence_id)
return jsonify(ref.to_dict())
####################### Etudiants ##################################
@bp.route("/etudiant/<int:etudid>", methods=["GET"])
@token_auth.login_required
def etudiant(etudid):
"""
Un dictionnaire avec les informations de l'étudiant correspondant à l'id passé en paramètres.
"""
etud: Identite = Identite.query.get_or_404(etudid)
return jsonify(etud.to_dict_bul())
@bp.route("/etudiant/<int:etudid>/semestre/<int:sem_id>/bulletin", methods=["GET"])
@token_auth.login_required
def etudiant_bulletin_semestre(etudid, sem_id):
"""
Le bulletin d'un étudiant en fonction de son id et d'un semestre donné
"""
# return jsonify(models.BulAppreciations.query.filter_by(etudid=etudid, formsemestre_id=sem_id))
return error_response(501, message="Not implemented")
@bp.route(
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/nip/<int:NIP>/releve",
methods=["GET"],
)
@bp.route(
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/id/<int:etudid>/releve",
methods=["GET"],
)
@bp.route(
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/ine/<int:numScodoc>/releve",
methods=["GET"],
)
@token_auth.login_required
def etudiant_bulletin(formsemestre_id, dept, etudid, format="json", *args, size):
"""
Un bulletin de note
"""
formsemestres = models.FormSemestre.query.filter_by(id=formsemestre_id)
depts = models.Departement.query.filter_by(acronym=dept)
etud = ""
data = []
if args[0] == "short":
pass
elif args[0] == "selectevals":
pass
elif args[0] == "long":
pass
else:
return "erreur"
# return jsonify(data)
return error_response(501, message="Not implemented")
@bp.route(
"/etudiant/<int:etudid>/semestre/<int:formsemestre_id>/groups", methods=["GET"]
)
@token_auth.login_required
def etudiant_groups(etudid: int, formsemestre_id: int):
"""
Liste des groupes auxquels appartient l'étudiant dans le semestre indiqué
"""
semestre = models.FormSemestre.query.filter_by(id=formsemestre_id)
etudiant = models.Identite.query.filter_by(id=etudid)
groups = models.Partition.query.filter(
models.Partition.formsemestre_id == semestre,
models.GroupDescr.etudiants == etudiant,
)
data = [d.to_dict() for d in groups]
# return jsonify(data)
return error_response(501, message="Not implemented")
#######################" Programmes de formations #########################
@bp.route("/formations", methods=["GET"])
@bp.route("/formations/<int:formation_id>", methods=["GET"])
@token_auth.login_required
def formations(formation_id: int):
"""
Liste des formations
"""
formations = models.Formation.query.filter_by(id=formation_id)
data = [d.to_dict() for d in formations]
# return jsonify(data)
return error_response(501, message="Not implemented")
@bp.route("/formations/formation_export/<int:formation_id>", methods=["GET"])
@token_auth.login_required
def formation_export(formation_id: int, export_ids=False):
"""
La formation, avec UE, matières, modules
"""
return error_response(501, message="Not implemented")
###################### UE #######################################
@bp.route(
"/departements/<string:dept>/formations/programme/<string:sem_id>", methods=["GET"]
)
@token_auth.login_required
def eus(dept: str, sem_id: int):
"""
Liste des UES, ressources et SAE d'un semestre
"""
return error_response(501, message="Not implemented")
######## Semestres de formation ###############
@bp.route("/formations/formsemestre/<int:formsemestre_id>", methods=["GET"])
@bp.route("/formations/apo/<int:etape_apo>", methods=["GET"])
@token_auth.login_required
def formsemestre(
id: int,
):
"""
Information sur les formsemestres
"""
return error_response(501, message="Not implemented")
############ Modules de formation ##############
@bp.route("/formations/moduleimpl/<int:moduleimpl_id>", methods=["GET"])
@bp.route(
"/formations/moduleimpl/<int:moduleimpl_id>/formsemestre/<int:formsemestre_id>",
methods=["GET"],
)
@token_auth.login_required
def moduleimpl(id: int):
"""
Liste de moduleimpl
"""
return error_response(501, message="Not implemented")
########### Groupes et partitions ###############
@bp.route("/partitions/<int:formsemestre_id>", methods=["GET"])
@token_auth.login_required
def partition(formsemestre_id: int):
"""
La liste de toutes les partitions d'un formsemestre
"""
partitions = models.Partition.query.filter_by(id=formsemestre_id)
data = [d.to_dict() for d in partitions]
# return jsonify(data)
return error_response(501, message="Not implemented")
@bp.route(
"/partitions/formsemestre/<int:formsemestre_id>/groups/group_ids?with_codes=&all_groups=&etat=",
methods=["GET"],
)
@token_auth.login_required
def groups(formsemestre_id: int, group_ids: int):
"""
Liste des étudiants dans un groupe
"""
return error_response(501, message="Not implemented")
@bp.route(
"/partitions/set_groups?partition_id=<int:partition_id>&groups=<int:groups>&groups_to_delete=<int:groups_to_delete>&groups_to_create=<int:groups_to_create>",
methods=["POST"],
)
@token_auth.login_required
def set_groups(
partition_id: int, groups: int, groups_to_delete: int, groups_to_create: int
):
"""
Set les groups
"""
return error_response(501, message="Not implemented")
####### Bulletins de notes ###########
@bp.route("/evaluations/<int:moduleimpl_id>", methods=["GET"])
@token_auth.login_required
def evaluations(moduleimpl_id: int):
"""
Liste des évaluations à partir de l'id d'un moduleimpl
"""
evals = models.Evaluation.query.filter_by(id=moduleimpl_id)
data = [d.to_dict() for d in evals]
# return jsonify(data)
return error_response(501, message="Not implemented")
@bp.route("/evaluations/eval_notes/<int:evaluation_id>", methods=["GET"])
@token_auth.login_required
def evaluation_notes(evaluation_id: int):
"""
Liste des notes à partir de l'id d'une évaluation donnée
"""
evals = models.Evaluation.query.filter_by(id=evaluation_id)
notes = evals.get_notes()
data = [d.to_dict() for d in notes]
# return jsonify(data)
return error_response(501, message="Not implemented")
@bp.route(
"/evaluations/eval_set_notes?eval_id=<int:eval_id>&etudid=<int:etudid>&note=<int:note>",
methods=["POST"],
)
@token_auth.login_required
def evaluation_set_notes(eval_id: int, etudid: int, note: float):
"""
Set les notes d'une évaluation pour un étudiant donnée
"""
return error_response(501, message="Not implemented")
############## Absences #############
@bp.route("/absences/<int:etudid>", methods=["GET"])
@bp.route("/absences/<int:etudid>/abs_just_only", methods=["GET"])
def absences(etudid: int):
"""
Liste des absences d'un étudiant donnée
"""
abs = models.Absence.query.filter_by(id=etudid)
data = [d.to_dict() for d in abs]
# return jsonify(data)
return error_response(501, message="Not implemented")
@bp.route("/absences/abs_signale", methods=["POST"])
@token_auth.login_required
def abs_signale():
"""
Retourne un html
"""
return error_response(501, message="Not implemented")
@bp.route("/absences/abs_annule", methods=["POST"])
@token_auth.login_required
def abs_annule():
"""
Retourne un html
"""
return error_response(501, message="Not implemented")
@bp.route("/absences/abs_annule_justif", methods=["POST"])
@token_auth.login_required
def abs_annule_justif():
"""
Retourne un html
"""
return error_response(501, message="Not implemented")
@bp.route(
"/absences/abs_group_etat/?group_ids=<int:group_ids>&date_debut=date_debut&date_fin=date_fin",
methods=["GET"],
)
@token_auth.login_required
def abs_groupe_etat(
group_ids: int, date_debut, date_fin, with_boursier=True, format="html"
):
"""
Liste des absences d'un ou plusieurs groupes entre deux dates
"""
return error_response(501, message="Not implemented")
################ Logos ################
@bp.route("/logos", methods=["GET"])
@token_auth.login_required
def liste_logos(format="json"):
"""
Liste des logos définis pour le site scodoc.
"""
return error_response(501, message="Not implemented")
@bp.route("/logos/<string:nom>", methods=["GET"])
@token_auth.login_required
def recup_logo_global(nom: str):
"""
Retourne l'image au format png ou jpg
"""
return error_response(501, message="Not implemented")
@bp.route("/departements/<string:dept>/logos", methods=["GET"])
@token_auth.login_required
def logo_dept(dept: str):
"""
Liste des logos définis pour le département visé.
"""
return error_response(501, message="Not implemented")
@bp.route("/departement/<string:dept>/logos/<string:nom>", methods=["GET"])
@token_auth.login_required
def recup_logo_dept_global(dept: str, nom: str):
"""
L'image format png ou jpg
"""
return error_response(501, message="Not implemented")

444
app/api/test_api.py Normal file
View File

@ -0,0 +1,444 @@
################################################## Tests ##############################################################
# XXX OBSOLETE ??? XXX
import requests
import os
from app import models
from app.api import bp, requested_format
from app.api.auth import token_auth
from app.api.errors import error_response
SCODOC_USER = "test"
SCODOC_PASSWORD = "test"
SCODOC_URL = "http://192.168.1.12:5000"
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
HEADERS = None
def get_token():
"""
Permet de set le token dans le header
"""
global HEADERS
global SCODOC_USER
global SCODOC_PASSWORD
r0 = requests.post(
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
token = r0.json()["token"]
HEADERS = {"Authorization": f"Bearer {token}"}
DEPT = None
FORMSEMESTRE = None
ETU = None
@bp.route("/test_dept", methods=["GET"])
def get_departement():
"""
Permet de tester departements() mais également de set un département dans DEPT pour la suite des tests
"""
get_token()
global HEADERS
global CHECK_CERTIFICATE
global SCODOC_USER
global SCODOC_PASSWORD
# print(HEADERS)
# departements
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
if r.status_code == 200:
dept_id = r.json()[0]
# print(dept_id)
dept = models.Departement.query.filter_by(id=dept_id).first()
dept = dept.to_dict()
fields = ["id", "acronym", "description", "visible", "date_creation"]
for field in dept:
if field not in fields:
return error_response(501, field + " field missing")
global DEPT
DEPT = dept
return error_response(200, "OK")
return error_response(409, "La requête ne peut être traitée en létat actuel")
@bp.route("/test_formsemestre", methods=["GET"])
def get_formsemestre():
"""
Permet de tester liste_semestres_courant() mais également de set un formsemestre dans FORMSEMESTRE
pour la suite des tests
"""
get_departement()
global DEPT
dept_acronym = DEPT["acronym"]
# liste_semestres_courant
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/" + dept_acronym + "/semestres_courants",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
if r.status_code == 200:
formsemestre = r.json()[0]
print(r.json()[0])
fields = [
"gestion_semestrielle",
"titre",
"scodoc7_id",
"date_debut",
"bul_bgcolor",
"date_fin",
"resp_can_edit",
"dept_id",
"etat",
"resp_can_change_ens",
"id",
"modalite",
"ens_can_edit_eval",
"formation_id",
"gestion_compensation",
"elt_sem_apo",
"semestre_id",
"bul_hide_xml",
"elt_annee_apo",
"block_moyennes",
"formsemestre_id",
"titre_num",
"date_debut_iso",
"date_fin_iso",
"responsables",
]
for field in formsemestre:
if field not in fields:
return error_response(501, field + " field missing")
global FORMSEMESTRE
FORMSEMESTRE = formsemestre
return error_response(200, "OK")
return error_response(409, "La requête ne peut être traitée en létat actuel")
@bp.route("/test_etu", methods=["GET"])
def get_etudiant():
"""
Permet de tester etudiants() mais également de set un etudiant dans ETU pour la suite des tests
"""
# etudiants
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
if r.status_code == 200:
etu = r.json()[0]
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
]
for field in etu:
if field not in fields:
return error_response(501, field + " field missing")
global ETU
ETU = etu
print(etu)
return error_response(200, "OK")
return error_response(409, "La requête ne peut être traitée en létat actuel")
############################################### Departements ##########################################################
@bp.route("/test_liste_etudiants")
def test_departements_liste_etudiants():
"""
Test la route liste_etudiants
"""
# Set un département et un formsemestre pour les tests
get_departement()
get_formsemestre()
global DEPT
global FORMSEMESTRE
# Set les fields à vérifier
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
]
# liste_etudiants (sans formsemestre)
r1 = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/etudiants/liste",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
if r1.status_code == 200: # Si la requête est "OK"
# On récupère la liste des étudiants
etudiants = r1.json()
# Vérification que tous les étudiants ont bien tous les bons champs
for etu in etudiants:
for field in etu:
if field not in fields:
return error_response(501, field + " field missing")
# liste_etudiants (avec formsemestre)
r2 = requests.get(
SCODOC_URL
+ "/ScoDoc/api/departements/"
+ DEPT["acronym"]
+ "/etudiants/liste/"
+ str(FORMSEMESTRE["formsemestre_id"]),
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
if r2.status_code == 200: # Si la requête est "OK"
# On récupère la liste des étudiants
etudiants = r2.json()
# Vérification que tous les étudiants ont bien tous les bons champs
for etu in etudiants:
for field in etu:
if field not in fields:
return error_response(501, field + " field missing")
return error_response(200, "OK")
return error_response(409, "La requête ne peut être traitée en létat actuel")
@bp.route("/test_referenciel_competences")
def test_departements_referenciel_competences():
"""
Test la route referenciel_competences
"""
get_departement()
get_formsemestre()
global DEPT
global FORMSEMESTRE
# referenciel_competences
r = requests.post(
SCODOC_URL
+ "/ScoDoc/api/departements/"
+ DEPT["acronym"]
+ "/formations/"
+ FORMSEMESTRE["formation_id"]
+ "/referentiel_competences",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
@bp.route("/test_liste_semestre_index")
def test_departements_semestre_index():
"""
Test la route semestre_index
"""
# semestre_index
r5 = requests.post(
SCODOC_URL
+ "/ScoDoc/api/departements/"
+ DEPT["acronym"]
+ "/formsemestre/"
+ FORMSEMESTRE["formation_id"]
+ "/programme",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
#################################################### Etudiants ########################################################
def test_routes_etudiants():
"""
Test les routes de la partie Etudiants
"""
# etudiants
r1 = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
# etudiants_courant
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant_formsemestres
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant_bulletin_semestre
r5 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant_groups
r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_formation():
"""
Test les routes de la partie Formation
"""
# formations
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# formations_by_id
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# formation_export_by_formation_id
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# formsemestre_apo
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# moduleimpls
r5 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# moduleimpls_sem
r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_formsemestres():
"""
Test les routes de la partie Formsemestres
"""
# formsemestre
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant_bulletin
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# bulletins
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# jury
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_partitions():
"""
Test les routes de la partie Partitions
"""
# partition
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etud_in_group
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# set_groups
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_evaluations():
"""
Test les routes de la partie Evaluations
"""
# evaluations
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# evaluation_notes
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# evaluation_set_notes
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_jury():
"""
Test les routes de la partie Jury
"""
# jury_preparation
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# jury_decisions
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# set_decision_jury
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# annule_decision_jury
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_absences():
"""
Test les routes de la partie Absences
"""
# absences
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# absences_justify
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# abs_signale
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# abs_annule
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# abs_annule_justif
r5 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# abs_groupe_etat
r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_logos():
"""
Test les routes de la partie Logos
"""
# liste_logos
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# recup_logo_global
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# logo_dept
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# recup_logo_dept_global
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))

22
app/api/tools.py Normal file
View File

@ -0,0 +1,22 @@
from app import models
def get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine):
"""
Fonction qui retourne un etudiant en fonction de l'etudid, code nip et code ine rentré en paramètres
etudid : None ou un int etudid
nip : None ou un int code_nip
ine : None ou un int code_ine
Exemple de résultat: <Itendite>
"""
if etudid is None:
if nip is None: # si ine
etud = models.Identite.query.filter_by(code_ine=str(ine)).first()
else: # si nip
etud = models.Identite.query.filter_by(code_nip=str(nip)).first()
else: # si etudid
etud = models.Identite.query.filter_by(id=etudid).first()
return etud

View File

@ -39,4 +39,12 @@ def get_dept_acronym(id):
return dept.acronym return dept.acronym
@bp.app_template_filter()
def get_civilité(civ):
if civ == "H":
return "Monsieur"
else:
return "Madame"
from app.entreprises import routes from app.entreprises import routes

View File

@ -131,38 +131,46 @@ def send_email_notifications_entreprise(subject, entreprise: Entreprise):
def verif_correspondant_data(correspondant_data): def verif_correspondant_data(correspondant_data):
""" """
Verifie les données d'une ligne Excel (correspondant) Verifie les données d'une ligne Excel (correspondant)
correspondant_data[0]: nom correspondant_data[0]: civilite
correspondant_data[1]: prenom correspondant_data[1]: nom
correspondant_data[2]: telephone correspondant_data[2]: prenom
correspondant_data[3]: mail correspondant_data[3]: telephone
correspondant_data[4]: poste correspondant_data[4]: mail
correspondant_data[5]: service correspondant_data[5]: poste
correspondant_data[6]: entreprise_id correspondant_data[6]: service
correspondant_data[7]: origine
correspondant_data[8]: notes
correspondant_data[9]: entreprise_siret
""" """
# champs obligatoires # champs obligatoires
if ( if (
correspondant_data[0].strip() == "" correspondant_data[0].strip() == ""
or correspondant_data[1].strip() == "" or correspondant_data[1].strip() == ""
or correspondant_data[6].strip() == "" or correspondant_data[2].strip() == ""
or correspondant_data[9].strip() == ""
): ):
return False return False
# civilite entre H ou F
if correspondant_data[0].strip() not in ["H", "F"]:
return False
# entreprise_id existant # entreprise_id existant
entreprise = Entreprise.query.filter_by(siret=correspondant_data[6].strip()).first() entreprise = Entreprise.query.filter_by(siret=correspondant_data[9].strip()).first()
if entreprise is None: if entreprise is None:
return False return False
# correspondant possède le meme nom et prénom dans la meme entreprise # correspondant possède le meme nom et prénom dans la meme entreprise
correspondant = EntrepriseCorrespondant.query.filter_by( correspondant = EntrepriseCorrespondant.query.filter_by(
nom=correspondant_data[0].strip(), nom=correspondant_data[1].strip(),
prenom=correspondant_data[1].strip(), prenom=correspondant_data[2].strip(),
entreprise_id=entreprise.id, entreprise_id=entreprise.id,
).first() ).first()
if correspondant is not None: if correspondant is not None:
return False return False
if ( if (
correspondant_data[2].strip() == "" and correspondant_data[3].strip() == "" correspondant_data[3].strip() == "" and correspondant_data[4].strip() == ""
): # 1 moyen de contact ): # 1 moyen de contact
return False return False
@ -182,7 +190,7 @@ def verif_entreprise_data(entreprise_data):
if data.strip() == "": if data.strip() == "":
return False return False
if EntreprisePreferences.get_check_siret(): if EntreprisePreferences.get_check_siret():
siret = entreprise_data[0].replace(" ", "") # vérification sur le siret siret = entreprise_data[0].strip().replace(" ", "") # vérification sur le siret
if re.match("^\d{14}$", siret) is None: if re.match("^\d{14}$", siret) is None:
return False return False
try: try:

View File

@ -80,6 +80,11 @@ class EntrepriseCreationForm(FlaskForm):
ville = _build_string_field("Ville de l'entreprise (*)") ville = _build_string_field("Ville de l'entreprise (*)")
pays = _build_string_field("Pays de l'entreprise", required=False) pays = _build_string_field("Pays de l'entreprise", required=False)
civilite = SelectField(
"Civilité du correspondant",
choices=[("M", "Monsieur"), ("F", "Madame")],
validators=[DataRequired(message=CHAMP_REQUIS)],
)
nom_correspondant = _build_string_field("Nom du correspondant", required=False) nom_correspondant = _build_string_field("Nom du correspondant", required=False)
prenom_correspondant = _build_string_field( prenom_correspondant = _build_string_field(
"Prénom du correspondant", required=False "Prénom du correspondant", required=False
@ -91,6 +96,8 @@ class EntrepriseCreationForm(FlaskForm):
) )
poste = _build_string_field("Poste du correspondant", required=False) poste = _build_string_field("Poste du correspondant", required=False)
service = _build_string_field("Service du correspondant", required=False) service = _build_string_field("Service du correspondant", required=False)
origine = _build_string_field("Origine du correspondant", required=False)
notes = _build_string_field("Notes sur le correspondant", required=False)
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
@ -106,6 +113,8 @@ class EntrepriseCreationForm(FlaskForm):
or self.mail.data.strip() or self.mail.data.strip()
or self.poste.data.strip() or self.poste.data.strip()
or self.service.data.strip() or self.service.data.strip()
or self.origine.data.strip()
or self.notes.data.strip()
): ):
if not self.nom_correspondant.data.strip(): if not self.nom_correspondant.data.strip():
self.nom_correspondant.errors.append("Ce champ est requis") self.nom_correspondant.errors.append("Ce champ est requis")
@ -114,19 +123,16 @@ class EntrepriseCreationForm(FlaskForm):
self.prenom_correspondant.errors.append("Ce champ est requis") self.prenom_correspondant.errors.append("Ce champ est requis")
validate = False validate = False
if not self.telephone.data.strip() and not self.mail.data.strip(): if not self.telephone.data.strip() and not self.mail.data.strip():
self.telephone.errors.append( msg = "Saisir un moyen de contact (mail ou téléphone)"
"Saisir un moyen de contact (mail ou téléphone)" self.telephone.errors.append(msg)
) self.mail.errors.append(msg)
self.mail.errors.append(
"Saisir un moyen de contact (mail ou téléphone)"
)
validate = False validate = False
return validate return validate
def validate_siret(self, siret): def validate_siret(self, siret):
if EntreprisePreferences.get_check_siret(): if EntreprisePreferences.get_check_siret():
siret_data = siret.data.replace(" ", "") siret_data = siret.data.strip().replace(" ", "")
self.siret.data = siret_data self.siret.data = siret_data
if re.match("^\d{14}$", siret_data) is None: if re.match("^\d{14}$", siret_data) is None:
raise ValidationError("Format incorrect") raise ValidationError("Format incorrect")
@ -164,6 +170,15 @@ class EntrepriseModificationForm(FlaskForm):
} }
class SiteCreationForm(FlaskForm):
nom = _build_string_field("Nom du site (*)")
adresse = _build_string_field("Adresse (*)")
codepostal = _build_string_field("Code postal (*)")
ville = _build_string_field("Ville (*)")
pays = _build_string_field("Pays", required=False)
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
class MultiCheckboxField(SelectMultipleField): class MultiCheckboxField(SelectMultipleField):
widget = ListWidget(prefix_label=False) widget = ListWidget(prefix_label=False)
option_widget = CheckboxInput() option_widget = CheckboxInput()
@ -184,11 +199,11 @@ class OffreCreationForm(FlaskForm):
"Missions (*)", validators=[DataRequired(message=CHAMP_REQUIS)] "Missions (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
) )
duree = _build_string_field("Durée (*)") duree = _build_string_field("Durée (*)")
depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int) depts = MultiCheckboxField("Départements (*)", validators=[Optional()], coerce=int)
expiration_date = DateField("Date expiration", validators=[Optional()]) expiration_date = DateField("Date expiration", validators=[Optional()])
correspondant = SelectField("Correspondant à contacté", validators=[Optional()]) correspondant = SelectField("Correspondant à contacté", validators=[Optional()])
fichier = FileField( fichier = FileField(
"Fichier (*)", "Fichier",
validators=[ validators=[
Optional(), Optional(),
FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"), FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"),
@ -237,7 +252,7 @@ class OffreModificationForm(FlaskForm):
"Missions (*)", validators=[DataRequired(message=CHAMP_REQUIS)] "Missions (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
) )
duree = _build_string_field("Durée (*)") duree = _build_string_field("Durée (*)")
depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int) depts = MultiCheckboxField("Départements (*)", validators=[Optional()], coerce=int)
expiration_date = DateField("Date expiration", validators=[Optional()]) expiration_date = DateField("Date expiration", validators=[Optional()])
correspondant = SelectField("Correspondant à contacté", validators=[Optional()]) correspondant = SelectField("Correspondant à contacté", validators=[Optional()])
submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
@ -269,6 +284,12 @@ class OffreModificationForm(FlaskForm):
class CorrespondantCreationForm(FlaskForm): class CorrespondantCreationForm(FlaskForm):
civilite = SelectField(
"Civilité (*)",
choices=[("H", "Monsieur"), ("F", "Madame")],
validators=[DataRequired(message=CHAMP_REQUIS)],
render_kw={"class": "form-control"},
)
nom = _build_string_field("Nom (*)", render_kw={"class": "form-control"}) nom = _build_string_field("Nom (*)", render_kw={"class": "form-control"})
prenom = _build_string_field("Prénom (*)", render_kw={"class": "form-control"}) prenom = _build_string_field("Prénom (*)", render_kw={"class": "form-control"})
telephone = _build_string_field( telephone = _build_string_field(
@ -285,14 +306,12 @@ class CorrespondantCreationForm(FlaskForm):
service = _build_string_field( service = _build_string_field(
"Service", required=False, render_kw={"class": "form-control"} "Service", required=False, render_kw={"class": "form-control"}
) )
# depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int) origine = _build_string_field(
"Origine", required=False, render_kw={"class": "form-control"}
# def __init__(self, *args, **kwargs): )
# super().__init__(*args, **kwargs) notes = _build_string_field(
"Notes", required=False, render_kw={"class": "form-control"}
# self.depts.choices = [ )
# (dept.id, dept.acronym) for dept in Departement.query.all()
# ]
def validate(self): def validate(self):
validate = True validate = True
@ -300,10 +319,9 @@ class CorrespondantCreationForm(FlaskForm):
validate = False validate = False
if not self.telephone.data and not self.mail.data: if not self.telephone.data and not self.mail.data:
self.telephone.errors.append( msg = "Saisir un moyen de contact (mail ou téléphone)"
"Saisir un moyen de contact (mail ou téléphone)" self.telephone.errors.append(msg)
) self.mail.errors.append(msg)
self.mail.errors.append("Saisir un moyen de contact (mail ou téléphone)")
validate = False validate = False
return validate return validate
@ -321,7 +339,7 @@ class CorrespondantsCreationForm(FlaskForm):
correspondant_list = [] correspondant_list = []
for entry in self.correspondants.entries: for entry in self.correspondants.entries:
if entry.nom.data.strip() and entry.prenom.data.strip(): if entry.nom.data and entry.prenom.data:
if ( if (
entry.nom.data.strip(), entry.nom.data.strip(),
entry.prenom.data.strip(), entry.prenom.data.strip(),
@ -351,6 +369,11 @@ class CorrespondantsCreationForm(FlaskForm):
class CorrespondantModificationForm(FlaskForm): class CorrespondantModificationForm(FlaskForm):
hidden_correspondant_id = HiddenField() hidden_correspondant_id = HiddenField()
hidden_entreprise_id = HiddenField() hidden_entreprise_id = HiddenField()
civilite = SelectField(
"Civilité (*)",
choices=[("H", "Monsieur"), ("F", "Madame")],
validators=[DataRequired(message=CHAMP_REQUIS)],
)
nom = _build_string_field("Nom (*)") nom = _build_string_field("Nom (*)")
prenom = _build_string_field("Prénom (*)") prenom = _build_string_field("Prénom (*)")
telephone = _build_string_field("Téléphone (*)", required=False) telephone = _build_string_field("Téléphone (*)", required=False)
@ -360,16 +383,10 @@ class CorrespondantModificationForm(FlaskForm):
) )
poste = _build_string_field("Poste", required=False) poste = _build_string_field("Poste", required=False)
service = _build_string_field("Service", required=False) service = _build_string_field("Service", required=False)
# depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int) origine = _build_string_field("Origine", required=False)
notes = _build_string_field("Notes", required=False)
submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
# self.depts.choices = [
# (dept.id, dept.acronym) for dept in Departement.query.all()
# ]
def validate(self): def validate(self):
validate = True validate = True
if not FlaskForm.validate(self): if not FlaskForm.validate(self):
@ -387,10 +404,9 @@ class CorrespondantModificationForm(FlaskForm):
validate = False validate = False
if not self.telephone.data and not self.mail.data: if not self.telephone.data and not self.mail.data:
self.telephone.errors.append( msg = "Saisir un moyen de contact (mail ou téléphone)"
"Saisir un moyen de contact (mail ou téléphone)" self.telephone.errors.append(msg)
) self.mail.errors.append(msg)
self.mail.errors.append("Saisir un moyen de contact (mail ou téléphone)")
validate = False validate = False
return validate return validate
@ -593,6 +609,11 @@ class SuppressionConfirmationForm(FlaskForm):
submit = SubmitField("Supprimer", render_kw=SUBMIT_MARGE) submit = SubmitField("Supprimer", render_kw=SUBMIT_MARGE)
class DesactivationConfirmationForm(FlaskForm):
notes_active = TextAreaField("Notes sur la désactivation", validators=[Optional()])
submit = SubmitField("Désactiver", render_kw=SUBMIT_MARGE)
class ValidationConfirmationForm(FlaskForm): class ValidationConfirmationForm(FlaskForm):
submit = SubmitField("Valider", render_kw=SUBMIT_MARGE) submit = SubmitField("Valider", render_kw=SUBMIT_MARGE)

View File

@ -9,14 +9,18 @@ class Entreprise(db.Model):
adresse = db.Column(db.Text) adresse = db.Column(db.Text)
codepostal = db.Column(db.Text) codepostal = db.Column(db.Text)
ville = db.Column(db.Text) ville = db.Column(db.Text)
pays = db.Column(db.Text, default="FRANCE") pays = db.Column(db.Text)
visible = db.Column(db.Boolean, default=False) visible = db.Column(db.Boolean, default=False)
correspondants = db.relationship( active = db.Column(db.Boolean, default=True)
"EntrepriseCorrespondant", notes_active = db.Column(db.Text)
sites = db.relationship(
"EntrepriseSite",
backref="entreprise", backref="entreprise",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan", cascade="all, delete-orphan",
) )
offres = db.relationship( offres = db.relationship(
"EntrepriseOffre", "EntrepriseOffre",
backref="entreprise", backref="entreprise",
@ -35,13 +39,24 @@ class Entreprise(db.Model):
} }
# class EntrepriseSite(db.Model): class EntrepriseSite(db.Model):
# __tablename__ = "are_sites" __tablename__ = "are_sites"
# id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
# entreprise_id = db.Column( entreprise_id = db.Column(
# db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade") db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
# ) )
# nom = db.Column(db.Text) nom = db.Column(db.Text)
adresse = db.Column(db.Text)
codepostal = db.Column(db.Text)
ville = db.Column(db.Text)
pays = db.Column(db.Text)
correspondants = db.relationship(
"EntrepriseCorrespondant",
backref="site",
lazy="dynamic",
cascade="all, delete-orphan",
)
class EntrepriseCorrespondant(db.Model): class EntrepriseCorrespondant(db.Model):
@ -50,23 +65,29 @@ class EntrepriseCorrespondant(db.Model):
entreprise_id = db.Column( entreprise_id = db.Column(
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade") db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
) )
# site_id = db.Column(db.Integer, db.ForeignKey("are_sites.id", ondelete="cascade")) site_id = db.Column(db.Integer, db.ForeignKey("are_sites.id", ondelete="cascade"))
civilite = db.Column(db.String(1))
nom = db.Column(db.Text) nom = db.Column(db.Text)
prenom = db.Column(db.Text) prenom = db.Column(db.Text)
telephone = db.Column(db.Text) telephone = db.Column(db.Text)
mail = db.Column(db.Text) mail = db.Column(db.Text)
poste = db.Column(db.Text) poste = db.Column(db.Text)
service = db.Column(db.Text) service = db.Column(db.Text)
origine = db.Column(db.Text)
notes = db.Column(db.Text)
def to_dict(self): def to_dict(self):
entreprise = Entreprise.query.filter_by(id=self.entreprise_id).first() entreprise = Entreprise.query.filter_by(id=self.entreprise_id).first()
return { return {
"civilite": self.civilite,
"nom": self.nom, "nom": self.nom,
"prenom": self.prenom, "prenom": self.prenom,
"telephone": self.telephone, "telephone": self.telephone,
"mail": self.mail, "mail": self.mail,
"poste": self.poste, "poste": self.poste,
"service": self.service, "service": self.service,
"origine": self.origine,
"notes": self.notes,
"entreprise_siret": entreprise.siret, "entreprise_siret": entreprise.siret,
} }
@ -161,15 +182,6 @@ class EntrepriseOffreDepartement(db.Model):
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id", ondelete="cascade")) dept_id = db.Column(db.Integer, db.ForeignKey("departement.id", ondelete="cascade"))
# class EntrepriseCorrespondantDepartement(db.Model):
# __tablename__ = "are_correspondant_departement"
# id = db.Column(db.Integer, primary_key=True)
# correspondant_id = db.Column(
# db.Integer, db.ForeignKey("are_correspondants.id", ondelete="cascade")
# )
# dept_id = db.Column(db.Integer, db.ForeignKey("departement.id", ondelete="cascade"))
class EntreprisePreferences(db.Model): class EntreprisePreferences(db.Model):
__tablename__ = "are_preferences" __tablename__ = "are_preferences"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)

View File

@ -13,8 +13,10 @@ from app.decorators import permission_required
from app.entreprises import LOGS_LEN from app.entreprises import LOGS_LEN
from app.entreprises.forms import ( from app.entreprises.forms import (
CorrespondantsCreationForm, CorrespondantsCreationForm,
DesactivationConfirmationForm,
EntrepriseCreationForm, EntrepriseCreationForm,
EntrepriseModificationForm, EntrepriseModificationForm,
SiteCreationForm,
SuppressionConfirmationForm, SuppressionConfirmationForm,
OffreCreationForm, OffreCreationForm,
OffreModificationForm, OffreModificationForm,
@ -36,6 +38,7 @@ from app.entreprises.models import (
EntrepriseCorrespondant, EntrepriseCorrespondant,
EntrepriseLog, EntrepriseLog,
EntrepriseContact, EntrepriseContact,
EntrepriseSite,
EntrepriseStageApprentissage, EntrepriseStageApprentissage,
EntrepriseEnvoiOffre, EntrepriseEnvoiOffre,
EntrepriseOffreDepartement, EntrepriseOffreDepartement,
@ -59,7 +62,7 @@ def index():
""" """
Permet d'afficher une page avec la liste des entreprises (visible) et une liste des dernières opérations Permet d'afficher une page avec la liste des entreprises (visible) et une liste des dernières opérations
""" """
entreprises = Entreprise.query.filter_by(visible=True) entreprises = Entreprise.query.filter_by(visible=True, active=True)
logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all() logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all()
return render_template( return render_template(
"entreprises/entreprises.html", "entreprises/entreprises.html",
@ -101,7 +104,7 @@ def validation():
@bp.route("/correspondants", methods=["GET"]) @bp.route("/correspondants", methods=["GET"])
@permission_required(Permission.RelationsEntreprisesView) @permission_required(Permission.RelationsEntreprisesCorrespondants)
def correspondants(): def correspondants():
""" """
Permet d'afficher une page avec la liste des correspondants des entreprises visibles et une liste des dernières opérations Permet d'afficher une page avec la liste des correspondants des entreprises visibles et une liste des dernières opérations
@ -109,7 +112,7 @@ def correspondants():
correspondants = ( correspondants = (
db.session.query(EntrepriseCorrespondant, Entreprise) db.session.query(EntrepriseCorrespondant, Entreprise)
.join(Entreprise, EntrepriseCorrespondant.entreprise_id == Entreprise.id) .join(Entreprise, EntrepriseCorrespondant.entreprise_id == Entreprise.id)
.filter_by(visible=True) .filter_by(visible=True, active=True)
) )
logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all() logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all()
return render_template( return render_template(
@ -129,9 +132,9 @@ def fiche_entreprise(id):
La fiche entreprise comporte les informations de l'entreprise, les correspondants de l'entreprise et La fiche entreprise comporte les informations de l'entreprise, les correspondants de l'entreprise et
les offres de l'entreprise. les offres de l'entreprise.
""" """
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404( entreprise = Entreprise.query.filter_by(
description=f"fiche entreprise {id} inconnue" id=id, visible=True, active=True
) ).first_or_404(description=f"fiche entreprise {id} inconnue")
offres_with_files = [] offres_with_files = []
depts = are.get_depts() depts = are.get_depts()
for offre in entreprise.offres: for offre in entreprise.offres:
@ -145,7 +148,7 @@ def fiche_entreprise(id):
offre_with_files = are.get_offre_files_and_depts(offre, depts) offre_with_files = are.get_offre_files_and_depts(offre, depts)
if offre_with_files is not None: if offre_with_files is not None:
offres_with_files.append(offre_with_files) offres_with_files.append(offre_with_files)
correspondants = entreprise.correspondants[:] sites = entreprise.sites[:]
logs = ( logs = (
EntrepriseLog.query.order_by(EntrepriseLog.date.desc()) EntrepriseLog.query.order_by(EntrepriseLog.date.desc())
.filter_by(object=id) .filter_by(object=id)
@ -163,7 +166,7 @@ def fiche_entreprise(id):
"entreprises/fiche_entreprise.html", "entreprises/fiche_entreprise.html",
title="Fiche entreprise", title="Fiche entreprise",
entreprise=entreprise, entreprise=entreprise,
correspondants=correspondants, sites=sites,
offres=offres_with_files, offres=offres_with_files,
logs=logs, logs=logs,
stages_apprentissages=stages_apprentissages, stages_apprentissages=stages_apprentissages,
@ -294,16 +297,31 @@ def add_entreprise():
) )
db.session.add(entreprise) db.session.add(entreprise)
db.session.commit() db.session.commit()
if form.nom_correspondant.data.strip():
db.session.refresh(entreprise) db.session.refresh(entreprise)
site = EntrepriseSite(
entreprise_id=entreprise.id,
nom=form.nom_entreprise.data.strip(),
adresse=form.adresse.data.strip(),
codepostal=form.codepostal.data.strip(),
ville=form.ville.data.strip(),
pays=form.pays.data.strip() if form.pays.data.strip() else "FRANCE",
)
db.session.add(site)
db.session.commit()
if form.nom_correspondant.data.strip():
db.session.refresh(site)
correspondant = EntrepriseCorrespondant( correspondant = EntrepriseCorrespondant(
entreprise_id=entreprise.id, entreprise_id=entreprise.id,
site_id=site.id,
civilite=form.civilite.data,
nom=form.nom_correspondant.data.strip(), nom=form.nom_correspondant.data.strip(),
prenom=form.prenom_correspondant.data.strip(), prenom=form.prenom_correspondant.data.strip(),
telephone=form.telephone.data.strip(), telephone=form.telephone.data.strip(),
mail=form.mail.data.strip(), mail=form.mail.data.strip(),
poste=form.poste.data.strip(), poste=form.poste.data.strip(),
service=form.service.data.strip(), service=form.service.data.strip(),
origine=form.origine.data.strip(),
notes=form.notes.data.strip(),
) )
db.session.add(correspondant) db.session.add(correspondant)
if current_user.has_permission(Permission.RelationsEntreprisesValidate, None): if current_user.has_permission(Permission.RelationsEntreprisesValidate, None):
@ -404,39 +422,27 @@ def edit_entreprise(id):
) )
@bp.route("/fiche_entreprise/delete_entreprise/<int:id>", methods=["GET", "POST"]) @bp.route("/fiche_entreprise/desactiver/<int:id>", methods=["GET", "POST"])
@permission_required(Permission.RelationsEntreprisesChange) @permission_required(Permission.RelationsEntreprisesChange)
def delete_entreprise(id): def fiche_entreprise_desactiver(id):
""" """
Permet de supprimer une entreprise de la base avec un formulaire de confirmation Permet de désactiver une entreprise
""" """
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404( entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
description=f"entreprise {id} inconnue" description=f"entreprise {id} inconnue"
) )
form = SuppressionConfirmationForm() form = DesactivationConfirmationForm()
if form.validate_on_submit(): if form.validate_on_submit():
db.session.delete(entreprise) entreprise.notes_active = form.notes_active.data.strip()
# supprime les fichiers attachés aux offres entreprise.active = False
path = os.path.join(
Config.SCODOC_VAR_DIR,
"entreprises",
f"{entreprise.id}",
)
if os.path.isdir(path):
shutil.rmtree(path)
log = EntrepriseLog(
authenticated_user=current_user.user_name,
object=entreprise.id,
text=f"Suppression de la fiche entreprise ({entreprise.nom})",
)
db.session.add(log)
db.session.commit() db.session.commit()
flash("L'entreprise a été supprimé de la liste.") flash("L'entreprise a été désactivé.")
return redirect(url_for("entreprises.index")) return redirect(url_for("entreprises.index"))
return render_template( return render_template(
"entreprises/delete_confirmation.html", "entreprises/confirmation_form.html",
title="Supression entreprise", title="Désactiver entreprise",
form=form, form=form,
info_message="Cliquez sur le bouton Désactiver pour confirmer la désactivation",
) )
@ -489,9 +495,10 @@ def delete_validation_entreprise(id):
flash("L'entreprise a été supprimé de la liste des entreprise à valider.") flash("L'entreprise a été supprimé de la liste des entreprise à valider.")
return redirect(url_for("entreprises.validation")) return redirect(url_for("entreprises.validation"))
return render_template( return render_template(
"entreprises/delete_confirmation.html", "entreprises/confirmation_form.html",
title="Supression entreprise", title="Supression entreprise",
form=form, form=form,
info_message="Cliquez sur le bouton Supprimer pour confirmer votre supression",
) )
@ -645,9 +652,10 @@ def delete_offre(id):
flash("L'offre a été supprimé de la fiche entreprise.") flash("L'offre a été supprimé de la fiche entreprise.")
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise_id)) return redirect(url_for("entreprises.fiche_entreprise", id=entreprise_id))
return render_template( return render_template(
"entreprises/delete_confirmation.html", "entreprises/confirmation_form.html",
title="Supression offre", title="Supression offre",
form=form, form=form,
info_message="Cliquez sur le bouton Supprimer pour confirmer votre supression",
) )
@ -683,26 +691,66 @@ def expired(id):
return redirect(url_for("entreprises.fiche_entreprise", id=offre.entreprise_id)) return redirect(url_for("entreprises.fiche_entreprise", id=offre.entreprise_id))
@bp.route("/fiche_entreprise/<int:id>/add_correspondant", methods=["GET", "POST"]) @bp.route(
"/fiche_entreprise/<int:id>/add_site",
methods=["GET", "POST"],
)
@permission_required(Permission.RelationsEntreprisesChange) @permission_required(Permission.RelationsEntreprisesChange)
def add_correspondant(id): def add_site(id):
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
description=f"entreprise {id} inconnue"
)
form = SiteCreationForm()
if form.validate_on_submit():
site = EntrepriseSite(
entreprise_id=entreprise.id,
nom=form.nom.data.strip(),
adresse=form.adresse.data.strip(),
codepostal=form.codepostal.data.strip(),
ville=form.ville.data.strip(),
pays=form.pays.data.strip() if form.pays.data.strip() else "FRANCE",
)
db.session.add(site)
db.session.commit()
flash("Le site a été créé et ajouté à la fiche entreprise")
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
return render_template(
"entreprises/form.html",
title="Ajout site",
form=form,
)
@bp.route(
"/fiche_entreprise/<int:id_entreprise>/add_correspondant/<int:id_site>",
methods=["GET", "POST"],
)
@permission_required(Permission.RelationsEntreprisesChange)
def add_correspondant(id_entreprise, id_site):
""" """
Permet d'ajouter un correspondant a une entreprise Permet d'ajouter un correspondant a une entreprise
""" """
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404( entreprise = Entreprise.query.filter_by(
description=f"entreprise {id} inconnue" id=id_entreprise, visible=True
).first_or_404(description=f"entreprise {id_entreprise} inconnue")
site = EntrepriseSite.query.filter_by(id=id_site).first_or_404(
description=f"site {id_site} inconnue"
) )
form = CorrespondantsCreationForm(hidden_entreprise_id=entreprise.id) form = CorrespondantsCreationForm(hidden_entreprise_id=entreprise.id)
if form.validate_on_submit(): if form.validate_on_submit():
for correspondant_entry in form.correspondants.entries: for correspondant_entry in form.correspondants.entries:
correspondant = EntrepriseCorrespondant( correspondant = EntrepriseCorrespondant(
entreprise_id=entreprise.id, entreprise_id=entreprise.id,
site_id=site.id,
civilite=correspondant_entry.civilite.data,
nom=correspondant_entry.nom.data.strip(), nom=correspondant_entry.nom.data.strip(),
prenom=correspondant_entry.prenom.data.strip(), prenom=correspondant_entry.prenom.data.strip(),
telephone=correspondant_entry.telephone.data.strip(), telephone=correspondant_entry.telephone.data.strip(),
mail=correspondant_entry.mail.data.strip(), mail=correspondant_entry.mail.data.strip(),
poste=correspondant_entry.poste.data.strip(), poste=correspondant_entry.poste.data.strip(),
service=correspondant_entry.service.data.strip(), service=correspondant_entry.service.data.strip(),
origine=correspondant_entry.origine.data.strip(),
notes=correspondant_entry.notes.data.strip(),
) )
log = EntrepriseLog( log = EntrepriseLog(
authenticated_user=current_user.user_name, authenticated_user=current_user.user_name,
@ -735,12 +783,15 @@ def edit_correspondant(id):
hidden_correspondant_id=correspondant.id, hidden_correspondant_id=correspondant.id,
) )
if form.validate_on_submit(): if form.validate_on_submit():
correspondant.civilite = form.civilite.data
correspondant.nom = form.nom.data.strip() correspondant.nom = form.nom.data.strip()
correspondant.prenom = form.prenom.data.strip() correspondant.prenom = form.prenom.data.strip()
correspondant.telephone = form.telephone.data.strip() correspondant.telephone = form.telephone.data.strip()
correspondant.mail = form.mail.data.strip() correspondant.mail = form.mail.data.strip()
correspondant.poste = form.poste.data.strip() correspondant.poste = form.poste.data.strip()
correspondant.service = form.service.data.strip() correspondant.service = form.service.data.strip()
correspondant.origine = form.origine.data.strip()
correspondant.notes = form.notes.data.strip()
log = EntrepriseLog( log = EntrepriseLog(
authenticated_user=current_user.user_name, authenticated_user=current_user.user_name,
object=correspondant.entreprise_id, object=correspondant.entreprise_id,
@ -750,15 +801,18 @@ def edit_correspondant(id):
db.session.commit() db.session.commit()
flash("Le correspondant a été modifié.") flash("Le correspondant a été modifié.")
return redirect( return redirect(
url_for("entreprises.fiche_entreprise", id=correspondant.entreprise.id) url_for("entreprises.fiche_entreprise", id=correspondant.entreprise_id)
) )
elif request.method == "GET": elif request.method == "GET":
form.civilite.data = correspondant.civilite
form.nom.data = correspondant.nom form.nom.data = correspondant.nom
form.prenom.data = correspondant.prenom form.prenom.data = correspondant.prenom
form.telephone.data = correspondant.telephone form.telephone.data = correspondant.telephone
form.mail.data = correspondant.mail form.mail.data = correspondant.mail
form.poste.data = correspondant.poste form.poste.data = correspondant.poste
form.service.data = correspondant.service form.service.data = correspondant.service
form.origine.data = correspondant.origine
form.notes.data = correspondant.notes
return render_template( return render_template(
"entreprises/form.html", "entreprises/form.html",
title="Modification correspondant", title="Modification correspondant",
@ -790,9 +844,10 @@ def delete_correspondant(id):
url_for("entreprises.fiche_entreprise", id=correspondant.entreprise_id) url_for("entreprises.fiche_entreprise", id=correspondant.entreprise_id)
) )
return render_template( return render_template(
"entreprises/delete_confirmation.html", "entreprises/confirmation_form.html",
title="Supression correspondant", title="Supression correspondant",
form=form, form=form,
info_message="Cliquez sur le bouton Supprimer pour confirmer votre supression",
) )
@ -1016,9 +1071,10 @@ def delete_stage_apprentissage(id):
) )
) )
return render_template( return render_template(
"entreprises/delete_confirmation.html", "entreprises/confirmation_form.html",
title="Supression stage/apprentissage", title="Supression stage/apprentissage",
form=form, form=form,
info_message="Cliquez sur le bouton Supprimer pour confirmer votre supression",
) )
@ -1243,12 +1299,15 @@ def export_correspondants():
) )
if correspondants: if correspondants:
keys = [ keys = [
"civilite",
"nom", "nom",
"prenom", "prenom",
"telephone", "telephone",
"mail", "mail",
"poste", "poste",
"service", "service",
"origine",
"notes",
"entreprise_siret", "entreprise_siret",
] ]
titles = keys[:] titles = keys[:]
@ -1272,12 +1331,15 @@ def get_import_correspondants_file_sample():
Permet de récupérer un fichier exemple vide pour pouvoir importer des correspondants Permet de récupérer un fichier exemple vide pour pouvoir importer des correspondants
""" """
keys = [ keys = [
"civilite",
"nom", "nom",
"prenom", "prenom",
"telephone", "telephone",
"mail", "mail",
"poste", "poste",
"service", "service",
"origine",
"notes",
"entreprise_siret", "entreprise_siret",
] ]
titles = keys[:] titles = keys[:]
@ -1306,12 +1368,15 @@ def import_correspondants():
correspondant_list = [] correspondant_list = []
ligne = 0 ligne = 0
titles = [ titles = [
"civilite",
"nom", "nom",
"prenom", "prenom",
"telephone", "telephone",
"mail", "mail",
"poste", "poste",
"service", "service",
"origine",
"notes",
"entreprise_siret", "entreprise_siret",
] ]
if data[1][0] != titles: if data[1][0] != titles:
@ -1326,29 +1391,32 @@ def import_correspondants():
if ( if (
are.verif_correspondant_data(correspondant_data) are.verif_correspondant_data(correspondant_data)
and ( and (
correspondant_data[0].strip(),
correspondant_data[1].strip(), correspondant_data[1].strip(),
correspondant_data[6].strip(), correspondant_data[2].strip(),
correspondant_data[9].strip(),
) )
not in correspondant_list not in correspondant_list
): ):
correspondant_list.append( correspondant_list.append(
( (
correspondant_data[0].strip(),
correspondant_data[1].strip(), correspondant_data[1].strip(),
correspondant_data[6].strip(), correspondant_data[2].strip(),
correspondant_data[9].strip(),
) )
) )
entreprise = Entreprise.query.filter_by( entreprise = Entreprise.query.filter_by(
siret=correspondant_data[6].strip() siret=correspondant_data[9].strip()
).first() ).first()
correspondant = EntrepriseCorrespondant( correspondant = EntrepriseCorrespondant(
nom=correspondant_data[0].strip(), civilite=correspondant_data[0].strip(),
prenom=correspondant_data[1].strip(), nom=correspondant_data[1].strip(),
telephone=correspondant_data[2].strip(), prenom=correspondant_data[2].strip(),
mail=correspondant_data[3].strip(), telephone=correspondant_data[3].strip(),
poste=correspondant_data[4].strip(), mail=correspondant_data[4].strip(),
service=correspondant_data[5].strip(), poste=correspondant_data[5].strip(),
service=correspondant_data[6].strip(),
origine=correspondant_data[7].strip(),
notes=correspondant_data[8].strip(),
entreprise_id=entreprise.id, entreprise_id=entreprise.id,
) )
correspondants_import.append(correspondant) correspondants_import.append(correspondant)
@ -1480,9 +1548,10 @@ def delete_offre_file(offre_id, filedir):
url_for("entreprises.fiche_entreprise", id=offre.entreprise_id) url_for("entreprises.fiche_entreprise", id=offre.entreprise_id)
) )
return render_template( return render_template(
"entreprises/delete_confirmation.html", "entreprises/confirmation_form.html",
title="Suppression fichier d'une offre", title="Suppression fichier d'une offre",
form=form, form=form,
info_message="Cliquez sur le bouton Supprimer pour confirmer votre supression",
) )

View File

@ -27,6 +27,20 @@ class Absence(db.Model):
# XXX TODO: contrainte ajoutée: vérifier suppression du module # XXX TODO: contrainte ajoutée: vérifier suppression du module
# (mettre à NULL sans supprimer) # (mettre à NULL sans supprimer)
def to_dict(self):
data = {
"id": self.id,
"etudid": self.etudid,
"jour": self.jour,
"estabs": self.estabs,
"estjust": self.estjust,
"matin": self.matin,
"description": self.description,
"entry_date": self.entry_date,
"moduleimpl_id": self.moduleimpl_id,
}
return data
class AbsenceNotification(db.Model): class AbsenceNotification(db.Model):
"""Notification d'absence émise""" """Notification d'absence émise"""

View File

@ -303,12 +303,14 @@ def YearTable(
return "\n".join(T) return "\n".join(T)
def list_abs_in_range(etudid, debut, fin, matin=None, moduleimpl_id=None, cursor=None): def list_abs_in_range(
etudid, debut=None, fin=None, matin=None, moduleimpl_id=None, cursor=None
):
"""Liste des absences entre deux dates. """Liste des absences entre deux dates.
Args: Args:
etudid: etudid:
debut: string iso date ("2020-03-12") debut: string iso date ("2020-03-12") ou None
end: string iso date ("2020-03-12") end: string iso date ("2020-03-12")
matin: None, True, False matin: None, True, False
moduleimpl_id: restreint le comptage aux absences dans ce module moduleimpl_id: restreint le comptage aux absences dans ce module
@ -335,9 +337,13 @@ def list_abs_in_range(etudid, debut, fin, matin=None, moduleimpl_id=None, cursor
AND A.ESTABS""" AND A.ESTABS"""
+ ismatin + ismatin
+ modul + modul
+ """ + (
""
if debut is None
else """
AND A.JOUR BETWEEN %(debut)s AND %(fin)s AND A.JOUR BETWEEN %(debut)s AND %(fin)s
""", """
),
{ {
"etudid": etudid, "etudid": etudid,
"debut": debut, "debut": debut,
@ -412,22 +418,31 @@ WHERE A.ETUDID = %(etudid)s
return res return res
def list_abs_date(etudid, beg_date, end_date): def list_abs_date(etudid, beg_date=None, end_date=None):
"""Liste des absences et justifs entre deux dates (inclues).""" """Liste des absences et justifs entre deux dates (inclues)."""
print("On rentre")
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"""SELECT jour, matin, estabs, estjust, description FROM ABSENCES A req = """SELECT jour, matin, estabs, estjust, description
WHERE A.ETUDID = %(etudid)s FROM ABSENCES A
WHERE A.ETUDID = %(etudid)s""" + (
""
if beg_date is None
else """
AND A.jour >= %(beg_date)s AND A.jour >= %(beg_date)s
AND A.jour <= %(end_date)s AND A.jour <= %(end_date)s
""", """
)
cursor.execute(
req,
vars(), vars(),
) )
Abs = cursor.dictfetchall()
absences = cursor.dictfetchall()
# remove duplicates # remove duplicates
A = {} # { (jour, matin) : abs } A = {} # { (jour, matin) : abs }
for a in Abs: for a in absences:
jour, matin = a["jour"], a["matin"] jour, matin = a["jour"], a["matin"]
if (jour, matin) in A: if (jour, matin) in A:
# garde toujours la description # garde toujours la description

View File

@ -47,6 +47,13 @@ _SCO_PERMISSIONS = (
), ),
(1 << 25, "RelationsEntreprisesSend", "Envoyer des offres"), (1 << 25, "RelationsEntreprisesSend", "Envoyer des offres"),
(1 << 26, "RelationsEntreprisesValidate", "Valide les entreprises"), (1 << 26, "RelationsEntreprisesValidate", "Valide les entreprises"),
(1 << 27, "RelationsEntreprisesCorrespondants", "Voir les correspondants"),
# 27 à 39 ... réservé pour "entreprises"
# Api scodoc9
(1 << 40, "APIView", "Voir"),
(1 << 41, "APIEtudChangeGroups", "Modifier les groupes"),
(1 << 42, "APIEditAllNotes", "Modifier toutes les notes"),
(1 << 43, "APIAbsChange", "Saisir des absences"),
) )

View File

@ -53,6 +53,7 @@ SCO_ROLES_DEFAULTS = {
p.ScoUsersAdmin, p.ScoUsersAdmin,
p.ScoUsersView, p.ScoUsersView,
p.ScoView, p.ScoView,
p.APIView,
), ),
# RespPE est le responsable poursuites d'études # RespPE est le responsable poursuites d'études
# il peut ajouter des tags sur les formations: # il peut ajouter des tags sur les formations:
@ -62,7 +63,11 @@ SCO_ROLES_DEFAULTS = {
# ObservateurEntreprise est un observateur de l'application entreprise # ObservateurEntreprise est un observateur de l'application entreprise
"ObservateurEntreprise": (p.RelationsEntreprisesView,), "ObservateurEntreprise": (p.RelationsEntreprisesView,),
# UtilisateurEntreprise est un utilisateur de l'application entreprise (droit de modification) # UtilisateurEntreprise est un utilisateur de l'application entreprise (droit de modification)
"UtilisateurEntreprise": (p.RelationsEntreprisesView, p.RelationsEntreprisesChange), "UtilisateurEntreprise": (
p.RelationsEntreprisesView,
p.RelationsEntreprisesChange,
p.RelationsEntreprisesCorrespondants,
),
# AdminEntreprise est un admin de l'application entreprise (toutes les actions possibles de l'application) # AdminEntreprise est un admin de l'application entreprise (toutes les actions possibles de l'application)
"AdminEntreprise": ( "AdminEntreprise": (
p.RelationsEntreprisesView, p.RelationsEntreprisesView,
@ -70,7 +75,10 @@ SCO_ROLES_DEFAULTS = {
p.RelationsEntreprisesExport, p.RelationsEntreprisesExport,
p.RelationsEntreprisesSend, p.RelationsEntreprisesSend,
p.RelationsEntreprisesValidate, p.RelationsEntreprisesValidate,
p.RelationsEntreprisesCorrespondants,
), ),
# LecteurAPI peut utiliser l'API en lecture
"LecteurAPI": (p.APIView,),
# Super Admin est un root: création/suppression de départements # Super Admin est un root: création/suppression de départements
# _tous_ les droits # _tous_ les droits
# Afin d'avoir tous les droits, il ne doit pas être asscoié à un département # Afin d'avoir tous les droits, il ne doit pas être asscoié à un département
@ -85,4 +93,5 @@ ROLES_ATTRIBUABLES_SCODOC = (
"ObservateurEntreprise", "ObservateurEntreprise",
"UtilisateurEntreprise", "UtilisateurEntreprise",
"AdminEntreprise", "AdminEntreprise",
"LecteurAPI",
) )

View File

@ -40,6 +40,11 @@
*background-color: #151515; *background-color: #151515;
} }
.btn-remove {
margin-top: 5px;
margin-bottom: 5px;
}
.fiche-entreprise .btn { .fiche-entreprise .btn {
margin-top: 5px; margin-top: 5px;
margin-bottom: 5px; margin-bottom: 5px;
@ -54,23 +59,24 @@
margin-bottom: -5px; margin-bottom: -5px;
} }
.entreprise, .correspondant, .offre { .entreprise, .correspondant, .offre, .site{
border: solid 2px; border: solid 2px;
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
margin-bottom: 10px; margin-bottom: 10px;
margin-top: 10px;
} }
.correspondants-et-offres { .sites-et-offres {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.correspondants-et-offres > div { .sites-et-offres > div {
flex: 1 0 0; flex: 1 0 0;
} }
.correspondants-et-offres > div:nth-child(2) { .sites-et-offres > div:nth-child(2) {
margin-left: 20px; margin-left: 20px;
} }

View File

@ -1,6 +1,7 @@
{# -*- mode: jinja-html -*- #} {# -*- mode: jinja-html -*- #}
<div class="correspondant"> <div class="correspondant">
<div> <div>
Civilité : {{ correspondant.civilite|get_civilité }}<br>
Nom : {{ correspondant.nom }}<br> Nom : {{ correspondant.nom }}<br>
Prénom : {{ correspondant.prenom }}<br> Prénom : {{ correspondant.prenom }}<br>
{% if correspondant.telephone %} {% if correspondant.telephone %}
@ -15,6 +16,12 @@
{% if correspondant.service %} {% if correspondant.service %}
Service : {{ correspondant.service }}<br> Service : {{ correspondant.service }}<br>
{% endif %} {% endif %}
{% if correspondant.origine %}
Origine : {{ correspondant.origine }}<br>
{% endif %}
{% if correspondant.notes %}
Notes : {{ correspondant.notes }}<br>
{% endif %}
</div> </div>
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %} {% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}

View File

@ -1,6 +1,6 @@
{# -*- mode: jinja-html -*- #} {# -*- mode: jinja-html -*- #}
<div class="offre"> <div class="offre">
<div> <div style="word-break:break-all; text-align: justify;">
Ajouté le {{ offre[0].date_ajout.strftime('%d/%m/%y') }} à {{ offre[0].date_ajout.strftime('%Hh%M') }}<br> Ajouté le {{ offre[0].date_ajout.strftime('%d/%m/%y') }} à {{ offre[0].date_ajout.strftime('%Hh%M') }}<br>
Intitulé : {{ offre[0].intitule }}<br> Intitulé : {{ offre[0].intitule }}<br>
Description : {{ offre[0].description }}<br> Description : {{ offre[0].description }}<br>

View File

@ -35,6 +35,14 @@
</div> </div>
<script> <script>
let allCorrepondantsFieldWrapper = document.getElementById('correspondants');
let allCorrepondantsForm = allCorrepondantsFieldWrapper.getElementsByTagName('li');
for(let i = 0; i < allCorrepondantsForm.length; i++) {
let form_id = allCorrepondantsForm[i].getElementsByTagName('table')[0].id
if(form_id.split('-')[1] != 0)
allCorrepondantsForm[i].insertAdjacentHTML('beforeend', `<div class="btn btn-default btn-remove" onclick="deleteForm('${form_id}')">Retirer ce correspondant</div>`)
}
window.onload = function(e) { window.onload = function(e) {
let addCorrespondantFieldBtn = document.getElementById('add-correspondant-field'); let addCorrespondantFieldBtn = document.getElementById('add-correspondant-field');
addCorrespondantFieldBtn.addEventListener('click', function(e){ addCorrespondantFieldBtn.addEventListener('click', function(e){
@ -48,9 +56,16 @@
} }
let newFieldName = `correspondants-${Math.max(...correspondantInputIds) + 1}`; let newFieldName = `correspondants-${Math.max(...correspondantInputIds) + 1}`;
allCorrepondantsFieldWrapper.insertAdjacentHTML('beforeend',` allCorrepondantsFieldWrapper.insertAdjacentHTML('beforeend',`
<li><label for="${newFieldName}">Correspondants-${Math.max(...correspondantInputIds) + 1}</label> <table id="${newFieldName}"><tr><th><label for="${newFieldName}-nom">Nom (*)</label></th><td><input class="form-control" id="${newFieldName}-nom" name="${newFieldName}-nom" required type="text" value=""></td></tr><tr><th><label for="${newFieldName}-prenom">Prénom (*)</label></th><td><input class="form-control" id="${newFieldName}-prenom" name="${newFieldName}-prenom" required type="text" value=""></td></tr><tr><th><label for="${newFieldName}-telephone">Téléphone (*)</label></th><td><input class="form-control" id="${newFieldName}-telephone" name="${newFieldName}-telephone" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-mail">Mail (*)</label></th><td><input class="form-control" id="${newFieldName}-mail" name="${newFieldName}-mail" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-poste">Poste</label></th><td><input class="form-control" id="${newFieldName}-poste" name="${newFieldName}-poste" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-service">Service</label></th><td><input class="form-control" id="${newFieldName}-service" name="${newFieldName}-service" type="text" value=""></td></tr></table><input id="${newFieldName}-csrf_token" name="${newFieldName}-csrf_token" type="hidden" value=${csrf_token}></li> <li><label for="${newFieldName}">Correspondants-${Math.max(...correspondantInputIds) + 1}</label><table id="${newFieldName}"><tr><th><label for="${newFieldName}-civilite">Civilité (*)</label></th><td><select class="form-control" id="${newFieldName}-civilite" name="${newFieldName}-civilite" required><option value="H">Monsieur</option><option value="F">Madame</option></select></td></tr><tr><th><label for="${newFieldName}-nom">Nom (*)</label></th><td><input class="form-control" id="${newFieldName}-nom" name="${newFieldName}-nom" required type="text" value=""></td></tr><tr><th><label for="${newFieldName}-prenom">Prénom (*)</label></th><td><input class="form-control" id="${newFieldName}-prenom" name="${newFieldName}-prenom" required type="text" value=""></td></tr><tr><th><label for="${newFieldName}-telephone">Téléphone (*)</label></th><td><input class="form-control" id="${newFieldName}-telephone" name="${newFieldName}-telephone" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-mail">Mail (*)</label></th><td><input class="form-control" id="${newFieldName}-mail" name="${newFieldName}-mail" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-poste">Poste</label></th><td><input class="form-control" id="${newFieldName}-poste" name="${newFieldName}-poste" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-service">Service</label></th><td><input class="form-control" id="${newFieldName}-service" name="${newFieldName}-service" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-origine">Origine</label></th><td><input class="form-control" id="${newFieldName}-origine" name="${newFieldName}-origine" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-notes">Notes</label></th><td><input class="form-control" id="${newFieldName}-notes" name="${newFieldName}-notes" type="text" value=""></td></tr></table><input id="${newFieldName}-csrf_token" name="${newFieldName}-csrf_token" type="hidden" value=${csrf_token}><div class="btn btn-default btn-remove" onclick="deleteForm('${newFieldName}')">Retirer ce correspondant</div></li>
`); `);
}); });
} }
function deleteForm(x) {
var li_form = document.querySelector(`label[for=${x}]`).closest('li');
if (li_form) {
li_form.remove()
}
}
</script> </script>
{% endblock %} {% endblock %}

View File

@ -5,7 +5,7 @@
{% block app_content %} {% block app_content %}
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
<br> <br>
<div style="color:red;">Cliquez sur le bouton Supprimer pour confirmer votre supression</div> <div style="color:red;">{{ info_message }}</div>
<br> <br>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">

View File

@ -67,7 +67,7 @@
</a> </a>
<ul class="dropdown-menu pull-left"> <ul class="dropdown-menu pull-left">
<li><a href="{{ url_for('entreprises.edit_entreprise', id=entreprise.id) }}">Modifier</a></li> <li><a href="{{ url_for('entreprises.edit_entreprise', id=entreprise.id) }}">Modifier</a></li>
<li><a href="{{ url_for('entreprises.delete_entreprise', id=entreprise.id) }}" style="color:red">Supprimer</a></li> <li><a href="{{ url_for('entreprises.fiche_entreprise_desactiver', id=entreprise.id)}}" style="color:red">Désactiver</a></li>
</ul> </ul>
</div> </div>
</td> </td>

View File

@ -36,6 +36,14 @@
</div> </div>
<script> <script>
let allResponsablesFieldWrapper = document.getElementById('responsables');
let allResponsablesField = allResponsablesFieldWrapper.getElementsByTagName('li');
for(let i = 0; i < allResponsablesField.length; i++) {
let form_id = allResponsablesField[i].getElementsByTagName('input')[0].id
if(form_id.split('-')[1] != 0)
allResponsablesField[i].insertAdjacentHTML('beforeend', `<div class="btn btn-default btn-remove" onclick="deleteForm('${form_id}')">Retirer</div>`)
}
window.onload = function(e) { window.onload = function(e) {
var responsables_options = { var responsables_options = {
script: "/ScoDoc/entreprises/responsables?", script: "/ScoDoc/entreprises/responsables?",
@ -63,10 +71,17 @@
} }
let newFieldName = `responsables-${Math.max(...responsableInputIds) + 1}`; let newFieldName = `responsables-${Math.max(...responsableInputIds) + 1}`;
allResponsablesFieldWrapper.insertAdjacentHTML('beforeend',` allResponsablesFieldWrapper.insertAdjacentHTML('beforeend',`
<li><label for="${newFieldName}">Responsable (*)</label> <input class="form-control" id="${newFieldName}" name="${newFieldName}" type="text" value="" placeholder="Tapez le nom du responsable de formation"></li> <li><label for="${newFieldName}">Responsable (*)</label><input class="form-control" id="${newFieldName}" name="${newFieldName}" type="text" value="" placeholder="Tapez le nom du responsable de formation"><div class="btn btn-default btn-remove" onclick="deleteForm('${newFieldName}')">Retirer</div></li>
`); `);
var as_r = new bsn.AutoSuggest(newFieldName, responsables_options); var as_r = new bsn.AutoSuggest(newFieldName, responsables_options);
}); });
} }
function deleteForm(x) {
var li_form = document.querySelector(`label[for=${x}]`).closest('li');
if (li_form) {
li_form.remove()
}
}
</script> </script>
{% endblock %} {% endblock %}

View File

@ -37,25 +37,39 @@
</div> </div>
</div> </div>
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
<div> <div>
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
<a class="btn btn-primary" href="{{ url_for('entreprises.edit_entreprise', id=entreprise.id) }}">Modifier</a> <a class="btn btn-primary" href="{{ url_for('entreprises.edit_entreprise', id=entreprise.id) }}">Modifier</a>
<a class="btn btn-danger" href="{{ url_for('entreprises.delete_entreprise', id=entreprise.id) }}">Supprimer</a> <a class="btn btn-danger" href="{{ url_for('entreprises.fiche_entreprise_desactiver', id=entreprise.id) }}">Désactiver</a>
<a class="btn btn-primary" href="{{ url_for('entreprises.add_site', id=entreprise.id) }}">Ajouter site</a>
<a class="btn btn-primary" href="{{ url_for('entreprises.add_offre', id=entreprise.id) }}">Ajouter offre</a> <a class="btn btn-primary" href="{{ url_for('entreprises.add_offre', id=entreprise.id) }}">Ajouter offre</a>
<a class="btn btn-primary" href="{{ url_for('entreprises.add_correspondant', id=entreprise.id) }}">Ajouter correspondant</a>
{% endif %} {% endif %}
<a class="btn btn-primary" href="{{ url_for('entreprises.contacts', id=entreprise.id) }}">Liste contacts</a> <a class="btn btn-primary" href="{{ url_for('entreprises.contacts', id=entreprise.id) }}">Liste contacts</a>
<a class="btn btn-primary" href="{{ url_for('entreprises.offres_expirees', id=entreprise.id) }}">Voir les offres expirées</a> <a class="btn btn-primary" href="{{ url_for('entreprises.offres_expirees', id=entreprise.id) }}">Voir les offres expirées</a>
<div> </div>
<div class="correspondants-et-offres"> <div class="sites-et-offres">
{% if correspondants %} {% if sites %}
<div> <div>
<h3>Correspondants</h3> <h3>Sites</h3>
{% for correspondant in correspondants %} {% for site in sites %}
<div class="site">
Nom : {{ site.nom }}<br>
Adresse : {{ site.adresse }}<br>
Code postal : {{ site.codepostal }}<br>
Ville : {{ site.ville }}<br>
Pays : {{ site.pays }}
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
<br><a class="btn btn-primary" href="{{ url_for('entreprises.add_correspondant', id_entreprise=entreprise.id, id_site=site.id) }}">Ajouter correspondant</a>
{% endif %}
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesCorrespondants, None) %}
{% for correspondant in site.correspondants %}
{% include 'entreprises/_correspondant.html' %} {% include 'entreprises/_correspondant.html' %}
{% endfor %} {% endfor %}
{% endif %}
</div>
{% endfor %}
</div> </div>
{% endif %} {% endif %}
@ -71,20 +85,20 @@
</div> </div>
<div style="margin-bottom: 10px;"> <div style="margin-bottom: 10px;">
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
<a class="btn btn-primary" href="{{ url_for('entreprises.add_stage_apprentissage', id=entreprise.id) }}">Ajouter stage ou apprentissage</a>
{% endif %}
<h3>Liste des stages et apprentissages réalisés au sein de l'entreprise</h3> <h3>Liste des stages et apprentissages réalisés au sein de l'entreprise</h3>
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
<a class="btn btn-primary" href="{{ url_for('entreprises.add_stage_apprentissage', id=entreprise.id) }}" style="margin-bottom:10px;">Ajouter stage ou apprentissage</a>
{% endif %}
<table id="table-stages-apprentissages"> <table id="table-stages-apprentissages">
<thead> <thead>
<tr> <tr>
<td data-priority="">Date début</td> <td data-priority="3">Date début</td>
<td data-priority="">Date fin</td> <td data-priority="4">Date fin</td>
<td data-priority="">Durée</td> <td data-priority="5">Durée</td>
<td data-priority="">Type</td> <td data-priority="2">Type</td>
<td data-priority="">Étudiant</td> <td data-priority="1">Étudiant</td>
<td data-priority="">Formation</td> <td data-priority="6">Formation</td>
<td data-priority="">Notes</td> <td data-priority="7">Notes</td>
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %} {% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
<td data-priority="3">Action</td> <td data-priority="3">Action</td>
{% endif %} {% endif %}

View File

@ -23,15 +23,15 @@
<script> <script>
{# pour les formulaires offre #} {# pour les formulaires offre #}
var champ_depts = document.getElementById("depts") var champ_depts = document.getElementById("depts")
if (champ_depts !== null) { if (champ_depts) {
var closest_form_control = champ_depts.closest(".form-control") var closest_form_control = champ_depts.closest(".form-control")
closest_form_control.classList.remove("form-control") closest_form_control.classList.remove("form-control")
} }
if(document.getElementById("expiration_date") !== null && document.getElementById("expiration_date").value === "") if(document.getElementById("expiration_date") && document.getElementById("expiration_date").value === "")
expiration() expiration()
if(document.getElementById("type_offre") !== null) if(document.getElementById("type_offre"))
document.getElementById("type_offre").addEventListener("change", expiration); document.getElementById("type_offre").addEventListener("change", expiration);
function expiration() { function expiration() {

View File

@ -25,13 +25,16 @@
{% if not correspondants_import %} {% if not correspondants_import %}
<table class="table"> <table class="table">
<thead><tr><td><b>Attribut</b></td><td><b>Type</b></td><td><b>Description</b></td></tr></thead> <thead><tr><td><b>Attribut</b></td><td><b>Type</b></td><td><b>Description</b></td></tr></thead>
<tr><td>civilite</td><td>text</td><td>civilite du correspondant (H ou F)</td></tr>
<tr><td>nom</td><td>text</td><td>nom du correspondant</td></tr> <tr><td>nom</td><td>text</td><td>nom du correspondant</td></tr>
<tr><td>prenom</td><td>text</td><td>prenom du correspondant</td></tr> <tr><td>prenom</td><td>text</td><td>prenom du correspondant</td></tr>
<tr><td>telephone</td><td>text</td><td>telephone du correspondant</td></tr> <tr><td>telephone</td><td>text</td><td>telephone du correspondant</td></tr>
<tr><td>mail</td><td>text</td><td>mail du correspondant</td></tr> <tr><td>mail</td><td>text</td><td>mail du correspondant</td></tr>
<tr><td>poste</td><td>text</td><td>poste du correspondant</td></tr> <tr><td>poste</td><td>text</td><td>poste du correspondant</td></tr>
<tr><td>service</td><td>text</td><td>service dans lequel travaille le correspondant</td></tr> <tr><td>service</td><td>text</td><td>service dans lequel travaille le correspondant</td></tr>
<tr><td>entreprise_siret</td><td>integer</td><td>SIRET de l'entreprise</td></tr> <tr><td>origine</td><td>text</td><td>origine du correspondant</td></tr>
<tr><td>notes</td><td>text</td><td>notes sur le correspondant</td></tr>
<tr><td>entreprise_siret</td><td>text</td><td>SIRET de l'entreprise</td></tr>
</table> </table>
{% endif %} {% endif %}
@ -40,6 +43,7 @@
{% for correspondant in correspondants_import %} {% for correspondant in correspondants_import %}
<div class="correspondant"> <div class="correspondant">
<div> <div>
Civilité : {{ correspondant.civilite|get_civilité }}<br>
Nom : {{ correspondant.nom }}<br> Nom : {{ correspondant.nom }}<br>
Prénom : {{ correspondant.prenom }}<br> Prénom : {{ correspondant.prenom }}<br>
{% if correspondant.telephone %} {% if correspondant.telephone %}
@ -54,7 +58,13 @@
{% if correspondant.service %} {% if correspondant.service %}
Service : {{ correspondant.service }}<br> Service : {{ correspondant.service }}<br>
{% endif %} {% endif %}
<a href="{{ url_for('entreprises.fiche_entreprise', id=correspondant.entreprise_id )}}">lien vers l'entreprise</a> {% if correspondant.origine %}
Origine : {{ correspondant.origine }}<br>
{% endif %}
{% if correspondant.notes %}
Notes : {{ correspondant.notes }}<br>
{% endif %}
<a href="{{ url_for('entreprises.fiche_entreprise', id=correspondant.entreprise_id )}}" target="_blank">lien vers l'entreprise</a>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

View File

@ -2,7 +2,9 @@
<nav class="nav-entreprise"> <nav class="nav-entreprise">
<ul> <ul>
<li><a href="{{ url_for('entreprises.index') }}">Entreprises</a></li> <li><a href="{{ url_for('entreprises.index') }}">Entreprises</a></li>
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesCorrespondants, None) %}
<li><a href="{{ url_for('entreprises.correspondants') }}">Correspondants</a></li> <li><a href="{{ url_for('entreprises.correspondants') }}">Correspondants</a></li>
{% endif %}
<li><a href="{{ url_for('entreprises.offres_recues') }}">Offres reçues</a></li> <li><a href="{{ url_for('entreprises.offres_recues') }}">Offres reçues</a></li>
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesValidate, None) %} {% if current_user.has_permission(current_user.Permission.RelationsEntreprisesValidate, None) %}
<li><a href="{{ url_for('entreprises.validation') }}">Entreprises à valider</a></li> <li><a href="{{ url_for('entreprises.validation') }}">Entreprises à valider</a></li>

View File

@ -9,7 +9,7 @@
{% if offres_recues %} {% if offres_recues %}
{% for offre in offres_recues %} {% for offre in offres_recues %}
<div class="offre offre-recue"> <div class="offre offre-recue">
<div> <div style="word-break:break-all; text-align: justify;">
Envoyé le {{ offre[0].date_envoi.strftime('%d/%m/%Y') }} à {{ offre[0].date_envoi.strftime('%Hh%M') }} par {{ offre[0].sender_id|get_nomcomplet_by_id }}<br> Envoyé le {{ offre[0].date_envoi.strftime('%d/%m/%Y') }} à {{ offre[0].date_envoi.strftime('%Hh%M') }} par {{ offre[0].sender_id|get_nomcomplet_by_id }}<br>
Intitulé : {{ offre[1].intitule }}<br> Intitulé : {{ offre[1].intitule }}<br>
Description : {{ offre[1].description }}<br> Description : {{ offre[1].description }}<br>
@ -20,12 +20,20 @@
{% if offre[1].correspondant_id %} {% if offre[1].correspondant_id %}
Contacté {{ offre[3].nom }} {{ offre[3].prenom }} Contacté {{ offre[3].nom }} {{ offre[3].prenom }}
{% if offre[3].mail and offre[3].telephone %} {% if offre[3].mail and offre[3].telephone %}
({{ offre[3].mail }} - {{ offre[3].telephone }})<br> ({{ offre[3].mail }} - {{ offre[3].telephone }})
{% else %} {% else %}
({{ offre[3].mail }}{{offre[3].telephone}})<br> ({{ offre[3].mail }}{{ offre[3].telephone }})
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if offre[3].poste %}
, poste : {{ offre[3].poste }}
{% endif %}
{% if offre[3].service %}
, service : {{ offre[3].service }}
{% endif %}
<br>
<a href="{{ url_for('entreprises.fiche_entreprise', id=offre[1].entreprise_id) }}">lien vers l'entreprise</a><br> <a href="{{ url_for('entreprises.fiche_entreprise', id=offre[1].entreprise_id) }}">lien vers l'entreprise</a><br>
{% for fichier in offre[2] %} {% for fichier in offre[2] %}

View File

@ -0,0 +1,65 @@
"""ajout colonnes tables relations entreprises, sites
Revision ID: d5b3bdd1d622
Revises: e97b2a10f86c
Create Date: 2022-04-25 18:29:50.841280
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "d5b3bdd1d622"
down_revision = "e97b2a10f86c"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"are_sites",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("entreprise_id", sa.Integer(), nullable=True),
sa.Column("nom", sa.Text(), nullable=True),
sa.Column("adresse", sa.Text(), nullable=True),
sa.Column("codepostal", sa.Text(), nullable=True),
sa.Column("ville", sa.Text(), nullable=True),
sa.Column("pays", sa.Text(), nullable=True),
sa.ForeignKeyConstraint(
["entreprise_id"], ["are_entreprises.id"], ondelete="cascade"
),
sa.PrimaryKeyConstraint("id"),
)
op.add_column(
"are_correspondants", sa.Column("site_id", sa.Integer(), nullable=True)
)
op.add_column(
"are_correspondants", sa.Column("civilite", sa.String(length=1), nullable=True)
)
op.add_column("are_correspondants", sa.Column("origine", sa.Text(), nullable=True))
op.add_column("are_correspondants", sa.Column("notes", sa.Text(), nullable=True))
op.create_foreign_key(
None, "are_correspondants", "are_sites", ["site_id"], ["id"], ondelete="cascade"
)
op.add_column("are_entreprises", sa.Column("active", sa.Boolean(), nullable=True))
op.add_column(
"are_entreprises", sa.Column("notes_active", sa.Text(), nullable=True)
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("are_entreprises", "notes_active")
op.drop_column("are_entreprises", "active")
op.drop_constraint(
"are_correspondants_site_id_fkey", "are_correspondants", type_="foreignkey"
)
op.drop_column("are_correspondants", "notes")
op.drop_column("are_correspondants", "origine")
op.drop_column("are_correspondants", "civilite")
op.drop_column("are_correspondants", "site_id")
op.drop_table("are_sites")
# ### end Alembic commands ###

View File

@ -93,7 +93,9 @@ def upgrade():
def downgrade(): def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, "are_offres", type_="foreignkey") op.drop_constraint(
"are_offres_correspondant_id_fkey", "are_offres", type_="foreignkey"
)
op.drop_column("are_offres", "correspondant_id") op.drop_column("are_offres", "correspondant_id")
op.add_column( op.add_column(
"are_contacts", "are_contacts",
@ -121,8 +123,10 @@ def downgrade():
op.add_column( op.add_column(
"are_contacts", sa.Column("nom", sa.TEXT(), autoincrement=False, nullable=True) "are_contacts", sa.Column("nom", sa.TEXT(), autoincrement=False, nullable=True)
) )
op.drop_constraint(None, "are_contacts", type_="foreignkey") op.drop_constraint(
op.drop_constraint(None, "are_contacts", type_="foreignkey") "are_contacts_entreprise_fkey", "are_contacts", type_="foreignkey"
)
op.drop_constraint("are_contacts_user_fkey", "are_contacts", type_="foreignkey")
op.create_foreign_key( op.create_foreign_key(
"are_contacts_entreprise_id_fkey", "are_contacts_entreprise_id_fkey",
"are_contacts", "are_contacts",

View File

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

View File

@ -73,7 +73,9 @@ token = r.json()["token"]
HEADERS = {"Authorization": f"Bearer {token}"} HEADERS = {"Authorization": f"Bearer {token}"}
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/list_depts", headers=HEADERS, verify=CHECK_CERTIFICATE SCODOC_URL + "/ScoDoc/api/list_depts",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
) )
if r.status_code != 200: if r.status_code != 200:
raise ScoError("erreur de connexion: vérifier adresse et identifiants") raise ScoError("erreur de connexion: vérifier adresse et identifiants")

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
"""Test Logos
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
"""
import os
import requests
SCODOC_USER = "test"
SCODOC_PASSWORD = "test"
SCODOC_URL = "http://192.168.1.12:5000"
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
def get_token():
"""
Permet de set le token dans le header
"""
r0 = requests.post(
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
token = r0.json()["token"]
return {"Authorization": f"Bearer {token}"}
HEADERS = get_token()

View File

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
"""Test Logos
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
Lancer :
pytest tests/api/test_api_absences.py
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# absences
def test_absences():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/etudid/<int:etudid>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/nip/<int:nip>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/ine/<int:ine>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# absences_justify
def test_absences_justify():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/etudid/1/just",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/nip/1/just",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/ine/1/just",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# abs_groupe_etat
def test_abs_groupe_etat():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/absences/abs_group_etat/?group_id=<int:group_id>&date_debut=date_debut&date_fin=date_fin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200

View File

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
"""Test Logos
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
Lancer :
pytest tests/api/test_api_departements.py
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# departements
def test_departements(): #XXX TODO pour Seb
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
# liste_etudiants
def test_liste_etudiants(): #XXX TODO pour Seb
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 16
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 16
# liste_semestres_courant
def test_semestres_courant(): #XXX TODO pour Seb
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
# referenciel_competences
def test_referenciel_competences():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200 or 204
# semestre_index
def test_semestre_index(): #XXX TODO pour Seb
ue_fields = [
"semestre_idx",
"type",
"formation_id",
"ue_code",
"id",
"ects",
"acronyme",
"is_external",
"numero",
"code_apogee",
"titre",
"coefficient",
"color",
"ue_id",
]
ressource_fields = [
"heures_tp",
"code_apogee",
"titre",
"coefficient",
"module_type",
"id",
"ects",
"abbrev",
"ue_id",
"code",
"formation_id",
"heures_cours",
"matiere_id",
"heures_td",
"semestre_id",
"numero",
"module_id",
]
sae_fields = [
"heures_tp",
"code_apogee",
"titre",
"coefficient",
"module_type",
"id",
"ects",
"abbrev",
"ue_id",
"code",
"formation_id",
"heures_cours",
"matiere_id",
"heures_td",
"semestre_id",
"numero",
"module_id",
]
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/formsemestre/1/programme",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 3
ue = r.json()["ues"][0]
ressource = r.json()["ressources"][0]
sae = r.json()["saes"][0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in ue:
if field not in ue_fields:
fields_OK = False
for field in ressource:
if field not in ressource_fields:
fields_OK = False
for field in sae:
if field not in sae_fields:
fields_OK = False
assert fields_OK is True

View File

@ -0,0 +1,478 @@
# -*- coding: utf-8 -*-
"""Test Logos
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
Lancer :
pytest tests/api/test_api_etudiants.py
"""
from random import randint
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# # etudiants
# def test_etudiants():
#
# fields = [
# "civilite",
# "code_ine",
# "code_nip",
# "date_naissance",
# "email",
# "emailperso",
# "etudid",
# "nom",
# "prenom",
# "nomprenom",
# "lieu_naissance",
# "dept_naissance",
# "nationalite",
# "boursier",
# "id",
# "domicile",
# "villedomicile",
# "telephone",
# "fax",
# "description",
# "codepostaldomicile",
# "paysdomicile",
# "telephonemobile",
# "typeadresse",
# ]
#
# r = requests.get(
# SCODOC_URL + "/ScoDoc/api/etudiants",
# headers=HEADERS,
# verify=CHECK_CERTIFICATE,
# )
# assert r.status_code == 200
# assert len(r.json()) == 16
#
# # Choisis aléatoirement un étudiant dans la liste des étudiants
# etu = r.json()[randint(0, len(r.json())) - 1]
#
# fields_OK = True
#
# # Vérifie si tous les champs sont bien présents
# for field in etu:
# if field not in fields:
# fields_OK = False
#
# assert fields_OK is True
# etudiants_courant
def test_etudiants_courant(): # XXX TODO pour Seb
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
"nomprenom",
"lieu_naissance",
"dept_naissance",
"nationalite",
"boursier",
"id",
"domicile",
"villedomicile",
"telephone",
"fax",
"description",
"codepostaldomicile",
"paysdomicile",
"telephonemobile",
"typeadresse",
]
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 16
# Choisis aléatoirement un étudiant dans la liste des étudiants
etu = r.json()[randint(0, len(r.json())) - 1]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in etu:
if field not in fields:
fields_OK = False
assert fields_OK is True
# etudiant
def test_etudiant(): # XXX TODO pour Seb
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
"nomprenom",
"lieu_naissance",
"dept_naissance",
"nationalite",
"boursier",
"id",
"domicile",
"villedomicile",
"telephone",
"fax",
"description",
"codepostaldomicile",
"paysdomicile",
"telephonemobile",
"typeadresse",
]
######### Test etudid #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 24
etu = r.json()
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in etu:
if field not in fields:
fields_OK = False
assert fields_OK is True
######### Test code nip #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 24
etu = r.json()
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in etu:
if field not in fields:
fields_OK = False
assert fields_OK is True
######### Test code ine #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 24
etu = r.json()
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in etu:
if field not in fields:
fields_OK = False
assert fields_OK is True
# etudiant_formsemestres
def test_etudiant_formsemestres(): # XXX TODO pour Seb
fields = [
"date_fin",
"resp_can_edit",
"dept_id",
"etat",
"resp_can_change_ens",
"id",
"modalite",
"ens_can_edit_eval",
"formation_id",
"gestion_compensation",
"elt_sem_apo",
"semestre_id",
"bul_hide_xml",
"elt_annee_apo",
"titre",
"block_moyennes",
"scodoc7_id",
"date_debut",
"gestion_semestrielle",
"bul_bgcolor",
"formsemestre_id",
"titre_num",
"date_debut_iso",
"date_fin_iso",
"responsables",
]
######### Test etudid #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestres",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
formsemestre = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in formsemestre:
if field not in fields:
fields_OK = False
assert fields_OK is True
######### Test code nip #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/formsemestres",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
formsemestre = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in formsemestre:
if field not in fields:
fields_OK = False
assert fields_OK is True
######### Test code ine #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/formsemestres",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
formsemestre = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in formsemestre:
if field not in fields:
fields_OK = False
assert fields_OK is True
# etudiant_bulletin_semestre
def test_etudiant_bulletin_semestre():
# fields = [
# "etudid",
# "formsemestre_id",
# "date",
# "publie",
# "etapes",
# "etudiant",
# "note",
# "rang",
# "rang_group",
# "note_max",
# "bonus_sport_culture",
# "ue",
# "ue_capitalisee",
# "absences",
# "appreciation",
# ]
######### Test etudid #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestre/1/bulletin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 13
# bulletin = r.json()
#
# fields_OK = True
#
# # Vérifie si tous les champs sont bien présents
# for field in bulletin:
# if field not in fields:
# fields_OK = False
#
# assert fields_OK is True
######### Test code nip #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/formsemestre/1/bulletin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 13
# bulletin = r.json()
#
# fields_OK = True
#
# # Vérifie si tous les champs sont bien présents
# for field in bulletin:
# if field not in fields:
# fields_OK = False
#
# assert fields_OK is True
######### Test code ine #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/formsemestre/1/bulletin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 13
# bulletin = r.json()
#
# fields_OK = True
#
# # Vérifie si tous les champs sont bien présents
# for field in bulletin:
# if field not in fields:
# fields_OK = False
#
# assert fields_OK is True
# etudiant_groups
def test_etudiant_groups():
fields = [
"partition_id",
"id",
"formsemestre_id",
"partition_name",
"numero",
"bul_show_rank",
"show_in_lists",
"group_id",
"group_name",
]
######### Test etudid #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/semestre/1/groups",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
groups = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in groups:
if field not in fields:
fields_OK = False
assert fields_OK is True
######### Test code nip #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/semestre/1/groups",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
groups = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in groups:
if field not in fields:
fields_OK = False
assert fields_OK is True
######### Test code ine #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/semestre/1/groups",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
groups = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in groups:
if field not in fields:
fields_OK = False
assert fields_OK is True

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
"""Test Logos
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
Lancer :
pytest tests/api/test_api_evaluations.py
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# evaluations
def test_evaluations():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/evaluations/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# evaluation_notes
def test_evaluation_notes():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/evaluations/eval_notes/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200

View File

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
"""Test Logos
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
Lancer :
pytest tests/api/test_api_formations.py
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# formations
def test_formations():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# formations_by_id
def test_formations_by_id():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# formation_export_by_formation_id
def test_formation_export_by_formation_id():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/formation_export/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# formsemestre_apo
def test_formsemestre_apo():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/apo/<string:etape_apo>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# moduleimpls
def test_moduleimpls():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/<int:moduleimpl_id>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# moduleimpls_sem
def test_moduleimpls_sem():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/formations/moduleimpl/formsemestre/1/liste",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200

View File

@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""Test Logos
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
Lancer :
pytest tests/api/test_api_formsemestre.py
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# formsemestre
def test_formsemestre():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
formsemestre = r.json()
fields = [
"date_fin",
"resp_can_edit",
"dept_id",
"etat",
"resp_can_change_ens",
"id",
"modalite",
"ens_can_edit_eval",
"formation_id",
"gestion_compensation",
"elt_sem_apo",
"semestre_id",
"bul_hide_xml",
"elt_annee_apo",
"titre",
"block_moyennes",
"scodoc7_id",
"date_debut",
"gestion_semestrielle",
"bul_bgcolor",
"formsemestre_id",
"titre_num",
"date_debut_iso",
"date_fin_iso",
"responsables",
]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in formsemestre:
if field not in fields:
fields_OK = False
assert fields_OK is True
# etudiant_bulletin
def test_etudiant_bulletin(): #XXX TODO pour Seb
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/etudid/1/bulletin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/nip/1/bulletin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/ine/1/bulletin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# bulletins
def test_bulletins():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/1/bulletins",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# # jury
# def test_jury():
# r = requests.get(
# SCODOC_URL + "/ScoDoc/api/formsemestre/1/jury",
# headers=HEADERS,
# verify=CHECK_CERTIFICATE,
# )
# assert r.status_code == 200

100
tests/api/test_api_jury.py Normal file
View File

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
"""Test Logos
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
Lancer :
pytest tests/api/test_api_jury.py
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# jury_preparation
def test_jury_preparation():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/formsemestre/<int:formsemestre_id>/preparation_jury",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# jury_decisions
def test_jury_decisions():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/formsemestre/<int:formsemestre_id>/decisions_jury",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# set_decision_jury
def test_set_decision_jury():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/set_decision/etudid?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/set_decision/nip?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/set_decision/ine?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# annule_decision_jury
def test_annule_decision_jury():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/nip/<int:nip>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200

View File

@ -9,7 +9,7 @@ utilisation:
pytest tests/api/test_api_logos.py pytest tests/api/test_api_logos.py
""" """
import pytest from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from scodoc import app from scodoc import app
from tests.unit.config_test_logos import ( from tests.unit.config_test_logos import (

View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""Test Logos
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
Lancer :
pytest tests/api/test_api_partitions.py
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# partition
def test_partition():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/partitions/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# etud_in_group
def test_etud_in_group():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/partitions/groups/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/partitions/groups/<int:group_id>/etat/<string:etat>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# set_groups
def test_set_groups():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/partitions/set_groups?partition_id=<int:partition_id>&groups_lists=<int:groups_lists>&"
"groups_to_create=<int:groups_to_create>&groups_to_delete=<int:groups_to_delete>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200

View File

@ -13,6 +13,11 @@
flask db upgrade flask db upgrade
flask sco-db-init --erase flask sco-db-init --erase
flask init-test-database flask init-test-database
flask user-role -a Admin -d TAPI test
flask user-password test
flask create-role APIUserViewer
flask edit-role APIUserViewer -a APIView
flask user-role test -a APIUserViewer
3) relancer ScoDoc: 3) relancer ScoDoc:
flask run --host 0.0.0.0 flask run --host 0.0.0.0
@ -81,6 +86,10 @@ def create_fake_etud(dept):
etud = models.Identite(civilite=civilite, nom=nom, prenom=prenom, dept_id=dept.id) etud = models.Identite(civilite=civilite, nom=nom, prenom=prenom, dept_id=dept.id)
db.session.add(etud) db.session.add(etud)
db.session.commit() db.session.commit()
etud.code_nip = etud.id
etud.code_ine = etud.id
db.session.add(etud)
db.session.commit()
adresse = models.Adresse( adresse = models.Adresse(
etudid=etud.id, email=f"{etud.prenom}.{etud.nom}@example.com" etudid=etud.id, email=f"{etud.prenom}.{etud.nom}@example.com"
) )
@ -103,7 +112,7 @@ def create_formsemestre(formation, user, semestre_idx=1):
semestre_id=semestre_idx, semestre_id=semestre_idx,
titre="Semestre test", titre="Semestre test",
date_debut=datetime.datetime(2021, 9, 1), date_debut=datetime.datetime(2021, 9, 1),
date_fin=datetime.datetime(2022, 1, 31), date_fin=datetime.datetime(2022, 8, 31),
modalite="FI", modalite="FI",
formation=formation, formation=formation,
) )
@ -136,6 +145,7 @@ def inscrit_etudiants(etuds, formsemestre):
def init_test_database(): def init_test_database():
dept = init_departement("TAPI") dept = init_departement("TAPI")
user = create_user(dept) user = create_user(dept)
etuds = create_etuds(dept) etuds = create_etuds(dept)