Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
12 changed files with 138 additions and 81 deletions
Showing only changes of commit bb331e31c1 - Show all commits

View File

@ -28,12 +28,14 @@
""" """
Formulaire configuration Module Assiduités Formulaire configuration Module Assiduités
""" """
import datetime
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import SubmitField, DecimalField from wtforms import DecimalField, SubmitField, ValidationError
from wtforms.fields.simple import StringField from wtforms.fields.simple import StringField
from wtforms.validators import Optional
from wtforms.widgets import TimeInput from wtforms.widgets import TimeInput
import datetime
class TimeField(StringField): class TimeField(StringField):
@ -72,9 +74,28 @@ class TimeField(StringField):
else: else:
raise ValueError raise ValueError
self.data = datetime.time(hour, minutes, seconds) self.data = datetime.time(hour, minutes, seconds)
except ValueError: except ValueError as exc:
self.data = None self.data = None
raise ValueError(self.gettext("Not a valid time string")) raise ValueError(self.gettext("Not a valid time string")) from exc
def check_tick_time(form, field):
"""Le tick_time doit être entre 0 et 60 minutes"""
if field.data < 1 or field.data > 59:
raise ValidationError("Valeur de granularité invalide (entre 1 et 59)")
def check_ics_path(form, field):
"""Vérifie que le chemin est bien un chemin absolu
et qu'il contient edt_id
"""
data = field.data.strip()
if not data:
return
if not data.startswith("/"):
raise ValidationError("Le chemin vers les ics doit commencer par /")
if not "{edt_id}" in data:
raise ValidationError("Le chemin vers les ics doit utiliser {edt_id}")
class ConfigAssiduitesForm(FlaskForm): class ConfigAssiduitesForm(FlaskForm):
@ -84,7 +105,20 @@ class ConfigAssiduitesForm(FlaskForm):
lunch_time = TimeField("Heure de midi (date pivot entre Matin et Après Midi)") lunch_time = TimeField("Heure de midi (date pivot entre Matin et Après Midi)")
afternoon_time = TimeField("Fin de la journée") afternoon_time = TimeField("Fin de la journée")
tick_time = DecimalField("Granularité de la Time Line (temps en minutes)", places=0) tick_time = DecimalField(
"Granularité de la timeline (temps en minutes)",
places=0,
validators=[check_tick_time],
)
edt_ics_path = StringField(
label="Chemin vers les ics",
description="""Chemin absolu unix sur le serveur vers le fichier ics donnant l'emploi
du temps d'un semestre. La balise <tt>{edt_id}</tt> sera remplacée par l'edt_id du
semestre (par défaut, son code étape Apogée).
Si ce champ n'est pas renseigné, les emplois du temps ne seront pas utilisés.""",
validators=[Optional(), check_ics_path],
)
submit = SubmitField("Valider") submit = SubmitField("Valider")
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True}) cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})

View File

@ -42,11 +42,13 @@ def check_cas_uid_from_mail_regexp(form, field):
if not ScoDocSiteConfig.cas_uid_from_mail_regexp_is_valid(field.data): if not ScoDocSiteConfig.cas_uid_from_mail_regexp_is_valid(field.data):
raise ValidationError("expression régulière invalide") raise ValidationError("expression régulière invalide")
def check_cas_edt_id_from_xml_regexp(form, field): def check_cas_edt_id_from_xml_regexp(form, field):
"Vérifie la regexp fournie pour l'extraction du CAS id" "Vérifie la regexp fournie pour l'extraction du CAS id"
if not ScoDocSiteConfig.cas_edt_id_from_xml_regexp_is_valid(field.data): if not ScoDocSiteConfig.cas_edt_id_from_xml_regexp_is_valid(field.data):
raise ValidationError("expression régulière pour edt_id invalide") raise ValidationError("expression régulière pour edt_id invalide")
class ConfigCASForm(FlaskForm): class ConfigCASForm(FlaskForm):
"Formulaire paramétrage CAS" "Formulaire paramétrage CAS"
cas_enable = BooleanField("Activer le CAS") cas_enable = BooleanField("Activer le CAS")

View File

