Merge branch 'new_api' of https://scodoc.org/git/ScoDoc/ScoDoc into new_api

This commit is contained in:
leonard_montalbano 2022-04-13 14:22:48 +02:00
commit 208aeb35a0
45 changed files with 1113 additions and 920 deletions

View File

@ -24,6 +24,10 @@
# 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.
from functools import wraps
from flask import abort
from flask import g
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
from app.auth.models import User
@ -63,15 +67,17 @@ def get_user_roles(user):
return user.roles
# def token_permission_required(permission):
# def decorator(f):
# @wraps(f)
# def decorated_function(*args, **kwargs):
# scodoc_dept = getattr(g, "scodoc_dept", None)
# if not current_user.has_permission(permission, scodoc_dept):
# abort(403)
# return f(*args, **kwargs)
def token_permission_required(permission):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
scodoc_dept = getattr(g, "scodoc_dept", None)
if hasattr(g, "current_user") and not g.current_user.has_permission(
permission, scodoc_dept
):
abort(403)
return f(*args, **kwargs)
# return login_required(decorated_function)
return decorated_function # login_required(decorated_function)
# return decorator
return decorator

View File

@ -1,23 +1,17 @@
############################################### Departements ##########################################################
from flask import g
from flask import jsonify
from app import models
from app.api import bp
from app.api.auth import token_auth
from app.api.auth import token_auth, token_permission_required
from app.api.errors import error_response
from app.decorators import permission_required
from app.models import ApcReferentielCompetences
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_prepajury import feuille_preparation_jury
from app.scodoc.sco_pvjury import formsemestre_pvjury
from app.scodoc.sco_recapcomplet import formsemestre_recapcomplet
from app.scodoc.sco_saisie_notes import notes_add
@bp.route("/departements", methods=["GET"])
@token_auth.login_required # Commenté le temps des tests
# @permission_required(Permission.ScoView)
@token_auth.login_required
@token_permission_required(Permission.APIView)
def departements():
"""
Retourne la liste des ids de départements visibles
@ -34,7 +28,9 @@ def departements():
@bp.route("/departements/<string:dept>/etudiants/liste", methods=["GET"])
@bp.route("/departements/<string:dept>/etudiants/liste/<int:formsemestre_id>", methods=["GET"])
@bp.route(
"/departements/<string:dept>/etudiants/liste/<int:formsemestre_id>", methods=["GET"]
)
@token_auth.login_required
# @permission_required(Permission.APIView)
def liste_etudiants(dept: str, formsemestre_id=None):
@ -154,7 +150,10 @@ def liste_semestres_courant(dept: str):
return jsonify(data)
@bp.route("/departements/<string:dept>/formations/<int:formation_id>/referentiel_competences", methods=["GET"])
@bp.route(
"/departements/<string:dept>/formations/<int:formation_id>/referentiel_competences",
methods=["GET"],
)
# @permission_required(Permission.APIView)
def referenciel_competences(dept: str, formation_id: int):
"""
@ -167,12 +166,16 @@ def referenciel_competences(dept: str, formation_id: int):
id_dept = depts[0].id
formations = models.Formation.query.filter_by(id=formation_id, dept_id=id_dept).all()
formations = models.Formation.query.filter_by(
id=formation_id, dept_id=id_dept
).all()
ref_comp = formations[0].referentiel_competence_id
if ref_comp is None:
return error_response(204, message="Pas de référenciel de compétences pour cette formation")
return error_response(
204, message="Pas de référenciel de compétences pour cette formation"
)
else:
return jsonify(ref_comp)
@ -181,7 +184,10 @@ def referenciel_competences(dept: str, formation_id: int):
# return jsonify(ref.to_dict())
@bp.route("/departements/<string:dept>/formsemestre/<string:formsemestre_id>/programme", methods=["GET"])
@bp.route(
"/departements/<string:dept>/formsemestre/<string:formsemestre_id>/programme",
methods=["GET"],
)
# @permission_required(Permission.APIView)
def semestre_index(dept: str, formsemestre_id: int):
"""

View File

@ -1,5 +1,8 @@
################################################## Tests ##############################################################
# XXX OBSOLETE ??? XXX
import requests
import os
@ -15,6 +18,7 @@ CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
HEADERS = None
def get_token():
"""
Permet de set le token dans le header
@ -25,7 +29,7 @@ def get_token():
r0 = requests.post(
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
)
token = r0.json()["token"]
HEADERS = {"Authorization": f"Bearer {token}"}
@ -52,7 +56,8 @@ def get_departement():
# departements
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements",
headers=HEADERS, verify=CHECK_CERTIFICATE,
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
if r.status_code == 200:
@ -90,17 +95,40 @@ def get_formsemestre():
# liste_semestres_courant
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/" + dept_acronym + "/semestres_courants",
auth=(SCODOC_USER, SCODOC_PASSWORD)
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"]
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:
@ -114,7 +142,6 @@ def get_formsemestre():
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():
"""
@ -124,14 +151,23 @@ def get_etudiant():
# etudiants
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
auth=(SCODOC_USER, SCODOC_PASSWORD)
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"]
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
]
for field in etu:
if field not in fields:
@ -162,15 +198,25 @@ def test_departements_liste_etudiants():
global FORMSEMESTRE
# Set les fields à vérifier
fields = ["civilite", "code_ine", "code_nip", "date_naissance", "email", "emailperso", "etudid", "nom", "prenom"]
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)
)
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
if r1.status_code == 200: # Si la requête est "OK"
if r1.status_code == 200: # Si la requête est "OK"
# On récupère la liste des étudiants
etudiants = r1.json()
@ -180,15 +226,17 @@ def test_departements_liste_etudiants():
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)
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"
if r2.status_code == 200: # Si la requête est "OK"
# On récupère la liste des étudiants
etudiants = r2.json()
@ -216,10 +264,14 @@ def test_departements_referenciel_competences():
# referenciel_competences
r = requests.post(
SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/formations/" +
FORMSEMESTRE["formation_id"] + "/referentiel_competences",
auth=(SCODOC_USER, SCODOC_PASSWORD)
)
SCODOC_URL
+ "/ScoDoc/api/departements/"
+ DEPT["acronym"]
+ "/formations/"
+ FORMSEMESTRE["formation_id"]
+ "/referentiel_competences",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
@bp.route("/test_liste_semestre_index")
@ -229,53 +281,42 @@ def test_departements_semestre_index():
"""
# semestre_index
r5 = requests.post(
SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/formsemestre/" +
FORMSEMESTRE["formation_id"] + "/programme",
auth=(SCODOC_USER, SCODOC_PASSWORD)
)
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)
)
SCODOC_URL + "/ScoDoc/api/etudiants", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
# etudiants_courant
r2 = requests.post(
SCODOC_URL + "/ScoDoc/api",
auth=(SCODOC_USER, SCODOC_PASSWORD)
)
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)
)
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)
)
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)
)
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)
)
r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_formation():
@ -283,40 +324,22 @@ def test_routes_formation():
Test les routes de la partie Formation
"""
# formations
r1 = requests.post(
SCODOC_URL + "/ScoDoc/api",
auth=(SCODOC_USER, SCODOC_PASSWORD)
)
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)
)
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)
)
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)
)
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)
)
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)
)
r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_formsemestres():
@ -324,28 +347,16 @@ def test_routes_formsemestres():
Test les routes de la partie Formsemestres
"""
# formsemestre
r1 = requests.post(
SCODOC_URL + "/ScoDoc/api",
auth=(SCODOC_USER, SCODOC_PASSWORD)
)
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)
)
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)
)
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)
)
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_partitions():
@ -353,22 +364,13 @@ def test_routes_partitions():
Test les routes de la partie Partitions
"""
# partition
r1 = requests.post(
SCODOC_URL + "/ScoDoc/api",
auth=(SCODOC_USER, SCODOC_PASSWORD)
)
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)
)
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)
)
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_evaluations():
@ -376,22 +378,13 @@ def test_routes_evaluations():
Test les routes de la partie Evaluations
"""
# evaluations
r1 = requests.post(
SCODOC_URL + "/ScoDoc/api",
auth=(SCODOC_USER, SCODOC_PASSWORD)
)
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)
)
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)
)
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_jury():
@ -399,28 +392,16 @@ 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)
)
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)
)
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)
)
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)
)
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_absences():
@ -428,40 +409,22 @@ def test_routes_absences():
Test les routes de la partie Absences
"""
# absences
r1 = requests.post(
SCODOC_URL + "/ScoDoc/api",
auth=(SCODOC_USER, SCODOC_PASSWORD)
)
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)
)
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)
)
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)
)
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)
)
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)
)
r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_logos():
@ -469,25 +432,13 @@ 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)
)
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)
)
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)
)
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)
)
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))

View File

@ -531,10 +531,11 @@ class BonusCachan1(BonusSportAdditif):
<ul>
<li> DUT/LP : la meilleure note d'option, si elle est supérieure à 10,
bonifie les moyennes d'UE (<b>sauf l'UE41 dont le code est UE41_E</b>) à raison
bonifie les moyennes d'UE (uniquement UE13_E pour le semestre 1, UE23_E
pour le semestre 2, UE33_E pour le semestre 3 et UE43_E pour le semestre
4) à raison
de <em>bonus = (option - 10)/10</em>.
</li>
<li> BUT : la meilleure note d'option, si elle est supérieure à 10, bonifie
les moyennes d'UE à raison de <em>bonus = (option - 10) * 3%</em>.</li>
</ul>
@ -545,6 +546,7 @@ class BonusCachan1(BonusSportAdditif):
seuil_moy_gen = 10.0 # tous les points sont comptés
proportion_point = 0.03
classic_use_bonus_ues = True
ues_bonifiables_cachan = {"UE13_E", "UE23_E", "UE33_E", "UE43_E"}
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus, avec réglage différent suivant le type de formation"""
@ -569,7 +571,7 @@ class BonusCachan1(BonusSportAdditif):
dtype=float,
)
else: # --- DUT
# pareil mais proportion différente et exclusion d'une UE
# pareil mais proportion différente et application à certaines UEs
proportion_point = 0.1
bonus_moy_arr = np.where(
note_bonus_max > self.seuil_moy_gen,
@ -582,10 +584,10 @@ class BonusCachan1(BonusSportAdditif):
columns=ues_idx,
dtype=float,
)
# Pas de bonus sur la ou les ue de code "UE41_E"
ue_exclues = [ue for ue in ues if ue.ue_code == "UE41_E"]
for ue in ue_exclues:
self.bonus_ues[ue.id] = 0.0
# Applique bonus seulement sur certaines UE de code connu:
for ue in ues:
if ue.ue_code not in self.ues_bonifiables_cachan:
self.bonus_ues[ue.id] = 0.0 # annule
class BonusCalais(BonusSportAdditif):
@ -1011,7 +1013,7 @@ class BonusTarbes(BonusSportAdditif):
"""
name = "bonus_tarbes"
displayed_name = "IUT de Tazrbes"
displayed_name = "IUT de Tarbes"
seuil_moy_gen = 10.0
proportion_point = 1 / 30.0
classic_use_bonus_ues = True

View File

@ -16,7 +16,7 @@ from app import models
from app.scodoc import notesdb as ndb
from app.scodoc.sco_bac import Baccalaureat
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_exceptions import ScoInvalidParamError
import app.scodoc.sco_utils as scu
@ -358,7 +358,7 @@ def make_etud_args(
try:
args = {"etudid": int(etudid)}
except ValueError as exc:
raise ScoValueError("Adresse invalide") from exc
raise ScoInvalidParamError() from exc
elif code_nip:
args = {"code_nip": code_nip}
elif use_request: # use form from current request (Flask global)

View File

@ -2,9 +2,21 @@
"""Evenements et logs divers
"""
import datetime
import re
from flask import g, url_for
from flask_login import current_user
from app import db
from app import email
from app import log
from app.auth.models import User
from app.models import SHORT_STR_LEN
from app.models.formsemestre import FormSemestre
from app.models.moduleimpls import ModuleImpl
import app.scodoc.sco_utils as scu
from app.scodoc import sco_preferences
class Scolog(db.Model):
@ -24,13 +36,213 @@ class Scolog(db.Model):
class ScolarNews(db.Model):
"""Nouvelles pour page d'accueil"""
NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id)
NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id)
NEWS_FORM = "FORM" # modification formation (object=formation_id)
NEWS_SEM = "SEM" # creation semestre (object=None)
NEWS_ABS = "ABS" # saisie absence
NEWS_MISC = "MISC" # unused
NEWS_MAP = {
NEWS_INSCR: "inscription d'étudiants",
NEWS_NOTE: "saisie note",
NEWS_FORM: "modification formation",
NEWS_SEM: "création semestre",
NEWS_MISC: "opération", # unused
}
NEWS_TYPES = list(NEWS_MAP.keys())
__tablename__ = "scolar_news"
id = db.Column(db.Integer, primary_key=True)
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
authenticated_user = db.Column(db.Text) # login, sans contrainte
date = db.Column(
db.DateTime(timezone=True), server_default=db.func.now(), index=True
)
authenticated_user = db.Column(db.Text, index=True) # login, sans contrainte
# type in 'INSCR', 'NOTES', 'FORM', 'SEM', 'MISC'
type = db.Column(db.String(SHORT_STR_LEN))
object = db.Column(db.Integer) # moduleimpl_id, formation_id, formsemestre_id
type = db.Column(db.String(SHORT_STR_LEN), index=True)
object = db.Column(
db.Integer, index=True
) # moduleimpl_id, formation_id, formsemestre_id
text = db.Column(db.Text)
url = db.Column(db.Text)
def __repr__(self):
return (
f"<{self.__class__.__name__}(id={self.id}, date='{self.date.isoformat()}')>"
)
def __str__(self):
"'Chargement notes dans Stage (S3 FI) par Aurélie Dupont'"
formsemestre = self.get_news_formsemestre()
user = User.query.filter_by(user_name=self.authenticated_user).first()
sem_text = (
f"""(<a href="{url_for('notes.formsemestre_status', scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
}">{formsemestre.sem_modalite()}</a>)"""
if formsemestre
else ""
)
author = f"par {user.get_nomcomplet()}" if user else ""
return f"{self.text} {sem_text} {author}"
def formatted_date(self) -> str:
"06 Avr 14h23"
mois = scu.MONTH_NAMES_ABBREV[self.date.month - 1]
return f"{self.date.day} {mois} {self.date.hour:02d}h{self.date.minute:02d}"
def to_dict(self):
return {
"date": {
"display": self.date.strftime("%d/%m/%Y %H:%M"),
"timestamp": self.date.timestamp(),
},
"type": self.NEWS_MAP.get(self.type, "?"),
"authenticated_user": self.authenticated_user,
"text": self.text,
}
@classmethod
def last_news(cls, n=1) -> list:
"The most recent n news. Returns list of ScolarNews instances."
return cls.query.order_by(cls.date.desc()).limit(n).all()
@classmethod
def add(cls, typ, obj=None, text="", url=None, max_frequency=0):
"""Enregistre une nouvelle
Si max_frequency, ne génère pas 2 nouvelles "identiques"
à moins de max_frequency secondes d'intervalle.
Deux nouvelles sont considérées comme "identiques" si elles ont
même (obj, typ, user).
La nouvelle enregistrée est aussi envoyée par mail.
"""
if max_frequency:
last_news = (
cls.query.filter_by(
dept_id=g.scodoc_dept_id,
authenticated_user=current_user.user_name,
type=typ,
object=obj,
)
.order_by(cls.date.desc())
.limit(1)
.first()
)
if last_news:
now = datetime.datetime.now(tz=last_news.date.tzinfo)
if (now - last_news.date) < datetime.timedelta(seconds=max_frequency):
# on n'enregistre pas
return
news = ScolarNews(
dept_id=g.scodoc_dept_id,
authenticated_user=current_user.user_name,
type=typ,
object=obj,
text=text,
url=url,
)
db.session.add(news)
db.session.commit()
log(f"news: {news}")
news.notify_by_mail()
def get_news_formsemestre(self) -> FormSemestre:
"""formsemestre concerné par la nouvelle
None si inexistant
"""
formsemestre_id = None
if self.type == self.NEWS_INSCR:
formsemestre_id = self.object
elif self.type == self.NEWS_NOTE:
moduleimpl_id = self.object
if moduleimpl_id:
modimpl = ModuleImpl.query.get(moduleimpl_id)
if modimpl is None:
return None # module does not exists anymore
formsemestre_id = modimpl.formsemestre_id
if not formsemestre_id:
return None
formsemestre = FormSemestre.query.get(formsemestre_id)
return formsemestre
def notify_by_mail(self):
"""Notify by email"""
formsemestre = self.get_news_formsemestre()
prefs = sco_preferences.SemPreferences(
formsemestre_id=formsemestre.id if formsemestre else None
)
destinations = prefs["emails_notifications"] or ""
destinations = [x.strip() for x in destinations.split(",")]
destinations = [x for x in destinations if x]
if not destinations:
return
#
txt = self.text
if formsemestre:
txt += f"""\n\nSemestre {formsemestre.titre_mois()}\n\n"""
txt += f"""<a href="{url_for("notes.formsemestre_status", _external=True,
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
}">{formsemestre.sem_modalite()}</a>
"""
user = User.query.filter_by(user_name=self.authenticated_user).first()
if user:
txt += f"\n\nEffectué par: {user.get_nomcomplet()}\n"
txt = (
"\n"
+ txt
+ """\n
--- Ceci est un message de notification automatique issu de ScoDoc
--- vous recevez ce message car votre adresse est indiquée dans les paramètres de ScoDoc.
"""
)
# Transforme les URL en URL absolues
base = scu.ScoURL()
txt = re.sub('href=/.*?"', 'href="' + base + "/", txt)
# Transforme les liens HTML en texte brut: '<a href="url">texte</a>' devient 'texte: url'
# (si on veut des messages non html)
txt = re.sub(r'<a.*?href\s*=\s*"(.*?)".*?>(.*?)</a>', r"\2: \1", txt)
subject = "[ScoDoc] " + self.NEWS_MAP.get(self.type, "?")
sender = prefs["email_from_addr"]
email.send_email(subject, sender, destinations, txt)
@classmethod
def scolar_news_summary_html(cls, n=5) -> str:
"""News summary, formated in HTML"""
news_list = cls.last_news(n=n)
if not news_list:
return ""
H = [
f"""<div class="news"><span class="newstitle"><a href="{
url_for("scolar.dept_news", scodoc_dept=g.scodoc_dept)
}">Dernières opérations</a>
</span><ul class="newslist">"""
]
for news in news_list:
H.append(
f"""<li class="newslist"><span class="newsdate">{news.formatted_date()}</span><span
class="newstext">{news}</span></li>"""
)
H.append("</ul>")
# Informations générales
H.append(
f"""<div>
Pour être informé des évolutions de ScoDoc,
vous pouvez vous
<a class="stdlink" href="{scu.SCO_ANNONCES_WEBSITE}">
abonner à la liste de diffusion</a>.
</div>
"""
)
H.append("</div>")
return "\n".join(H)

