Compare commits

..

No commits in common. "a42e23b0ec9904fb0ddb238889224d3c90a53440" and "f99be92fad9729687b5baa353364c9cf2f351007" have entirely different histories.

14 changed files with 635 additions and 769 deletions

View File

@ -8,10 +8,8 @@
"""
import datetime
from flask import render_template, url_for
from flask import render_template
import app
from app import Departement
from app.models import FormSemestre, FormSemestreInscription
from app.views import ScoData
@ -20,13 +18,9 @@ def feuille_preparation_jury_but(formsemestre_id: int):
formsemestre: FormSemestre = FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
departement: Departement = Departement.query.filter_by(
id=formsemestre.dept_id
).first_or_404()
return render_template(
"but/jury_export.j2",
datetime=datetime,
formsemestre=formsemestre,
dept = departement.acronym,
sco=ScoData(formsemestre=formsemestre),
)

View File

@ -269,12 +269,10 @@ class FormSemestre(db.Model):
return default_partition.groups.first()
raise ScoValueError("Le semestre n'a pas de groupe par défaut")
def get_edt_ids(self) -> list[str]:
"l'ids pour l'emploi du temps: à défaut, les codes étape Apogée"
def get_edt_id(self) -> str:
"l'id pour l'emploi du temps: à défaut, le 1er code étape Apogée"
return (
scu.split_id(self.edt_id)
or [e.etape_apo.strip() for e in self.etapes if e.etape_apo]
or []
self.edt_id or "" or (self.etapes[0].etape_apo if len(self.etapes) else "")
)
def get_infos_dict(self) -> dict:
@ -1042,33 +1040,6 @@ class FormSemestre(db.Model):
nb_recorded += 1
return nb_recorded
def change_formation(self, formation_dest: Formation):
"""Associe ce formsemestre à une autre formation.
Ce n'est possible que si la formation destination possède des modules de
même code que ceux utilisés dans la formation d'origine du formsemestre.
S'il manque un module, l'opération est annulée.
Commit (or rollback) session.
"""
ok = True
for mi in self.modimpls:
dest_modules = formation_dest.modules.filter_by(code=mi.module.code).all()
match len(dest_modules):
case 1:
mi.module = dest_modules[0]
db.session.add(mi)
case 0:
print(f"Argh ! no module found with code={mi.module.code}")
ok = False
case _:
print(f"Arg ! several modules found with code={mi.module.code}")
ok = False
if ok:
self.formation_id = formation_dest.id
db.session.commit()
else:
db.session.rollback()
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
notes_formsemestre_responsables = db.Table(

View File

@ -58,12 +58,12 @@ class ModuleImpl(db.Model):
return {x.strip() for x in self.code_apogee.split(",") if x}
return self.module.get_codes_apogee()
def get_edt_ids(self) -> list[str]:
"les ids pour l'emploi du temps: à défaut, les codes Apogée"
def get_edt_id(self) -> str:
"l'id pour l'emploi du temps: à défaut, le 1er code Apogée"
return (
scu.split_id(self.edt_id)
or scu.split_id(self.code_apogee)
or self.module.get_edt_ids()
self.edt_id
or (self.code_apogee.split(",")[0] if self.code_apogee else "")
or self.module.get_edt_id()
)
def get_evaluations_poids(self) -> pd.DataFrame:

View File

@ -285,9 +285,13 @@ class Module(db.Model):
return {x.strip() for x in self.code_apogee.split(",") if x}
return set()
def get_edt_ids(self) -> list[str]:
"les ids pour l'emploi du temps: à défaut, le 1er code Apogée"
return scu.split_id(self.edt_id) or scu.split_id(self.code_apogee) or []
def get_edt_id(self) -> str:
"l'id pour l'emploi du temps: à défaut, le 1er code Apogée"
return (
self.edt_id
or (self.code_apogee.split(",")[0] if self.code_apogee else "")
or ""
)
def get_parcours(self) -> list[ApcParcours]:
"""Les parcours utilisant ce module.

View File

