forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -7,7 +7,6 @@
|
|||||||
"""
|
"""
|
||||||
ScoDoc 9 API : accès aux utilisateurs
|
ScoDoc 9 API : accès aux utilisateurs
|
||||||
"""
|
"""
|
||||||
import datetime
|
|
||||||
|
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
@ -15,13 +14,14 @@ from flask_login import current_user, login_required
|
|||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
|
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
|
||||||
from app.scodoc.sco_utils import json_error
|
|
||||||
from app.auth.models import User, Role, UserRole
|
from app.auth.models import User, Role, UserRole
|
||||||
from app.auth.models import is_valid_password
|
from app.auth.models import is_valid_password
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import Departement
|
from app.models import Departement, ScoDocSiteConfig
|
||||||
|
from app.scodoc import sco_edt_cal
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
from app.scodoc.sco_utils import json_error
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
@ -441,3 +441,63 @@ def role_delete(role_name: str):
|
|||||||
db.session.delete(role)
|
db.session.delete(role)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"OK": True}
|
return {"OK": True}
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/user/<int:uid>/edt")
|
||||||
|
@api_web_bp.route("/user/<int:uid>/edt")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
@as_json
|
||||||
|
def user_edt(uid: int):
|
||||||
|
"""L'emploi du temps de l'utilisateur.
|
||||||
|
Si ok, une liste d'évènements. Sinon, une chaine indiquant un message d'erreur.
|
||||||
|
|
||||||
|
show_modules_titles affiche le titre complet du module (défaut), sinon juste le code.
|
||||||
|
|
||||||
|
Il faut la permission ScoView + (UsersView ou bien être connecté comme l'utilisateur demandé)
|
||||||
|
"""
|
||||||
|
if g.scodoc_dept is None: # route API non départementale
|
||||||
|
if not current_user.has_permission(Permission.UsersView):
|
||||||
|
return scu.json_error(403, "accès non autorisé")
|
||||||
|
user: User = db.session.get(User, uid)
|
||||||
|
if user is None:
|
||||||
|
return json_error(404, "user not found")
|
||||||
|
# Check permission
|
||||||
|
if current_user.id != user.id:
|
||||||
|
if g.scodoc_dept:
|
||||||
|
allowed_depts = current_user.get_depts_with_permission(Permission.UsersView)
|
||||||
|
if (None not in allowed_depts) and (user.dept not in allowed_depts):
|
||||||
|
return json_error(404, "user not found")
|
||||||
|
|
||||||
|
show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
|
||||||
|
|
||||||
|
# Cherche ics
|
||||||
|
if not user.edt_id:
|
||||||
|
return json_error(404, "user not configured")
|
||||||
|
ics_filename = sco_edt_cal.get_ics_user_edt_filename(user.edt_id)
|
||||||
|
if not ics_filename:
|
||||||
|
return json_error(404, "no calendar for this user")
|
||||||
|
|
||||||
|
_, calendar = sco_edt_cal.load_calendar(ics_filename)
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# - Construire mapping edt2modimpl: edt_id -> modimpl
|
||||||
|
# pour cela, considérer tous les formsemestres de la période de l'edt
|
||||||
|
# (soit on considère l'année scolaire du 1er event, ou celle courante,
|
||||||
|
# soit on cherche min, max des dates des events)
|
||||||
|
# - Modifier décodage des groupes dans convert_ics pour avoi run mapping
|
||||||
|
# de groupe par semestre (retrouvé grâce au modimpl associé à l'event)
|
||||||
|
|
||||||
|
raise NotImplementedError() # TODO XXX WIP
|
||||||
|
|
||||||
|
events_scodoc, _ = sco_edt_cal.convert_ics(
|
||||||
|
calendar,
|
||||||
|
edt2group=edt2group,
|
||||||
|
default_group=default_group,
|
||||||
|
edt2modimpl=edt2modimpl,
|
||||||
|
)
|
||||||
|
edt_dict = sco_edt_cal.translate_calendar(
|
||||||
|
events_scodoc, group_ids, show_modules_titles=show_modules_titles
|
||||||
|
)
|
||||||
|
return edt_dict
|
||||||
|
@ -177,12 +177,26 @@ def formsemestre_edt_dict(
|
|||||||
TODO: spécifier intervalle de dates start et end
|
TODO: spécifier intervalle de dates start et end
|
||||||
"""
|
"""
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
group_ids_set = set(group_ids) if group_ids else set()
|
|
||||||
try:
|
try:
|
||||||
events_scodoc, _ = load_and_convert_ics(formsemestre)
|
events_scodoc, _ = load_and_convert_ics(formsemestre)
|
||||||
except ScoValueError as exc:
|
except ScoValueError as exc:
|
||||||
return exc.args[0]
|
return exc.args[0]
|
||||||
# Génération des événements pour le calendrier html
|
edt_dict = translate_calendar(
|
||||||
|
events_scodoc, group_ids, show_modules_titles=show_modules_titles
|
||||||
|
)
|
||||||
|
log(
|
||||||
|
f"formsemestre_edt_dict: loaded edt for {formsemestre} in {(time.time()-t0):g}s"
|
||||||
|
)
|
||||||
|
return edt_dict
|
||||||
|
|
||||||
|
|
||||||
|
def translate_calendar(
|
||||||
|
events_scodoc: list[dict],
|
||||||
|
group_ids: list[int] = None,
|
||||||
|
show_modules_titles=True,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""Génération des événements pour le calendrier html"""
|
||||||
|
group_ids_set = set(group_ids) if group_ids else set()
|
||||||
promo_icon = f"""<img height="18px" src="{scu.STATIC_DIR}/icons/promo.svg"
|
promo_icon = f"""<img height="18px" src="{scu.STATIC_DIR}/icons/promo.svg"
|
||||||
title="promotion complète" alt="promotion"/>"""
|
title="promotion complète" alt="promotion"/>"""
|
||||||
abs_icon = f"""<img height="28px" src="{scu.STATIC_DIR}/icons/absences.svg"
|
abs_icon = f"""<img height="28px" src="{scu.STATIC_DIR}/icons/absences.svg"
|
||||||
@ -211,7 +225,6 @@ def formsemestre_edt_dict(
|
|||||||
url_for(
|
url_for(
|
||||||
"assiduites.signal_assiduites_group",
|
"assiduites.signal_assiduites_group",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
group_ids=group.id,
|
group_ids=group.id,
|
||||||
heure_deb=event["heure_deb"],
|
heure_deb=event["heure_deb"],
|
||||||
heure_fin=event["heure_fin"],
|
heure_fin=event["heure_fin"],
|
||||||
@ -271,17 +284,17 @@ def formsemestre_edt_dict(
|
|||||||
"start": event["start"],
|
"start": event["start"],
|
||||||
"end": event["end"],
|
"end": event["end"],
|
||||||
"backgroundColor": event["group_bg_color"],
|
"backgroundColor": event["group_bg_color"],
|
||||||
|
"raw": event["raw"],
|
||||||
# Infos brutes pour usage API éventuel
|
# Infos brutes pour usage API éventuel
|
||||||
"edt_ens_ids": event["edt_ens_ids"],
|
"edt_ens_ids": event["edt_ens_ids"],
|
||||||
"ens_user_names": ens_user_names,
|
"ens_user_names": ens_user_names,
|
||||||
"group_id": group.id if group else None,
|
"group_id": group.id if group else None,
|
||||||
"group_edt_id": event["edt_group"],
|
"group_edt_id": event["edt_group"],
|
||||||
"moduleimpl_id": modimpl.id if modimpl else None,
|
"moduleimpl_id": modimpl.id if modimpl else None,
|
||||||
|
"UID": event["UID"], # icalendar event UID
|
||||||
}
|
}
|
||||||
events_cal.append(d)
|
events_cal.append(d)
|
||||||
log(
|
|
||||||
f"formsemestre_edt_dict: loaded edt for {formsemestre} in {(time.time()-t0):g}s"
|
|
||||||
)
|
|
||||||
return events_cal
|
return events_cal
|
||||||
|
|
||||||
|
|
||||||
@ -302,7 +315,35 @@ def get_ics_uid_pattern() -> re.Pattern:
|
|||||||
|
|
||||||
|
|
||||||
def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[str]]:
|
def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[str]]:
|
||||||
"""Chargement fichier ics, filtrage et extraction des identifiants.
|
"""Chargement fichier ics.
|
||||||
|
Renvoie une liste d'évènements, et la liste des identifiants de groupes
|
||||||
|
trouvés (utilisée pour l'aide).
|
||||||
|
"""
|
||||||
|
# Chargement du calendier ics
|
||||||
|
_, calendar = formsemestre_load_calendar(formsemestre)
|
||||||
|
if not calendar:
|
||||||
|
return [], []
|
||||||
|
# --- Correspondances id edt -> id scodoc pour groupes
|
||||||
|
edt2group = formsemestre_retreive_groups_from_edt_id(formsemestre)
|
||||||
|
default_group = formsemestre.get_default_group()
|
||||||
|
# --- Correspondances id edt -> id scodoc pour modimpls
|
||||||
|
edt2modimpl = formsemestre_retreive_modimpls_from_edt_id(formsemestre)
|
||||||
|
return convert_ics(
|
||||||
|
calendar,
|
||||||
|
edt2group=edt2group,
|
||||||
|
default_group=default_group,
|
||||||
|
edt2modimpl=edt2modimpl,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_ics(
|
||||||
|
calendar: icalendar.cal.Calendar,
|
||||||
|
edt2group: dict[str, GroupDescr] = None,
|
||||||
|
default_group: GroupDescr = None,
|
||||||
|
edt2modimpl: dict[str, ModuleImpl] = None,
|
||||||
|
) -> tuple[list[dict], list[str]]:
|
||||||
|
"""Filtrage et extraction des identifiants des évènements calendrier.
|
||||||
|
|
||||||
Renvoie une liste d'évènements, et la liste des identifiants de groupes
|
Renvoie une liste d'évènements, et la liste des identifiants de groupes
|
||||||
trouvés (utilisée pour l'aide).
|
trouvés (utilisée pour l'aide).
|
||||||
|
|
||||||
@ -310,10 +351,6 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
- False si extraction regexp non configuré
|
- False si extraction regexp non configuré
|
||||||
- "tous" (promo) si pas de correspondance trouvée.
|
- "tous" (promo) si pas de correspondance trouvée.
|
||||||
"""
|
"""
|
||||||
# Chargement du calendier ics
|
|
||||||
_, calendar = formsemestre_load_calendar(formsemestre)
|
|
||||||
if not calendar:
|
|
||||||
return []
|
|
||||||
# --- Paramètres d'extraction
|
# --- Paramètres d'extraction
|
||||||
edt_ics_title_field = ScoDocSiteConfig.get("edt_ics_title_field")
|
edt_ics_title_field = ScoDocSiteConfig.get("edt_ics_title_field")
|
||||||
edt_ics_title_regexp = ScoDocSiteConfig.get("edt_ics_title_regexp")
|
edt_ics_title_regexp = ScoDocSiteConfig.get("edt_ics_title_regexp")
|
||||||
@ -348,15 +385,13 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
edt_ics_uid_field = ScoDocSiteConfig.get("edt_ics_uid_field")
|
edt_ics_uid_field = ScoDocSiteConfig.get("edt_ics_uid_field")
|
||||||
edt_ics_uid_pattern = get_ics_uid_pattern()
|
edt_ics_uid_pattern = get_ics_uid_pattern()
|
||||||
|
|
||||||
# --- Correspondances id edt -> id scodoc pour groupes, modules et enseignants
|
# --- Groupes
|
||||||
edt2group = formsemestre_retreive_groups_from_edt_id(formsemestre)
|
|
||||||
group_colors = {
|
group_colors = {
|
||||||
group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1]
|
group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1]
|
||||||
for i, group_name in enumerate(edt2group)
|
for i, group_name in enumerate(edt2group)
|
||||||
}
|
}
|
||||||
edt_groups_ids = set() # les ids de groupes normalisés tels que dans l'ics
|
edt_groups_ids = set() # les ids de groupes normalisés tels que dans l'ics
|
||||||
default_group = formsemestre.get_default_group()
|
|
||||||
edt2modimpl = formsemestre_retreive_modimpls_from_edt_id(formsemestre)
|
|
||||||
edt2user: dict[str, User | None] = {} # construit au fur et à mesure (cache)
|
edt2user: dict[str, User | None] = {} # construit au fur et à mesure (cache)
|
||||||
# ---
|
# ---
|
||||||
events = [e for e in calendar.walk() if e.name == "VEVENT"]
|
events = [e for e in calendar.walk() if e.name == "VEVENT"]
|
||||||
@ -371,29 +406,6 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
)
|
)
|
||||||
# title remplacé par le nom du module scodoc quand il est trouvé
|
# title remplacé par le nom du module scodoc quand il est trouvé
|
||||||
title = title_edt
|
title = title_edt
|
||||||
# --- Group
|
|
||||||
if edt_ics_group_pattern:
|
|
||||||
edt_group = extract_event_edt_id(
|
|
||||||
event, edt_ics_group_field, edt_ics_group_pattern
|
|
||||||
)
|
|
||||||
edt_groups_ids.add(edt_group)
|
|
||||||
# si pas de groupe dans l'event, ou si groupe non reconnu,
|
|
||||||
# prend toute la promo ("tous")
|
|
||||||
group: GroupDescr = (
|
|
||||||
edt2group.get(edt_group, default_group)
|
|
||||||
if edt_group
|
|
||||||
else default_group
|
|
||||||
)
|
|
||||||
group_bg_color = (
|
|
||||||
group_colors.get(edt_group, _EVENT_DEFAULT_COLOR)
|
|
||||||
if group
|
|
||||||
else "lightgrey"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
edt_group = ""
|
|
||||||
group = False
|
|
||||||
group_bg_color = _EVENT_DEFAULT_COLOR
|
|
||||||
|
|
||||||
# --- ModuleImpl
|
# --- ModuleImpl
|
||||||
if edt_ics_mod_pattern:
|
if edt_ics_mod_pattern:
|
||||||
edt_module = extract_event_edt_id(
|
edt_module = extract_event_edt_id(
|
||||||
@ -405,6 +417,34 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
else:
|
else:
|
||||||
modimpl = False
|
modimpl = False
|
||||||
edt_module = ""
|
edt_module = ""
|
||||||
|
# --- Group
|
||||||
|
if edt_ics_group_pattern:
|
||||||
|
edt_group = extract_event_edt_id(
|
||||||
|
event, edt_ics_group_field, edt_ics_group_pattern
|
||||||
|
)
|
||||||
|
edt_groups_ids.add(edt_group)
|
||||||
|
# si pas de groupe dans l'event, ou si groupe non reconnu,
|
||||||
|
# prend toute la promo ("tous")
|
||||||
|
event_default_group = (
|
||||||
|
default_group
|
||||||
|
if default_group
|
||||||
|
else (modimpl.formsemestre.get_default_group() if modimpl else None)
|
||||||
|
)
|
||||||
|
group: GroupDescr = (
|
||||||
|
edt2group.get(edt_group, event_default_group)
|
||||||
|
if edt_group
|
||||||
|
else event_default_group
|
||||||
|
)
|
||||||
|
group_bg_color = (
|
||||||
|
group_colors.get(edt_group, _EVENT_DEFAULT_COLOR)
|
||||||
|
if group
|
||||||
|
else "lightgrey"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
edt_group = ""
|
||||||
|
group = False
|
||||||
|
group_bg_color = _EVENT_DEFAULT_COLOR
|
||||||
|
|
||||||
# --- Enseignants
|
# --- Enseignants
|
||||||
users: list[User] = []
|
users: list[User] = []
|
||||||
if edt_ics_uid_pattern:
|
if edt_ics_uid_pattern:
|
||||||
@ -446,6 +486,8 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
"jour": event.decoded("dtstart").date().isoformat(),
|
"jour": event.decoded("dtstart").date().isoformat(),
|
||||||
"start": event.decoded("dtstart").isoformat(),
|
"start": event.decoded("dtstart").isoformat(),
|
||||||
"end": event.decoded("dtend").isoformat(),
|
"end": event.decoded("dtend").isoformat(),
|
||||||
|
"UID": event.decoded("UID").decode("utf-8"),
|
||||||
|
"raw": event.to_ical().decode("utf-8"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return events_sco, sorted(edt_groups_ids)
|
return events_sco, sorted(edt_groups_ids)
|
||||||
|
@ -144,3 +144,7 @@ span.ens-non-reconnu {
|
|||||||
.btn:active {
|
.btn:active {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.raw-event {
|
||||||
|
display: none;
|
||||||
|
}
|
@ -126,7 +126,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
time: function(event) {
|
time: function(event) {
|
||||||
const date_start = new Date(event.start);
|
const date_start = new Date(event.start);
|
||||||
const start = hm_formatter.format(date_start);
|
const start = hm_formatter.format(date_start);
|
||||||
return `<strong>${start}</strong> <span>${event.title}</span>`;
|
return `<strong>${start}</strong> <span>${event.title}</span> <div class="raw-event">${event.raw}</div>`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
timezone: {
|
timezone: {
|
||||||
@ -249,10 +249,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if ((iso_date_start > "{{ formsemestre.date_fin.isoformat() }}")
|
if ((iso_date_start > "{{ formsemestre.date_fin.isoformat() }}")
|
||||||
|| (iso_date_end < "{{ formsemestre.date_debut.isoformat() }}")) {
|
|| (iso_date_end < "{{ formsemestre.date_debut.isoformat() }}")) {
|
||||||
cal_warning.style.display = 'inline-block';
|
cal_warning.style.display = 'inline-block';
|
||||||
console.log("OUTSIDE");
|
|
||||||
} else {
|
} else {
|
||||||
cal_warning.style.display = 'none';
|
cal_warning.style.display = 'none';
|
||||||
console.log("INSIDE");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// View menu
|
// View menu
|
||||||
|
@ -947,12 +947,13 @@ def choix_date() -> str:
|
|||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.AbsChange)
|
||||||
def signal_assiduites_group():
|
def signal_assiduites_group():
|
||||||
"""
|
"""
|
||||||
signal_assiduites_group Saisie des assiduités des groupes pour le jour donnée
|
signal_assiduites_group Saisie des assiduités des groupes pour le jour donné
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: l'html généré
|
str: l'html généré
|
||||||
"""
|
"""
|
||||||
# Récupération des paramètres de l'url
|
# Récupération des paramètres de l'url
|
||||||
|
# formsemestre_id est optionnel si modimpl est indiqué
|
||||||
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
||||||
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
||||||
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
||||||
@ -972,13 +973,20 @@ def signal_assiduites_group():
|
|||||||
moduleimpl_id = int(moduleimpl_id)
|
moduleimpl_id = int(moduleimpl_id)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
moduleimpl_id = None
|
moduleimpl_id = None
|
||||||
|
if moduleimpl_id >= 0 and moduleimpl_id is not None:
|
||||||
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
|
else:
|
||||||
|
modimpl = None
|
||||||
# Vérification du formsemestre_id
|
# Vérification du formsemestre_id
|
||||||
try:
|
try:
|
||||||
formsemestre_id = int(formsemestre_id)
|
formsemestre_id = int(formsemestre_id)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
formsemestre_id = None
|
formsemestre_id = None
|
||||||
|
|
||||||
|
if (formsemestre_id < 0 or formsemestre_id is None) and modimpl:
|
||||||
|
# si le module est spécifié mais pas le semestre:
|
||||||
|
formsemestre_id = modimpl.formsemestre_id
|
||||||
|
|
||||||
# Gestion des groupes
|
# Gestion des groupes
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids,
|
group_ids,
|
||||||
|
@ -48,7 +48,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
|
|
||||||
def _calendar_factory():
|
def _calendar_factory() -> icalendar.Calendar:
|
||||||
"Create a new calendar"
|
"Create a new calendar"
|
||||||
cal = icalendar.Calendar()
|
cal = icalendar.Calendar()
|
||||||
cal.add(
|
cal.add(
|
||||||
|
Loading…
Reference in New Issue
Block a user