diff --git a/app/forms/main/config_personalized_links.py b/app/forms/main/config_personalized_links.py new file mode 100644 index 000000000..1aed31302 --- /dev/null +++ b/app/forms/main/config_personalized_links.py @@ -0,0 +1,72 @@ +""" +Formulaire configuration liens personalisés (menu "Liens") +""" + +from flask import g, url_for +from flask_wtf import FlaskForm +from wtforms import FieldList, Form, validators +from wtforms.fields.simple import BooleanField, StringField, SubmitField + +from app.models import ScoDocSiteConfig + + +class _PersonalizedLinksForm(FlaskForm): + "form. définition des liens personnalisés" + # construit dynamiquement ci-dessous + + +def PersonalizedLinksForm() -> _PersonalizedLinksForm: + "Création d'un formulaire pour éditer les liens" + + # Formulaire dynamique, on créé une classe ad-hoc + class F(_PersonalizedLinksForm): + pass + + F.links_by_id = dict(enumerate(ScoDocSiteConfig.get_perso_links())) + + def _gen_link_form(idx): + setattr( + F, + f"link_{idx}", + StringField( + f"Titre", + validators=[ + validators.Optional(), + validators.Length(min=1, max=80), + ], + default="", + render_kw={"size": 6}, + ), + ) + setattr( + F, + f"link_url_{idx}", + StringField( + f"URL", + description="adresse, incluant le http.", + validators=[ + validators.Optional(), + validators.URL(), + validators.Length(min=1, max=256), + ], + default="", + ), + ) + setattr( + F, + f"link_with_args_{idx}", + BooleanField( + f"ajouter arguments", + description="query string avec ids", + ), + ) + + # Initialise un champ de saisie par lien + for idx in F.links_by_id: + _gen_link_form(idx) + _gen_link_form("new") + + F.submit = SubmitField("Valider") + F.cancel = SubmitField("Annuler", render_kw={"formnovalidate": True}) + + return F() diff --git a/app/models/config.py b/app/models/config.py index ca1af2881..c436248fc 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -3,9 +3,13 @@ """Model : site config WORK IN PROGRESS #WIP """ +import json +import urllib.parse + from flask import flash from app import current_app, db, log from app.comp import bonus_spo +from app.scodoc.sco_exceptions import ScoValueError from app.scodoc import sco_utils as scu from datetime import time @@ -342,3 +346,47 @@ class ScoDocSiteConfig(db.Model): log(f"set_month_debut_periode2({month})") return True return False + + @classmethod + def get_perso_links(cls) -> list["PersonalizedLink"]: + "Return links" + data_links = cls.get("personalized_links") + if not data_links: + return [] + try: + links_dict = json.loads(data_links) + except json.decoder.JSONDecodeError as exc: + # Corrupted data ? erase content + cls.set("personalized_links", "") + raise ScoValueError( + "Attention: liens personnalisés erronés: ils ont été effacés." + ) + return [PersonalizedLink(**item) for item in links_dict] + + @classmethod + def set_perso_links(cls, links: list["PersonalizedLink"] = None): + "Store all links" + if not links: + links = [] + links_dict = [link.to_dict() for link in links] + data_links = json.dumps(links_dict) + cls.set("personalized_links", data_links) + + +class PersonalizedLink: + def __init__(self, title: str = "", url: str = "", with_args: bool = False): + self.title = str(title or "") + self.url = str(url or "") + self.with_args = bool(with_args) + + def get_url(self, params: dict = {}) -> str: + if not self.with_args: + return self.url + query_string = urllib.parse.urlencode(params) + if "?" in self.url: + return self.url + "&" + query_string + return self.url + "?" + query_string + + def to_dict(self) -> dict: + "as dict" + return {"title": self.title, "url": self.url, "with_args": self.with_args} diff --git a/app/scodoc/html_sco_header.py b/app/scodoc/html_sco_header.py index 76e8727ab..fd6273709 100644 --- a/app/scodoc/html_sco_header.py +++ b/app/scodoc/html_sco_header.py @@ -30,7 +30,7 @@ import html -from flask import render_template +from flask import g, render_template from flask import request from flask_login import current_user @@ -148,6 +148,8 @@ def sco_header( "Main HTML page header for ScoDoc" from app.scodoc.sco_formsemestre_status import formsemestre_page_title + if etudid is not None: + g.current_etudid = etudid scodoc_flash_status_messages() # Get head message from http request: diff --git a/app/scodoc/sco_formsemestre_custommenu.py b/app/scodoc/sco_formsemestre_custommenu.py index ce9557eb0..3e41fde3e 100644 --- a/app/scodoc/sco_formsemestre_custommenu.py +++ b/app/scodoc/sco_formsemestre_custommenu.py @@ -29,7 +29,10 @@ """ import flask from flask import g, url_for, request +from flask_login import current_user +from app.models.config import ScoDocSiteConfig, PersonalizedLink +from app.models import FormSemestre import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.TrivialFormulator import TrivialFormulator @@ -58,6 +61,28 @@ def formsemestre_custommenu_get(formsemestre_id): return vals +def build_context_dict(formsemestre_id: int) -> dict: + """returns a dict with "current" ids, to pass to external links""" + params = { + "dept": g.scodoc_dept, + "formsemestre_id": formsemestre_id, + "user_name": current_user.user_name, + } + cas_id = getattr(current_user, "cas_id", None) + if cas_id: + params["cas_id"] = cas_id + etudid = getattr(g, "current_etudid", None) + if etudid is not None: + params["etudid"] = etudid + evaluation_id = getattr(g, "current_evaluation_id", None) + if evaluation_id is not None: + params["evaluation_id"] = evaluation_id + moduleimpl_id = getattr(g, "current_moduleimpl_id", None) + if moduleimpl_id is not None: + params["moduleimpl_id"] = moduleimpl_id + return params + + def formsemestre_custommenu_html(formsemestre_id): "HTML code for custom menu" menu = [] @@ -66,6 +91,13 @@ def formsemestre_custommenu_html(formsemestre_id): ics_url = sco_edt_cal.formsemestre_get_ics_url(sem) if ics_url: menu.append({"title": "Emploi du temps (ics)", "url": ics_url}) + # Liens globaux (config. générale) + params = build_context_dict(formsemestre_id) + for link in ScoDocSiteConfig.get_perso_links(): + if link.title: + menu.append({"title": link.title, "url": link.get_url(params=params)}) + + # Liens propres à ce semestre menu += formsemestre_custommenu_get(formsemestre_id) menu.append( { @@ -79,9 +111,11 @@ def formsemestre_custommenu_html(formsemestre_id): def formsemestre_custommenu_edit(formsemestre_id): """Dialog to edit the custom menu""" - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - dest_url = ( - scu.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id + formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) + dest_url = url_for( + "notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre_id, ) H = [ html_sco_header.html_sem_header("Modification du menu du semestre "), diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index ac834c4ea..170bfdcae 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -194,6 +194,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): if not isinstance(moduleimpl_id, int): raise ScoInvalidIdType("moduleimpl_id must be an integer !") modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id) + g.current_moduleimpl_id = modimpl.id module: Module = modimpl.module formsemestre_id = modimpl.formsemestre_id formsemestre: FormSemestre = modimpl.formsemestre diff --git a/app/templates/config_personalized_links.j2 b/app/templates/config_personalized_links.j2 new file mode 100644 index 000000000..ff3a7f61e --- /dev/null +++ b/app/templates/config_personalized_links.j2 @@ -0,0 +1,84 @@ +{% extends "base.j2" %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block styles %} +{{super()}} + + +{% endblock %} + + +{% block app_content %} +
Les liens définis ici seront affichés dans le menu Liens de tous + les semestres de tous les départements.
+ +Si on coche "ajouter arguments", une query string est ajoutée par ScoDoc + à la fin du lien, pour passer des informations sur le contexte:
+ +