forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -39,6 +39,15 @@ def after_cas_login():
|
|||||||
"scodoc_cas_login_date"
|
"scodoc_cas_login_date"
|
||||||
] = datetime.datetime.now().isoformat()
|
] = datetime.datetime.now().isoformat()
|
||||||
user.cas_last_login = datetime.datetime.utcnow()
|
user.cas_last_login = datetime.datetime.utcnow()
|
||||||
|
if flask.session.get("CAS_EDT_ID"):
|
||||||
|
# essaie de récupérer l'edt_id s'il est présent
|
||||||
|
# cet ID peut être renvoyé par le CAS et extrait par ScoDoc
|
||||||
|
# via l'expression `cas_edt_id_from_xml_regexp`
|
||||||
|
# voir flask_cas.routing
|
||||||
|
edt_id = flask.session.get("CAS_EDT_ID")
|
||||||
|
current_app.logger.info(f"""after_cas_login: storing edt_id for {
|
||||||
|
user.user_name}: '{edt_id}'""")
|
||||||
|
user.edt_id = edt_id
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return flask.redirect(url_for("scodoc.index"))
|
return flask.redirect(url_for("scodoc.index"))
|
||||||
|
@ -185,18 +185,7 @@ class User(UserMixin, db.Model):
|
|||||||
return self._migrate_scodoc7_password(password)
|
return self._migrate_scodoc7_password(password)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
password_ok = check_password_hash(self.password_hash, password)
|
return check_password_hash(self.password_hash, password)
|
||||||
if password_ok and cas_enabled and flask.session.get("CAS_EDT_ID"):
|
|
||||||
# essaie de récupérer l'edt_id s'il est présent
|
|
||||||
# cet ID peut être renvoyé par le CAS et extrait par ScoDoc
|
|
||||||
# via l'expression `cas_edt_id_from_xml_regexp`
|
|
||||||
# voir flask_cas.routing
|
|
||||||
edt_id = flask.session.get("CAS_EDT_ID")
|
|
||||||
log(f"Storing edt_id for {self.user_name}: '{edt_id}'")
|
|
||||||
self.edt_id = edt_id
|
|
||||||
db.session.add(self)
|
|
||||||
db.session.commit()
|
|
||||||
return password_ok
|
|
||||||
|
|
||||||
def _migrate_scodoc7_password(self, password) -> bool:
|
def _migrate_scodoc7_password(self, password) -> bool:
|
||||||
"""After migration, rehash password."""
|
"""After migration, rehash password."""
|
||||||
|
@ -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})
|
||||||
|
@ -38,11 +38,17 @@ from app.models import ScoDocSiteConfig
|
|||||||
|
|
||||||
|
|
||||||
def check_cas_uid_from_mail_regexp(form, field):
|
def check_cas_uid_from_mail_regexp(form, field):
|
||||||
"Vérifie la regexp fournie pur l'extraction du CAS id"
|
"Vérifie la regexp fournie pour l'extraction du CAS id"
|
||||||
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):
|
||||||
|
"Vérifie la regexp fournie pour l'extraction du CAS id"
|
||||||
|
if not ScoDocSiteConfig.cas_edt_id_from_xml_regexp_is_valid(field.data):
|
||||||
|
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")
|
||||||
@ -58,18 +64,18 @@ class ConfigCASForm(FlaskForm):
|
|||||||
description="""url complète. Commence en général par <tt>https://</tt>.""",
|
description="""url complète. Commence en général par <tt>https://</tt>.""",
|
||||||
)
|
)
|
||||||
cas_login_route = StringField(
|
cas_login_route = StringField(
|
||||||
label="Route du login CAS",
|
label="Optionnel: route du login CAS",
|
||||||
description="""ajouté à l'URL du serveur: exemple <tt>/cas</tt>
|
description="""ajouté à l'URL du serveur: exemple <tt>/cas</tt>
|
||||||
(si commence par <tt>/</tt>, part de la racine)""",
|
(si commence par <tt>/</tt>, part de la racine)""",
|
||||||
default="/cas",
|
default="/cas",
|
||||||
)
|
)
|
||||||
cas_logout_route = StringField(
|
cas_logout_route = StringField(
|
||||||
label="Route du logout CAS",
|
label="Optionnel: route du logout CAS",
|
||||||
description="""ajouté à l'URL du serveur: exemple <tt>/cas/logout</tt>""",
|
description="""ajouté à l'URL du serveur: exemple <tt>/cas/logout</tt>""",
|
||||||
default="/cas/logout",
|
default="/cas/logout",
|
||||||
)
|
)
|
||||||
cas_validate_route = StringField(
|
cas_validate_route = StringField(
|
||||||
label="Route de validation CAS",
|
label="Optionnel: route de validation CAS",
|
||||||
description="""ajouté à l'URL du serveur: exemple <tt>/cas/serviceValidate</tt>""",
|
description="""ajouté à l'URL du serveur: exemple <tt>/cas/serviceValidate</tt>""",
|
||||||
default="/cas/serviceValidate",
|
default="/cas/serviceValidate",
|
||||||
)
|
)
|
||||||
@ -81,7 +87,7 @@ class ConfigCASForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
cas_uid_from_mail_regexp = StringField(
|
cas_uid_from_mail_regexp = StringField(
|
||||||
label="Expression pour extraire l'identifiant utilisateur",
|
label="Optionnel: expression pour extraire l'identifiant utilisateur",
|
||||||
description="""regexp python appliquée au mail institutionnel de l'utilisateur,
|
description="""regexp python appliquée au mail institutionnel de l'utilisateur,
|
||||||
dont le premier groupe doit donner l'identifiant CAS.
|
dont le premier groupe doit donner l'identifiant CAS.
|
||||||
Si non fournie, le super-admin devra saisir cet identifiant pour chaque compte.
|
Si non fournie, le super-admin devra saisir cet identifiant pour chaque compte.
|
||||||
@ -92,6 +98,17 @@ class ConfigCASForm(FlaskForm):
|
|||||||
validators=[Optional(), check_cas_uid_from_mail_regexp],
|
validators=[Optional(), check_cas_uid_from_mail_regexp],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cas_edt_id_from_xml_regexp = StringField(
|
||||||
|
label="Optionnel: expression pour extraire l'identifiant edt",
|
||||||
|
description="""regexp python appliquée à la réponse XML du serveur CAS pour
|
||||||
|
retrouver l'id de l'utilisateur sur le SI de l'institution, et notamment sur les
|
||||||
|
calendrier d'emploi du temps. Par exemple, si cet id est renvoyé dans le champ
|
||||||
|
<b>supannEmpId</b>, utiliser:
|
||||||
|
<tt><cas:supannEmpId>(.*?)</cas:supannEmpId></tt>
|
||||||
|
""",
|
||||||
|
validators=[Optional(), check_cas_edt_id_from_xml_regexp],
|
||||||
|
)
|
||||||
|
|
||||||
cas_ssl_verify = BooleanField("Vérification du certificat SSL")
|
cas_ssl_verify = BooleanField("Vérification du certificat SSL")
|
||||||
cas_ssl_certificate_file = FileField(
|
cas_ssl_certificate_file = FileField(
|
||||||
label="Certificat (PEM)",
|
label="Certificat (PEM)",
|
||||||
|
@ -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",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -286,7 +286,7 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def set(cls, name: str, value: str) -> bool:
|
def set(cls, name: str, value: str) -> bool:
|
||||||
"Set parameter, returns True if change. Commit session."
|
"Set parameter, returns True if change. Commit session."
|
||||||
value_str = str(value or "")
|
value_str = str(value or "").strip()
|
||||||
if (cls.get(name) or "") != value_str:
|
if (cls.get(name) or "") != value_str:
|
||||||
cfg = ScoDocSiteConfig.query.filter_by(name=name).first()
|
cfg = ScoDocSiteConfig.query.filter_by(name=name).first()
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
@ -429,7 +429,17 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
return False
|
return False
|
||||||
# and returns at least one group on a simple cannonical address
|
# and returns at least one group on a simple cannonical address
|
||||||
match = pattern.search("emmanuel@exemple.fr")
|
match = pattern.search("emmanuel@exemple.fr")
|
||||||
return len(match.groups()) > 0
|
return match is not None and len(match.groups()) > 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cas_edt_id_from_xml_regexp_is_valid(cls, exp: str) -> bool:
|
||||||
|
"True si l'expression régulière semble valide"
|
||||||
|
# check that it compiles
|
||||||
|
try:
|
||||||
|
_ = re.compile(exp)
|
||||||
|
except re.error:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def assi_get_rounded_time(cls, label: str, default: str) -> float:
|
def assi_get_rounded_time(cls, label: str, default: str) -> float:
|
||||||
|
@ -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)}>"
|
||||||
|
|
||||||
|
@ -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",
|
||||||
@ -171,7 +172,7 @@ def do_module_delete(oid):
|
|||||||
d'en créer une nouvelle version pour la modifier sans affecter
|
d'en créer une nouvelle version pour la modifier sans affecter
|
||||||
les semestres déjà en place.
|
les semestres déjà en place.
|
||||||
</p>
|
</p>
|
||||||
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
|
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=mod["formation_id"])}">reprendre</a>
|
formation_id=mod["formation_id"])}">reprendre</a>
|
||||||
"""
|
"""
|
||||||
raise ScoGenError(err_page)
|
raise ScoGenError(err_page)
|
||||||
@ -645,7 +646,7 @@ def module_edit(
|
|||||||
"title": "Code Apogée",
|
"title": "Code Apogée",
|
||||||
"size": 25,
|
"size": 25,
|
||||||
"explanation": """(optionnel) code élément pédagogique Apogée ou liste de codes ELP
|
"explanation": """(optionnel) code élément pédagogique Apogée ou liste de codes ELP
|
||||||
séparés par des virgules (ce code est propre à chaque établissement, se rapprocher
|
séparés par des virgules (ce code est propre à chaque établissement, se rapprocher
|
||||||
du référent Apogée).
|
du référent Apogée).
|
||||||
""",
|
""",
|
||||||
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
||||||
@ -682,7 +683,7 @@ def module_edit(
|
|||||||
]
|
]
|
||||||
+ ["-1"],
|
+ ["-1"],
|
||||||
"explanation": """Parcours dans lesquels est utilisé ce module.<br>
|
"explanation": """Parcours dans lesquels est utilisé ce module.<br>
|
||||||
Attention: si le module ne doit pas avoir les mêmes coefficients suivant le parcours,
|
Attention: si le module ne doit pas avoir les mêmes coefficients suivant le parcours,
|
||||||
il faut en créer plusieurs versions, car dans ScoDoc chaque module a ses coefficients.""",
|
il faut en créer plusieurs versions, car dans ScoDoc chaque module a ses coefficients.""",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -746,7 +747,7 @@ def module_edit(
|
|||||||
"input_type": "separator",
|
"input_type": "separator",
|
||||||
"title": f"""<span class="fontred">{scu.EMO_WARNING }
|
"title": f"""<span class="fontred">{scu.EMO_WARNING }
|
||||||
Pas de parcours:
|
Pas de parcours:
|
||||||
<a class="stdlink" href="{ url_for('notes.refcomp_assoc_formation',
|
<a class="stdlink" href="{ url_for('notes.refcomp_assoc_formation',
|
||||||
scodoc_dept=g.scodoc_dept, formation_id=formation.id)
|
scodoc_dept=g.scodoc_dept, formation_id=formation.id)
|
||||||
}">associer un référentiel de compétence</a>
|
}">associer un référentiel de compétence</a>
|
||||||
</span>""",
|
</span>""",
|
||||||
|
@ -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",
|
||||||
@ -571,7 +572,7 @@ def view_formsemestre_by_etape(etape_apo=None, fmt="html"):
|
|||||||
),
|
),
|
||||||
html_title=html_title,
|
html_title=html_title,
|
||||||
html_next_section="""<form action="view_formsemestre_by_etape">
|
html_next_section="""<form action="view_formsemestre_by_etape">
|
||||||
Etape: <input name="etape_apo" type="text" size="8"></input>
|
Etape: <input name="etape_apo" type="text" size="8"></input>
|
||||||
</form>""",
|
</form>""",
|
||||||
)
|
)
|
||||||
tab.base_url = "%s?etape_apo=%s" % (request.base_url, etape_apo or "")
|
tab.base_url = "%s?etape_apo=%s" % (request.base_url, etape_apo or "")
|
||||||
|
@ -40,6 +40,7 @@ from app.models import (
|
|||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
Evaluation,
|
Evaluation,
|
||||||
UniteEns,
|
UniteEns,
|
||||||
|
ScoDocSiteConfig,
|
||||||
ScolarFormSemestreValidation,
|
ScolarFormSemestreValidation,
|
||||||
ScolarAutorisationInscription,
|
ScolarAutorisationInscription,
|
||||||
ApcValidationAnnee,
|
ApcValidationAnnee,
|
||||||
@ -321,7 +322,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
"size": 40,
|
"size": 40,
|
||||||
"title": "Nom de ce semestre",
|
"title": "Nom de ce semestre",
|
||||||
"explanation": f"""n'indiquez pas les dates, ni le semestre, ni la modalité dans
|
"explanation": f"""n'indiquez pas les dates, ni le semestre, ni la modalité dans
|
||||||
le titre: ils seront automatiquement ajoutés <input type="button"
|
le titre: ils seront automatiquement ajoutés <input type="button"
|
||||||
value="remettre titre par défaut" onClick="document.tf.titre.value='{
|
value="remettre titre par défaut" onClick="document.tf.titre.value='{
|
||||||
_default_sem_title(formation)}';"/>""",
|
_default_sem_title(formation)}';"/>""",
|
||||||
"allow_null": False,
|
"allow_null": False,
|
||||||
@ -445,13 +446,25 @@ 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",
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||||
}">Modifier les coefficients des UE capitalisées</a>
|
}">Modifier les coefficients des UE capitalisées</a>
|
||||||
</p>
|
</p>
|
||||||
<h3>Sélectionner les modules, leurs responsables et les étudiants
|
<h3>Sélectionner les modules, leurs responsables et les étudiants
|
||||||
à inscrire:</h3>
|
à inscrire:</h3>
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
@ -510,7 +523,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
{
|
{
|
||||||
"input_type": "boolcheckbox",
|
"input_type": "boolcheckbox",
|
||||||
"title": "",
|
"title": "",
|
||||||
"explanation": """Autoriser tous les enseignants associés
|
"explanation": """Autoriser tous les enseignants associés
|
||||||
à un module à y créer des évaluations""",
|
à un module à y créer des évaluations""",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -585,8 +598,8 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
{
|
{
|
||||||
"input_type": "separator",
|
"input_type": "separator",
|
||||||
"title": f"""<span class="fontred">{scu.EMO_WARNING }
|
"title": f"""<span class="fontred">{scu.EMO_WARNING }
|
||||||
Pas de parcours:
|
Pas de parcours:
|
||||||
<a class="stdlink" href="{ url_for('notes.ue_table',
|
<a class="stdlink" href="{ url_for('notes.ue_table',
|
||||||
scodoc_dept=g.scodoc_dept, formation_id=formation.id)
|
scodoc_dept=g.scodoc_dept, formation_id=formation.id)
|
||||||
}">vérifier la formation</a>
|
}">vérifier la formation</a>
|
||||||
</span>""",
|
</span>""",
|
||||||
@ -784,7 +797,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
|
|
||||||
if tf[0] == 0 or msg:
|
if tf[0] == 0 or msg:
|
||||||
return f"""<p>Formation <a class="discretelink" href="{
|
return f"""<p>Formation <a class="discretelink" href="{
|
||||||
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept,
|
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=formation.id)
|
formation_id=formation.id)
|
||||||
}"><em>{formation.titre}</em> ({formation.acronyme}), version {
|
}"><em>{formation.titre}</em> ({formation.acronyme}), version {
|
||||||
formation.version}, code {formation.formation_code}</a>
|
formation.version}, code {formation.formation_code}</a>
|
||||||
@ -969,11 +982,11 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
</li>
|
</li>
|
||||||
</ul></span>
|
</ul></span>
|
||||||
</div>
|
</div>
|
||||||
{"<p>Modification effectuée</p>" if ok
|
{"<p>Modification effectuée</p>" if ok
|
||||||
else "<p>Modules non modifiés</p>"
|
else "<p>Modules non modifiés</p>"
|
||||||
}
|
}
|
||||||
<a class="stdlink" href="{
|
<a class="stdlink" href="{
|
||||||
url_for('notes.formsemestre_status',
|
url_for('notes.formsemestre_status',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||||
}">retour au tableau de bord</a>
|
}">retour au tableau de bord</a>
|
||||||
"""
|
"""
|
||||||
@ -1309,11 +1322,11 @@ def formsemestre_delete(formsemestre_id):
|
|||||||
html_sco_header.html_sem_header("Suppression du semestre"),
|
html_sco_header.html_sem_header("Suppression du semestre"),
|
||||||
"""<div class="ue_warning"><span>Attention !</span>
|
"""<div class="ue_warning"><span>Attention !</span>
|
||||||
<p class="help">A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement,
|
<p class="help">A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement,
|
||||||
<b>un semestre ne doit jamais être supprimé</b>
|
<b>un semestre ne doit jamais être supprimé</b>
|
||||||
(on perd la mémoire des notes et de tous les événements liés à ce semestre !).
|
(on perd la mémoire des notes et de tous les événements liés à ce semestre !).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="help">Tous les modules de ce semestre seront supprimés.
|
<p class="help">Tous les modules de ce semestre seront supprimés.
|
||||||
Ceci n'est possible que si :
|
Ceci n'est possible que si :
|
||||||
</p>
|
</p>
|
||||||
<ol>
|
<ol>
|
||||||
@ -1497,24 +1510,24 @@ def do_formsemestre_delete(formsemestre_id):
|
|||||||
req = "DELETE FROM sco_prefs WHERE formsemestre_id=%(formsemestre_id)s"
|
req = "DELETE FROM sco_prefs WHERE formsemestre_id=%(formsemestre_id)s"
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||||
# --- Suppression des groupes et partitions
|
# --- Suppression des groupes et partitions
|
||||||
req = """DELETE FROM group_membership
|
req = """DELETE FROM group_membership
|
||||||
WHERE group_id IN
|
WHERE group_id IN
|
||||||
(SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd
|
(SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd
|
||||||
WHERE gm.group_id = gd.id AND gd.partition_id = p.id
|
WHERE gm.group_id = gd.id AND gd.partition_id = p.id
|
||||||
AND p.formsemestre_id=%(formsemestre_id)s)
|
AND p.formsemestre_id=%(formsemestre_id)s)
|
||||||
"""
|
"""
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||||
req = """DELETE FROM group_descr
|
req = """DELETE FROM group_descr
|
||||||
WHERE id IN
|
WHERE id IN
|
||||||
(SELECT gd.id FROM group_descr gd, partition p
|
(SELECT gd.id FROM group_descr gd, partition p
|
||||||
WHERE gd.partition_id = p.id
|
WHERE gd.partition_id = p.id
|
||||||
AND p.formsemestre_id=%(formsemestre_id)s)
|
AND p.formsemestre_id=%(formsemestre_id)s)
|
||||||
"""
|
"""
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||||
req = "DELETE FROM partition WHERE formsemestre_id=%(formsemestre_id)s"
|
req = "DELETE FROM partition WHERE formsemestre_id=%(formsemestre_id)s"
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||||
# --- Responsables
|
# --- Responsables
|
||||||
req = """DELETE FROM notes_formsemestre_responsables
|
req = """DELETE FROM notes_formsemestre_responsables
|
||||||
WHERE formsemestre_id=%(formsemestre_id)s"""
|
WHERE formsemestre_id=%(formsemestre_id)s"""
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||||
# --- Etapes
|
# --- Etapes
|
||||||
@ -1606,7 +1619,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
|||||||
Indiquez "auto" (ou laisser vide) pour que ScoDoc calcule automatiquement le coefficient,
|
Indiquez "auto" (ou laisser vide) pour que ScoDoc calcule automatiquement le coefficient,
|
||||||
ou bien entrez une valeur (nombre réel).
|
ou bien entrez une valeur (nombre réel).
|
||||||
</p>
|
</p>
|
||||||
<p class="help">Dans le doute, si le mode auto n'est pas applicable et que tous les étudiants sont inscrits aux mêmes modules de ce semestre, prenez comme coefficient la somme indiquée.
|
<p class="help">Dans le doute, si le mode auto n'est pas applicable et que tous les étudiants sont inscrits aux mêmes modules de ce semestre, prenez comme coefficient la somme indiquée.
|
||||||
Sinon, référez vous au programme pédagogique. Les lignes en <font color="red">rouge</font>
|
Sinon, référez vous au programme pédagogique. Les lignes en <font color="red">rouge</font>
|
||||||
sont à changer.
|
sont à changer.
|
||||||
</p>
|
</p>
|
||||||
@ -1734,7 +1747,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
|||||||
|
|
||||||
return f"""{html_sco_header.html_sem_header("Coefficients des UE du semestre")}
|
return f"""{html_sco_header.html_sem_header("Coefficients des UE du semestre")}
|
||||||
{" ".join(message)}
|
{" ".join(message)}
|
||||||
<p><a class="stdlink" href="{url_for("notes.formsemestre_status",
|
<p><a class="stdlink" href="{url_for("notes.formsemestre_status",
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||||
}">Revenir au tableau de bord</a>
|
}">Revenir au tableau de bord</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -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
|
||||||
|
@ -1408,7 +1408,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é
|
||||||
*/
|
*/
|
||||||
@ -1685,17 +1685,17 @@ function generateEtudRow(
|
|||||||
const HTML = `<div class="etud_row ${conflit} ${defdem}" id="etud_row_${
|
const HTML = `<div class="etud_row ${conflit} ${defdem}" id="etud_row_${
|
||||||
etud.id
|
etud.id
|
||||||
}">
|
}">
|
||||||
|
|
||||||
<div class="index">${index}</div>
|
<div class="index">${index}</div>
|
||||||
<div class="name_field">
|
<div class="name_field">
|
||||||
|
|
||||||
<img class="pdp" src="${pdp_url}">
|
<img class="pdp" src="${pdp_url}">
|
||||||
|
|
||||||
<a class="name_set" href="BilanEtud?etudid=${etud.id}">
|
<a class="name_set" href="BilanEtud?etudid=${etud.id}">
|
||||||
<h4 class="nom">${etud.nom}</h4>
|
<h4 class="nom">${etud.nom}</h4>
|
||||||
<h5 class="prenom">${etud.prenom}</h5>
|
<h5 class="prenom">${etud.prenom}</h5>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="assiduites_bar">
|
<div class="assiduites_bar">
|
||||||
<div id="prevDateAssi" class="${assiduite.prevAssiduites?.etat?.toLowerCase()}">
|
<div id="prevDateAssi" class="${assiduite.prevAssiduites?.etat?.toLowerCase()}">
|
||||||
@ -1704,12 +1704,12 @@ function generateEtudRow(
|
|||||||
<fieldset class="btns_field single" etudid="${etud.id}" assiduite_id="${
|
<fieldset class="btns_field single" etudid="${etud.id}" assiduite_id="${
|
||||||
assiduite.id
|
assiduite.id
|
||||||
}" type="${assiduite.type}">
|
}" type="${assiduite.type}">
|
||||||
|
|
||||||
${assi}
|
${assi}
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
return HTML;
|
return HTML;
|
||||||
|
@ -13,26 +13,36 @@ 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="col-md-8">
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ wtf.form_errors(form, hiddens="only") }}
|
||||||
|
|
||||||
<form class="form form-horizontal" method="post" enctype="multipart/form-data" role="form">
|
{{ wtf.form_field(form.morning_time) }}
|
||||||
{{ form.hidden_tag() }}
|
{{ wtf.form_field(form.lunch_time) }}
|
||||||
{{ wtf.form_errors(form, hiddens="only") }}
|
{{ wtf.form_field(form.afternoon_time) }}
|
||||||
|
{{ wtf.form_field(form.tick_time) }}
|
||||||
{{ wtf.form_field(form.morning_time) }}
|
</div>
|
||||||
{{ wtf.form_field(form.lunch_time) }}
|
|
||||||
{{ wtf.form_field(form.afternoon_time) }}
|
|
||||||
{{ wtf.form_field(form.tick_time) }}
|
|
||||||
<div class="form-group">
|
|
||||||
{{ wtf.form_field(form.submit) }}
|
|
||||||
{{ wtf.form_field(form.cancel) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</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">
|
||||||
|
{{ wtf.form_field(form.submit) }}
|
||||||
|
{{ wtf.form_field(form.cancel) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
{% endblock %}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
{{ wtf.form_field(form.cas_validate_route) }}
|
{{ wtf.form_field(form.cas_validate_route) }}
|
||||||
{{ wtf.form_field(form.cas_attribute_id) }}
|
{{ wtf.form_field(form.cas_attribute_id) }}
|
||||||
{{ wtf.form_field(form.cas_uid_from_mail_regexp) }}
|
{{ wtf.form_field(form.cas_uid_from_mail_regexp) }}
|
||||||
|
{{ wtf.form_field(form.cas_edt_id_from_xml_regexp) }}
|
||||||
<div class="cas_settings">
|
<div class="cas_settings">
|
||||||
{{ wtf.form_field(form.cas_ssl_verify) }}
|
{{ wtf.form_field(form.cas_ssl_verify) }}
|
||||||
{{ wtf.form_field(form.cas_ssl_certificate_file) }}
|
{{ wtf.form_field(form.cas_ssl_certificate_file) }}
|
||||||
@ -40,8 +41,8 @@
|
|||||||
{{ wtf.form_field(form.cancel) }}
|
{{ wtf.form_field(form.cancel) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-top:16px;">
|
<div class="form-group" style="margin-top:16px;">
|
||||||
ℹ️ <em>Note: si le CAS est forcé, le super-admin et les utilisateurs autorisés
|
ℹ️ <em>Note: si le CAS est forcé, le super-admin et les utilisateurs autorisés
|
||||||
à "se connecter via ScoDoc" pourront toujours se
|
à "se connecter via ScoDoc" pourront toujours se
|
||||||
connecter via l'adresse spéciale</em>
|
connecter via l'adresse spéciale</em>
|
||||||
<tt style="color: blue;">{{url_for("auth.login_scodoc", _external=True)}}</tt>
|
<tt style="color: blue;">{{url_for("auth.login_scodoc", _external=True)}}</tt>
|
||||||
</div>
|
</div>
|
||||||
@ -50,6 +51,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
{% endblock %}
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -273,6 +273,10 @@ def config_cas():
|
|||||||
"cas_uid_from_mail_regexp", form.data["cas_uid_from_mail_regexp"]
|
"cas_uid_from_mail_regexp", form.data["cas_uid_from_mail_regexp"]
|
||||||
):
|
):
|
||||||
flash("Expression extraction identifiant CAS enregistrée")
|
flash("Expression extraction identifiant CAS enregistrée")
|
||||||
|
if ScoDocSiteConfig.set(
|
||||||
|
"cas_edt_id_from_xml_regexp", form.data["cas_edt_id_from_xml_regexp"]
|
||||||
|
):
|
||||||
|
flash("Expression extraction identifiant edt enregistrée")
|
||||||
if ScoDocSiteConfig.set("cas_ssl_verify", form.data["cas_ssl_verify"]):
|
if ScoDocSiteConfig.set("cas_ssl_verify", form.data["cas_ssl_verify"]):
|
||||||
flash("Vérification SSL modifiée")
|
flash("Vérification SSL modifiée")
|
||||||
if form.cas_ssl_certificate_file.data:
|
if form.cas_ssl_certificate_file.data:
|
||||||
@ -300,6 +304,9 @@ def config_cas():
|
|||||||
form.cas_uid_from_mail_regexp.data = ScoDocSiteConfig.get(
|
form.cas_uid_from_mail_regexp.data = ScoDocSiteConfig.get(
|
||||||
"cas_uid_from_mail_regexp"
|
"cas_uid_from_mail_regexp"
|
||||||
)
|
)
|
||||||
|
form.cas_edt_id_from_xml_regexp.data = ScoDocSiteConfig.get(
|
||||||
|
"cas_edt_id_from_xml_regexp"
|
||||||
|
)
|
||||||
form.cas_ssl_verify.data = ScoDocSiteConfig.get("cas_ssl_verify")
|
form.cas_ssl_verify.data = ScoDocSiteConfig.get("cas_ssl_verify")
|
||||||
return render_template(
|
return render_template(
|
||||||
"config_cas.j2",
|
"config_cas.j2",
|
||||||
@ -316,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")
|
||||||
@ -323,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)
|
||||||
)
|
)
|
||||||
@ -349,12 +352,13 @@ 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",
|
||||||
form=form,
|
form=form,
|
||||||
title="Configuration du module Assiduité",
|
title="Configuration du module Assiduité",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ScoDoc/config_codes_decisions", methods=["GET", "POST"])
|
@bp.route("/ScoDoc/config_codes_decisions", methods=["GET", "POST"])
|
||||||
|
Loading…
Reference in New Issue
Block a user