View File

@ -374,6 +374,16 @@ class FormSemestre(db.Model):
return self.titre
return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}"
def sem_modalite(self) -> str:
"""Le semestre et la modialité, ex "S2 FI" ou "S3 APP" """
if self.semestre_id > 0:
descr_sem = f"S{self.semestre_id}"
else:
descr_sem = ""
if self.modalite:
descr_sem += " " + self.modalite
return descr_sem
def get_abs_count(self, etudid):
"""Les comptes d'absences de cet étudiant dans ce semestre:
tuple (nb abs, nb abs justifiées)

View File

@ -32,6 +32,7 @@ from flask import g, request
from flask_login import current_user
import app
from app.models import ScolarNews
import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission
@ -40,9 +41,7 @@ import app.scodoc.notesdb as ndb
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_modalites
from app.scodoc import sco_news
from app.scodoc import sco_preferences
from app.scodoc import sco_up_to_date
from app.scodoc import sco_users
@ -53,7 +52,7 @@ def index_html(showcodes=0, showsemtable=0):
H = []
# News:
H.append(sco_news.scolar_news_summary_html())
H.append(ScolarNews.scolar_news_summary_html())
# Avertissement de mise à jour:
H.append("""<div id="update_warning"></div>""")

View File

@ -37,6 +37,7 @@ from app.models import SHORT_STR_LEN
from app.models.formations import Formation
from app.models.modules import Module
from app.models.ues import UniteEns
from app.models import ScolarNews
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -44,13 +45,10 @@ from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError
from app.scodoc import html_sco_header
from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_news
def formation_delete(formation_id=None, dialog_confirmed=False):
@ -117,11 +115,10 @@ def do_formation_delete(oid):
sco_formations._formationEditor.delete(cnx, oid)
# news
sco_news.add(
typ=sco_news.NEWS_FORM,
object=oid,
text="Suppression de la formation %(acronyme)s" % F,
max_frequency=3,
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=oid,
text=f"Suppression de la formation {F['acronyme']}",
)
@ -281,10 +278,9 @@ def do_formation_create(args):
#
r = sco_formations._formationEditor.create(cnx, args)
sco_news.add(
typ=sco_news.NEWS_FORM,
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
text="Création de la formation %(titre)s (%(acronyme)s)" % args,
max_frequency=3,
)
return r

View File

@ -30,6 +30,7 @@
"""
import flask
from flask import g, url_for, request
from app.models.events import ScolarNews
from app.models.formations import Matiere
import app.scodoc.notesdb as ndb
@ -78,8 +79,7 @@ def do_matiere_edit(*args, **kw):
def do_matiere_create(args):
"create a matiere"
from app.scodoc import sco_edit_ue
from app.scodoc import sco_formations
from app.scodoc import sco_news
from app.models import ScolarNews
cnx = ndb.GetDBConnexion()
# check
@ -89,11 +89,11 @@ def do_matiere_create(args):
# news
formation = Formation.query.get(ue["formation_id"])
sco_news.add(
typ=sco_news.NEWS_FORM,
object=ue["formation_id"],
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=ue["formation_id"],
text=f"Modification de la formation {formation.acronyme}",
max_frequency=3,
max_frequency=10 * 60,
)
formation.invalidate_cached_sems()
return r
@ -174,10 +174,8 @@ def can_delete_matiere(matiere: Matiere) -> tuple[bool, str]:
def do_matiere_delete(oid):
"delete matiere and attached modules"
from app.scodoc import sco_formations
from app.scodoc import sco_edit_ue
from app.scodoc import sco_edit_module
from app.scodoc import sco_news
cnx = ndb.GetDBConnexion()
# check
@ -197,11 +195,11 @@ def do_matiere_delete(oid):
# news
formation = Formation.query.get(ue["formation_id"])
sco_news.add(
typ=sco_news.NEWS_FORM,
object=ue["formation_id"],
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=ue["formation_id"],
text=f"Modification de la formation {formation.acronyme}",
max_frequency=3,
max_frequency=10 * 60,
)
formation.invalidate_cached_sems()

