Compare commits
7 Commits
f87902d1ac
...
3550e4290a
Author | SHA1 | Date |
---|---|---|
Cléo Baras | 3550e4290a | |
Emmanuel Viennet | 787e514dca | |
Emmanuel Viennet | e25f7d4fc9 | |
Emmanuel Viennet | 39b3cd9e05 | |
Emmanuel Viennet | d1074a8227 | |
Emmanuel Viennet | 79f07deac0 | |
Emmanuel Viennet | 431dd20911 |
|
@ -9,6 +9,7 @@ from app.models.but_refcomp import ApcNiveau
|
|||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
|
||||
class ApcValidationRCUE(db.Model):
|
||||
|
@ -218,15 +219,18 @@ def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
|
|||
decisions["descr_decisions_rcue"] = ""
|
||||
decisions["descr_decisions_niveaux"] = ""
|
||||
# --- Année: prend la validation pour l'année scolaire et l'ordre de ce semestre
|
||||
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||
validation = ApcValidationAnnee.query.filter_by(
|
||||
etudid=etud.id,
|
||||
annee_scolaire=formsemestre.annee_scolaire(),
|
||||
ordre=annee_but,
|
||||
referentiel_competence_id=formsemestre.formation.referentiel_competence_id,
|
||||
).first()
|
||||
if validation:
|
||||
decisions["decision_annee"] = validation.to_dict_bul()
|
||||
if sco_preferences.get_preference("bul_but_code_annuel", formsemestre.id):
|
||||
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||
validation = ApcValidationAnnee.query.filter_by(
|
||||
etudid=etud.id,
|
||||
annee_scolaire=formsemestre.annee_scolaire(),
|
||||
ordre=annee_but,
|
||||
referentiel_competence_id=formsemestre.formation.referentiel_competence_id,
|
||||
).first()
|
||||
if validation:
|
||||
decisions["decision_annee"] = validation.to_dict_bul()
|
||||
else:
|
||||
decisions["decision_annee"] = None
|
||||
else:
|
||||
decisions["decision_annee"] = None
|
||||
return decisions
|
||||
|
|
|
@ -5,6 +5,7 @@ from flask import g
|
|||
import pandas as pd
|
||||
|
||||
from app import db, log
|
||||
from app import models
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models.but_refcomp import ApcNiveau, ApcParcours
|
||||
|
@ -12,7 +13,7 @@ from app.models.modules import Module
|
|||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
class UniteEns(db.Model):
|
||||
class UniteEns(models.ScoDocModel):
|
||||
"""Unité d'Enseignement (UE)"""
|
||||
|
||||
__tablename__ = "notes_ue"
|
||||
|
@ -81,7 +82,7 @@ class UniteEns(db.Model):
|
|||
'EXTERNE' if self.is_external else ''})>"""
|
||||
|
||||
def clone(self):
|
||||
"""Create a new copy of this ue.
|
||||
"""Create a new copy of this ue, add to session.
|
||||
Ne copie pas le code, ni le code Apogée, ni les liens au réf. de comp.
|
||||
(parcours et niveau).
|
||||
"""
|
||||
|
@ -100,8 +101,26 @@ class UniteEns(db.Model):
|
|||
coef_rcue=self.coef_rcue,
|
||||
color=self.color,
|
||||
)
|
||||
db.session.add(ue)
|
||||
return ue
|
||||
|
||||
@classmethod
|
||||
def convert_dict_fields(cls, args: dict) -> dict:
|
||||
"""Convert fields from the given dict to model's attributes values. No side effect.
|
||||
|
||||
args: dict with args in application.
|
||||
returns: dict to store in model's db.
|
||||
"""
|
||||
args = args.copy()
|
||||
if "type" in args:
|
||||
args["type"] = int(args["type"] or 0)
|
||||
if "is_external" in args:
|
||||
args["is_external"] = scu.to_bool(args["is_external"])
|
||||
if "ects" in args:
|
||||
args["ects"] = float(args["ects"])
|
||||
|
||||
return args
|
||||
|
||||
def to_dict(self, convert_objects=False, with_module_ue_coefs=True):
|
||||
"""as a dict, with the same conversions as in ScoDoc7.
|
||||
If convert_objects, convert all attributes to native types
|
||||
|
|
|
@ -259,10 +259,17 @@ class EtudiantsJuryPE:
|
|||
} # les semestres de n°i de l'étudiant
|
||||
self.cursus[etudid][nom_sem] = semestres_i
|
||||
|
||||
def get_trajectoire(self, etudid: int, formsemestre_final: FormSemestre):
|
||||
def get_trajectoire(self, etudid: int, formsemestre_final: FormSemestre, nom_aggregat: str):
|
||||
"""Ensemble des semestres parcourus par
|
||||
un étudiant pour l'amener à un semestre terminal.
|
||||
|
||||
Si nom_aggregat est de type "Si", limite les semestres à ceux de numéro i.
|
||||
Par ex: si formsemestre_terminal est un S3 et nom_agrregat "S3", ne prend en compte que les
|
||||
semestres 3.
|
||||
|
||||
Si nom_aggregat est de type "iA" ou "iS" (incluant plusieurs numéros de semestres), prend en
|
||||
compte les dit numéros de semestres.
|
||||
|
||||
Par ex: si formsemestre_terminal est un S3, ensemble des S1,
|
||||
S2, S3 suivi pour l'amener au S3 (il peut y avoir plusieurs S1,
|
||||
ou S2, ou S3 s'il a redoublé).
|
||||
|
@ -277,12 +284,19 @@ class EtudiantsJuryPE:
|
|||
numero_semestre_terminal = formsemestre_final.semestre_id
|
||||
semestres_significatifs = self.get_semestres_significatifs(etudid)
|
||||
|
||||
# Semestres de n° inférieur (pax ex: des S1, S2, S3 pour un S3 terminal)
|
||||
# et qui lui sont antérieurs
|
||||
if nom_aggregat.startswith("S"): # les semestres
|
||||
numero_semestres_possibles =[numero_semestre_terminal]
|
||||
elif nom_aggregat.endswith("A"): # les années
|
||||
numero_semestres_possibles = [int(sem[-1]) for sem in pe_comp.PARCOURS[nom_aggregat]["aggregat"]]
|
||||
assert numero_semestre_terminal in numero_semestres_possibles
|
||||
else: # les xS = tous les semestres jusqu'à Sx (pax ex: des S1, S2, S3 pour un S3 terminal)
|
||||
numero_semestres_possibles = list(range(1, numero_semestre_terminal+1))
|
||||
|
||||
semestres_aggreges = {}
|
||||
for fid, semestre in semestres_significatifs.items():
|
||||
# Semestres parmi ceux de n° possibles & qui lui sont antérieurs
|
||||
if (
|
||||
semestre.semestre_id <= numero_semestre_terminal
|
||||
semestre.semestre_id in numero_semestres_possibles
|
||||
and semestre.date_fin <= formsemestre_final.date_fin
|
||||
):
|
||||
semestres_aggreges[fid] = semestre
|
||||
|
|
|
@ -318,29 +318,35 @@ class JuryPE(object):
|
|||
}
|
||||
|
||||
for aggregat in aggregats:
|
||||
"""La trajectoire de l'étudiant sur l'aggrégat"""
|
||||
# La trajectoire de l'étudiant sur l'aggrégat
|
||||
trajectoire = self.trajectoires.suivi[etudid][aggregat]
|
||||
"""Les moyennes par tag de cette trajectoire"""
|
||||
|
||||
# Les moyennes par tag de cette trajectoire
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} notes ": "-",
|
||||
f"{aggregat} class. (groupe)": "-",
|
||||
f"{aggregat} min/moy/max (groupe)": "-",
|
||||
}
|
||||
if trajectoire:
|
||||
trajectoire_tagguee = self.trajectoires_tagguees[
|
||||
trajectoire.trajectoire_id
|
||||
]
|
||||
bilan = trajectoire_tagguee.moyennes_tags[tag]
|
||||
if tag in trajectoire_tagguee.moyennes_tags:
|
||||
bilan = trajectoire_tagguee.moyennes_tags[tag]
|
||||
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} notes ": round(bilan['notes'].loc[etudid], 2),
|
||||
f"{aggregat} class. (groupe)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
|
||||
f"{aggregat} min/moy/max (groupe)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}",
|
||||
}
|
||||
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} notes ": f"{bilan['notes'].loc[etudid]:.1f}",
|
||||
f"{aggregat} class. (groupe)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
|
||||
f"{aggregat} min/moy/max (groupe)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}",
|
||||
}
|
||||
else:
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} notes ": "-",
|
||||
f"{aggregat} class. (groupe)": "-",
|
||||
f"{aggregat} min/moy/max (groupe)": "-",
|
||||
}
|
||||
|
||||
"""L'interclassement"""
|
||||
interclass = self.interclassements_taggues[aggregat]
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} class. (promo)": "-",
|
||||
f"{aggregat} min/moy/max (promo)": "-",
|
||||
}
|
||||
if tag in interclass.moyennes_tags:
|
||||
bilan = interclass.moyennes_tags[tag]
|
||||
|
||||
|
@ -348,11 +354,6 @@ class JuryPE(object):
|
|||
f"{aggregat} class. (promo)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
|
||||
f"{aggregat} min/moy/max (promo)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}",
|
||||
}
|
||||
else:
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} class. (promo)": "-",
|
||||
f"{aggregat} min/moy/max (promo)": "-",
|
||||
}
|
||||
|
||||
# Fin de l'aggrégat
|
||||
# Construction du dataFrame
|
||||
|
|
|
@ -36,8 +36,7 @@ class Trajectoire:
|
|||
"""Les semestres à aggréger"""
|
||||
self.semestres_aggreges = {}
|
||||
|
||||
|
||||
def add_semestres_a_aggreger(self, semestres: dict[int: FormSemestre]):
|
||||
def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]):
|
||||
"""Ajoute des semestres au semestre à aggréger
|
||||
|
||||
Args:
|
||||
|
@ -45,8 +44,6 @@ class Trajectoire:
|
|||
"""
|
||||
self.semestres_aggreges = self.semestres_aggreges | semestres
|
||||
|
||||
|
||||
|
||||
def get_repr(self):
|
||||
"""Représentation textuelle d'une trajectoire
|
||||
basée sur ses semestres aggrégés"""
|
||||
|
@ -72,11 +69,10 @@ class TrajectoiresJuryPE:
|
|||
|
||||
self.annee_diplome = annee_diplome
|
||||
"""Toutes les trajectoires possibles"""
|
||||
self.trajectoires: dict[tuple: Trajectoire] = {}
|
||||
self.trajectoires: dict[tuple:Trajectoire] = {}
|
||||
"""Quelle trajectoires pour quel étudiant :
|
||||
dictionnaire {etudid: {nom_aggregat: Trajectoire}}"""
|
||||
self.suivi: dict[int: str] = {}
|
||||
|
||||
self.suivi: dict[int:str] = {}
|
||||
|
||||
def cree_trajectoires(self, etudiants: EtudiantsJuryPE):
|
||||
"""Créé toutes les trajectoires, au regard du cursus des étudiants
|
||||
|
@ -84,15 +80,17 @@ class TrajectoiresJuryPE:
|
|||
"""
|
||||
|
||||
for nom_aggregat in pe_comp.TOUS_LES_SEMESTRES + pe_comp.TOUS_LES_AGGREGATS:
|
||||
|
||||
"""L'aggrégat considéré (par ex: 3S=S1+S2+S3), son nom de son semestre terminal (par ex: S3) et son numéro (par ex: 3)"""
|
||||
noms_semestre_de_aggregat = pe_comp.PARCOURS[nom_aggregat]["aggregat"]
|
||||
nom_semestre_terminal = noms_semestre_de_aggregat[-1]
|
||||
|
||||
for etudid in etudiants.cursus:
|
||||
if etudid not in self.suivi:
|
||||
self.suivi[etudid] = {aggregat: None
|
||||
for aggregat in pe_comp.TOUS_LES_SEMESTRES + pe_comp.TOUS_LES_AGGREGATS}
|
||||
self.suivi[etudid] = {
|
||||
aggregat: None
|
||||
for aggregat in pe_comp.TOUS_LES_SEMESTRES
|
||||
+ pe_comp.TOUS_LES_AGGREGATS
|
||||
}
|
||||
|
||||
"""Le formsemestre terminal (dernier en date) associé au
|
||||
semestre marquant la fin de l'aggrégat
|
||||
|
@ -111,7 +109,9 @@ class TrajectoiresJuryPE:
|
|||
|
||||
"""La liste des semestres de l'étudiant à prendre en compte
|
||||
pour cette trajectoire"""
|
||||
semestres_a_aggreger = etudiants.get_trajectoire(etudid, formsemestre_final)
|
||||
semestres_a_aggreger = etudiants.get_trajectoire(
|
||||
etudid, formsemestre_final, nom_aggregat
|
||||
)
|
||||
|
||||
"""Ajout des semestres à la trajectoire"""
|
||||
trajectoire.add_semestres_a_aggreger(semestres_a_aggreger)
|
||||
|
@ -138,7 +138,7 @@ def get_trajectoires_etudid(trajectoires, etudid):
|
|||
liste.append(trajet.trajectoire_id)
|
||||
return liste
|
||||
|
||||
|
||||
|
||||
def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int):
|
||||
"""Pour un nom d'aggrégat donné (par ex: 'S3') et un semestre terminal cible
|
||||
identifié par son formsemestre_id (par ex: 'S3 2022-2023'),
|
||||
|
@ -162,4 +162,3 @@ def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int)
|
|||
formsemestres_etudiant = cursus_etudiant[formsemestre_id_terminal]
|
||||
formsemestres = formsemestres | formsemestres_etudiant
|
||||
return formsemestres
|
||||
|
||||
|
|
|
@ -300,6 +300,7 @@ class EditableTable(object):
|
|||
output_formators={},
|
||||
input_formators={},
|
||||
aux_tables=[],
|
||||
convert_empty_to_nulls=True, # les arguments vides sont traduits en NULL
|
||||
convert_null_outputs_to_empty=True,
|
||||
html_quote=False, # changed in 9.0.10
|
||||
fields_creators={}, # { field : [ sql_command_to_create_it ] }
|
||||
|
@ -321,6 +322,7 @@ class EditableTable(object):
|
|||
self.output_formators = output_formators
|
||||
self.input_formators = input_formators
|
||||
self.convert_null_outputs_to_empty = convert_null_outputs_to_empty
|
||||
self.convert_empty_to_nulls = convert_empty_to_nulls
|
||||
self.html_quote = html_quote
|
||||
self.fields_creators = fields_creators
|
||||
self.filter_nulls = filter_nulls
|
||||
|
@ -351,6 +353,7 @@ class EditableTable(object):
|
|||
self.table_name,
|
||||
vals,
|
||||
commit=True,
|
||||
convert_empty_to_nulls=self.convert_empty_to_nulls,
|
||||
return_id=(self.id_name is not None),
|
||||
ignore_conflicts=self.insert_ignore_conflicts,
|
||||
)
|
||||
|
@ -444,7 +447,7 @@ def dictfilter(d, fields, filter_nulls=True):
|
|||
"""returns a copy of d with only keys listed in "fields" and non null values"""
|
||||
r = {}
|
||||
for f in fields:
|
||||
if f in d and (d[f] != None or not filter_nulls):
|
||||
if f in d and (d[f] is not None or not filter_nulls):
|
||||
try:
|
||||
val = d[f].strip()
|
||||
except:
|
||||
|
|
|
@ -795,7 +795,9 @@ def etud_descr_situation_semestre(
|
|||
descr_mention = ""
|
||||
|
||||
# Décisions APC / BUT
|
||||
if pv.get("decision_annee", {}):
|
||||
if pv.get("decision_annee", {}) and sco_preferences.get_preference(
|
||||
"bul_but_code_annuel", formsemestre.id
|
||||
):
|
||||
infos["descr_decision_annee"] = "Décision année: " + pv.get(
|
||||
"decision_annee", {}
|
||||
).get("code", "")
|
||||
|
|
|
@ -89,6 +89,7 @@ _ueEditor = ndb.EditableTable(
|
|||
"color",
|
||||
"niveau_competence_id",
|
||||
),
|
||||
convert_empty_to_nulls=False, # necessaire pour ue_code == ""
|
||||
sortkey="numero",
|
||||
input_formators={
|
||||
"type": ndb.int_null_is_zero,
|
||||
|
@ -110,7 +111,7 @@ def ue_list(*args, **kw):
|
|||
return _ueEditor.list(cnx, *args, **kw)
|
||||
|
||||
|
||||
def do_ue_create(args):
|
||||
def do_ue_create(args, allow_empty_ue_code=False):
|
||||
"create an ue"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
# check duplicates
|
||||
|
@ -120,18 +121,18 @@ def do_ue_create(args):
|
|||
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
|
||||
(chaque UE doit avoir un acronyme unique dans la formation)"""
|
||||
)
|
||||
if (
|
||||
(not "ue_code" in args)
|
||||
or (args["ue_code"] is None)
|
||||
or (not args["ue_code"].strip())
|
||||
):
|
||||
# évite les conflits de code
|
||||
while True:
|
||||
cursor = db.session.execute(sa.text("select notes_newid_ucod();"))
|
||||
code = cursor.fetchone()[0]
|
||||
if UniteEns.query.filter_by(ue_code=code).count() == 0:
|
||||
break
|
||||
args["ue_code"] = code
|
||||
if "ue_code" not in args or args["ue_code"] is None or not args["ue_code"].strip():
|
||||
if allow_empty_ue_code:
|
||||
args["ue_code"] = ""
|
||||
else:
|
||||
# évite les conflits: génère nouveau ue_code
|
||||
while True:
|
||||
cursor = db.session.execute(sa.text("select notes_newid_ucod();"))
|
||||
code = cursor.fetchone()[0]
|
||||
if UniteEns.query.filter_by(ue_code=code).count() == 0:
|
||||
break
|
||||
args["ue_code"] = code
|
||||
|
||||
# create
|
||||
ue_id = _ueEditor.create(cnx, args)
|
||||
log(f"do_ue_create: created {ue_id} with {args}")
|
||||
|
|
|
@ -168,16 +168,14 @@ def formsemestre_associate_new_version(
|
|||
formation_id=new_formation_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
else:
|
||||
raise ScoValueError("Méthode invalide")
|
||||
)
|
||||
raise ScoValueError("Méthode invalide")
|
||||
|
||||
|
||||
def do_formsemestres_associate_new_version(
|
||||
|
|
|
@ -117,6 +117,7 @@ def formation_export_dict(
|
|||
ues = ues.all()
|
||||
ues.sort(key=lambda u: (u.semestre_idx or 0, u.numero or 0, u.acronyme))
|
||||
f_dict["ue"] = []
|
||||
ue: UniteEns
|
||||
for ue in ues:
|
||||
ue_dict = ue.to_dict()
|
||||
f_dict["ue"].append(ue_dict)
|
||||
|
@ -142,8 +143,8 @@ def formation_export_dict(
|
|||
|
||||
if not export_codes_apo:
|
||||
ue_dict.pop("code_apogee", None)
|
||||
if ue_dict["ects"] is None:
|
||||
del ue_dict["ects"]
|
||||
if ue_dict.get("ects") is None:
|
||||
ue_dict.pop("ects", None)
|
||||
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
|
||||
mats.sort(key=lambda m: m["numero"] or 0)
|
||||
ue_dict["matiere"] = mats
|
||||
|
@ -363,7 +364,9 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
|
|||
ue_info[1]["niveau_competence_id"] = _formation_retreive_apc_niveau(
|
||||
referentiel_competence_id, ue_info[1]
|
||||
)
|
||||
ue_id = sco_edit_ue.do_ue_create(ue_info[1])
|
||||
# Note: si le code est indiqué "" dans le xml, il faut le conserver vide
|
||||
# pour la comparaison ultérieure des formations XXX
|
||||
ue_id = sco_edit_ue.do_ue_create(ue_info[1], allow_empty_ue_code=True)
|
||||
ue: UniteEns = db.session.get(UniteEns, ue_id)
|
||||
assert ue
|
||||
if xml_ue_id:
|
||||
|
|
|
@ -1592,7 +1592,9 @@ def formsemestre_edit_options(formsemestre_id):
|
|||
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
||||
if not ok:
|
||||
return err
|
||||
return sco_preferences.SemPreferences(formsemestre_id).edit(categories=["bul"])
|
||||
return sco_preferences.SemPreferences(formsemestre_id).edit(
|
||||
categories=["bul", "bul_but_pdf"]
|
||||
)
|
||||
|
||||
|
||||
def formsemestre_change_publication_bul(
|
||||
|
|
|
@ -233,7 +233,7 @@ PREF_CATEGORIES = (
|
|||
(
|
||||
"bul_but_pdf",
|
||||
{
|
||||
"title": "Réglages des bulletins BUT (pdf)",
|
||||
"title": "Réglages des bulletins BUT",
|
||||
"related": (
|
||||
"bul",
|
||||
"bul_margins",
|
||||
|
@ -1742,6 +1742,17 @@ class BasePreferences:
|
|||
"category": "bul_but_pdf",
|
||||
},
|
||||
),
|
||||
(
|
||||
"bul_but_code_annuel",
|
||||
{
|
||||
"initvalue": 1,
|
||||
"title": "Bulletins BUT: afficher la décision annuelle",
|
||||
"explanation": "car en cours d'année elle n'a parfois pas de sens",
|
||||
"input_type": "boolcheckbox",
|
||||
"labels": ["non", "oui"],
|
||||
"category": "bul_but_pdf",
|
||||
},
|
||||
),
|
||||
# XXX A COMPLETER, voir sco_formsemestre_edit.py XXX
|
||||
# bul_mail
|
||||
(
|
||||
|
@ -2245,7 +2256,7 @@ class BasePreferences:
|
|||
self.load()
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Préférences",
|
||||
page_title=f"Préférences {g.scodoc_dept}",
|
||||
javascripts=["js/detail_summary_persistence.js"],
|
||||
),
|
||||
f"<h2>Préférences globales pour le département {g.scodoc_dept}</h2>",
|
||||
|
|
|
@ -106,7 +106,7 @@ class ScoTag(object):
|
|||
"""
|
||||
if not title or len(title) > 32:
|
||||
return False
|
||||
if re.match(r"^[A-Za-z0-9\-_$!\.]*(:[0-9]*)?$", title):
|
||||
if re.match(r"^[\w0-9\-_$!?+=,&\.]*(:[0-9]*)?$", title):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -259,6 +259,7 @@ def module_tag_set(module_id="", taglist=None):
|
|||
# Check tags syntax
|
||||
for tag in taglist:
|
||||
if not ScoTag.check_tag_title(tag):
|
||||
log(f"module_tag_set({module_id}): invalid tag title")
|
||||
return scu.json_error(404, "invalid tag")
|
||||
|
||||
# TODO code à moderniser (+ revoir classe ScoTag, utiliser modèle)
|
||||
|
|
|
@ -23,21 +23,24 @@
|
|||
|
||||
{% block app_content %}
|
||||
<h1>Modification du compte ScoDoc <tt>{{form.user_name.data}}</tt></h1>
|
||||
<div class="help">
|
||||
<p>Identifiez-vous avec votre mot de passe actuel</p>
|
||||
<div class="help" style="margin-top: 32px; margin-bottom: 32px;">
|
||||
<p>Le mot de passe ScoDoc doit être suffisament complexe.
|
||||
Il n'a rien à voir avec celui de votre compte ENT (utilisé pour le service CAS).
|
||||
</p>
|
||||
</div>
|
||||
<form method=post>
|
||||
<form method="post">
|
||||
{{ form.user_name }}
|
||||
{{ form.csrf_token }}
|
||||
|
||||
<table class="tf">
|
||||
<tbody>
|
||||
{{ render_field(form.old_password, size=14, auth_name=auth_username,
|
||||
style="padding:1px; margin-left: 1em; margin-top: 4px;") }}
|
||||
<tr>
|
||||
<td colspan="" 2">
|
||||
<p>Vous pouvez changer le mot de passe et/ou l'adresse email.</p>
|
||||
<p>Les champs laissés vides ne seront pas modifiés.</p>
|
||||
</td>
|
||||
<td colspan="2">Vous pouvez changer le mot de passe et/ou l'adresse email.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">Les champs laissés vides ne seront pas modifiés.</td>
|
||||
</tr>
|
||||
{{ render_field(form.new_password, size=14,
|
||||
style="padding:1px; margin-left: 1em; margin-top: 12px;") }}
|
||||
|
|
|
@ -82,6 +82,15 @@
|
|||
<a class="stdlink" href="{{url_for('auth.logout')}}">logout</a>
|
||||
</b>
|
||||
</p>
|
||||
{% if not (ScoDocSiteConfig.is_cas_enabled() and not user.cas_allow_scodoc_login) %}
|
||||
<p><b>
|
||||
<a class="stdlink" href="{{
|
||||
url_for( 'users.form_change_password',
|
||||
scodoc_dept=g.scodoc_dept, user_name=user.user_name)
|
||||
}}">Changer votre mot de passe ScoDoc ou votre mail</a>
|
||||
</b>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -824,7 +824,6 @@ def ue_clone():
|
|||
ue_id = int(request.form.get("ue_id"))
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
ue2 = ue.clone()
|
||||
db.session.add(ue2)
|
||||
db.session.commit()
|
||||
flash(f"UE {ue.acronyme} dupliquée")
|
||||
return flask.redirect(
|
||||
|
|
Loading…
Reference in New Issue