@ -29,7 +29,7 @@ def PersonalizedLinksForm() -> _PersonalizedLinksForm:
F, F,
f"link_{idx}", f"link_{idx}",
StringField( StringField(
f"Titre", "Titre",
validators=[ validators=[
validators.Optional(), validators.Optional(),
validators.Length(min=1, max=80), validators.Length(min=1, max=80),
@ -42,7 +42,7 @@ def PersonalizedLinksForm() -> _PersonalizedLinksForm:
F, F,
f"link_url_{idx}", f"link_url_{idx}",
StringField( StringField(
f"URL", "URL",
description="adresse, incluant le http.", description="adresse, incluant le http.",
validators=[ validators=[
validators.Optional(), validators.Optional(),
@ -56,7 +56,7 @@ def PersonalizedLinksForm() -> _PersonalizedLinksForm:
F, F,
f"link_with_args_{idx}", f"link_with_args_{idx}",
BooleanField( BooleanField(
f"ajouter arguments", "ajouter arguments",
description="query string avec ids", description="query string avec ids",
), ),
) )

View File

@ -42,9 +42,6 @@ class ModuleImpl(db.Model):
viewonly=True, viewonly=True,
) )
def __init__(self, **kwargs):
super(ModuleImpl, self).__init__(**kwargs)
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.id} module={repr(self.module)}>" return f"<{self.__class__.__name__} {self.id} module={repr(self.module)}>"

View File

@ -74,7 +74,8 @@ _moduleEditor = ndb.EditableTable(
"semestre_id", "semestre_id",
"numero", "numero",
"code_apogee", "code_apogee",
"module_type" "module_type",
"edt_id",
#'ects' #'ects'
), ),
sortkey="numero, code, titre", sortkey="numero, code, titre",

View File

@ -68,6 +68,7 @@ _formsemestreEditor = ndb.EditableTable(
"ens_can_edit_eval", "ens_can_edit_eval",
"elt_sem_apo", "elt_sem_apo",
"elt_annee_apo", "elt_annee_apo",
"edt_id",
), ),
filter_dept=True, filter_dept=True,
sortkey="date_debut", sortkey="date_debut",

View File

@ -40,6 +40,7 @@ from app.models import (
ModuleImpl, ModuleImpl,
Evaluation, Evaluation,
UniteEns, UniteEns,
ScoDocSiteConfig,
ScolarFormSemestreValidation, ScolarFormSemestreValidation,
ScolarAutorisationInscription, ScolarAutorisationInscription,
ApcValidationAnnee, ApcValidationAnnee,
@ -445,6 +446,18 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
}, },
) )
) )
if ScoDocSiteConfig.get("edt_ics_path"):
modform.append(
(
"edt_id",
{
"size": 32,
"title": "Identifiant EDT",
"explanation": "optionnel, identifiant sur le logiciel emploi du temps (par défaut, utilise la première étape Apogée).",
"allow_null": True,
},
)
)
if edit: if edit:
formtit = f""" formtit = f"""
<p><a class="stdlink" href="{url_for("notes.formsemestre_edit_uecoefs", <p><a class="stdlink" href="{url_for("notes.formsemestre_edit_uecoefs",

View File

@ -79,7 +79,9 @@ partitionEditor = ndb.EditableTable(
) )
groupEditor = ndb.EditableTable( groupEditor = ndb.EditableTable(
"group_descr", "group_id", ("group_id", "partition_id", "group_name", "numero") "group_descr",
"group_id",
("group_id", "partition_id", "group_name", "numero", "edt_id"),
) )
group_list = groupEditor.list group_list = groupEditor.list

View File

@ -1052,7 +1052,7 @@ function editAssiduite(assiduite_id, etat, assi) {
} }
/** /**
* Récupération des assiduités conflictuelles avec la période de la time line * Récupération des assiduités conflictuelles avec la période de la timeline
* @param {String | Number} etudid identifiant de l'étudiant * @param {String | Number} etudid identifiant de l'étudiant
* @returns {Array[Assiduité]} un tableau d'assiduité * @returns {Array[Assiduité]} un tableau d'assiduité
*/ */

View File