View File

@ -38,6 +38,7 @@ from app import models
from app.models import APO_CODE_STR_LEN
from app.models import Formation, Matiere, Module, UniteEns
from app.models import FormSemestre, ModuleImpl
from app.models import ScolarNews
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -53,7 +54,6 @@ from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_news
_moduleEditor = ndb.EditableTable(
"notes_modules",
@ -98,18 +98,16 @@ def module_list(*args, **kw):
def do_module_create(args) -> int:
"Create a module. Returns id of new object."
# create
from app.scodoc import sco_formations
cnx = ndb.GetDBConnexion()
r = _moduleEditor.create(cnx, args)
# news
formation = Formation.query.get(args["formation_id"])
sco_news.add(
typ=sco_news.NEWS_FORM,
object=formation.id,
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=formation.id,
text=f"Modification de la formation {formation.acronyme}",
max_frequency=3,
max_frequency=10 * 60,
)
formation.invalidate_cached_sems()
return r
@ -396,11 +394,11 @@ def do_module_delete(oid):
# news
formation = module.formation
sco_news.add(
typ=sco_news.NEWS_FORM,
object=mod["formation_id"],
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=mod["formation_id"],
text=f"Modification de la formation {formation.acronyme}",
max_frequency=3,
max_frequency=10 * 60,
)
formation.invalidate_cached_sems()

View File

@ -37,6 +37,7 @@ from app import db
from app import log
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
from app.models import Formation, UniteEns, ModuleImpl, Module
from app.models import ScolarNews
from app.models.formations import Matiere
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -55,15 +56,11 @@ from app.scodoc import html_sco_header
from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_apc
from app.scodoc import sco_edit_formation
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module
from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_news
from app.scodoc import sco_permissions
from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module
@ -138,11 +135,11 @@ def do_ue_create(args):
ue = UniteEns.query.get(ue_id)
flash(f"UE créée (code {ue.ue_code})")
formation = Formation.query.get(args["formation_id"])
sco_news.add(
typ=sco_news.NEWS_FORM,
object=args["formation_id"],
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=args["formation_id"],
text=f"Modification de la formation {formation.acronyme}",
max_frequency=3,
max_frequency=10 * 60,
)
formation.invalidate_cached_sems()
return ue_id
@ -222,11 +219,11 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
sco_cache.invalidate_formsemestre()
# news
F = sco_formations.formation_list(args={"formation_id": ue.formation_id})[0]
sco_news.add(
typ=sco_news.NEWS_FORM,
object=ue.formation_id,
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=ue.formation_id,
text="Modification de la formation %(acronyme)s" % F,
max_frequency=3,
max_frequency=10 * 60,
)
#
if not force:

View File

@ -637,7 +637,7 @@ def create_etud(cnx, args={}):
Returns:
etud, l'étudiant créé.
"""
from app.scodoc import sco_news
from app.models import ScolarNews
# creation d'un etudiant
etudid = etudident_create(cnx, args)
@ -671,9 +671,8 @@ def create_etud(cnx, args={}):
etud = etudident_list(cnx, {"etudid": etudid})[0]
fill_etuds_info([etud])
etud["url"] = url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
sco_news.add(
typ=sco_news.NEWS_INSCR,
object=None, # pas d'object pour ne montrer qu'un etudiant
ScolarNews.add(
typ=ScolarNews.NEWS_INSCR,
text='Nouvel étudiant <a href="%(url)s">%(nomprenom)s</a>' % etud,
url=etud["url"],
)

View File

@ -28,7 +28,6 @@
"""Gestion evaluations (ScoDoc7, sans SQlAlchemy)
"""
import datetime
import pprint
import flask
@ -37,6 +36,7 @@ from flask_login import current_user
from app import log
from app.models import ScolarNews
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -44,9 +44,7 @@ from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc import sco_cache
from app.scodoc import sco_edit_module
from app.scodoc import sco_formsemestre
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_news
from app.scodoc import sco_permissions_check
@ -179,9 +177,9 @@ def do_evaluation_create(
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
mod["moduleimpl_id"] = M["moduleimpl_id"]
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
sco_news.add(
typ=sco_news.NEWS_NOTE,
object=moduleimpl_id,
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,
obj=moduleimpl_id,
text='Création d\'une évaluation dans <a href="%(url)s">%(titre)s</a>' % mod,
url=mod["url"],
)
@ -240,9 +238,9 @@ def do_evaluation_delete(evaluation_id):
mod["url"] = (
scu.NotesURL() + "/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
)
sco_news.add(
typ=sco_news.NEWS_NOTE,
object=moduleimpl_id,
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,
obj=moduleimpl_id,
text='Suppression d\'une évaluation dans <a href="%(url)s">%(titre)s</a>' % mod,
url=mod["url"],
)

View File

@ -53,7 +53,11 @@ def evaluations_recap(formsemestre_id: int) -> str:
for row in rows:
H.append(f"{scu.gen_row(column_ids, row, with_col_classes=True)}\n")
H.append("""</tbody></table></div>""")
H.append(
"""</tbody></table></div>
<div class="help">Les étudiants démissionnaires ou défaillants ne sont pas pris en compte dans cette table.</div>
"""
)
H.append(
html_sco_header.sco_footer(),
)

View File

@ -36,32 +36,27 @@ from flask import g
from flask_login import current_user
from flask import request
from app import log
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models import ScolarNews
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
import sco_version
from app.scodoc.gen_tables import GenTable
from app.scodoc import html_sco_header
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_abs
from app.scodoc import sco_cache
from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_news
from app.scodoc import sco_permissions_check
from app.scodoc import sco_preferences
from app.scodoc import sco_users
import sco_version
# --------------------------------------------------------------------
@ -633,13 +628,16 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
'<span class="evallink"><a class="stdlink" href="evaluation_listenotes?moduleimpl_id=%s">voir toutes les notes du module</a></span>'
% moduleimpl_id
)
mod_descr = '<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s' % (
moduleimpl_id,
Mod["code"] or "",
Mod["titre"] or "?",
nomcomplet,
resp,
link,
mod_descr = (
'<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s'
% (
moduleimpl_id,
Mod["code"] or "",
Mod["titre"] or "?",
nomcomplet,
resp,
link,
)
)
etit = E["description"] or ""

View File

@ -63,6 +63,17 @@ class ScoFormatError(ScoValueError):
pass
class ScoInvalidParamError(ScoValueError):
"""Paramètres requete invalides.
A utilisée lorsqu'une route est appelée avec des paramètres invalides
(id strings, ...)
"""
def __init__(self, msg=None, dest_url=None):
msg = msg or "Adresse invalide. Vérifiez vos signets."
super().__init__(msg, dest_url=dest_url)
class ScoPDFFormatError(ScoValueError):
"erreur génération PDF (templates platypus, ...)"

View File

@ -39,12 +39,12 @@ import app.scodoc.notesdb as ndb
from app import db
from app import log
from app.models import Formation, Module
from app.models import ScolarNews
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_formsemestre
from app.scodoc import sco_news
from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module
from app.scodoc import sco_xml
@ -351,10 +351,13 @@ def formation_list_table(formation_id=None, args={}):
else:
but_locked = '<span class="but_placeholder"></span>'
if editable and not locked:
but_suppr = '<a class="stdlink" href="formation_delete?formation_id=%s" id="delete-formation-%s">%s</a>' % (
f["formation_id"],
f["acronyme"].lower().replace(" ", "-"),
suppricon,
but_suppr = (
'<a class="stdlink" href="formation_delete?formation_id=%s" id="delete-formation-%s">%s</a>'
% (
f["formation_id"],
f["acronyme"].lower().replace(" ", "-"),
suppricon,
)
)
else:
but_suppr = '<span class="but_placeholder"></span>'
@ -422,9 +425,9 @@ def formation_create_new_version(formation_id, redirect=True):
new_id, modules_old2new, ues_old2new = formation_import_xml(xml_data)
# news
F = formation_list(args={"formation_id": new_id})[0]
sco_news.add(
typ=sco_news.NEWS_FORM,
object=new_id,
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=new_id,
text="Nouvelle version de la formation %(acronyme)s" % F,
)
if redirect:

View File

@ -229,7 +229,7 @@ def etapes_apo_str(etapes):
def do_formsemestre_create(args, silent=False):
"create a formsemestre"
from app.scodoc import sco_groups
from app.scodoc import sco_news
from app.models import ScolarNews
cnx = ndb.GetDBConnexion()
formsemestre_id = _formsemestreEditor.create(cnx, args)
@ -254,8 +254,8 @@ def do_formsemestre_create(args, silent=False):
args["formsemestre_id"] = formsemestre_id
args["url"] = "Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s" % args
if not silent:
sco_news.add(
typ=sco_news.NEWS_SEM,
ScolarNews.add(
typ=ScolarNews.NEWS_SEM,
text='Création du semestre <a href="%(url)s">%(titre)s</a>' % args,
url=args["url"],
)

View File

@ -36,6 +36,7 @@ from app import db
from app.auth.models import User
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
from app.models import Module, ModuleImpl, Evaluation, EvaluationUEPoids, UniteEns
from app.models import ScolarNews
from app.models.formations import Formation
from app.models.formsemestre import FormSemestre
import app.scodoc.notesdb as ndb
@ -191,10 +192,10 @@ def do_formsemestre_createwithmodules(edit=False):
modimpl.responsable_id,
f"inconnu numéro {modimpl.responsable_id} resp. de {modimpl.id} !",
)
initvalues["responsable_id"] = uid2display.get(
sem["responsables"][0], sem["responsables"][0]
)
if sem["responsables"]:
initvalues["responsable_id"] = uid2display.get(
sem["responsables"][0], sem["responsables"][0]
)
if len(sem["responsables"]) > 1:
initvalues["responsable_id2"] = uid2display.get(
sem["responsables"][1], sem["responsables"][1]
@ -1493,11 +1494,9 @@ def do_formsemestre_delete(formsemestre_id):
sco_formsemestre._formsemestreEditor.delete(cnx, formsemestre_id)
# news
from app.scodoc import sco_news
sco_news.add(
typ=sco_news.NEWS_SEM,
object=formsemestre_id,
ScolarNews.add(
typ=ScolarNews.NEWS_SEM,
obj=formsemestre_id,
text="Suppression du semestre %(titre)s" % sem,
)

View File

@ -1186,6 +1186,7 @@ def formsemestre_tableau_modules(
H.append("<td>")
if mod.module_type in (ModuleType.RESSOURCE, ModuleType.SAE):
coefs = mod.ue_coefs_list()
H.append(f'<a class="invisible_link" href="#" title="{mod_descr}">')
for coef in coefs:
if coef[1] > 0:
H.append(
@ -1197,6 +1198,7 @@ def formsemestre_tableau_modules(
)
else:
H.append(f"""<span class="mod_coef_indicator_zero"></span>""")
H.append("</a>")
H.append("</td>")
if mod.module_type in (
None, # ne devrait pas être nécessaire car la migration a remplacé les NULLs

View File

@ -40,6 +40,8 @@ from flask import g, url_for
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.models import ScolarNews
from app.scodoc.sco_excel import COLORS
from app.scodoc.sco_formsemestre_inscriptions import (
do_formsemestre_inscription_with_modules,
@ -54,14 +56,13 @@ from app.scodoc.sco_exceptions import (
ScoLockedFormError,
ScoGenError,
)
from app.scodoc import html_sco_header
from app.scodoc import sco_cache
from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_excel
from app.scodoc import sco_groups_view
from app.scodoc import sco_news
from app.scodoc import sco_preferences
# format description (in tools/)
@ -472,11 +473,11 @@ def scolars_import_excel_file(
diag.append("Import et inscription de %s étudiants" % len(created_etudids))
sco_news.add(
typ=sco_news.NEWS_INSCR,
ScolarNews.add(
typ=ScolarNews.NEWS_INSCR,
text="Inscription de %d étudiants" # peuvent avoir ete inscrits a des semestres differents
% len(created_etudids),
object=formsemestre_id,
obj=formsemestre_id,
)
log("scolars_import_excel_file: completing transaction")

View File

@ -1,276 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""Gestion des "nouvelles"
"""
import re
import time
from operator import itemgetter
from flask import g
from flask_login import current_user
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc import sco_formsemestre
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_preferences
from app import email
_scolar_news_editor = ndb.EditableTable(
"scolar_news",
"news_id",
("date", "authenticated_user", "type", "object", "text", "url"),
filter_dept=True,
sortkey="date desc",
output_formators={"date": ndb.DateISOtoDMY},
input_formators={"date": ndb.DateDMYtoISO},
html_quote=False, # no user supplied data, needed to store html links
)
NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id)
NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id)
NEWS_FORM = "FORM" # modification formation (object=formation_id)
NEWS_SEM = "SEM" # creation semestre (object=None)
NEWS_MISC = "MISC" # unused
NEWS_MAP = {
NEWS_INSCR: "inscription d'étudiants",
NEWS_NOTE: "saisie note",
NEWS_FORM: "modification formation",
NEWS_SEM: "création semestre",
NEWS_MISC: "opération", # unused
}
NEWS_TYPES = list(NEWS_MAP.keys())
scolar_news_create = _scolar_news_editor.create
scolar_news_list = _scolar_news_editor.list
_LAST_NEWS = {} # { (authuser_name, type, object) : time }
def add(typ, object=None, text="", url=None, max_frequency=False):
"""Ajoute une nouvelle.
Si max_frequency, ne genere pas 2 nouvelles identiques à moins de max_frequency
secondes d'intervalle.
"""
from app.scodoc import sco_users
authuser_name = current_user.user_name
cnx = ndb.GetDBConnexion()
args = {
"authenticated_user": authuser_name,
"user_info": sco_users.user_info(authuser_name),
"type": typ,
"object": object,
"text": text,
"url": url,
}
t = time.time()
if max_frequency:
last_news_time = _LAST_NEWS.get((authuser_name, typ, object), False)
if last_news_time and (t - last_news_time < max_frequency):
# log("not recording")
return
log("news: %s" % args)
_LAST_NEWS[(authuser_name, typ, object)] = t
_send_news_by_mail(args)
return scolar_news_create(cnx, args)
def scolar_news_summary(n=5):
"""Return last n news.
News are "compressed", ie redondant events are joined.
"""
from app.scodoc import sco_etud
from app.scodoc import sco_users
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"""SELECT id AS news_id, *
FROM scolar_news
WHERE dept_id=%(dept_id)s
ORDER BY date DESC LIMIT 100
""",
{"dept_id": g.scodoc_dept_id},
)
selected_news = {} # (type,object) : news dict
news = cursor.dictfetchall() # la plus récente d'abord
for r in reversed(news): # la plus ancienne d'abord
# si on a deja une news avec meme (type,object)
# et du meme jour, on la remplace
dmy = ndb.DateISOtoDMY(r["date"]) # round
key = (r["type"], r["object"], dmy)
selected_news[key] = r
news = list(selected_news.values())
# sort by date, descending
news.sort(key=itemgetter("date"), reverse=True)
news = news[:n]
# mimic EditableTable.list output formatting:
for n in news:
n["date822"] = n["date"].strftime("%a, %d %b %Y %H:%M:%S %z")
# heure
n["hm"] = n["date"].strftime("%Hh%M")
for k in n.keys():
if n[k] is None:
n[k] = ""
if k in _scolar_news_editor.output_formators:
n[k] = _scolar_news_editor.output_formators[k](n[k])
# date resumee
j, m = n["date"].split("/")[:2]
mois = scu.MONTH_NAMES_ABBREV[int(m) - 1]
n["formatted_date"] = "%s %s %s" % (j, mois, n["hm"])
# indication semestre si ajout notes:
infos = _get_formsemestre_infos_from_news(n)
if infos:
n["text"] += (
' (<a href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(descr_sem)s</a>)'
% infos
)
n["text"] += (
" par " + sco_users.user_info(n["authenticated_user"])["nomcomplet"]
)
return news
def _get_formsemestre_infos_from_news(n):
"""Informations sur le semestre concerné par la nouvelle n
{} si inexistant
"""
formsemestre_id = None
if n["type"] == NEWS_INSCR:
formsemestre_id = n["object"]
elif n["type"] == NEWS_NOTE:
moduleimpl_id = n["object"]
if n["object"]:
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
if not mods:
return {} # module does not exists anymore
return {} # pas d'indication du module
mod = mods[0]
formsemestre_id = mod["formsemestre_id"]
if not formsemestre_id:
return {}
try:
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
except:
# semestre n'existe plus
return {}
if sem["semestre_id"] > 0:
descr_sem = "S%d" % sem["semestre_id"]
else:
descr_sem = ""
if sem["modalite"]:
descr_sem += " " + sem["modalite"]
return {"formsemestre_id": formsemestre_id, "sem": sem, "descr_sem": descr_sem}
def scolar_news_summary_html(n=5):
"""News summary, formated in HTML"""
news = scolar_news_summary(n=n)
if not news:
return ""
H = ['<div class="news"><span class="newstitle">Dernières opérations']
H.append('</span><ul class="newslist">')
for n in news:
H.append(
'<li class="newslist"><span class="newsdate">%(formatted_date)s</span><span class="newstext">%(text)s</span></li>'
% n
)
H.append("</ul>")
# Informations générales
H.append(
"""<div>
Pour être informé des évolutions de ScoDoc,
vous pouvez vous
<a class="stdlink" href="%s">
abonner à la liste de diffusion</a>.
</div>
"""
% scu.SCO_ANNONCES_WEBSITE
)
H.append("</div>")
return "\n".join(H)
def _send_news_by_mail(n):
"""Notify by email"""
infos = _get_formsemestre_infos_from_news(n)
formsemestre_id = infos.get("formsemestre_id", None)
prefs = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id)
destinations = prefs["emails_notifications"] or ""
destinations = [x.strip() for x in destinations.split(",")]
destinations = [x for x in destinations if x]
if not destinations:
return
#
txt = n["text"]
if infos:
txt += "\n\nSemestre %(titremois)s\n\n" % infos["sem"]
txt += (
"""<a href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(descr_sem)s</a>
"""
% infos
)
txt += "\n\nEffectué par: %(nomcomplet)s\n" % n["user_info"]
txt = (
"\n"
+ txt
+ """\n
--- Ceci est un message de notification automatique issu de ScoDoc
--- vous recevez ce message car votre adresse est indiquée dans les paramètres de ScoDoc.
"""
)
# Transforme les URL en URL absolue
base = scu.ScoURL()
txt = re.sub('href=.*?"', 'href="' + base + "/", txt)
# Transforme les liens HTML en texte brut: '<a href="url">texte</a>' devient 'texte: url'
# (si on veut des messages non html)
txt = re.sub(r'<a.*?href\s*=\s*"(.*?)".*?>(.*?)</a>', r"\2: \1", txt)
subject = "[ScoDoc] " + NEWS_MAP.get(n["type"], "?")
sender = prefs["email_from_addr"]
email.send_email(subject, sender, destinations, txt)

