1
0
forked from ScoDoc/ScoDoc

Compare commits

..

No commits in common. "c456133487b56f57aefa71fb33138381e6c8cd89" and "e11101b53bb6a2c1d0b2f1da1768352565f0293d" have entirely different histories.

14 changed files with 24 additions and 152 deletions

View File

@ -394,32 +394,6 @@ def group_edit(group_id: int):
return group.to_dict(with_partition=True)
@bp.route("/group/<int:group_id>/set_edt_id/<string:edt_id>", methods=["POST"])
@api_web_bp.route("/group/<int:group_id>/set_edt_id/<string:edt_id>", methods=["POST"])
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def group_set_edt_id(group_id: int, edt_id: str):
"""Set edt_id for this group.
Contrairement à /edit, peut-être changé pour toute partition
ou formsemestre non verrouillé.
"""
query = GroupDescr.query.filter_by(id=group_id)
if g.scodoc_dept:
query = (
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
)
group: GroupDescr = query.first_or_404()
if not group.partition.formsemestre.can_change_groups():
return json_error(401, "opération non autorisée")
log(f"group_set_edt_id( {group_id}, '{edt_id}' )")
group.edt_id = edt_id
db.session.add(group)
db.session.commit()
return group.to_dict(with_partition=True)
@bp.route("/formsemestre/<int:formsemestre_id>/partition/create", methods=["POST"])
@api_web_bp.route(
"/formsemestre/<int:formsemestre_id>/partition/create", methods=["POST"]
@ -520,7 +494,6 @@ def formsemestre_order_partitions(formsemestre_id: int):
db.session.commit()
app.set_sco_dept(formsemestre.departement.acronym)
sco_cache.invalidate_formsemestre(formsemestre_id)
log(f"formsemestre_order_partitions({partition_ids})")
return [
partition.to_dict()
for partition in formsemestre.partitions.order_by(Partition.numero)

View File

@ -139,7 +139,7 @@ class ConfigAssiduitesForm(FlaskForm):
)
edt_ics_title_field = StringField(
label="Champ contenant le titre",
label="Champs contenant le titre",
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
validators=[Optional(), check_ics_field],
)
@ -152,7 +152,7 @@ class ConfigAssiduitesForm(FlaskForm):
validators=[Optional(), check_ics_regexp],
)
edt_ics_group_field = StringField(
label="Champ contenant le groupe",
label="Champs contenant le groupe",
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
validators=[Optional(), check_ics_field],
)
@ -165,7 +165,7 @@ class ConfigAssiduitesForm(FlaskForm):
validators=[Optional(), check_ics_regexp],
)
edt_ics_mod_field = StringField(
label="Champ contenant le module",
label="Champs contenant le module",
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
validators=[Optional(), check_ics_field],
)
@ -177,18 +177,6 @@ class ConfigAssiduitesForm(FlaskForm):
""",
validators=[Optional(), check_ics_regexp],
)
edt_ics_uid_field = StringField(
label="Champ contenant l'enseignant",
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
validators=[Optional(), check_ics_field],
)
edt_ics_uid_regexp = StringField(
label="Extraction de l'enseignant",
description=r"""expression régulière python dont le premier groupe doit
correspondre à l'identifiant (edt_id) de l'enseignant associé à l'évènement.
Exemple: <tt>Enseignant : ([0-9]+)</tt>
""",
validators=[Optional(), check_ics_regexp],
)
submit = SubmitField("Valider")
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})

View File

@ -242,12 +242,10 @@ class GroupDescr(ScoDocModel):
def to_dict(self, with_partition=True) -> dict:
"""as a dict, with or without partition"""
if with_partition:
partition_dict = self.partition.to_dict(with_groups=False)
d = dict(self.__dict__)
d.pop("_sa_instance_state", None)
if with_partition:
d["partition"] = partition_dict
d["partition"] = self.partition.to_dict(with_groups=False)
return d
def get_edt_ids(self) -> list[str]:

View File

@ -36,7 +36,6 @@ import icalendar
from flask import g, url_for
from app import log
from app.auth.models import User
from app.models import FormSemestre, GroupDescr, ModuleImpl, ScoDocSiteConfig
from app.scodoc.sco_exceptions import ScoValueError
import app.scodoc.sco_utils as scu
@ -57,11 +56,8 @@ def formsemestre_load_calendar(
Raises ScoValueError if not configured or not available or invalid format.
"""
edt_ids = []
if edt_id is None:
if formsemestre:
if edt_id is None and formsemestre:
edt_ids = formsemestre.get_edt_ids()
else:
edt_ids = [edt_id]
if not edt_ids:
raise ScoValueError(
"accès aux emplois du temps non configuré pour ce semestre (pas d'edt_id)"
@ -136,10 +132,8 @@ def formsemestre_edt_dict(
except ScoValueError as exc:
return exc.args[0]
# Génération des événements pour le calendrier html
promo_icon = f"""<img height="28px" src="{scu.STATIC_DIR}/icons/promo.svg"
promo_icon = f"""<img height="24px" 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"
title="saisir absences" alt="saisir absences"/>"""
events_cal = []
for event in events_scodoc:
group: GroupDescr | bool = event["group"]
@ -197,26 +191,19 @@ def formsemestre_edt_dict(
# --- Lien saisie abs
link_abs = (
f"""<div class="module-edt link-abs"><a class="stdlink" href="{
url_abs}">{abs_icon}</a>
url_abs}">absences</a>
</div>"""
if url_abs
else ""
)
ens_user_name = event["ens"].user_name if event["ens"] else None
ens_nomprenom = event["ens"].get_nomprenom() if event["ens"] else None
d = {
# Champs utilisés par tui.calendar
"calendarId": "cal1",
"title": f"""{title} {group_disp} {
'('+ens_nomprenom+')' if ens_nomprenom else ''
} {link_abs}""",
"title": f"""{title} {group_disp} {link_abs}""",
"start": event["start"],
"end": event["end"],
"backgroundColor": event["group_bg_color"],
# Infos brutes pour usage API éventuel
"ens_edt": event["edt_ens"],
"ens_user_name": ens_user_name,
"group_id": group.id if group else None,
"group_edt_id": event["edt_group"],
"moduleimpl_id": modimpl.id if modimpl else None,
@ -270,16 +257,6 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
raise ScoValueError(
"expression d'extraction du module depuis l'emploi du temps invalide"
) from exc
edt_ics_uid_field = ScoDocSiteConfig.get("edt_ics_uid_field")
edt_ics_uid_regexp = ScoDocSiteConfig.get("edt_ics_uid_regexp")
try:
edt_ics_uid_pattern = (
re.compile(edt_ics_uid_regexp) if edt_ics_uid_regexp else None
)
except re.error as exc:
raise ScoValueError(
"expression d'extraction de l'enseignant depuis l'emploi du temps invalide"
) from exc
# --- Correspondances id edt -> id scodoc pour groupes, modules et enseignants
edt2group = formsemestre_retreive_groups_from_edt_id(formsemestre)
group_colors = {
@ -289,7 +266,6 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
edt_groups_ids = set() # les ids de groupes 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"]
events_sco = []
@ -337,19 +313,7 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
else:
modimpl = False
edt_module = ""
# --- Enseignant
if edt_ics_uid_pattern:
edt_ens = extract_event_data(
event, edt_ics_uid_field, edt_ics_uid_pattern
)
if edt_ens in edt2user:
ens = edt2user[edt_ens]
else:
ens = User.query.filter_by(edt_id=edt_ens).first()
edt2user[edt_ens] = ens
else:
ens = None
edt_ens = ""
# --- TODO: enseignant
#
events_sco.append(
{
@ -360,9 +324,6 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
"group_bg_color": group_bg_color, # associée au groupe
"modimpl": modimpl, # False si extracteur non configuré
"edt_module": edt_module, # id module edt non traduit
# Enseignant
"edt_ens": edt_ens, # id ens edt, non traduit
"ens": ens,
# heures pour saisie abs: en heure LOCALE DU SERVEUR
"heure_deb": event.decoded("dtstart")
.replace(tzinfo=timezone.utc)

View File

@ -633,7 +633,3 @@ h3 {
#zoneGroupes .groupe[data-idgroupe=aucun]>div:nth-child(1) {
color: red;
}
#zonePartitions button span.editing:not(:first-child) {
margin-left: 8px;
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -96,7 +96,7 @@ affectent notamment les comptages d'absences de tous les bulletins des
<button id="test_load_ics" type="button" onclick="load_ics_sample()" disabled>
Essayer de charger l'ics</button>
<div id="raw-ics-sample-zone">
<div>Voici un évènement chargé, pris au hasard au milieu de ce calendrier.
<div>Voici un évènement chargé au milieu de ce calendrier.
Utilisez cet exemple pour configurer les expressions d'extraction
en bas de ce formulaire.
</div>
@ -121,10 +121,7 @@ affectent notamment les comptages d'absences de tous les bulletins des
{{ wtf.form_field(form.edt_ics_mod_field) }}
{{ wtf.form_field(form.edt_ics_mod_regexp) }}
</div>
<div class="config-edt">
{{ wtf.form_field(form.edt_ics_uid_field) }}
{{ wtf.form_field(form.edt_ics_uid_regexp) }}
</div>
<div class="form-group">
{{ wtf.form_field(form.submit) }}

View File

@ -93,6 +93,6 @@
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
<script>
const SCO_URL = "{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)[:-11] }}";
const SCO_TIMEZONE = "{{ scu.TIME_ZONE }}";
const SCO_TIMEZONE = {{ scu.TIME_ZONE }}
</script>
{% endblock %}

View File

@ -4,9 +4,6 @@
{% block styles %}
{{super()}}
<style>
.form-titre {
font-weight: bold;
}
span.mod-label {
display: inline-block;
min-width: 300px;
@ -71,17 +68,13 @@ Pour les modifier, aller dans l'édition de la formation.
<form id="mf" class="form form-horizontal" method="post" enctype="multipart/form-data" role="form">
{{ form.hidden_tag() }}
{{ wtf.form_errors(form, hiddens="only") }}
<div class="form-group form-titre">
<div class="form-group">
<span class="mod-label">
<label>Module</label>
<span class="code-apo-module" title="codes dans la formation">Code Apo. Module</span>
<span class="code-apo-module">Code Apo. Module</span>
<span class="field-apo">Code(s) Apogée</span>
<span class="field-edt">Code EDT</span>
</span>
<span class="field-apo"
title="codes spécifiques à ce semestre (si différents de ceux de la formation)">
Code(s) Apogée</span>
<span class="field-edt"
title="identifiant dans l'emploi du temps (si différent du code Apogée)">
Code EDT</span>
</div>
{% for modimpl in formsemestre.modimpls_sorted %}
{{ render_text_field(form["modimpl_apo_" ~ modimpl.id], form["modimpl_edt_" ~ modimpl.id], modimpl.module.get_codes_apogee()) }}

View File

@ -47,23 +47,6 @@ table#edt2group tbody tr.active-row {
si vous voyez ici de nombreuses lignes, il est possible que l'expression régulière
d'extraction soit incorrecte (voir configuration globale) ou bien que votre logiciel d'emploi du temps génère de nombreux évènements non associés à un groupe donné.
</div>
{% if ScoDocSiteConfig.get("edt_ics_group_field") %}
<div>Les groupes sont extrait du champs <b>{{ScoDocSiteConfig.get("edt_ics_group_field")}}</b>
à l'aide de l'expression régulière: <tt>{{ScoDocSiteConfig.get("edt_ics_group_regexp")}}</tt>
</div>
{% else %}
<div class="fontred">extraction non configuré</div>
{% endif %}
{% if current_user.has_permission(sco.Permission.ScoSuperAdmin) %}
<div>Pour changer ce réglage, <a href="{{url_for('scodoc.config_assiduites')}}"
class="stdlink">voir la page de configuration</a>.</div>
{% else %}
<div class="fontred">au besoin, l'administrateur peut changer ces réglages dans
le panneau de configuration générale.</div>
{% endif %}
<div>Voici ce qui a été extrait de l'emploi du temps par l'expression régulière configurée:
</div>
<ul>

View File

@ -251,7 +251,7 @@
div.classList.add("dt-button");
div.dataset.idgroupe = groupe.id;
div.dataset.idedt = groupe.edt_id || "";
let title_EDT = `Identifiant EDT: ${groupe.edt_id || groupe.group_name}`;
let title_EDT = groupe.edt_id || "";
div.innerHTML = `
<span class="editing move">||</span>
<span>${groupe.group_name}</span>
@ -259,7 +259,7 @@
<span class="editing calendarEdit" title="${title_EDT}">📅</span>
<span class="editing suppr">❌</span>`;
if (groupe.edt_id) {
if (title_EDT) {
div.querySelector(".calendarEdit").classList.add("actif");
}
@ -801,7 +801,7 @@
let btnGroupe = document.querySelector(`#zonePartitions .groupes [data-idgroupe="${idGroupe}"]`);
btnGroupe.dataset.idedt = id_EDT;
btnGroupe.querySelector(".calendarEdit").title = `Identifiant EDT: ${id_EDT || this.parentElement.parentElement.querySelector("span").innerText}`;
btnGroupe.querySelector(".calendarEdit").title = id_EDT || "";
if (id_EDT) {
btnGroupe.querySelector(".calendarEdit").classList.add("actif");
@ -815,7 +815,7 @@
fetch(url, { method: "POST" })
.then(r => { return r.json() })
.then(r => {
if (r.id != idGroupe) {
if (r.OK != true) {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (5).</h2>";
}
});
@ -870,7 +870,7 @@
.then(r => { return r.json() })
.then(r => {
if (r.OK != true) {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (6).</h2>";
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (5).</h2>";
}
listeGroupesAutoaffectation();
})

View File

@ -209,6 +209,4 @@ def formsemestre_edt_help_config(formsemestre_id: int):
edt_groups_ids=edt_groups_ids,
events_sco=events_sco,
sco=ScoData(formsemestre=formsemestre),
ScoDocSiteConfig=ScoDocSiteConfig,
title="Aide configuration EDT",
)

View File

@ -52,7 +52,7 @@ from PIL import Image as PILImage
from werkzeug.exceptions import BadRequest, NotFound
from app import db, log
from app import db
from app.auth.models import User, Role
from app.auth.cas import set_cas_configuration
from app.decorators import (
@ -332,8 +332,6 @@ def config_assiduites():
("edt_ics_group_regexp", "Expression extraction groupe"),
("edt_ics_mod_field", "Champ contenant module"),
("edt_ics_mod_regexp", "Expression extraction module"),
("edt_ics_uid_field", "Champ contenant l'enseignant"),
("edt_ics_uid_regexp", "Expression extraction de l'enseignant"),
)
if form.validate_on_submit():
@ -382,20 +380,16 @@ def config_assiduites():
@admin_required
def ics_raw_sample(edt_id: str):
"Renvoie un extrait de l'ics brut, pour aider à configurer les extractions"
log(f"ics_raw_sample/{edt_id}")
try:
raw_ics, _ = sco_edt_cal.formsemestre_load_calendar(edt_id=edt_id)
except ScoValueError as exc:
log(f"ics_raw_sample: formsemestre_load_calendar({edt_id}) failed")
return exc.args[0]
try:
ics = raw_ics.decode(scu.SCO_ENCODING)
except SyntaxError:
log("ics_raw_sample: raw_ics.decode failed")
return f"Erreur lors de la conversion vers {scu.SCO_ENCODING}"
evs = ics.split("BEGIN:VEVENT")
if len(evs) < 1:
log("ics_raw_sample: empty calendar")
return "pas d'évènements VEVENT détectés dans ce fichier"
return "BEGIN:VEVENT" + evs[len(evs) // 2]

View File

@ -120,14 +120,6 @@ def test_formsemestre_partition(api_headers):
assert group["group_name"] == group_d["group_name"]
assert group["edt_id"] == "GEDT2"
# Change edt_id via route dédiée:
group_t = POST_JSON(
f"/group/{group_r['id']}/set_edt_id/GEDT3",
headers=headers,
)
assert group_t["id"] == group_r["id"]
assert group_t["edt_id"] == "GEDT3"
# Place un étudiant dans le groupe
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants", headers=headers)[0]
repl = POST_JSON(f"/group/{group['id']}/set_etudiant/{etud['id']}", headers=headers)