From e71e4b27ec12a1f122ba66b7e41406e55d45fe41 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 6 Nov 2023 22:05:38 +0100 Subject: [PATCH 1/3] EDT: ajout des edt_id dans formsemestre, groupes, modules, users --- app/auth/models.py | 20 +++- app/models/config.py | 7 +- app/models/formsemestre.py | 2 + app/models/groups.py | 42 ++++++- app/models/modules.py | 4 +- app/scodoc/sco_edt_cal.py | 5 +- app/scodoc/sco_groups.py | 125 ++++----------------- app/scodoc/sco_groups_edit.py | 69 +++++++++++- app/scodoc/sco_groups_view.py | 4 - app/static/css/partition_editor.css | 6 +- app/static/css/scodoc.css | 14 ++- app/templates/scolar/partition_editor.j2 | 13 ++- app/views/scolar.py | 2 +- flask_cas/routing.py | 36 ++++-- migrations/versions/6fb956addd69_edt_id.py | 58 ++++++++++ 15 files changed, 259 insertions(+), 148 deletions(-) create mode 100644 migrations/versions/6fb956addd69_edt_id.py diff --git a/app/auth/models.py b/app/auth/models.py index 1330b9b7..899a5ab2 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -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.""" diff --git a/app/models/config.py b/app/models/config.py index 60ce884b..fe76bec8 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -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 diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 1d99342c..d020eb92 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -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( diff --git a/app/models/groups.py b/app/models/groups.py index a4a5792f..8d445eee 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -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: diff --git a/app/models/modules.py b/app/models/modules.py index b4aa00ad..fe297ebd 100644 --- a/app/models/modules.py +++ b/app/models/modules.py @@ -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: diff --git a/app/scodoc/sco_edt_cal.py b/app/scodoc/sco_edt_cal.py index 613428ed..2174f258 100644 --- a/app/scodoc/sco_edt_cal.py +++ b/app/scodoc/sco_edt_cal.py @@ -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 diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 6a4d77e8..46b650b7 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -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"""{suppricon} """ ) @@ -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"

Renommer un groupe de {group.partition.partition_name or '-'}

"] - 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 diff --git a/app/scodoc/sco_groups_edit.py b/app/scodoc/sco_groups_edit.py index 4fd1e103..0a9173ec 100644 --- a/app/scodoc/sco_groups_edit.py +++ b/app/scodoc/sco_groups_edit.py @@ -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"

Renommer un groupe de {group.partition.partition_name or '-'}

"] + 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) diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index c44e3c0c..2f20b6ce 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -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 diff --git a/app/static/css/partition_editor.css b/app/static/css/partition_editor.css index 09f042fe..52576891 100644 --- a/app/static/css/partition_editor.css +++ b/app/static/css/partition_editor.css @@ -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; -} \ No newline at end of file +} diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index c31d0de6..e5db153a 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -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); -} \ No newline at end of file +} + +.edt_id { + color: rgb(85, 255, 24); +} diff --git a/app/templates/scolar/partition_editor.j2 b/app/templates/scolar/partition_editor.j2 index 4aa03fc2..23ddc686 100644 --- a/app/templates/scolar/partition_editor.j2 +++ b/app/templates/scolar/partition_editor.j2 @@ -8,7 +8,8 @@

Filtres

@@ -212,7 +213,7 @@
+
- +
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 ? `[${groupe.edt_id}]` : ""; div.innerHTML = ` || - ${groupe.group_name} - ✏️ + ${groupe.group_name} ${edt_id_str} + ✏️ `; 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 @@ } - \ No newline at end of file + diff --git a/app/views/scolar.py b/app/views/scolar.py index 0388c4c2..ac908ce7 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -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"], ) diff --git a/flask_cas/routing.py b/flask_cas/routing.py index ec9eef63..6078b79c 100644 --- a/flask_cas/routing.py +++ b/flask_cas/routing.py @@ -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: diff --git a/migrations/versions/6fb956addd69_edt_id.py b/migrations/versions/6fb956addd69_edt_id.py new file mode 100644 index 00000000..801ba62b --- /dev/null +++ b/migrations/versions/6fb956addd69_edt_id.py @@ -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") From 2c209313c6af89acb9c7488b2834e52a5212c415 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Nov 2023 12:51:24 +0100 Subject: [PATCH 2/3] Corrige script anonymisation (qui violait nouvelles contraintes sur la base) --- sco_version.py | 2 +- tools/anonymize_db.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sco_version.py b/sco_version.py index 3fa1a685..7771d1df 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.6.50" +SCOVERSION = "9.6.51" SCONAME = "ScoDoc" diff --git a/tools/anonymize_db.py b/tools/anonymize_db.py index 1d95a11b..cc7a5a04 100755 --- a/tools/anonymize_db.py +++ b/tools/anonymize_db.py @@ -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, From 37f86f02cd1fe2e3655fc3b80071b0dcb929e490 Mon Sep 17 00:00:00 2001 From: Iziram Date: Wed, 8 Nov 2023 15:09:49 +0100 Subject: [PATCH 3/3] =?UTF-8?q?Assiduit=C3=A9=20:=20bug=20fix=20saisie=20j?= =?UTF-8?q?our=20readOnly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/js/assiduites.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index 90ed21d6..d11b5df4 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -84,7 +84,7 @@ function validateSelectors(btn) { ); }); - if (getModuleImplId() == null && window.forceModule) { + if (getModuleImplId() == null && window.forceModule && !readOnly) { const HTML = `

Attention, le module doit obligatoirement être renseigné.

Cela vient de la configuration du semestre ou plus largement du département.