View File

@ -350,7 +350,7 @@ class BasePreferences(object):
"initvalue": "",
"title": "e-mails à qui notifier les opérations",
"size": 70,
"explanation": "adresses séparées par des virgules; notifie les opérations (saisies de notes, etc). (vous pouvez préférer utiliser le flux rss)",
"explanation": "adresses séparées par des virgules; notifie les opérations (saisies de notes, etc).",
"category": "general",
"only_global": False, # peut être spécifique à un semestre
},

View File

@ -39,6 +39,7 @@ from flask_login import current_user
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models import ScolarNews
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb
@ -48,6 +49,7 @@ from app.scodoc.sco_exceptions import (
InvalidNoteValue,
NoteProcessError,
ScoGenError,
ScoInvalidParamError,
ScoValueError,
)
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
@ -64,7 +66,6 @@ from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_groups_view
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_news
from app.scodoc import sco_permissions_check
from app.scodoc import sco_undo_notes
from app.scodoc import sco_etud
@ -274,11 +275,12 @@ def do_evaluation_upload_xls():
moduleimpl_id=mod["moduleimpl_id"],
_external=True,
)
sco_news.add(
typ=sco_news.NEWS_NOTE,
object=M["moduleimpl_id"],
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,
obj=M["moduleimpl_id"],
text='Chargement notes dans <a href="%(url)s">%(titre)s</a>' % mod,
url=mod["url"],
max_frequency=30 * 60, # 30 minutes
)
msg = (
@ -359,11 +361,12 @@ def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
scodoc_dept=g.scodoc_dept,
moduleimpl_id=mod["moduleimpl_id"],
)
sco_news.add(
typ=sco_news.NEWS_NOTE,
object=M["moduleimpl_id"],
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,
obj=M["moduleimpl_id"],
text='Initialisation notes dans <a href="%(url)s">%(titre)s</a>' % mod,
url=mod["url"],
max_frequency=30 * 60,
)
return (
html_sco_header.sco_header()
@ -451,9 +454,9 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
mod["moduleimpl_id"] = M["moduleimpl_id"]
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
sco_news.add(
typ=sco_news.NEWS_NOTE,
object=M["moduleimpl_id"],
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,
obj=M["moduleimpl_id"],
text='Suppression des notes d\'une évaluation dans <a href="%(url)s">%(titre)s</a>'
% mod,
url=mod["url"],
@ -893,10 +896,12 @@ def has_existing_decision(M, E, etudid):
def saisie_notes(evaluation_id, group_ids=[]):
"""Formulaire saisie notes d'une évaluation pour un groupe"""
if not isinstance(evaluation_id, int):
raise ScoInvalidParamError()
group_ids = [int(group_id) for group_id in group_ids]
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
raise ScoValueError("invalid evaluation_id")
raise ScoValueError("évaluation inexistante")
E = evals[0]
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
formsemestre_id = M["formsemestre_id"]
@ -1283,9 +1288,9 @@ def save_note(etudid=None, evaluation_id=None, value=None, comment=""):
nbchanged, _, existing_decisions = notes_add(
authuser, evaluation_id, L, comment=comment, do_it=True
)
sco_news.add(
typ=sco_news.NEWS_NOTE,
object=M["moduleimpl_id"],
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,
obj=M["moduleimpl_id"],
text='Chargement notes dans <a href="%(url)s">%(titre)s</a>' % Mod,
url=Mod["url"],
max_frequency=30 * 60, # 30 minutes

View File

@ -29,12 +29,14 @@
"""
import time
import pprint
from operator import itemgetter
from flask import g, url_for
from flask_login import current_user
from app import log
from app.models import ScolarNews
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc import html_sco_header
@ -43,11 +45,8 @@ from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_inscr_passage
from app.scodoc import sco_news
from app.scodoc import sco_excel
from app.scodoc import sco_portal_apogee
from app.scodoc import sco_etud
from app import log
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission
@ -701,10 +700,10 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
sco_cache.invalidate_formsemestre()
raise
sco_news.add(
typ=sco_news.NEWS_INSCR,
ScolarNews.add(
typ=ScolarNews.NEWS_INSCR,
text="Import Apogée de %d étudiants en " % len(created_etudids),
object=sem["formsemestre_id"],
obj=sem["formsemestre_id"],
)

View File

@ -487,6 +487,16 @@ div.news {
border-radius: 8px;
}
div.news a {
color: black;
text-decoration: none;
}
div.news a:hover {
color: rgb(153, 51, 51);
text-decoration: underline;
}
span.newstitle {
font-weight: bold;
}
@ -987,9 +997,34 @@ span.wtf-field ul.errors li {
font-weight: bold;
}
.configuration_logo div.img {}
.configuration_logo summary {
display: list-item !important;
}
.configuration_logo div.img-container {
.configuration_logo h1 {
display: inline-block;
}
.configuration_logo h2 {
display: inline-block;
}
.configuration_logo h3 {
display: inline-block;
}
.configuration_logo details>*:not(summary) {
margin-left: 32px;
}
.configuration_logo .content {
display: grid;
grid-template-columns: auto auto 1fr;
}
.configuration_logo .image_logo {
vertical-align: top;
grid-column: 1/2;
width: 256px;
}
@ -997,8 +1032,27 @@ span.wtf-field ul.errors li {
max-width: 100%;
}
.configuration_logo div.img-data {
vertical-align: top;
.configuration_logo .infos_logo {
grid-column: 2/3;
}
.configuration_logo .actions_logo {
grid-column: 3/5;
display: grid;
grid-template-columns: auto auto;
grid-column-gap: 10px;
align-self: start;
grid-row-gap: 10px;
}
.configuration_logo .actions_logo .action_label {
grid-column: 1/2;
grid-template-columns: auto auto;
}
.configuration_logo .actions_logo .action_button {
grid-column: 2/3;
align-self: start;
}
.configuration_logo logo-edit titre {
@ -2432,6 +2486,12 @@ span.bul_minmax:before {
content: " ";
}
a.invisible_link,
a.invisible_link:hover {
text-decoration: none;
color: rgb(20, 30, 30);
}
a.bull_link {
text-decoration: none;
color: rgb(20, 30, 30);
@ -3842,3 +3902,11 @@ table.evaluations_recap tr.evaluation.incomplete td a {
table.evaluations_recap tr.evaluation.incomplete td a.incomplete {
font-weight: bold;
}
table.evaluations_recap td.inscrits,
table.evaluations_recap td.manquantes,
table.evaluations_recap td.nb_abs,
table.evaluations_recap td.nb_att,
table.evaluations_recap td.nb_exc {
text-align: center;
}

View File

@ -20,73 +20,70 @@
{% endmacro %}
{% macro render_add_logo(add_logo_form) %}
<div class="logo-add">
<h3>Ajouter un logo</h3>
{{ add_logo_form.hidden_tag() }}
{{ render_field(add_logo_form.name) }}
{{ render_field(add_logo_form.upload) }}
{{ render_field(add_logo_form.do_insert, False, onSubmit="submit_form") }}
</div>
<details>
<summary>
<h3>Ajouter un logo</h3>
</summary>
<div>
{{ render_field(add_logo_form.name) }}
{{ render_field(add_logo_form.upload) }}
{{ render_field(add_logo_form.do_insert, False, onSubmit="submit_form") }}
</div>
</details>
{% endmacro %}
{% macro render_logo(dept_form, logo_form) %}
<div class="logo-edit">
{{ logo_form.hidden_tag() }}
{% if logo_form.titre %}
<tr class="logo-edit">
<td colspan="3" class="titre">
<div class="nom">
<h3>{{ logo_form.titre }}</h3>
</div>
<div class="description">{{ logo_form.description or "" }}</div>
</td>
</tr>
{% else %}
<tr class="logo-edit">
<td colspan="3" class="titre">
<span class="nom">
<h3>Logo personalisé: {{ logo_form.logo_id.data }}</h3>
</span>
<span class="description">{{ logo_form.description or "" }}</span>
</td>
</tr>
{% endif %}
<tr>
<td style="padding-right: 20px; ">
<div class="img-container">
<details>
{{ logo_form.hidden_tag() }}
<summary>
{% if logo_form.titre %}
<h3 class="titre_logo">{{ logo_form.titre }}</h3>
{% if logo_form.description %}
<div class="sco_help">{{ logo_form.description }}</div>
{% endif %}
{% else %}
<h3 class="titre_logo">Logo personalisé: {{ logo_form.logo_id.data }}</h3>
{% if logo_form.description %}
<div class="sco_help">{{ logo_form.description }}</div>
{% endif %}
{% endif %}
</summary>
<div class="content">
<div class="image_logo">
<img src="{{ logo_form.logo.get_url_small() }}" alt="pas de logo chargé" />
</div>
</td>
<td class="img-data">
<h3>{{ logo_form.logo.logoname }} (Format: {{ logo_form.logo.suffix }})</h3>
Taille: {{ logo_form.logo.size }} px
{% if logo_form.logo.mm %} &nbsp; / &nbsp; {{ logo_form.logo.mm }} mm {% endif %}<br />
Aspect ratio: {{ logo_form.logo.aspect_ratio }}<br />
Usage: <span style="font-family: system-ui">{{ logo_form.logo.get_usage() }}</span>
</td>
<td class="" img-action">
<p>Modifier l'image</p>
<span class="wtf-field">{{ render_field(logo_form.upload, False, onchange="submit_form()") }}</span>
{% if logo_form.can_delete %}
<p>Supprimer l'image</p>
{{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }}
{% endif %}
</td>
</tr>
</div>
<div class="infos_logo">
<h4>{{ logo_form.logo.logoname }} (Format: {{ logo_form.logo.suffix }})</h4>
Taille: {{ logo_form.logo.size }} px
{% if logo_form.logo.mm %} &nbsp; / &nbsp; {{ logo_form.logo.mm }} mm {% endif %}<br />
Aspect ratio: {{ logo_form.logo.aspect_ratio }}<br />
Usage: <span style="font-family: system-ui">{{ logo_form.logo.get_usage() }}</span>
</div>
<div class="actions_logo">
<div class="action_label">Modifier l'image</div>
<div class="action_button">
<span class="wtf-field">{{ render_field(logo_form.upload, False, onchange="submit_form()") }}</span>
</div>
{% if logo_form.can_delete %}
<div class="action_label">Supprimer l'image</div>
<div class="action_button">
{{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }}
</div>
{% endif %}
</div>
</div>
</details>
{% endmacro %}
{% macro render_logos(dept_form) %}
<table>
{% for logo_entry in dept_form.logos.entries %}
{% set logo_form = logo_entry.form %}
{{ render_logo(dept_form, logo_form) }}
{% else %}
<p class="logo-edit">
<h3>Aucun logo défini en propre à ce département</h3>
<p class="logo-titre_logo">
<h3 class="titre_logo">Aucun logo défini en propre à ce département</h3>
</p>
{% endfor %}
</table>
{% endmacro %}
{% block app_content %}
@ -100,25 +97,26 @@
<div class="configuration_logo">
<h1>Bibliothèque de logos</h1>
{% for dept_entry in form.depts.entries %}
{% set dept_form = dept_entry.form %}
{{ dept_entry.form.hidden_tag() }}
{% if dept_entry.form.is_local() %}
<div class="departement">
<h2>Département {{ dept_form.dept_name.data }}</h2>
<h3>Logos locaux</h3>
<div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br />
Les logos du département se substituent aux logos de même nom définis globalement:</div>
</div>
{% else %}
<div class="departement">
<h2>Logos généraux</h2>
<div class="sco_help">Les images de cette section sont utilisé pour tous les départements,
mais peuvent être redéfinies localement au niveau de chaque département
(il suffit de définir un logo local de même nom)</div>
</div>
{% endif %}
{{ render_logos(dept_form) }}
{{ render_add_logo(dept_form.add_logo.form) }}
<details>
{% set dept_form = dept_entry.form %}
{{ dept_entry.form.hidden_tag() }}
<summary>
{% if dept_entry.form.is_local() %}
<h2>Département {{ dept_form.dept_name.data }}</h2>
<div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br />
Les logos du département se substituent aux logos de même nom définis globalement:</div>
{% else %}
<h2>Logos généraux</h2>
<div class="sco_help">Les images de cette section sont utilisé pour tous les départements,
mais peuvent être redéfinies localement au niveau de chaque département
(il suffit de définir un logo local de même nom)</div>
{% endif %}
</summary>
<div>
{{ render_logos(dept_form) }}
{{ render_add_logo(dept_form.add_logo.form) }}
</div>
</details>
{% endfor %}
</div>
</form>

View File

@ -0,0 +1,47 @@
{# -*- mode: jinja-html -*- #}
{% extends "sco_page.html" %}
{% block styles %}
{{super()}}
{% endblock %}
{% block app_content %}
<h2>Opérations dans le département {{g.scodoc_dept}}</h2>
<table id="dept_news" class="table table-striped">
<thead>
<tr>
<th>Date</th>
<th>Type</th>
<th>Auteur</th>
<th>Détail</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block scripts %}
{{super()}}
<script>
$(document).ready(function () {
$('#dept_news').DataTable({
ajax: '{{url_for("scolar.dept_news_json", scodoc_dept=g.scodoc_dept)}}',
serverSide: true,
columns: [
{
data: {
_: "date.display",
sort: "date.timestamp"
}
},
{data: 'type', searchable: false},
{data: 'authenticated_user', orderable: false, searchable: true},
{data: 'text', orderable: false, searchable: true}
],
"order": [[ 0, "desc" ]]
});
});
</script>
{% endblock %}

View File

@ -1738,12 +1738,13 @@ def evaluation_listenotes():
evaluation_id = None
moduleimpl_id = None
vals = scu.get_request_args()
if "evaluation_id" in vals:
evaluation_id = int(vals["evaluation_id"])
mode = "eval"
if "moduleimpl_id" in vals and vals["moduleimpl_id"]:
moduleimpl_id = int(vals["moduleimpl_id"])
mode = "module"
try:
if "evaluation_id" in vals:
evaluation_id = int(vals["evaluation_id"])
if "moduleimpl_id" in vals and vals["moduleimpl_id"]:
moduleimpl_id = int(vals["moduleimpl_id"])
except ValueError as exc:
raise ScoValueError("adresse invalide !") from exc
format = vals.get("format", "html")
html_content, page_title = sco_liste_notes.do_evaluation_listenotes(

View File

@ -41,6 +41,7 @@ from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import SubmitField
from app import db
from app import log
from app.decorators import (
scodoc,
@ -52,8 +53,10 @@ from app.decorators import (
)
from app.models.etudiants import Identite
from app.models.etudiants import make_etud_args
from app.models.events import ScolarNews
from app.views import scolar_bp as bp
from app.views import ScoData
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -339,6 +342,67 @@ def install_info():
return sco_up_to_date.is_up_to_date()
@bp.route("/dept_news")
@scodoc
@permission_required(Permission.ScoView)
def dept_news():
"Affiche table des dernières opérations"
return render_template(
"dept_news.html", title=f"Opérations {g.scodoc_dept}", sco=ScoData()
)
@bp.route("/dept_news_json")
@scodoc
@permission_required(Permission.ScoView)
def dept_news_json():
"Table des news du département"
start = request.args.get("start", type=int)
length = request.args.get("length", type=int)
log(f"dept_news_json( start={start}, length={length})")
query = ScolarNews.query.filter_by(dept_id=g.scodoc_dept_id)
# search
search = request.args.get("search[value]")
if search:
query = query.filter(
db.or_(
ScolarNews.authenticated_user.like(f"%{search}%"),
ScolarNews.text.like(f"%{search}%"),
)
)
total_filtered = query.count()
# sorting
order = []
i = 0
while True:
col_index = request.args.get(f"order[{i}][column]")
if col_index is None:
break
col_name = request.args.get(f"columns[{col_index}][data]")
if col_name not in ["date", "type", "authenticated_user"]:
col_name = "date"
descending = request.args.get(f"order[{i}][dir]") == "desc"
col = getattr(ScolarNews, col_name)
if descending:
col = col.desc()
order.append(col)
i += 1
if order:
query = query.order_by(*order)
# pagination
query = query.offset(start).limit(length)
data = [news.to_dict() for news in query]
# response
return {
"data": data,
"recordsFiltered": total_filtered,
"recordsTotal": ScolarNews.query.count(),
"draw": request.args.get("draw", type=int),
}
sco_publish(
"/trombino", sco_trombino.trombino, Permission.ScoView, methods=["GET", "POST"]
)

View File

@ -1,5 +1,5 @@
alembic==1.7.5
astroid==2.9.1
astroid==2.11.2
attrs==21.4.0
Babel==2.9.1
blinker==1.4
@ -35,6 +35,7 @@ isort==5.10.1
itsdangerous==2.0.1
Jinja2==3.0.3
lazy-object-proxy==1.7.1
lxml==4.8.0
Mako==1.1.6
MarkupSafe==2.0.1
mccabe==0.6.1
@ -53,6 +54,7 @@ pyOpenSSL==21.0.0
pyparsing==3.0.6
pytest==6.2.5
python-dateutil==2.8.2
python-docx==0.8.11
python-dotenv==0.19.2
python-editor==1.0.4
pytz==2021.3

View File

@ -1,13 +1,21 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.2.0a"
SCOVERSION = "9.2.1"
SCONAME = "ScoDoc"
SCONEWS = """
<h4>Année 2021</h4>
<ul>
<li>ScoDoc 9.2:
<ul>
<li>Tableau récap. complet pour BUT et autres formations.</li>
<li>Tableau état évaluations</li>
<li>Version alpha du module "relations entreprises"</li>
<li>Export des trombinoscope en document docx</li>
<li>Très nombreux correctifs</li>
</ul>
<li>ScoDoc 9.1.75: bulletins BUT pdf</li>
<li>ScoDoc 9.1.50: nombreuses amélioration gestion BUT</li>
<li>ScoDoc 9.1: gestion des formations par compétences, type BUT.</li>

View File

@ -77,6 +77,7 @@ def make_shell_context():
"pp": pp,
"Role": Role,
"scolar": scolar,
"ScolarNews": models.ScolarNews,
"scu": scu,
"UniteEns": UniteEns,
"User": User,

View File

@ -73,7 +73,9 @@ token = r.json()["token"]
HEADERS = {"Authorization": f"Bearer {token}"}
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:
raise ScoError("erreur de connexion: vérifier adresse et identifiants")

View File

@ -16,25 +16,22 @@ Utilisation :
import os
import requests
SCODOC_USER = "test"
SCODOC_PASSWORD = "test"
SCODOC_URL = "http://192.168.1.12:5000"
SCODOC_USER = "api_tester"
SCODOC_PASSWORD = "api_tester"
SCODOC_URL = "http://deb11.viennet.net: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}"}
return {"Authorization": f"Bearer {token}"}
get_token()
HEADERS = get_token()

View File

@ -18,126 +18,156 @@ Utilisation :
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE
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
)
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
)
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
)
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/<int:etudid>/abs_just_only",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/absences/etudid/<int:etudid>/abs_just_only",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/nip/<int:nip>/abs_just_only",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/absences/nip/<int:nip>/abs_just_only",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/ine/<int:ine>/abs_just_only",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/absences/ine/<int:ine>/abs_just_only",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# abs_signale
def test_abs_signale():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_signale?etudid=<int:etudid>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_signale?etudid=<int:etudid>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_signale?nip=<int:nip>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_signale?nip=<int:nip>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>&moduleimpl_id=<int:moduleimpl_id>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>&moduleimpl_id=<int:moduleimpl_id>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# abs_annule
def test_abs_annule():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_annule?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_annule?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_annule?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_annule?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_annule?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_annule?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# abs_annule_justif
def test_abs_annule_justif():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_annule_justif?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_annule_justif?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_annule_justif?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_annule_justif?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/abs_annule_justif?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/absences/abs_annule_justif?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>",
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
)
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

