forked from ScoDoc/ScoDoc
Merge branch 'main96' into sans_moment
This commit is contained in:
commit
f1006cff88
@ -12,6 +12,7 @@ from typing import Optional
|
|||||||
|
|
||||||
import cracklib # pylint: disable=import-error
|
import cracklib # pylint: disable=import-error
|
||||||
|
|
||||||
|
import flask
|
||||||
from flask import current_app, g
|
from flask import current_app, g
|
||||||
from flask_login import UserMixin, AnonymousUserMixin
|
from flask_login import UserMixin, AnonymousUserMixin
|
||||||
|
|
||||||
@ -88,7 +89,8 @@ class User(UserMixin, db.Model):
|
|||||||
"""
|
"""
|
||||||
cas_last_login = db.Column(db.DateTime, nullable=True)
|
cas_last_login = db.Column(db.DateTime, nullable=True)
|
||||||
"""date du dernier login via CAS"""
|
"""date du dernier login via CAS"""
|
||||||
|
edt_id = db.Column(db.Text(), index=True, nullable=True)
|
||||||
|
"identifiant emplois du temps (unicité non imposée)"
|
||||||
password_hash = db.Column(db.String(128))
|
password_hash = db.Column(db.String(128))
|
||||||
password_scodoc7 = db.Column(db.String(42))
|
password_scodoc7 = db.Column(db.String(42))
|
||||||
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
|
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
@ -172,7 +174,8 @@ class User(UserMixin, db.Model):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# if CAS activated and forced, allow only super-user and users with cas_allow_scodoc_login
|
# if CAS activated and forced, allow only super-user and users with cas_allow_scodoc_login
|
||||||
if ScoDocSiteConfig.is_cas_enabled() and ScoDocSiteConfig.get("cas_force"):
|
cas_enabled = ScoDocSiteConfig.is_cas_enabled()
|
||||||
|
if cas_enabled and ScoDocSiteConfig.get("cas_force"):
|
||||||
if (not self.is_administrator()) and not self.cas_allow_scodoc_login:
|
if (not self.is_administrator()) and not self.cas_allow_scodoc_login:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -182,7 +185,18 @@ class User(UserMixin, db.Model):
|
|||||||
return self._migrate_scodoc7_password(password)
|
return self._migrate_scodoc7_password(password)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return check_password_hash(self.password_hash, password)
|
password_ok = 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."""
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import urllib.parse
|
|
||||||
import re
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from flask import flash
|
from flask import flash
|
||||||
from app import current_app, db, log
|
from app import current_app, db, log
|
||||||
@ -13,8 +13,6 @@ from app.comp import bonus_spo
|
|||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
from datetime import time
|
|
||||||
|
|
||||||
from app.scodoc.codes_cursus import (
|
from app.scodoc.codes_cursus import (
|
||||||
ABAN,
|
ABAN,
|
||||||
ABL,
|
ABL,
|
||||||
@ -105,6 +103,7 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
"cas_validate_route": str,
|
"cas_validate_route": str,
|
||||||
"cas_attribute_id": str,
|
"cas_attribute_id": str,
|
||||||
"cas_uid_from_mail_regexp": str,
|
"cas_uid_from_mail_regexp": str,
|
||||||
|
"cas_edt_id_from_xml_regexp": str,
|
||||||
# Assiduité
|
# Assiduité
|
||||||
"morning_time": str,
|
"morning_time": str,
|
||||||
"lunch_time": str,
|
"lunch_time": str,
|
||||||
|
@ -64,6 +64,8 @@ class FormSemestre(db.Model):
|
|||||||
titre = db.Column(db.Text(), nullable=False)
|
titre = db.Column(db.Text(), nullable=False)
|
||||||
date_debut = db.Column(db.Date(), nullable=False)
|
date_debut = db.Column(db.Date(), nullable=False)
|
||||||
date_fin = db.Column(db.Date(), nullable=False)
|
date_fin = db.Column(db.Date(), nullable=False)
|
||||||
|
edt_id: str | None = db.Column(db.Text(), index=True, nullable=True)
|
||||||
|
"identifiant emplois du temps (unicité non imposée)"
|
||||||
etat = db.Column(db.Boolean(), nullable=False, default=True, server_default="true")
|
etat = db.Column(db.Boolean(), nullable=False, default=True, server_default="true")
|
||||||
"False si verrouillé"
|
"False si verrouillé"
|
||||||
modalite = db.Column(
|
modalite = db.Column(
|
||||||
|
@ -213,10 +213,12 @@ class GroupDescr(db.Model):
|
|||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
group_id = db.synonym("id")
|
group_id = db.synonym("id")
|
||||||
partition_id = db.Column(db.Integer, db.ForeignKey("partition.id"))
|
partition_id = db.Column(db.Integer, db.ForeignKey("partition.id"))
|
||||||
# "A", "C2", ... (NULL for 'all'):
|
|
||||||
group_name = db.Column(db.String(GROUPNAME_STR_LEN))
|
group_name = db.Column(db.String(GROUPNAME_STR_LEN))
|
||||||
# Numero = ordre de presentation
|
"""nom du groupe: "A", "C2", ... (NULL for 'all')"""
|
||||||
|
edt_id: str | None = db.Column(db.Text(), index=True, nullable=True)
|
||||||
|
"identifiant emplois du temps (unicité non imposée)"
|
||||||
numero = db.Column(db.Integer, nullable=False, default=0)
|
numero = db.Column(db.Integer, nullable=False, default=0)
|
||||||
|
"Numero = ordre de presentation"
|
||||||
|
|
||||||
etuds = db.relationship(
|
etuds = db.relationship(
|
||||||
"Identite",
|
"Identite",
|
||||||
@ -272,6 +274,40 @@ class GroupDescr(db.Model):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def set_name(
|
||||||
|
self, group_name: str, edt_id: str | bool = False, dest_url: str = None
|
||||||
|
):
|
||||||
|
"""Set group name, and optionally edt_id.
|
||||||
|
Check permission and invalidate caches. Commit session.
|
||||||
|
dest_url is used for error messages.
|
||||||
|
"""
|
||||||
|
if not self.partition.formsemestre.can_change_groups():
|
||||||
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
|
if self.group_name is None:
|
||||||
|
raise ValueError("can't set a name to default group")
|
||||||
|
|
||||||
|
if group_name:
|
||||||
|
group_name = group_name.strip()
|
||||||
|
if not group_name:
|
||||||
|
raise ScoValueError("nom de groupe vide !", dest_url=dest_url)
|
||||||
|
if group_name != self.group_name and not GroupDescr.check_name(
|
||||||
|
self.partition, group_name
|
||||||
|
):
|
||||||
|
raise ScoValueError(
|
||||||
|
"Le nom de groupe existe déjà dans la partition", dest_url=dest_url
|
||||||
|
)
|
||||||
|
|
||||||
|
self.group_name = group_name
|
||||||
|
if edt_id is not False:
|
||||||
|
if isinstance(edt_id, str):
|
||||||
|
edt_id = edt_id.strip() or None
|
||||||
|
self.edt_id = edt_id
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
sco_cache.invalidate_formsemestre(
|
||||||
|
formsemestre_id=self.partition.formsemestre_id
|
||||||
|
)
|
||||||
|
|
||||||
def remove_etud(self, etud: "Identite"):
|
def remove_etud(self, etud: "Identite"):
|
||||||
"Enlève l'étudiant de ce groupe s'il en fait partie (ne fait rien sinon)"
|
"Enlève l'étudiant de ce groupe s'il en fait partie (ne fait rien sinon)"
|
||||||
if etud in self.etuds:
|
if etud in self.etuds:
|
||||||
|
@ -34,8 +34,10 @@ class Module(db.Model):
|
|||||||
# note: en APC, le semestre qui fait autorité est celui de l'UE
|
# note: en APC, le semestre qui fait autorité est celui de l'UE
|
||||||
semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
|
semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
|
||||||
numero = db.Column(db.Integer, nullable=False, default=0) # ordre de présentation
|
numero = db.Column(db.Integer, nullable=False, default=0) # ordre de présentation
|
||||||
# id de l'element pedagogique Apogee correspondant:
|
|
||||||
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
|
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
|
||||||
|
"id de l'element pedagogique Apogee correspondant"
|
||||||
|
edt_id: str | None = db.Column(db.Text(), index=True, nullable=True)
|
||||||
|
"identifiant emplois du temps (unicité non imposée)"
|
||||||
# Type: ModuleType.STANDARD, MALUS, RESSOURCE, SAE (enum)
|
# Type: ModuleType.STANDARD, MALUS, RESSOURCE, SAE (enum)
|
||||||
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
|
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
|
||||||
# Relations:
|
# Relations:
|
||||||
|
@ -34,16 +34,13 @@ XXX incompatible avec les ics HyperPlanning Paris 13 (était pour GPU).
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import icalendar
|
import icalendar
|
||||||
import pprint
|
|
||||||
import traceback
|
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc import html_sco_header
|
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_groups_view
|
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ from app import cache, db, log
|
|||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import FormSemestre, Identite, Scolog
|
from app.models import FormSemestre, Identite, Scolog
|
||||||
from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
|
from app.models import SHORT_STR_LEN
|
||||||
from app.models.groups import GroupDescr, Partition
|
from app.models.groups import GroupDescr, Partition
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
@ -1299,85 +1299,6 @@ def partition_set_name(partition_id, partition_name, redirect=1):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def group_set_name(group: GroupDescr, group_name: str, redirect=True):
|
|
||||||
"""Set group name"""
|
|
||||||
if not group.partition.formsemestre.can_change_groups():
|
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
|
||||||
if group.group_name is None:
|
|
||||||
raise ValueError("can't set a name to default group")
|
|
||||||
destination = url_for(
|
|
||||||
"scolar.affect_groups",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
partition_id=group.partition_id,
|
|
||||||
)
|
|
||||||
if group_name:
|
|
||||||
group_name = group_name.strip()
|
|
||||||
if not group_name:
|
|
||||||
raise ScoValueError("nom de groupe vide !", dest_url=destination)
|
|
||||||
if not GroupDescr.check_name(group.partition, group_name):
|
|
||||||
raise ScoValueError(
|
|
||||||
"Le nom de groupe existe déjà dans la partition", dest_url=destination
|
|
||||||
)
|
|
||||||
|
|
||||||
redirect = int(redirect)
|
|
||||||
group.group_name = group_name
|
|
||||||
db.session.add(group)
|
|
||||||
db.session.commit()
|
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=group.partition.formsemestre_id)
|
|
||||||
|
|
||||||
# redirect to partition edit page:
|
|
||||||
if redirect:
|
|
||||||
return flask.redirect(destination)
|
|
||||||
|
|
||||||
|
|
||||||
def group_rename(group_id):
|
|
||||||
"""Form to rename a group"""
|
|
||||||
group = GroupDescr.query.get_or_404(group_id)
|
|
||||||
formsemestre_id = group.partition.formsemestre_id
|
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
||||||
if not formsemestre.can_change_groups():
|
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
|
||||||
H = [f"<h2>Renommer un groupe de {group.partition.partition_name or '-'}</h2>"]
|
|
||||||
tf = TrivialFormulator(
|
|
||||||
request.base_url,
|
|
||||||
scu.get_request_args(),
|
|
||||||
(
|
|
||||||
("group_id", {"default": group_id, "input_type": "hidden"}),
|
|
||||||
(
|
|
||||||
"group_name",
|
|
||||||
{
|
|
||||||
"title": "Nouveau nom",
|
|
||||||
"default": group.group_name,
|
|
||||||
"size": 12,
|
|
||||||
"allow_null": False,
|
|
||||||
"validator": lambda val, _: len(val) < GROUPNAME_STR_LEN,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
submitlabel="Renommer",
|
|
||||||
cancelbutton="Annuler",
|
|
||||||
)
|
|
||||||
if tf[0] == 0:
|
|
||||||
return (
|
|
||||||
html_sco_header.sco_header()
|
|
||||||
+ "\n".join(H)
|
|
||||||
+ "\n"
|
|
||||||
+ tf[1]
|
|
||||||
+ html_sco_header.sco_footer()
|
|
||||||
)
|
|
||||||
elif tf[0] == -1:
|
|
||||||
return flask.redirect(
|
|
||||||
url_for(
|
|
||||||
"scolar.affect_groups",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
partition_id=group.partition_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# form submission
|
|
||||||
return group_set_name(group, tf[2]["group_name"])
|
|
||||||
|
|
||||||
|
|
||||||
def groups_auto_repartition(partition: Partition):
|
def groups_auto_repartition(partition: Partition):
|
||||||
"""Réparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
"""Réparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
||||||
et la mixité.
|
et la mixité.
|
||||||
|
@ -27,11 +27,15 @@
|
|||||||
|
|
||||||
"""Formulaires gestion des groupes
|
"""Formulaires gestion des groupes
|
||||||
"""
|
"""
|
||||||
from flask import render_template
|
import flask
|
||||||
|
from flask import flash, g, render_template, request, url_for
|
||||||
|
|
||||||
from app.models import Partition
|
from app.models import FormSemestre, GroupDescr, Partition
|
||||||
|
from app.models import GROUPNAME_STR_LEN
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc.sco_exceptions import AccessDenied
|
from app.scodoc.sco_exceptions import AccessDenied
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
|
|
||||||
|
|
||||||
def affect_groups(partition_id):
|
def affect_groups(partition_id):
|
||||||
@ -59,3 +63,64 @@ def affect_groups(partition_id):
|
|||||||
),
|
),
|
||||||
formsemestre_id=formsemestre.id,
|
formsemestre_id=formsemestre.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def group_rename(group_id):
|
||||||
|
"""Form to rename a group"""
|
||||||
|
group: GroupDescr = GroupDescr.query.get_or_404(group_id)
|
||||||
|
formsemestre_id = group.partition.formsemestre_id
|
||||||
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
|
if not formsemestre.can_change_groups():
|
||||||
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
|
H = [f"<h2>Renommer un groupe de {group.partition.partition_name or '-'}</h2>"]
|
||||||
|
tf = TrivialFormulator(
|
||||||
|
request.base_url,
|
||||||
|
scu.get_request_args(),
|
||||||
|
(
|
||||||
|
("group_id", {"default": group_id, "input_type": "hidden"}),
|
||||||
|
(
|
||||||
|
"group_name",
|
||||||
|
{
|
||||||
|
"title": "Nouveau nom",
|
||||||
|
"default": group.group_name,
|
||||||
|
"size": 12,
|
||||||
|
"allow_null": False,
|
||||||
|
"validator": lambda val, _: len(val) < GROUPNAME_STR_LEN,
|
||||||
|
"explanation": "doit être unique dans cette partition",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"edt_id",
|
||||||
|
{
|
||||||
|
"title": "Id EDT",
|
||||||
|
"default": group.edt_id or "",
|
||||||
|
"size": 12,
|
||||||
|
"allow_null": True,
|
||||||
|
"explanation": "optionnel : identifiant du groupe dans le logiciel d'emploi du temps",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
submitlabel="Renommer",
|
||||||
|
cancelbutton="Annuler",
|
||||||
|
)
|
||||||
|
dest_url = url_for(
|
||||||
|
"scolar.partition_editor",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=group.partition.formsemestre_id,
|
||||||
|
edit_partition=1,
|
||||||
|
)
|
||||||
|
if tf[0] == 0:
|
||||||
|
return (
|
||||||
|
html_sco_header.sco_header()
|
||||||
|
+ "\n".join(H)
|
||||||
|
+ "\n"
|
||||||
|
+ tf[1]
|
||||||
|
+ html_sco_header.sco_footer()
|
||||||
|
)
|
||||||
|
elif tf[0] == -1:
|
||||||
|
return flask.redirect(dest_url)
|
||||||
|
else:
|
||||||
|
# form submission
|
||||||
|
group.set_name(tf[2]["group_name"], edt_id=tf[2]["edt_id"], dest_url=dest_url)
|
||||||
|
flash("groupe modifié")
|
||||||
|
return flask.redirect(dest_url)
|
||||||
|
@ -31,11 +31,8 @@
|
|||||||
|
|
||||||
# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
|
# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
|
||||||
|
|
||||||
import collections
|
|
||||||
import datetime
|
import datetime
|
||||||
import urllib
|
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
from flask import url_for, g, request
|
from flask import url_for, g, request
|
||||||
@ -45,7 +42,6 @@ from app import db
|
|||||||
from app.models import FormSemestre
|
from app.models import FormSemestre
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_cal
|
|
||||||
from app.scodoc import sco_excel
|
from app.scodoc import sco_excel
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
|
@ -302,6 +302,10 @@ body.editionActivated .filtres>div>div>div>div {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#zonePartitions span.editing a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.editionActivated #zonePartitions .filtres .config {
|
.editionActivated #zonePartitions .filtres .config {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -4834,3 +4834,7 @@ div.cas_etat_certif_ssl {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: rgb(231, 0, 0);
|
color: rgb(231, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edt_id {
|
||||||
|
color: rgb(85, 255, 24);
|
||||||
|
}
|
||||||
|
@ -449,7 +449,7 @@ function validateSelectors(btn) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (getModuleImplId() == null && window.forceModule) {
|
if (getModuleImplId() == null && window.forceModule && !readOnly) {
|
||||||
const HTML = `
|
const HTML = `
|
||||||
<p>Attention, le module doit obligatoirement être renseigné.</p>
|
<p>Attention, le module doit obligatoirement être renseigné.</p>
|
||||||
<p>Cela vient de la configuration du semestre ou plus largement du département.</p>
|
<p>Cela vient de la configuration du semestre ou plus largement du département.</p>
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
<h2>Filtres</h2>
|
<h2>Filtres</h2>
|
||||||
<div>
|
<div>
|
||||||
<label class="edition">
|
<label class="edition">
|
||||||
<input type="checkbox" autocomplete="off" id="inputModif">
|
<input type="checkbox" autocomplete="off" id="inputModif"
|
||||||
|
{% if edit_partition %}checked{% endif %}>
|
||||||
Modifier les partitions et groupes
|
Modifier les partitions et groupes
|
||||||
</label>
|
</label>
|
||||||
<div class="filtres"></div>
|
<div class="filtres"></div>
|
||||||
@ -246,15 +247,15 @@
|
|||||||
let div = document.createElement("button");
|
let div = document.createElement("button");
|
||||||
div.classList.add("dt-button");
|
div.classList.add("dt-button");
|
||||||
div.dataset.idgroupe = groupe.id;
|
div.dataset.idgroupe = groupe.id;
|
||||||
|
let edt_id_str = groupe.edt_id ? `<tt class="edt_id" title="id edt">[${groupe.edt_id}]</tt>` : "";
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<span class="editing move">||</span>
|
<span class="editing move">||</span>
|
||||||
<span>${groupe.group_name}</span>
|
<span>${groupe.group_name} ${edt_id_str}</span>
|
||||||
<span class="editing modif">✏️</span>
|
<span class="editing"><a href="/ScoDoc/{{formsemestre.departement.acronym}}/Scolarite/group_rename?group_id=${groupe.id}">✏️</a></span>
|
||||||
<span class="editing suppr">❌</span>`;
|
<span class="editing suppr">❌</span>`;
|
||||||
|
|
||||||
div.addEventListener("click", filtre);
|
div.addEventListener("click", filtre);
|
||||||
div.querySelector(".move").addEventListener("mousedown", moveStart);
|
div.querySelector(".move").addEventListener("mousedown", moveStart);
|
||||||
div.querySelector(".modif").addEventListener("click", editText);
|
|
||||||
div.querySelector(".suppr").addEventListener("click", suppr);
|
div.querySelector(".suppr").addEventListener("click", suppr);
|
||||||
|
|
||||||
return div;
|
return div;
|
||||||
|
@ -842,7 +842,7 @@ sco_publish("/setGroups", sco_groups.setGroups, Permission.ScoView, methods=["PO
|
|||||||
|
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/group_rename",
|
"/group_rename",
|
||||||
sco_groups.group_rename,
|
sco_groups_edit.group_rename,
|
||||||
Permission.ScoView,
|
Permission.ScoView,
|
||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
|
"""
|
||||||
|
Routes for CAS authentication
|
||||||
|
Modified for ScoDoc
|
||||||
|
"""
|
||||||
|
import re
|
||||||
import ssl
|
import ssl
|
||||||
|
from urllib.error import URLError
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from xmltodict import parse
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from xmltodict import parse
|
||||||
|
|
||||||
from .cas_urls import create_cas_login_url
|
from .cas_urls import create_cas_login_url
|
||||||
from .cas_urls import create_cas_logout_url
|
from .cas_urls import create_cas_logout_url
|
||||||
from .cas_urls import create_cas_validate_url
|
from .cas_urls import create_cas_validate_url
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from urllib import urlopen # python 2
|
|
||||||
except ImportError:
|
|
||||||
from urllib.request import urlopen # python 3
|
|
||||||
from urllib.error import URLError
|
|
||||||
|
|
||||||
blueprint = flask.Blueprint("cas", __name__)
|
blueprint = flask.Blueprint("cas", __name__)
|
||||||
|
|
||||||
|
|
||||||
@ -53,7 +55,6 @@ def login():
|
|||||||
flask.session[cas_token_session_key] = flask.request.args["ticket"]
|
flask.session[cas_token_session_key] = flask.request.args["ticket"]
|
||||||
|
|
||||||
if cas_token_session_key in flask.session:
|
if cas_token_session_key in flask.session:
|
||||||
|
|
||||||
if validate(flask.session[cas_token_session_key]):
|
if validate(flask.session[cas_token_session_key]):
|
||||||
if "CAS_AFTER_LOGIN_SESSION_URL" in flask.session:
|
if "CAS_AFTER_LOGIN_SESSION_URL" in flask.session:
|
||||||
redirect_url = flask.session.pop("CAS_AFTER_LOGIN_SESSION_URL")
|
redirect_url = flask.session.pop("CAS_AFTER_LOGIN_SESSION_URL")
|
||||||
@ -64,7 +65,7 @@ def login():
|
|||||||
else:
|
else:
|
||||||
flask.session.pop(cas_token_session_key, None)
|
flask.session.pop(cas_token_session_key, None)
|
||||||
|
|
||||||
current_app.logger.debug("Redirecting to: {redirect_url}")
|
current_app.logger.debug(f"cas.login: redirecting to {redirect_url}")
|
||||||
|
|
||||||
return flask.redirect(redirect_url)
|
return flask.redirect(redirect_url)
|
||||||
|
|
||||||
@ -84,6 +85,7 @@ def logout():
|
|||||||
flask.session.pop(cas_username_session_key, None)
|
flask.session.pop(cas_username_session_key, None)
|
||||||
flask.session.pop(cas_attributes_session_key, None)
|
flask.session.pop(cas_attributes_session_key, None)
|
||||||
flask.session.pop(cas_token_session_key, None) # added by EV
|
flask.session.pop(cas_token_session_key, None) # added by EV
|
||||||
|
flask.session.pop("CAS_EDT_ID", None) # added by EV
|
||||||
|
|
||||||
cas_after_logout = current_app.config["CAS_AFTER_LOGOUT"]
|
cas_after_logout = current_app.config["CAS_AFTER_LOGOUT"]
|
||||||
if cas_after_logout is not None:
|
if cas_after_logout is not None:
|
||||||
@ -102,7 +104,7 @@ def logout():
|
|||||||
else:
|
else:
|
||||||
redirect_url = create_cas_logout_url(current_app.config["CAS_SERVER"], None)
|
redirect_url = create_cas_logout_url(current_app.config["CAS_SERVER"], None)
|
||||||
|
|
||||||
current_app.logger.debug(f"Redirecting to: {redirect_url}")
|
current_app.logger.debug(f"cas.logout: redirecting to {redirect_url}")
|
||||||
return flask.redirect(redirect_url)
|
return flask.redirect(redirect_url)
|
||||||
|
|
||||||
|
|
||||||
@ -114,11 +116,12 @@ def validate(ticket):
|
|||||||
key `CAS_USERNAME_SESSION_KEY` while the validated attributes dictionary
|
key `CAS_USERNAME_SESSION_KEY` while the validated attributes dictionary
|
||||||
is saved under the key 'CAS_ATTRIBUTES_SESSION_KEY'.
|
is saved under the key 'CAS_ATTRIBUTES_SESSION_KEY'.
|
||||||
"""
|
"""
|
||||||
|
from app.models.config import ScoDocSiteConfig
|
||||||
|
|
||||||
cas_username_session_key = current_app.config["CAS_USERNAME_SESSION_KEY"]
|
cas_username_session_key = current_app.config["CAS_USERNAME_SESSION_KEY"]
|
||||||
cas_attributes_session_key = current_app.config["CAS_ATTRIBUTES_SESSION_KEY"]
|
cas_attributes_session_key = current_app.config["CAS_ATTRIBUTES_SESSION_KEY"]
|
||||||
cas_error_callback = current_app.config.get("CAS_ERROR_CALLBACK")
|
cas_error_callback = current_app.config.get("CAS_ERROR_CALLBACK")
|
||||||
current_app.logger.debug("validating token {0}".format(ticket))
|
current_app.logger.debug(f"validating token {ticket}")
|
||||||
|
|
||||||
cas_validate_url = create_cas_validate_url(
|
cas_validate_url = create_cas_validate_url(
|
||||||
current_app.config["CAS_SERVER"],
|
current_app.config["CAS_SERVER"],
|
||||||
@ -182,7 +185,7 @@ def validate(ticket):
|
|||||||
attributes = xml_from_dict.get("cas:attributes", {})
|
attributes = xml_from_dict.get("cas:attributes", {})
|
||||||
|
|
||||||
if attributes and "cas:memberOf" in attributes:
|
if attributes and "cas:memberOf" in attributes:
|
||||||
if isinstance(attributes["cas:memberOf"], basestring):
|
if isinstance(attributes["cas:memberOf"], str):
|
||||||
attributes["cas:memberOf"] = (
|
attributes["cas:memberOf"] = (
|
||||||
attributes["cas:memberOf"].lstrip("[").rstrip("]").split(",")
|
attributes["cas:memberOf"].lstrip("[").rstrip("]").split(",")
|
||||||
)
|
)
|
||||||
@ -190,6 +193,15 @@ def validate(ticket):
|
|||||||
attributes["cas:memberOf"][group_number] = (
|
attributes["cas:memberOf"][group_number] = (
|
||||||
attributes["cas:memberOf"][group_number].lstrip(" ").rstrip(" ")
|
attributes["cas:memberOf"][group_number].lstrip(" ").rstrip(" ")
|
||||||
)
|
)
|
||||||
|
# Extract auxiliary informations (utilisé pour edt_id)
|
||||||
|
exp = ScoDocSiteConfig.get("cas_edt_id_from_xml_regexp")
|
||||||
|
if exp:
|
||||||
|
m = re.search(exp, xmldump)
|
||||||
|
if m and len(m.groups()) > 0:
|
||||||
|
cas_edt_id = m.group(1)
|
||||||
|
if cas_edt_id:
|
||||||
|
flask.session["CAS_EDT_ID"] = cas_edt_id
|
||||||
|
|
||||||
flask.session[cas_username_session_key] = username
|
flask.session[cas_username_session_key] = username
|
||||||
flask.session[cas_attributes_session_key] = attributes
|
flask.session[cas_attributes_session_key] = attributes
|
||||||
else:
|
else:
|
||||||
|
58
migrations/versions/6fb956addd69_edt_id.py
Normal file
58
migrations/versions/6fb956addd69_edt_id.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"""edt_id
|
||||||
|
|
||||||
|
Revision ID: 6fb956addd69
|
||||||
|
Revises: fd805feb7ba8
|
||||||
|
Create Date: 2023-11-06 12:14:42.808476
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "6fb956addd69"
|
||||||
|
down_revision = "fd805feb7ba8"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
with op.batch_alter_table("group_descr", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("edt_id", sa.Text(), nullable=True))
|
||||||
|
batch_op.create_index(
|
||||||
|
batch_op.f("ix_group_descr_edt_id"), ["edt_id"], unique=False
|
||||||
|
)
|
||||||
|
|
||||||
|
with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("edt_id", sa.Text(), nullable=True))
|
||||||
|
batch_op.create_index(
|
||||||
|
batch_op.f("ix_notes_formsemestre_edt_id"), ["edt_id"], unique=False
|
||||||
|
)
|
||||||
|
|
||||||
|
with op.batch_alter_table("notes_modules", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("edt_id", sa.Text(), nullable=True))
|
||||||
|
batch_op.create_index(
|
||||||
|
batch_op.f("ix_notes_modules_edt_id"), ["edt_id"], unique=False
|
||||||
|
)
|
||||||
|
|
||||||
|
with op.batch_alter_table("user", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("edt_id", sa.Text(), nullable=True))
|
||||||
|
batch_op.create_index(batch_op.f("ix_user_edt_id"), ["edt_id"], unique=False)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
with op.batch_alter_table("user", schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f("ix_user_edt_id"))
|
||||||
|
batch_op.drop_column("edt_id")
|
||||||
|
|
||||||
|
with op.batch_alter_table("notes_modules", schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f("ix_notes_modules_edt_id"))
|
||||||
|
batch_op.drop_column("edt_id")
|
||||||
|
|
||||||
|
with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f("ix_notes_formsemestre_edt_id"))
|
||||||
|
batch_op.drop_column("edt_id")
|
||||||
|
|
||||||
|
with op.batch_alter_table("group_descr", schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f("ix_group_descr_edt_id"))
|
||||||
|
batch_op.drop_column("edt_id")
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.50"
|
SCOVERSION = "9.6.51"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ def usage():
|
|||||||
|
|
||||||
anonymize_name = "random_text_md5(8)"
|
anonymize_name = "random_text_md5(8)"
|
||||||
anonymize_date = "'1970-01-01'"
|
anonymize_date = "'1970-01-01'"
|
||||||
|
anonymize_false = "FALSE"
|
||||||
anonymize_question_str = "'?'"
|
anonymize_question_str = "'?'"
|
||||||
anonymize_null = "NULL"
|
anonymize_null = "NULL"
|
||||||
|
|
||||||
@ -69,13 +70,14 @@ ANONYMIZED_FIELDS = {
|
|||||||
"identite.nom": anonymize_name,
|
"identite.nom": anonymize_name,
|
||||||
"identite.prenom": anonymize_name,
|
"identite.prenom": anonymize_name,
|
||||||
"identite.nom_usuel": anonymize_null,
|
"identite.nom_usuel": anonymize_null,
|
||||||
"identite.civilite": "'X'",
|
"identite.civilite_etat_civil" : anonymize_null,
|
||||||
|
"identite.prenom_etat_civil" : anonymize_null,
|
||||||
"identite.date_naissance": anonymize_date,
|
"identite.date_naissance": anonymize_date,
|
||||||
"identite.lieu_naissance": anonymize_question_str,
|
"identite.lieu_naissance": anonymize_question_str,
|
||||||
"identite.dept_naissance": anonymize_question_str,
|
"identite.dept_naissance": anonymize_question_str,
|
||||||
"identite.nationalite": anonymize_question_str,
|
"identite.nationalite": anonymize_question_str,
|
||||||
"identite.statut": anonymize_null,
|
"identite.statut": anonymize_null,
|
||||||
"identite.boursier": anonymize_null,
|
"identite.boursier": anonymize_false,
|
||||||
"identite.photo_filename": anonymize_null,
|
"identite.photo_filename": anonymize_null,
|
||||||
"identite.code_nip": anonymize_null,
|
"identite.code_nip": anonymize_null,
|
||||||
"identite.code_ine": anonymize_null,
|
"identite.code_ine": anonymize_null,
|
||||||
|
Loading…
Reference in New Issue
Block a user