@ -273,8 +273,7 @@ def formation_edit(formation_id=None, create=False):
"\n".join(H)
+ tf_error_message(
f"""Valeurs incorrectes: il existe déjà <a href="{
url_for('notes.ue_table',
scodoc_dept=g.scodoc_dept, formation_id=other_formations[0].id)
url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=other_formations[0].id)
}">une formation</a> avec même titre,
acronyme et version.
"""
@ -286,9 +285,9 @@ def formation_edit(formation_id=None, create=False):
if create:
formation = do_formation_create(tf[2])
else:
if do_formation_edit(tf[2]):
do_formation_edit(tf[2])
flash(
f"""Modification de la formation {
f"""Création de la formation {
formation.titre} ({formation.acronyme}) version {formation.version}"""
)
return flask.redirect(
@ -336,8 +335,8 @@ def do_formation_create(args: dict) -> Formation:
return formation
def do_formation_edit(args) -> bool:
"edit a formation, returns True if modified"
def do_formation_edit(args):
"edit a formation"
# On ne peut jamais supprimer le code formation:
if "formation_code" in args and not args["formation_code"]:
@ -351,16 +350,11 @@ def do_formation_edit(args) -> bool:
if "type_parcours" in args:
del args["type_parcours"]
modified = False
for field in formation.__dict__:
if field in args:
value = args[field].strip() if isinstance(args[field], str) else args[field]
if field and field[0] != "_" and getattr(formation, field, None) != value:
if field and field[0] != "_":
setattr(formation, field, value)
modified = True
if not modified:
return False
db.session.add(formation)
try:
@ -376,7 +370,6 @@ def do_formation_edit(args) -> bool:
),
) from exc
formation.invalidate_cached_sems()
return True
def module_move(module_id, after=0, redirect=True):

View File

@ -34,7 +34,7 @@ from datetime import timezone
import re
import icalendar
from flask import g, url_for
from flask import flash, g, url_for
from app import log
from app.models import FormSemestre, GroupDescr, ModuleImpl, ScoDocSiteConfig
from app.scodoc.sco_exceptions import ScoValueError
@ -56,14 +56,12 @@ def formsemestre_load_calendar(
Raises ScoValueError if not configured or not available or invalid format.
"""
if edt_id is None and formsemestre:
edt_ids = formsemestre.get_edt_ids()
if not edt_ids:
edt_id = formsemestre.get_edt_id()
if not edt_id:
raise ScoValueError(
"accès aux emplois du temps non configuré pour ce semestre (pas d'edt_id)"
)
# Ne charge qu'un seul ics pour le semestre, prend uniquement
# le premier edt_id
ics_filename = get_ics_filename(edt_ids[0])
ics_filename = get_ics_filename(edt_id)
if ics_filename is None:
raise ScoValueError("accès aux emplois du temps non configuré (pas de chemin)")
try:
@ -149,51 +147,40 @@ def formsemestre_edt_dict(
if group and group_ids_set and group.id not in group_ids_set:
continue # ignore cet évènement
modimpl: ModuleImpl | bool = event["modimpl"]
url_abs = (
url_for(
"assiduites.signal_assiduites_group",
if modimpl is False:
mod_disp = f"""<div class="module-edt"
title="extraction emploi du temps non configurée">
{scu.EMO_WARNING} non configuré
</div>"""
else:
mod_disp = (
f"""<div class="module-edt mod-name" title="{modimpl.module.abbrev or ""}">{
modimpl.module.code}</div>"""
if modimpl
else f"""<div class="module-edt mod-etd" title="code module non trouvé dans ScoDoc.
Vérifier configuration.">{
scu.EMO_WARNING} {event['edt_module']}</div>"""
)
# --- Lien saisie abs
link_abs = (
f"""<div class="module-edt link-abs"><a class="stdlink" href="{
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"],
moduleimpl_id=modimpl.id,
jour=event["jour"],
)
if modimpl and group
else None
)
match modimpl:
case False: # EDT non configuré
mod_disp = f"""<span>{scu.EMO_WARNING} non configuré</span>"""
bubble = "extraction emploi du temps non configurée"
case None: # Module edt non trouvé dans ScoDoc
mod_disp = f"""<span class="mod-etd">{
scu.EMO_WARNING} {event['edt_module']}</span>"""
bubble = "code module non trouvé dans ScoDoc. Vérifier configuration."
case _: # module EDT bien retrouvé dans ScoDoc
mod_disp = f"""<span class="mod-name mod-code" title="{
modimpl.module.abbrev or ""} ({event['edt_module']})">{
modimpl.module.code}</span>"""
bubble = f"{modimpl.module.abbrev or ''} ({event['edt_module']})"
title = f"""<div class = "module-edt" title="{bubble} {event['title_edt']}">
<a class="discretelink" href="{url_abs or ''}">{mod_disp} <span>{event['title']}</span></a>
</div>
"""
# --- Lien saisie abs
link_abs = (
f"""<div class="module-edt link-abs"><a class="stdlink" href="{
url_abs}">absences</a>
jour = event["jour"],
)}">absences</a>
</div>"""
if url_abs
if modimpl and group
else ""
)
d = {
# Champs utilisés par tui.calendar
"calendarId": "cal1",
"title": f"""{title} {group_disp} {link_abs}""",
"title": event["title"] + group_disp + mod_disp + link_abs,
"start": event["start"],
"end": event["end"],
"backgroundColor": event["group_bg_color"],
@ -258,13 +245,11 @@ def _load_and_convert_ics(formsemestre: FormSemestre) -> list[dict]:
for event in events:
if "DESCRIPTION" in event:
# --- Titre de l'évènement
title_edt = (
title = (
extract_event_data(event, edt_ics_title_field, edt_ics_title_pattern)
if edt_ics_title_pattern
else "non configuré"
)
# 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_data(
@ -293,8 +278,6 @@ def _load_and_convert_ics(formsemestre: FormSemestre) -> list[dict]:
event, edt_ics_mod_field, edt_ics_mod_pattern
)
modimpl: ModuleImpl = edt2modimpl.get(edt_module, None)
if modimpl:
title = modimpl.module.titre_str()
else:
modimpl = False
edt_module = ""
@ -302,8 +285,7 @@ def _load_and_convert_ics(formsemestre: FormSemestre) -> list[dict]:
#
events_sco.append(
{
"title": title, # titre event ou nom module
"title_edt": title_edt, # titre event
"title": title,
"edt_group": edt_group, # id group edt non traduit
"group": group, # False si extracteur non configuré
"group_bg_color": group_bg_color, # associée au groupe
@ -394,11 +376,8 @@ def formsemestre_retreive_modimpls_from_edt_id(
formsemestre: FormSemestre,
) -> dict[str, ModuleImpl]:
"""Construit un dict donnant le moduleimpl de chaque edt_id"""
edt2modimpl = {}
for modimpl in formsemestre.modimpls:
for edt_id in modimpl.get_edt_ids():
if edt_id:
edt2modimpl[edt_id] = modimpl
edt2modimpl = {modimpl.get_edt_id(): modimpl for modimpl in formsemestre.modimpls}
edt2modimpl.pop("", None)
return edt2modimpl

View File

@ -322,13 +322,8 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
referentiel_competence_id = _formation_retreive_refcomp(f_dict)
f_dict["referentiel_competence_id"] = referentiel_competence_id
# find new version number
acronyme_lower = f_dict["acronyme"].lower() if f_dict["acronyme"] else ""
titre_lower = f_dict["titre"].lower() if f_dict["titre"] else ""
formations: list[Formation] = Formation.query.filter_by(
dept_id=f_dict["dept_id"]
).filter(
db.func.lower(Formation.acronyme) == acronyme_lower,
db.func.lower(Formation.titre) == titre_lower,
acronyme=f_dict["acronyme"], titre=f_dict["titre"], dept_id=f_dict["dept_id"]
)
if formations.count():
version = max(f.version or 0 for f in formations)
@ -523,7 +518,6 @@ def formation_list_table() -> GenTable:
"_titre_link_class": "stdlink",
"_titre_id": f"""titre-{acronyme_no_spaces}""",
"version": formation.version or 0,
"commentaire": formation.commentaire or "",
}
# Ajoute les semestres associés à chaque formation:
row["formsemestres"] = formation.formsemestres.order_by(
@ -600,12 +594,10 @@ def formation_list_table() -> GenTable:
"formation_code",
"version",
"titre",
"commentaire",
"sems_list_txt",
)
titles = {
"buttons": "",
"commentaire": "Commentaire",
"acronyme": "Acro.",
"parcours_name": "Type",
"titre": "Titre",

View File

@ -764,23 +764,13 @@ FORBIDDEN_CHARS_EXP = re.compile(r"[*\|~\(\)\\]")
ALPHANUM_EXP = re.compile(r"^[\w-]+$", re.UNICODE)
def is_valid_code_nip(s: str) -> bool:
def is_valid_code_nip(s):
"""True si s peut être un code NIP: au moins 6 chiffres décimaux"""
if not s:
return False
return re.match(r"^[0-9]{6,32}$", s)
def split_id(ident: str) -> list[str]:
"""ident est une chaine 'X, Y, Z'
Renvoie ['X','Y', 'Z']
"""
if ident:
ident = ident.strip()
return [x.strip() for x in ident.strip().split(",")] if ident else []
return []
def strnone(s):
"convert s to string, '' if s is false"
if s:

View File

@ -6,17 +6,8 @@
font-size: 12pt;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.module-edt {
display: inline;
}
.mod-code {
font-weight: bold;
color: rgb(21, 21, 116);
font-size: 110%;
}
.group-name {
color: rgb(25, 113, 25);
display: inline;
}
.group-edt {
color: red;

View File

@ -2319,10 +2319,7 @@ table.formation_list_table td.buttons span.but_placeholder {
}
.formation_list_table td.titre {
width: 45%;
}
.formation_list_table td.commentaire {
font-style: italic;
width: 50%;
}
.formation_list_table td.sems_list_txt {

File diff suppressed because it is too large Load Diff

View File

@ -7,46 +7,24 @@
{% block app_content %}
<H1>Export xlsx de fichier de jury</H1>
<releve-but>{{ formsemestre.id }}</releve-but>
<form onsubmit="do_export()">
Formsemestre: {{ formsemestre.id }}
API root: {{ api_root }}
<button>Exporter</button>
</form>
{% endblock %}
{% block scripts %}
{{super()}}
<script src="{{scu.STATIC_DIR}}/js/export-jury-but.js"></script>
{# <script src="{{scu.STATIC_DIR}}/js/releve-but.js"></script>#}
<script>
let dataSrc = "/ScoDoc/{{dept}}/api/formsemestre/{{formsemestre.id}}/etudiants/long";
let formsemestres = [];
fetch(dataSrc)
.then(r => { return r.json(); })
.then(json => {
let etudiants = json;
etudiants.forEach((etudiant) => {
let url =`/ScoDoc/{{dept}}/api/etudiant/nip/${etudiant.code_nip}/formsemestres`;
console.log(url);
etudiant.formsemestres_id = [];
fetch(`/ScoDoc/{{dept}}/api/etudiant/nip/${etudiant.code_nip}/formsemestres`)
.then(r => {return r.json; })
.then(json => {
let forms = json;
forms.forEach((formsem) => {
if (formsem.is_apc) {
etudiant.formsemestres_id.push(formsem.formsemestre_id);
}
});
});
});
//let releve = document.querySelector("releve-but");
//releve.showData = json;
// Style custom à ajouter
//let dataSrc = "{{bul_url|safe}}";
//fetch(dataSrc)
// .then(r => { return r.json() })
// .then(json => {
// let releve = document.querySelector("releve-but");
// releve.showData = json;
// // Syle custom à ajouter
// let style = document.createElement("style");
// style.textContent = `
// .module>div,
@ -55,9 +33,8 @@
// .dateNaissance{
// display: none;
// }`;
//releve.shadowRoot.appendChild(style);
})
.catch(error => { console.log(error)});
// releve.shadowRoot.appendChild(style);
// });
// .catch(error => {
// let div = document.createElement("div");
// div.innerText = "Une erreur s'est produite lors du transfert des données.";

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.6.58"
SCOVERSION = "9.6.56"
SCONAME = "ScoDoc"