forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -7,7 +7,6 @@
|
||||
"""
|
||||
ScoDoc 9 API : accès aux utilisateurs
|
||||
"""
|
||||
import datetime
|
||||
|
||||
from flask import g, request
|
||||
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.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 is_valid_password
|
||||
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_permissions import Permission
|
||||
from app.scodoc.sco_utils import json_error
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
@ -441,3 +441,63 @@ def role_delete(role_name: str):
|
||||
db.session.delete(role)
|
||||
db.session.commit()
|
||||
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
|
||||
"""
|
||||
t0 = time.time()
|
||||
group_ids_set = set(group_ids) if group_ids else set()
|
||||
try:
|
||||
events_scodoc, _ = load_and_convert_ics(formsemestre)
|
||||
except ScoValueError as exc:
|
||||
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"
|
||||
title="promotion complète" alt="promotion"/>"""
|
||||
abs_icon = f"""<img height="28px" src="{scu.STATIC_DIR}/icons/absences.svg"
|
||||
@ -211,7 +225,6 @@ def formsemestre_edt_dict(
|
||||
url_for(
|
||||
"assiduites.signal_assiduites_group",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
group_ids=group.id,
|
||||
heure_deb=event["heure_deb"],
|
||||
heure_fin=event["heure_fin"],
|
||||
@ -271,17 +284,17 @@ def formsemestre_edt_dict(
|
||||
"start": event["start"],
|
||||
"end": event["end"],
|
||||
"backgroundColor": event["group_bg_color"],
|
||||
"raw": event["raw"],
|
||||
# Infos brutes pour usage API éventuel
|
||||
"edt_ens_ids": event["edt_ens_ids"],
|
||||
"ens_user_names": ens_user_names,
|
||||
"group_id": group.id if group else None,
|
||||
"group_edt_id": event["edt_group"],
|
||||
"moduleimpl_id": modimpl.id if modimpl else None,
|
||||
"UID": event["UID"], # icalendar event UID
|
||||
}
|
||||
events_cal.append(d)
|
||||
log(
|
||||
f"formsemestre_edt_dict: loaded edt for {formsemestre} in {(time.time()-t0):g}s"
|
||||
)
|
||||
|
||||
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]]:
|
||||
"""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
|
||||
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é
|
||||
- "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
|
||||
edt_ics_title_field = ScoDocSiteConfig.get("edt_ics_title_field")
|
||||
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_pattern = get_ics_uid_pattern()
|
||||
|
||||
# --- Correspondances id edt -> id scodoc pour groupes, modules et enseignants
|
||||
edt2group = formsemestre_retreive_groups_from_edt_id(formsemestre)
|
||||
# --- Groupes
|
||||
group_colors = {
|
||||
group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1]
|
||||
for i, group_name in enumerate(edt2group)
|
||||
}
|
||||
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)
|
||||
# ---
|
||||
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 = 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
|
||||
if edt_ics_mod_pattern:
|
||||
edt_module = extract_event_edt_id(
|
||||
@ -405,6 +417,34 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
||||
else:
|
||||
modimpl = False
|
||||
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
|
||||
users: list[User] = []
|
||||
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(),
|
||||
"start": event.decoded("dtstart").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)
|
||||
|
@ -144,3 +144,7 @@ span.ens-non-reconnu {
|
||||
.btn:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.raw-event {
|
||||
display: none;
|
||||
}
|
@ -126,7 +126,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
time: function(event) {
|
||||
const date_start = new Date(event.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: {
|
||||
@ -249,10 +249,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if ((iso_date_start > "{{ formsemestre.date_fin.isoformat() }}")
|
||||
|| (iso_date_end < "{{ formsemestre.date_debut.isoformat() }}")) {
|
||||
cal_warning.style.display = 'inline-block';
|
||||
console.log("OUTSIDE");
|
||||
} else {
|
||||
cal_warning.style.display = 'none';
|
||||
console.log("INSIDE");
|
||||
}
|
||||
}
|
||||
// View menu
|
||||
|
@ -947,12 +947,13 @@ def choix_date() -> str:
|
||||
@permission_required(Permission.AbsChange)
|
||||
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:
|
||||
str: l'html généré
|
||||
"""
|
||||
# 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)
|
||||
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
||||
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
||||
@ -972,13 +973,20 @@ def signal_assiduites_group():
|
||||
moduleimpl_id = int(moduleimpl_id)
|
||||
except (TypeError, ValueError):
|
||||
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
|
||||
try:
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
except (TypeError, ValueError):
|
||||
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
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids,
|
||||
|
@ -48,7 +48,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
import sco_version
|
||||
|
||||
|
||||
def _calendar_factory():
|
||||
def _calendar_factory() -> icalendar.Calendar:
|
||||
"Create a new calendar"
|
||||
cal = icalendar.Calendar()
|
||||
cal.add(
|
||||
|
Loading…
Reference in New Issue
Block a user