diff --git a/app/but/cursus_but.py b/app/but/cursus_but.py
index d55d1e88f..efd375217 100644
--- a/app/but/cursus_but.py
+++ b/app/but/cursus_but.py
@@ -563,7 +563,8 @@ def formation_semestre_niveaux_warning(formation: Formation, semestre_idx: int)
if nb_niveaux_tc != nb_ues_tc:
H.append(
f"""
{nb_niveaux_tc} niveaux de compétences de tronc commun,
- mais {nb_ues_tc} UEs de tronc commun !"""
+ mais {nb_ues_tc} UEs de tronc commun ! (c'est normal si
+ vous avez des UEs différenciées par parcours)"""
)
if H:
diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py
index 02eba72f8..a2edbd946 100644
--- a/app/scodoc/sco_dept.py
+++ b/app/scodoc/sco_dept.py
@@ -208,6 +208,8 @@ def index_html(showcodes=0, showsemtable=0):
"""
Assistance
"""
diff --git a/sco_version.py b/sco_version.py
index 41ebc3033..960e79a53 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.6.949"
+SCOVERSION = "9.6.950"
SCONAME = "ScoDoc"
diff --git a/tools/anonymize_db.py b/tools/anonymize_db.py
index e537d43f0..b2583faec 100755
--- a/tools/anonymize_db.py
+++ b/tools/anonymize_db.py
@@ -30,12 +30,15 @@
Runned as user "scodoc" with scodoc and postgresql up.
-E. Viennet, Jan 2019
-"""
+Travaille entièrement au niveau SQL, n'utilise aucun modèle SQLAlchemy.
-import psycopg2
+E. Viennet, Jan 2019, Fev 2024
+"""
+import random
import sys
import traceback
+import psycopg2
+from psycopg2 import extras
def log(msg):
@@ -59,9 +62,21 @@ anonymize_false = "FALSE"
anonymize_question_str = "'?'"
anonymize_null = "NULL"
+# --- Listes de noms et prénoms pour remplacer les identités
+NOMS = [
+ x.strip()
+ for x in open("/opt/scodoc/tools/fakeportal/nomsprenoms/noms.txt", encoding="utf8")
+]
+PRENOMS = [
+ x.strip()
+ for x in open(
+ "/opt/scodoc/tools/fakeportal/nomsprenoms/prenoms.txt", encoding="utf8"
+ )
+]
+
# --- Champs à anonymiser (cette configuration pourrait être placé dans
# un fichier séparé et le code serait alors générique pour toute base
-# posgresql.
+# postgresql.
#
# On essaie de retirer les données personnelles des étudiants et des entreprises
#
@@ -111,6 +126,26 @@ def anonymize_column(cursor, tablecolumn):
cursor.execute(f"UPDATE {table} SET {column} = {anonymized};")
+def rename_students(cursor):
+ """Remet des noms/prenoms fictifs aux étuduiants"""
+ # Change les noms/prenoms
+ cursor.execute("""SELECT * FROM "identite";""")
+ etuds = cursor.fetchall()
+ for etud in etuds:
+ nom, prenom = random.choice(NOMS), random.choice(PRENOMS)
+ cursor.execute(
+ """UPDATE "identite"
+ SET nom=%(nom)s, prenom=%(prenom)s
+ WHERE id=%(id)s
+ """,
+ {
+ "id": etud["id"],
+ "nom": nom,
+ "prenom": prenom,
+ },
+ )
+
+
def anonymize_users(cursor):
"""Anonymise la table utilisateurs"""
log("processing user table")
@@ -121,8 +156,51 @@ def anonymize_users(cursor):
cursor.execute("""UPDATE "user" SET date_expiration = '2201-12-31';""")
cursor.execute("""UPDATE "user" SET token = NULL;""")
cursor.execute("""UPDATE "user" SET token_expiration = NULL;""")
- cursor.execute("""UPDATE "user" SET nom=CONCAT('nom_', id);""")
- cursor.execute("""UPDATE "user" SET prenom=CONCAT('nom_', id);""")
+ # Change les noms/prenoms/mail
+ cursor.execute("""SELECT * FROM "user";""")
+ users = cursor.fetchall() # fetch tout car modifie cette table ds la boucle
+ used_user_names = {u["user_name"] for u in users}
+ for user in users:
+ user_name = user["user_name"]
+ nom, prenom = random.choice(NOMS), random.choice(PRENOMS)
+ new_name = (prenom[0] + nom).lower()
+ # unique ?
+ while new_name in used_user_names:
+ new_name += "x"
+ used_user_names.add(new_name)
+ print(f"{user_name} > {new_name}")
+ cursor.execute(
+ """UPDATE "user"
+ SET nom=%(nom)s, prenom=%(prenom)s, email=%(email)s, user_name=%(new_name)s
+ WHERE id=%(id)s
+ """,
+ {
+ "email": f"{prenom}.{nom}@ano.nyme",
+ "id": user["id"],
+ "nom": nom,
+ "prenom": prenom,
+ "new_name": new_name,
+ },
+ )
+ # Change les username: utilisés en référence externe
+ # dans diverses tables:
+ for table, field in (
+ ("etud_annotations", "author"),
+ ("scolog", "authenticated_user"),
+ ("scolar_news", "authenticated_user"),
+ ("notes_appreciations", "author"),
+ ("are_historique", "authenticated_user"),
+ ):
+ cursor.execute(
+ f"""UPDATE "{table}"
+ SET {field}=%(new_name)s
+ WHERE {field}=%(user_name)s
+ """,
+ {
+ "new_name": new_name,
+ "user_name": user_name,
+ },
+ )
def anonymize_db(cursor):
@@ -131,32 +209,33 @@ def anonymize_db(cursor):
anonymize_column(cursor, tablecolumn)
-process_users = False
-if len(sys.argv) < 2 or len(sys.argv) > 3:
- usage()
-if len(sys.argv) > 2:
- if sys.argv[1] != "--users":
+if __name__ == "__main__":
+ PROCESS_USERS = False
+ if len(sys.argv) < 2 or len(sys.argv) > 3:
usage()
- dbname = sys.argv[2]
- process_users = True
-else:
- dbname = sys.argv[1]
+ if len(sys.argv) > 2:
+ if sys.argv[1] != "--users":
+ usage()
+ dbname = sys.argv[2]
+ PROCESS_USERS = True
+ else:
+ dbname = sys.argv[1]
-log(f"\nAnonymizing database {dbname}")
-cnx_string = "dbname=" + dbname
-try:
- cnx = psycopg2.connect(cnx_string)
-except Exception as e:
- log(f"\n*** Error: can't connect to database {dbname} ***\n")
- log(f"""connexion string was "{cnx_string}" """)
- traceback.print_exc()
+ log(f"\nAnonymizing database {dbname}")
+ cnx_string = "dbname=" + dbname
+ try:
+ cnx = psycopg2.connect(cnx_string)
+ except Exception as e:
+ log(f"\n*** Error: can't connect to database {dbname} ***\n")
+ log(f"""connexion string was "{cnx_string}" """)
+ traceback.print_exc()
-cnx.set_session(autocommit=False)
-cursor = cnx.cursor()
+ cnx.set_session(autocommit=False)
+ cursor = cnx.cursor(cursor_factory=psycopg2.extras.DictCursor)
-anonymize_db(cursor)
-if process_users:
- anonymize_users(cursor)
+ anonymize_db(cursor)
+ if PROCESS_USERS:
+ anonymize_users(cursor)
-cnx.commit()
-cnx.close()
+ cnx.commit()
+ cnx.close()