@ -18,14 +18,16 @@ Utilisation :
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# departements
def test_departements():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements",
headers=HEADERS, verify=CHECK_CERTIFICATE
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
@ -34,16 +36,18 @@ def test_departements():
# liste_etudiants
def test_liste_etudiants():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
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
)
SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 16
@ -51,9 +55,10 @@ def test_liste_etudiants():
# liste_semestres_courant
def test_semestres_courant():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
@ -61,9 +66,11 @@ def test_semestres_courant():
# referenciel_competences
def test_referenciel_competences():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200 or 204
@ -74,21 +81,3 @@ def test_referenciel_competences():
# headers=HEADERS, verify=CHECK_CERTIFICATE
# )
# assert r.status_code == 200

View File

@ -19,23 +19,34 @@ Utilisation :
from random import randint
import requests
from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# etudiants
def test_etudiants():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants",
headers=HEADERS, verify=CHECK_CERTIFICATE
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]
etu = r.json()[randint(0, len(r.json())) - 1]
fields = ["civilite", "code_ine", "code_nip", "date_naissance", "email", "emailperso", "etudid", "nom",
"prenom", "nomprenom"]
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
"nomprenom",
]
fields_OK = True
@ -51,7 +62,8 @@ def test_etudiants():
def test_etudiants_courant():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
headers=HEADERS, verify=CHECK_CERTIFICATE
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 0
@ -61,7 +73,8 @@ def test_etudiants_courant():
def test_etudiant():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1",
headers=HEADERS, verify=CHECK_CERTIFICATE
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 10
@ -82,7 +95,8 @@ def test_etudiant():
def test_etudiant_formsemestres():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestres",
headers=HEADERS, verify=CHECK_CERTIFICATE
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
@ -110,29 +124,29 @@ def test_etudiant_formsemestres():
# assert len(r.json()) == 1
# r = requests.get(
# SCODOC_URL + "/ScoDoc/api/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin",
# headers=HEADERS, verify=CHECK_CERTIFICATE
# )
# assert r.status_code == 200
#
# r = requests.get(
# SCODOC_URL + "/ScoDoc/api/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin",
# headers=HEADERS, verify=CHECK_CERTIFICATE
# )
# assert r.status_code == 200
# r = requests.get(
# SCODOC_URL + "/ScoDoc/api/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin",
# headers=HEADERS, verify=CHECK_CERTIFICATE
# )
# assert r.status_code == 200
#
# r = requests.get(
# SCODOC_URL + "/ScoDoc/api/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin",
# headers=HEADERS, verify=CHECK_CERTIFICATE
# )
# assert r.status_code == 200
# etudiant_groups
def test_etudiant_groups():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/semestre/1/groups",
headers=HEADERS, verify=CHECK_CERTIFICATE
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
# r = requests.get(
# SCODOC_URL + "/ScoDoc/api/etudiant/nip/<int:nip>/semestre/<int:formsemestre_id>/groups",
# headers=HEADERS, verify=CHECK_CERTIFICATE