@ -13,10 +13,9 @@ affectent notamment les comptages d'absences de tous les bulletins des
</div> </div>
<div class="row"> <form class="form form-horizontal" method="post" enctype="multipart/form-data" role="form">
<div class="row">
<div class="col-md-8"> <div class="col-md-8">
<form class="form form-horizontal" method="post" enctype="multipart/form-data" role="form">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
{{ wtf.form_errors(form, hiddens="only") }} {{ wtf.form_errors(form, hiddens="only") }}
@ -24,13 +23,24 @@ affectent notamment les comptages d'absences de tous les bulletins des
{{ wtf.form_field(form.lunch_time) }} {{ wtf.form_field(form.lunch_time) }}
{{ wtf.form_field(form.afternoon_time) }} {{ wtf.form_field(form.afternoon_time) }}
{{ wtf.form_field(form.tick_time) }} {{ wtf.form_field(form.tick_time) }}
</div>
</div>
<div class="row">
<h1>Emplois du temps</h1>
<div class="help">ScoDoc peut récupérer les emplois du temps de chaque session.</div>
<div class="col-md-8">
<div class="config-edt">
{{ wtf.form_field(form.edt_ics_path) }}
</div>
<div class="form-group"> <div class="form-group">
{{ wtf.form_field(form.submit) }} {{ wtf.form_field(form.submit) }}
{{ wtf.form_field(form.cancel) }} {{ wtf.form_field(form.cancel) }}
</div> </div>
</form>
</div> </div>
</div> </div>
</form>

View File

@ -73,8 +73,8 @@ Heure: <b><tt>{{ time.strftime("%d/%m/%Y %H:%M") }}</tt></b>
</p> </p>
</section> </section>
<section> <section>
<h2>Assiduité</h2> <h2>Assiduité et emplois du temps</h2>
<p><a class="stdlink" href="{{url_for('scodoc.config_assiduites')}}">Configuration du suivi de l'assiduité</a> <p><a class="stdlink" href="{{url_for('scodoc.config_assiduites')}}">Configuration du suivi de l'assiduité et accès aux emplois du temps</a>
</p> </p>
</section> </section>

View File

@ -323,6 +323,7 @@ def config_assiduites():
form = ConfigAssiduitesForm() form = ConfigAssiduitesForm()
if request.method == "POST" and form.cancel.data: # cancel button if request.method == "POST" and form.cancel.data: # cancel button
return redirect(url_for("scodoc.index")) return redirect(url_for("scodoc.index"))
if form.validate_on_submit(): if form.validate_on_submit():
if ScoDocSiteConfig.set("assi_morning_time", form.data["morning_time"]): if ScoDocSiteConfig.set("assi_morning_time", form.data["morning_time"]):
flash("Heure du début de la journée enregistrée") flash("Heure du début de la journée enregistrée")
@ -330,18 +331,13 @@ def config_assiduites():
flash("Heure de midi enregistrée") flash("Heure de midi enregistrée")
if ScoDocSiteConfig.set("assi_afternoon_time", form.data["afternoon_time"]): if ScoDocSiteConfig.set("assi_afternoon_time", form.data["afternoon_time"]):
flash("Heure de fin de la journée enregistrée") flash("Heure de fin de la journée enregistrée")
if ( if ScoDocSiteConfig.set("assi_tick_time", float(form.data["tick_time"])):
form.data["tick_time"] > 0
and form.data["tick_time"] < 60
and ScoDocSiteConfig.set("assi_tick_time", float(form.data["tick_time"]))
):
flash("Granularité de la timeline enregistrée") flash("Granularité de la timeline enregistrée")
else: if ScoDocSiteConfig.set("edt_ics_path", form.data["edt_ics_path"]):
flash("Erreur : Granularité invalide ou identique") flash("Chemin vers les calendriers ics enregistré")
return redirect(url_for("scodoc.configuration")) return redirect(url_for("scodoc.configuration"))
elif request.method == "GET": if request.method == "GET":
form.morning_time.data = ScoDocSiteConfig.get( form.morning_time.data = ScoDocSiteConfig.get(
"assi_morning_time", datetime.time(8, 0, 0) "assi_morning_time", datetime.time(8, 0, 0)
) )
@ -356,6 +352,7 @@ def config_assiduites():
except ValueError: except ValueError:
form.tick_time.data = 15.0 form.tick_time.data = 15.0
ScoDocSiteConfig.set("assi_tick_time", 15.0) ScoDocSiteConfig.set("assi_tick_time", 15.0)
form.edt_ics_path.data = ScoDocSiteConfig.get("edt_ics_path")
return render_template( return render_template(
"assiduites/pages/config_assiduites.j2", "assiduites/pages/config_assiduites.j2",