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 flask
|
||||
from flask import current_app, g
|
||||
from flask_login import UserMixin, AnonymousUserMixin
|
||||
|
||||
@ -88,7 +89,8 @@ class User(UserMixin, db.Model):
|
||||
"""
|
||||
cas_last_login = db.Column(db.DateTime, nullable=True)
|
||||
"""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_scodoc7 = db.Column(db.String(42))
|
||||
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
@ -172,7 +174,8 @@ class User(UserMixin, db.Model):
|
||||
return False
|
||||
|
||||
# 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:
|
||||
return False
|
||||
|
||||
@ -182,7 +185,18 @@ class User(UserMixin, db.Model):
|
||||
return self._migrate_scodoc7_password(password)
|
||||
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:
|
||||
"""After migration, rehash password."""
|
||||
|
@ -4,8 +4,8 @@
|
||||
"""
|
||||
|
||||
import json
|
||||
import urllib.parse
|
||||
import re
|
||||
import urllib.parse
|
||||
|
||||
from flask import flash
|
||||
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 import sco_utils as scu
|
||||
|
||||
from datetime import time
|
||||
|
||||
from app.scodoc.codes_cursus import (
|
||||
ABAN,
|
||||
ABL,
|
||||
@ -105,6 +103,7 @@ class ScoDocSiteConfig(db.Model):
|
||||
"cas_validate_route": str,
|
||||
"cas_attribute_id": str,
|
||||
"cas_uid_from_mail_regexp": str,
|
||||
"cas_edt_id_from_xml_regexp": str,
|
||||
# Assiduité
|
||||
"morning_time": str,
|
||||
"lunch_time": str,
|
||||
@ -174,7 +173,7 @@ class ScoDocSiteConfig(db.Model):
|
||||
klass = bonus_spo.get_bonus_class_dict().get(class_name)
|
||||
if klass is None:
|
||||
flash(
|
||||
f"""Fonction de calcul bonus sport inexistante: {class_name}.
|
||||
f"""Fonction de calcul bonus sport inexistante: {class_name}.
|
||||
Changez là ou contactez votre administrateur local."""
|
||||
)
|
||||
return klass
|
||||
|
@ -64,6 +64,8 @@ class FormSemestre(db.Model):
|
||||
titre = db.Column(db.Text(), nullable=False)
|
||||
date_debut = 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")
|
||||
"False si verrouillé"
|
||||
modalite = db.Column(
|
||||
|
@ -180,7 +180,7 @@ class Partition(db.Model):
|
||||
"Crée un groupe dans cette partition"
|
||||
if not self.formsemestre.can_change_groups():
|
||||
raise AccessDenied(
|
||||
"""Vous n'avez pas le droit d'effectuer cette opération,
|
||||
"""Vous n'avez pas le droit d'effectuer cette opération,
|
||||
ou bien le semestre est verrouillé !"""
|
||||
)
|
||||
if group_name:
|
||||
@ -213,10 +213,12 @@ class GroupDescr(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
group_id = db.synonym("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))
|
||||
# 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 = ordre de presentation"
|
||||
|
||||
etuds = db.relationship(
|
||||
"Identite",
|
||||
@ -272,6 +274,40 @@ class GroupDescr(db.Model):
|
||||
return False
|
||||
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"):
|
||||
"Enlève l'étudiant de ce groupe s'il en fait partie (ne fait rien sinon)"
|
||||
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
|
||||
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
|
||||
# id de l'element pedagogique Apogee correspondant:
|
||||
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)
|
||||
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
|
||||
# Relations:
|
||||
|
@ -34,16 +34,13 @@ XXX incompatible avec les ics HyperPlanning Paris 13 (était pour GPU).
|
||||
"""
|
||||
|
||||
import icalendar
|
||||
import pprint
|
||||
import traceback
|
||||
|
||||
import urllib
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_view
|
||||
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.res_compat import NotesTableCompat
|
||||
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
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -136,7 +136,7 @@ def get_partitions_list(formsemestre_id, with_default=True) -> list[dict]:
|
||||
partitions = ndb.SimpleDictFetch(
|
||||
"""SELECT p.id AS partition_id, p.*
|
||||
FROM partition p
|
||||
WHERE formsemestre_id=%(formsemestre_id)s
|
||||
WHERE formsemestre_id=%(formsemestre_id)s
|
||||
ORDER BY numero""",
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
@ -258,14 +258,14 @@ def get_group_members(group_id, etat=None):
|
||||
Trié par nom_usuel (ou nom) puis prénom
|
||||
"""
|
||||
req = """SELECT i.id as etudid, i.*, a.*, gm.*, ins.etat
|
||||
FROM identite i, adresse a, group_membership gm,
|
||||
group_descr gd, partition p, notes_formsemestre_inscription ins
|
||||
WHERE i.id = gm.etudid
|
||||
and a.etudid = i.id
|
||||
and ins.etudid = i.id
|
||||
and ins.formsemestre_id = p.formsemestre_id
|
||||
and p.id = gd.partition_id
|
||||
and gd.id = gm.group_id
|
||||
FROM identite i, adresse a, group_membership gm,
|
||||
group_descr gd, partition p, notes_formsemestre_inscription ins
|
||||
WHERE i.id = gm.etudid
|
||||
and a.etudid = i.id
|
||||
and ins.etudid = i.id
|
||||
and ins.formsemestre_id = p.formsemestre_id
|
||||
and p.id = gd.partition_id
|
||||
and gd.id = gm.group_id
|
||||
and gm.group_id=%(group_id)s
|
||||
"""
|
||||
if etat is not None:
|
||||
@ -350,12 +350,12 @@ def get_etud_groups(etudid: int, formsemestre_id: int, exclude_default=False):
|
||||
"""Infos sur groupes de l'etudiant dans ce semestre
|
||||
[ group + partition_name ]
|
||||
"""
|
||||
req = """SELECT p.id AS partition_id, p.*,
|
||||
req = """SELECT p.id AS partition_id, p.*,
|
||||
g.id AS group_id, g.numero as group_numero, g.group_name
|
||||
FROM group_descr g, partition p, group_membership gm
|
||||
WHERE gm.etudid=%(etudid)s
|
||||
and gm.group_id = g.id
|
||||
and g.partition_id = p.id
|
||||
FROM group_descr g, partition p, group_membership gm
|
||||
WHERE gm.etudid=%(etudid)s
|
||||
and gm.group_id = g.id
|
||||
and g.partition_id = p.id
|
||||
and p.formsemestre_id = %(formsemestre_id)s
|
||||
"""
|
||||
if exclude_default:
|
||||
@ -393,7 +393,7 @@ def formsemestre_get_etud_groupnames(formsemestre_id, attr="group_name"):
|
||||
p.id AS partition_id,
|
||||
gd.group_name,
|
||||
gd.id AS group_id
|
||||
FROM
|
||||
FROM
|
||||
notes_formsemestre_inscription i,
|
||||
partition p,
|
||||
group_descr gd,
|
||||
@ -967,8 +967,8 @@ def edit_partition_form(formsemestre_id=None):
|
||||
for p in partitions:
|
||||
if p["partition_name"] is not None:
|
||||
H.append(
|
||||
f"""<tr><td class="epnav"><a class="stdlink"
|
||||
href="{url_for("scolar.partition_delete",
|
||||
f"""<tr><td class="epnav"><a class="stdlink"
|
||||
href="{url_for("scolar.partition_delete",
|
||||
scodoc_dept=g.scodoc_dept, partition_id=p["partition_id"])
|
||||
}">{suppricon}</a> </td><td class="epnav">"""
|
||||
)
|
||||
@ -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):
|
||||
"""Réparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
||||
et la mixité.
|
||||
@ -1570,7 +1491,7 @@ def do_evaluation_listeetuds_groups(
|
||||
return [] # no groups, so no students
|
||||
rg = ["gm.group_id = '%(group_id)s'" % g for g in groups]
|
||||
rq = """and Isem.etudid = gm.etudid
|
||||
and gd.partition_id = p.id
|
||||
and gd.partition_id = p.id
|
||||
and p.formsemestre_id = Isem.formsemestre_id
|
||||
"""
|
||||
r = rq + " AND (" + " or ".join(rg) + " )"
|
||||
@ -1583,9 +1504,9 @@ def do_evaluation_listeetuds_groups(
|
||||
"SELECT distinct Im.etudid, Isem.etat FROM "
|
||||
+ ", ".join(fromtables)
|
||||
+ """ WHERE Isem.etudid = Im.etudid
|
||||
and Im.moduleimpl_id = M.id
|
||||
and Isem.formsemestre_id = M.formsemestre_id
|
||||
and E.moduleimpl_id = M.id
|
||||
and Im.moduleimpl_id = M.id
|
||||
and Isem.formsemestre_id = M.formsemestre_id
|
||||
and E.moduleimpl_id = M.id
|
||||
and E.id = %(evaluation_id)s
|
||||
"""
|
||||
)
|
||||
@ -1612,7 +1533,7 @@ def do_evaluation_listegroupes(evaluation_id, include_default=False):
|
||||
cursor = cnx.cursor()
|
||||
cursor.execute(
|
||||
"""SELECT DISTINCT gd.id AS group_id
|
||||
FROM group_descr gd, group_membership gm, partition p,
|
||||
FROM group_descr gd, group_membership gm, partition p,
|
||||
notes_moduleimpl m, notes_evaluation e
|
||||
WHERE gm.group_id = gd.id
|
||||
and gd.partition_id = p.id
|
||||
|
@ -27,11 +27,15 @@
|
||||
|
||||
"""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.sco_exceptions import AccessDenied
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
|
||||
|
||||
def affect_groups(partition_id):
|
||||
@ -59,3 +63,64 @@ def affect_groups(partition_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)
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import urllib
|
||||
from urllib.parse import parse_qs
|
||||
import time
|
||||
|
||||
|
||||
from flask import url_for, g, request
|
||||
@ -45,7 +42,6 @@ from app import db
|
||||
from app.models import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cal
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
|
@ -302,6 +302,10 @@ body.editionActivated .filtres>div>div>div>div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#zonePartitions span.editing a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.editionActivated #zonePartitions .filtres .config {
|
||||
display: block;
|
||||
}
|
||||
@ -598,4 +602,4 @@ h3 {
|
||||
|
||||
#zoneGroupes .groupe[data-idgroupe=aucun]>div:nth-child(1) {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
@ -1327,7 +1327,7 @@ table.gt_table tr.etuddem td a {
|
||||
table.gt_table tr.etuddem td.etudinfo:first-child::after {
|
||||
color: red;
|
||||
content: " (dém.)";
|
||||
}
|
||||
}
|
||||
|
||||
td.etudabs,
|
||||
td.etudabs a.discretelink,
|
||||
@ -3921,9 +3921,9 @@ div#update_warning>div:nth-child(2) {
|
||||
padding-left: 8ex;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
Titres des tabs:
|
||||
.nav-tabs li a {
|
||||
.nav-tabs li a {
|
||||
font-variant: small-caps;
|
||||
font-size: 13pt;
|
||||
}
|
||||
@ -4354,7 +4354,7 @@ button.unselect {
|
||||
|
||||
/* Non supproté par les navigateurs (en Fev. 2023)
|
||||
.table_recap button:has(span a.clearreaload) {
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
div.table_recap table.table_recap,
|
||||
@ -4833,4 +4833,8 @@ div.cas_etat_certif_ssl {
|
||||
margin-bottom: 8px;
|
||||
font-style: italic;
|
||||
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 = `
|
||||
<p>Attention, le module doit obligatoirement être renseigné.</p>
|
||||
<p>Cela vient de la configuration du semestre ou plus largement du département.</p>
|
||||
|
@ -8,7 +8,8 @@
|
||||
<h2>Filtres</h2>
|
||||
<div>
|
||||
<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
|
||||
</label>
|
||||
<div class="filtres"></div>
|
||||
@ -212,7 +213,7 @@
|
||||
</button>
|
||||
<div class="editing ajoutGroupe">+</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Config -->
|
||||
<div class=config>
|
||||
Configuration
|
||||
@ -246,15 +247,15 @@
|
||||
let div = document.createElement("button");
|
||||
div.classList.add("dt-button");
|
||||
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 = `
|
||||
<span class="editing move">||</span>
|
||||
<span>${groupe.group_name}</span>
|
||||
<span class="editing modif">✏️</span>
|
||||
<span>${groupe.group_name} ${edt_id_str}</span>
|
||||
<span class="editing"><a href="/ScoDoc/{{formsemestre.departement.acronym}}/Scolarite/group_rename?group_id=${groupe.id}">✏️</a></span>
|
||||
<span class="editing suppr">❌</span>`;
|
||||
|
||||
div.addEventListener("click", filtre);
|
||||
div.querySelector(".move").addEventListener("mousedown", moveStart);
|
||||
div.querySelector(".modif").addEventListener("click", editText);
|
||||
div.querySelector(".suppr").addEventListener("click", suppr);
|
||||
|
||||
return div;
|
||||
@ -945,4 +946,4 @@
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
@ -842,7 +842,7 @@ sco_publish("/setGroups", sco_groups.setGroups, Permission.ScoView, methods=["PO
|
||||
|
||||
sco_publish(
|
||||
"/group_rename",
|
||||
sco_groups.group_rename,
|
||||
sco_groups_edit.group_rename,
|
||||
Permission.ScoView,
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
|
@ -1,19 +1,21 @@
|
||||
"""
|
||||
Routes for CAS authentication
|
||||
Modified for ScoDoc
|
||||
"""
|
||||
import re
|
||||
import ssl
|
||||
from urllib.error import URLError
|
||||
from urllib.request import urlopen
|
||||
|
||||
import flask
|
||||
from xmltodict import parse
|
||||
from flask import current_app
|
||||
from xmltodict import parse
|
||||
|
||||
from .cas_urls import create_cas_login_url
|
||||
from .cas_urls import create_cas_logout_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__)
|
||||
|
||||
|
||||
@ -53,7 +55,6 @@ def login():
|
||||
flask.session[cas_token_session_key] = flask.request.args["ticket"]
|
||||
|
||||
if cas_token_session_key in flask.session:
|
||||
|
||||
if validate(flask.session[cas_token_session_key]):
|
||||
if "CAS_AFTER_LOGIN_SESSION_URL" in flask.session:
|
||||
redirect_url = flask.session.pop("CAS_AFTER_LOGIN_SESSION_URL")
|
||||
@ -64,7 +65,7 @@ def login():
|
||||
else:
|
||||
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)
|
||||
|
||||
@ -84,6 +85,7 @@ def logout():
|
||||
flask.session.pop(cas_username_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_EDT_ID", None) # added by EV
|
||||
|
||||
cas_after_logout = current_app.config["CAS_AFTER_LOGOUT"]
|
||||
if cas_after_logout is not None:
|
||||
@ -102,7 +104,7 @@ def logout():
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
@ -114,11 +116,12 @@ def validate(ticket):
|
||||
key `CAS_USERNAME_SESSION_KEY` while the validated attributes dictionary
|
||||
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_attributes_session_key = current_app.config["CAS_ATTRIBUTES_SESSION_KEY"]
|
||||
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(
|
||||
current_app.config["CAS_SERVER"],
|
||||
@ -182,7 +185,7 @@ def validate(ticket):
|
||||
attributes = xml_from_dict.get("cas: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"].lstrip("[").rstrip("]").split(",")
|
||||
)
|
||||
@ -190,6 +193,15 @@ def validate(ticket):
|
||||
attributes["cas:memberOf"][group_number] = (
|
||||
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_attributes_session_key] = attributes
|
||||
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 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.50"
|
||||
SCOVERSION = "9.6.51"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
@ -55,6 +55,7 @@ def usage():
|
||||
|
||||
anonymize_name = "random_text_md5(8)"
|
||||
anonymize_date = "'1970-01-01'"
|
||||
anonymize_false = "FALSE"
|
||||
anonymize_question_str = "'?'"
|
||||
anonymize_null = "NULL"
|
||||
|
||||
@ -69,13 +70,14 @@ ANONYMIZED_FIELDS = {
|
||||
"identite.nom": anonymize_name,
|
||||
"identite.prenom": anonymize_name,
|
||||
"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.lieu_naissance": anonymize_question_str,
|
||||
"identite.dept_naissance": anonymize_question_str,
|
||||
"identite.nationalite": anonymize_question_str,
|
||||
"identite.statut": anonymize_null,
|
||||
"identite.boursier": anonymize_null,
|
||||
"identite.boursier": anonymize_false,
|
||||
"identite.photo_filename": anonymize_null,
|
||||
"identite.code_nip": anonymize_null,
|
||||
"identite.code_ine": anonymize_null,
|
||||
|
Loading…
Reference in New Issue
Block a user