forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
72
app/forms/main/config_personalized_links.py
Normal file
72
app/forms/main/config_personalized_links.py
Normal file
@ -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()
|
@ -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}
|
||||
|
@ -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:
|
||||
|
@ -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 "),
|
||||
|
@ -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
|
||||
|
84
app/templates/config_personalized_links.j2
Normal file
84
app/templates/config_personalized_links.j2
Normal file
@ -0,0 +1,84 @@
|
||||
{% extends "base.j2" %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<style>
|
||||
div.lien_perso {
|
||||
border-left: 3px solid blue;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
div.lien_perso>div:first-child {
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
div.lien_perso .form-group, div.lien_perso .checkbox {
|
||||
margin-left: 32px;
|
||||
}
|
||||
div.url > .form-group {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
div.validation-buttons {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block app_content %}
|
||||
<h1>{{title}}</h1>
|
||||
|
||||
<div class="help">
|
||||
|
||||
<p>Les liens définis ici seront affichés dans le menu <b>Liens</b> de tous
|
||||
les semestres de tous les départements.</p>
|
||||
|
||||
<p>Si on coche "ajouter arguments", une query string est ajoutée par ScoDoc
|
||||
à la fin du lien, pour passer des informations sur le contexte:</p>
|
||||
|
||||
<ul>
|
||||
<li><tt>dept</tt> : acronyme du département
|
||||
<li><tt>formsemestre_id</tt> : id du formsemestre affiché
|
||||
<li><tt>moduleimpl_id</tt> : id du moduleimpl affiché (si page module)
|
||||
<li><tt>evaluation_id</tt> : id de l'évaluation affichée (si page d'évaluation)
|
||||
<li><tt>etudid</tt> : id de l'étudiant (si un étudiant est sélectionné)
|
||||
<li><tt>user_name</tt> : login scodoc de l'utilisateur
|
||||
<li><tt>cas_id</tt> : login CAS de l'utilisateur
|
||||
<ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
|
||||
<form class="form form-horizontal form-personalized-links" method="post" enctype="multipart/form-data" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ wtf.form_errors(form, hiddens="only") }}
|
||||
|
||||
{% for idx, link in form.links_by_id.items() %}
|
||||
<div class="lien_perso">
|
||||
<div>Lien personnalisé {{idx}}</div>
|
||||
{{ wtf.form_field( form["link_"+idx|string] ) }}
|
||||
<div class="url">{{ wtf.form_field( form["link_url_"+idx|string] ) }}</div>
|
||||
{{ wtf.form_field( form["link_with_args_"+idx|string] ) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="lien_perso">
|
||||
<div>Nouveau lien personnalisé</div>
|
||||
{{ wtf.form_field( form["link_new"] ) }}
|
||||
<div class="url">{{ wtf.form_field( form["link_url_new"] ) }}</div>
|
||||
{{ wtf.form_field( form["link_with_args_new"] ) }}
|
||||
</div>
|
||||
<div class="form-group validation-buttons">
|
||||
{{ wtf.form_field(form.submit) }}
|
||||
{{ wtf.form_field(form.cancel) }}
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -24,6 +24,20 @@
|
||||
<h1>Configuration générale</h1>
|
||||
<div class="sco_help greenboldtext">Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements).</div>
|
||||
|
||||
<h2>ScoDoc</h2>
|
||||
<form id="configuration_form_scodoc" class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
||||
{{ form_scodoc.hidden_tag() }}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
{{ wtf.quick_form(form_scodoc) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 16px;">
|
||||
<a class="stdlink" href="{{url_for('scodoc.config_personalized_links')}}">Éditer des liens personnalisés</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section>
|
||||
<h2>Calcul des "bonus" définis par l'établissement</h2>
|
||||
<form id="configuration_form" class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
||||
@ -74,15 +88,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2>ScoDoc</h2>
|
||||
<form id="configuration_form_scodoc" class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
||||
{{ form_scodoc.hidden_tag() }}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
{{ wtf.quick_form(form_scodoc) }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
|
@ -61,16 +61,23 @@ from app.decorators import (
|
||||
scodoc,
|
||||
)
|
||||
from app.forms.main import config_logos, config_main
|
||||
from app.forms.main.create_dept import CreateDeptForm
|
||||
from app.forms.main.config_assiduites import ConfigAssiduitesForm
|
||||
from app.forms.main.config_apo import CodesDecisionsForm
|
||||
from app.forms.main.config_cas import ConfigCASForm
|
||||
from app.forms.main.config_assiduites import ConfigAssiduitesForm
|
||||
from app.forms.main.config_personalized_links import PersonalizedLinksForm
|
||||
from app.forms.main.create_dept import CreateDeptForm
|
||||
from app import models
|
||||
from app.models import Departement, Identite
|
||||
from app.models import (
|
||||
Departement,
|
||||
FormSemestre,
|
||||
FormSemestreInscription,
|
||||
Identite,
|
||||
ScoDocSiteConfig,
|
||||
UniteEns,
|
||||
)
|
||||
from app.models import departements
|
||||
from app.models import FormSemestre, FormSemestreInscription
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models import UniteEns
|
||||
from app.models.config import PersonalizedLink
|
||||
|
||||
|
||||
from app.scodoc import sco_find_etud
|
||||
from app.scodoc import sco_logos
|
||||
@ -260,6 +267,38 @@ def config_codes_decisions():
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/ScoDoc/config_personalized_links", methods=["GET", "POST"])
|
||||
@admin_required
|
||||
def config_personalized_links():
|
||||
"""Form config liens perso"""
|
||||
form = PersonalizedLinksForm()
|
||||
if request.method == "POST" and form.cancel.data: # cancel button
|
||||
return redirect(url_for("scodoc.index"))
|
||||
if form.validate_on_submit():
|
||||
links = []
|
||||
for idx in list(form.links_by_id) + ["new"]:
|
||||
title = form.data.get(f"link_{idx}")
|
||||
url = form.data.get(f"link_url_{idx}")
|
||||
with_args = form.data.get(f"link_with_args_{idx}")
|
||||
if title and url:
|
||||
links.append(
|
||||
PersonalizedLink(title=title, url=url, with_args=with_args)
|
||||
)
|
||||
ScoDocSiteConfig.set_perso_links(links)
|
||||
flash("Liens enregistrés")
|
||||
return redirect(url_for("scodoc.configuration"))
|
||||
|
||||
for idx, link in form.links_by_id.items():
|
||||
getattr(form, f"link_{idx}").data = link.title
|
||||
getattr(form, f"link_url_{idx}").data = link.url
|
||||
getattr(form, f"link_with_args_{idx}").data = link.with_args
|
||||
return render_template(
|
||||
"config_personalized_links.j2",
|
||||
form=form,
|
||||
title="Configuration des liens personnalisés",
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
|
||||
@login_required
|
||||
def table_etud_in_accessible_depts():
|
||||
|
@ -8,7 +8,7 @@ Utiliser comme:
|
||||
|
||||
"""
|
||||
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models.config import ScoDocSiteConfig, PersonalizedLink
|
||||
from app.comp.bonus_spo import BonusIUTRennes1
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
@ -55,3 +55,14 @@ def test_scodoc_site_config(test_client):
|
||||
ScoDocSiteConfig.get_month_debut_annee_scolaire()
|
||||
== scu.MONTH_DEBUT_ANNEE_SCOLAIRE
|
||||
)
|
||||
# Links:
|
||||
assert ScoDocSiteConfig.get_perso_links() == []
|
||||
ScoDocSiteConfig.set_perso_links(
|
||||
[
|
||||
PersonalizedLink(title="lien 1", url="http://foo.bar/bar", with_args=True),
|
||||
PersonalizedLink(title="lien 1", url="http://foo.bar?x=1", with_args=True),
|
||||
]
|
||||
)
|
||||
links = ScoDocSiteConfig.get_perso_links()
|
||||
assert links[0].get_url(params={"y": 2}) == "http://foo.bar/bar?y=2"
|
||||
assert links[1].get_url(params={"y": 2}) == "http://foo.bar?x=1&y=2"
|
||||
|
Loading…
Reference in New Issue
Block a user