View File

@ -18,41 +18,51 @@ Utilisation :
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# evaluations
def test_evaluations():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/evaluations/<int:moduleimpl_id>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/evaluations/<int:moduleimpl_id>",
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/<int:evaluation_id>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/evaluations/eval_notes/<int:evaluation_id>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# evaluation_set_notes
def test_evaluation_set_notes():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/evaluations/eval_set_notes?eval_id=<int:eval_id>&etudid=<int:etudid>&note=<float:note>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/evaluations/eval_set_notes?eval_id=<int:eval_id>&etudid=<int:etudid>&note=<float:note>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/evaluations/eval_set_notes?eval_id=<int:eval_id>&nip=<int:nip>&note=<float:note>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/evaluations/eval_set_notes?eval_id=<int:eval_id>&nip=<int:nip>&note=<float:note>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/evaluations/eval_set_notes?eval_id=<int:eval_id>&ine=<int:ine>&note=<float:note>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/evaluations/eval_set_notes?eval_id=<int:eval_id>&ine=<int:ine>&note=<float:note>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200

View File

@ -18,55 +18,65 @@ Utilisation :
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE
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
)
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/<int:formation_id>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/formations/<int:formation_id>",
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/<int:formation_id>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/formations/formation_export/<int:formation_id>",
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
)
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
)
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/<int:moduleimpl_id>/formsemestre/<int:formsemestre_id>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/formations/moduleimpl/<int:moduleimpl_id>/formsemestre/<int:formsemestre_id>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200

View File

@ -18,50 +18,61 @@ Utilisation :
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# formsemestre
def test_formsemestre():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/formsemestre/<int:formsemestre_id>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/formations/formsemestre/<int:formsemestre_id>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# etudiant_bulletin
def test_etudiant_bulletin():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/etudid/<int:etudid>/bulletin",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/etudid/<int:etudid>/bulletin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/nip/<int:nip>/bulletin",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/nip/<int:nip>/bulletin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/ine/<int:ine>/bulletin",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL
+ "/ScoDoc/api/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/ine/<int:ine>/bulletin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# bulletins
def test_bulletins():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/<int:formsemestre_id>/bulletins",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/formsemestre/<int:formsemestre_id>/bulletins",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# jury
def test_jury():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/<int:formsemestre_id>/jury",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/formsemestre/<int:formsemestre_id>/jury",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200

View File

@ -18,66 +18,83 @@ Utilisation :
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE
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
)
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
)
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
)
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
)
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
)
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
)
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/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
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
"""
import pytest
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from scodoc import app
from tests.unit.config_test_logos import (

View File

@ -18,36 +18,42 @@ Utilisation :
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# partition
def test_partition():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/partitions/<int:formsemestre_id>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/partitions/<int:formsemestre_id>",
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/<int:group_id>",
headers=HEADERS, verify=CHECK_CERTIFICATE
)
SCODOC_URL + "/ScoDoc/api/partitions/groups/<int:group_id>",
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
)
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>&"
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
)
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200