diff --git a/ImportScolars.py b/ImportScolars.py
index 8713ac3cd..8a417632b 100644
--- a/ImportScolars.py
+++ b/ImportScolars.py
@@ -28,10 +28,15 @@
 """ Importation des etudiants à partir de fichiers CSV
 """
 
-import os, sys, time, pdb
+import os
+import sys
+import time
+import pdb
+import collections
+import types
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 from notes_log import log
 import scolars
 import sco_formsemestre
@@ -42,6 +47,15 @@ import sco_news
 from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC
 from sco_formsemestre_inscriptions import do_formsemestre_inscription_with_modules
 from gen_tables import GenTable
+from sco_exceptions import (
+    AccessDenied,
+    FormatError,
+    ScoException,
+    ScoValueError,
+    ScoInvalidDateError,
+    ScoLockedFormError,
+    ScoGenError,
+)
 
 # format description (relative to Product directory))
 FORMAT_FILE = "misc/format_import_etudiants.txt"
@@ -249,7 +263,7 @@ def scolars_import_excel_file(
     """
     log("scolars_import_excel_file: formsemestre_id=%s" % formsemestre_id)
     cnx = context.GetDBConnexion(autocommit=False)
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     annee_courante = time.localtime()[0]
     always_require_ine = context.get_preference("always_require_ine")
     exceldata = datafile.read()
@@ -423,7 +437,7 @@ def scolars_import_excel_file(
         log("scolars_import_excel_file: aborting transaction !")
         # Nota: db transaction is sometimes partly commited...
         # here we try to remove all created students
-        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
         for etudid in created_etudids:
             log("scolars_import_excel_file: deleting etudid=%s" % etudid)
             cursor.execute(
@@ -729,19 +743,19 @@ def adm_get_fields(titles, formsemestre_id):
 
 
 def adm_convert_text(v):
-    if type(v) == FloatType:
+    if type(v) == types.FloatType:
         return "{:g}".format(v)  # evite "1.0"
     return v
 
 
 def adm_convert_int(v):
-    if type(v) != IntType and not v:
+    if type(v) != types.IntType and not v:
         return None
     return int(float(v))  # accept "10.0"
 
 
 def adm_convert_real(v):
-    if type(v) != FloatType and not v:
+    if type(v) != types.FloatType and not v:
         return None
     return float(v)
 
diff --git a/ZScoUsers.py b/ZScoUsers.py
index 9e9dd9246..64355f434 100644
--- a/ZScoUsers.py
+++ b/ZScoUsers.py
@@ -27,28 +27,45 @@
 
 """ Gestion des utilisateurs (table SQL pour Zope User Folder)
 """
-import string, re
+import string
+import re
 import time
-import md5, base64
+import md5
+import base64
+import jaxml
 
-
-from sco_zope import *
+from sco_zope import *  # pylint: disable=unused-wildcard-import
 
 # ---------------
-
-import notesdb
-from notesdb import *
+import sco_utils as scu
+import notesdb as ndb
 from notes_log import log
 from scolog import logdb
-from sco_utils import *
 from scolars import format_prenom, format_nom
-import sco_import_users, sco_excel
-from TrivialFormulator import TrivialFormulator, TF
+import sco_import_users
+import sco_excel
+from TrivialFormulator import TrivialFormulator, TF, tf_error_message
 from gen_tables import GenTable
 import scolars
 import sco_cache
 import sco_users
-
+from sco_permissions import (
+    ScoEditAllEvals,
+    ScoEditAllNotes,
+    ScoImplement,
+    ScoSuperAdmin,
+    ScoUsersAdmin,
+    ScoUsersView,
+    ScoView,
+)
+from sco_exceptions import (
+    AccessDenied,
+    ScoException,
+    ScoValueError,
+    ScoInvalidDateError,
+    ScoLockedFormError,
+    ScoGenError,
+)
 
 # ---------------
 # cache global: chaque instance,  repérée par son URL, a un cache
@@ -93,7 +110,7 @@ class ZScoUsers(
     # Ugly but necessary during transition out of Zope:
     _db_cnx_string = "dbname=SCOUSERS port=5432"
     security.declareProtected("Change DTML Documents", "GetUsersDBConnexion")
-    GetUsersDBConnexion = notesdb.GetUsersDBConnexion
+    GetUsersDBConnexion = ndb.GetUsersDBConnexion
 
     # --------------------------------------------------------------------
     #
@@ -162,7 +179,7 @@ class ZScoUsers(
         F = self.sco_footer(REQUEST)
         return "\n".join(H) + F
 
-    _userEditor = EditableTable(
+    _userEditor = ndb.EditableTable(
         "sco_users",
         "user_id",
         (
@@ -180,12 +197,12 @@ class ZScoUsers(
             "date_expiration",
         ),
         output_formators={
-            "date_modif_passwd": DateISOtoDMY,
-            "date_expiration": DateISOtoDMY,
+            "date_modif_passwd": ndb.DateISOtoDMY,
+            "date_expiration": ndb.DateISOtoDMY,
         },
         input_formators={
-            "date_modif_passwd": DateDMYtoISO,
-            "date_expiration": DateDMYtoISO,
+            "date_modif_passwd": ndb.DateDMYtoISO,
+            "date_expiration": ndb.DateDMYtoISO,
         },
         sortkey="nom",
         filter_nulls=False,
@@ -217,12 +234,12 @@ class ZScoUsers(
         u = self._user_list(args={"user_name": user_name})[0]
         if u["status"] == "old" and u["roles"] and u["roles"][0] != "-":
             roles = ["-" + r for r in u["roles"].split(",")]
-            cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+            cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
             self.acl_users.scodoc_editUser(cursor, user_name, roles=roles)
             self.get_userlist_cache().inval_cache()
         elif not u["status"] and u["roles"] and u["roles"][0] == "-":
             roles = [r[1:] for r in u["roles"].split(",") if (r and r[0] == "-")]
-            cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+            cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
             self.acl_users.scodoc_editUser(cursor, user_name, roles=roles)
             self.get_userlist_cache().inval_cache()
 
@@ -268,7 +285,7 @@ class ZScoUsers(
                 "nom_fmt": user_name,
                 "nomcomplet": user_name,
                 "nomplogin": user_name,
-                "nomnoacc": suppress_accents(user_name),
+                "nomnoacc": scu.suppress_accents(user_name),
                 "passwd_temp": 0,
                 "status": "",
                 "date_expiration": None,
@@ -289,7 +306,7 @@ class ZScoUsers(
             else:
                 n = user_name
 
-            prenom_abbrv = abbrev_prenom(p)
+            prenom_abbrv = scu.abbrev_prenom(p)
             # nomprenom est le nom capitalisé suivi de l'initiale du prénom
             info["nomprenom"] = (n + " " + prenom_abbrv).strip()
             # prenomnom est l'initiale du prénom suivie du nom
@@ -301,9 +318,9 @@ class ZScoUsers(
             info["nomcomplet"] = info["prenom_fmt"] + " " + info["nom_fmt"]
             # nomplogin est le nom en majuscules suivi du prénom et du login
             # e.g. Dupont Pierre (dupont)
-            info["nomplogin"] = "%s %s (%s)" % (strupper(n), p, info["user_name"])
+            info["nomplogin"] = "%s %s (%s)" % (scu.strupper(n), p, info["user_name"])
             # nomnoacc est le nom en minuscules sans accents
-            info["nomnoacc"] = suppress_accents(strlower(info["nom"]))
+            info["nomnoacc"] = scu.suppress_accents(scu.strlower(info["nom"]))
 
             return info
 
@@ -346,7 +363,7 @@ class ZScoUsers(
         assert len(user) == 1, "database inconsistency: len(user)=%d" % len(user)
         # should not occur, already tested in _can_handle_passwd
         cnx = self.GetUsersDBConnexion()  # en mode autocommit
-        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
         cursor.execute(
             "update sco_users set date_modif_passwd=now(), passwd_temp=0 where user_name=%(user_name)s",
             {"user_name": user_name},
@@ -417,7 +434,7 @@ class ZScoUsers(
 <meta http-equiv="Content-Type" content="text/html; charset=%s" />
 <body><h1>Mot de passe changé !</h1>
 """
-                    % (SCO_ENCODING, SCO_ENCODING)
+                    % (scu.SCO_ENCODING, scu.SCO_ENCODING)
                     + "\n".join(H)
                     + '<a href="%s"  class="stdlink">Continuer</a></body></html>'
                     % self.ScoURL()
@@ -536,7 +553,7 @@ class ZScoUsers(
                 )
             else:
                 for p in scoperms:
-                    permname, value = p[:2]
+                    permname, _ = p[:2]
                     if thisuser.has_permission(permname, self):
                         b = "oui"
                     else:
@@ -903,7 +920,7 @@ class ZScoUsers(
 
         # Des noms/prénoms semblables existent ?
         cnx = self.GetUsersDBConnexion()
-        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
         cursor.execute(
             "select * from sco_users where lower(nom) ~ %(nom)s and lower(prenom) ~ %(prenom)s;",
             {"nom": nom.lower().strip(), "prenom": prenom.lower().strip()},
@@ -1004,13 +1021,13 @@ class ZScoUsers(
     def create_user(self, args, REQUEST=None):
         "creation utilisateur zope"
         cnx = self.GetUsersDBConnexion()
-        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
         passwd = args["passwd"]
         args["passwd"] = "undefined"
         if "passwd2" in args:
             del args["passwd2"]
         log("create_user: args=%s" % args)  # log apres supr. du mot de passe !
-        r = self._userEditor.create(cnx, args)
+        _ = self._userEditor.create(cnx, args)
         self.get_userlist_cache().inval_cache()  # >
 
         # call exUserFolder to set passwd
@@ -1100,15 +1117,15 @@ class ZScoUsers(
 
             # Convert dates to ISO if XML output
             if format == "xml" and u["date_modif_passwd"] != "NA":
-                u["date_modif_passwd"] = DateDMYtoISO(u["date_modif_passwd"]) or ""
+                u["date_modif_passwd"] = ndb.DateDMYtoISO(u["date_modif_passwd"]) or ""
 
             # Convert date_expiration and date_modif_passwd to ISO to ease sorting
             if u["date_expiration"]:
-                u["date_expiration_iso"] = DateDMYtoISO(u["date_expiration"])
+                u["date_expiration_iso"] = ndb.DateDMYtoISO(u["date_expiration"])
             else:
                 u["date_expiration_iso"] = ""
             if u["date_modif_passwd"]:
-                u["date_modif_passwd_iso"] = DateDMYtoISO(u["date_expiration"])
+                u["date_modif_passwd_iso"] = ndb.DateDMYtoISO(u["date_expiration"])
             else:
                 u["date_modif_passwd_iso"] = ""
 
@@ -1202,13 +1219,13 @@ class ZScoUsers(
         """Returns XML list of users with name (nomplogin) starting with start.
         Used for forms auto-completion."""
         userlist = self.get_userlist(dept=dept)
-        start = suppression_diacritics(unicode(start, "utf-8"))
-        start = strlower(str(start))
+        start = scu.suppression_diacritics(unicode(start, "utf-8"))
+        start = scu.strlower(str(start))
 
         userlist = [user for user in userlist if user["nomnoacc"].startswith(start)]
         if REQUEST:
-            REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
-        doc = jaxml.XML_document(encoding=SCO_ENCODING)
+            REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
+        doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
         doc.results()
         for user in userlist[:limit]:
             doc._push()
@@ -1269,14 +1286,14 @@ Il devra ensuite se connecter et le changer.
         log("reset_password: %s" % user_name)
         # Check that user has valid mail
         info = self.user_info(user_name=user_name)
-        if not is_valid_mail(info["email"]):
+        if not scu.is_valid_mail(info["email"]):
             raise Exception("pas de mail valide associé à l'utilisateur")
         # Generate random password
         password = sco_import_users.generate_password()
         self.do_change_password(user_name, password)
         # Flag it as temporary:
         cnx = self.GetUsersDBConnexion()
-        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
         ui = {"user_name": user_name}
         cursor.execute(
             "update sco_users set passwd_temp=1 where user_name='%(user_name)s'" % ui
diff --git a/debug.py b/debug.py
index 55c64e264..d0b8a4dde 100644
--- a/debug.py
+++ b/debug.py
@@ -32,10 +32,13 @@ nt = context.Notes._getNotesCache().get_NotesTable(context.Notes, formsemestre_i
 
 """
 import pdb
+import pprint
 
-from notesdb import *
+import notesdb as ndb
+from notesdb import *  # pylint: disable=unused-wildcard-import
 from notes_log import log
-from sco_utils import *
+import sco_utils as scu
+from sco_utils import *  # pylint: disable=unused-wildcard-import
 
 from gen_tables import GenTable
 import sco_archives
diff --git a/html_sco_header.py b/html_sco_header.py
index 05ef45f26..d66a875b3 100644
--- a/html_sco_header.py
+++ b/html_sco_header.py
@@ -25,6 +25,8 @@
 #
 ##############################################################################
 
+import cgi
+
 from sco_utils import *
 from sco_formsemestre_status import formsemestre_page_title
 
diff --git a/html_sidebar.py b/html_sidebar.py
index df7bb59bc..1c11cf57a 100644
--- a/html_sidebar.py
+++ b/html_sidebar.py
@@ -27,6 +27,12 @@
 
 from sco_utils import *
 from sco_abs import getAbsSemEtud
+from sco_permissions import (
+    ScoUsersAdmin,
+    ScoUsersView,
+    ScoChangePreferences,
+    ScoAbsChange,
+)
 
 """
 Génération de la "sidebar" (marge gauche des pages HTML)
diff --git a/intervals.py b/intervals.py
index 74140c0a9..7517e603d 100644
--- a/intervals.py
+++ b/intervals.py
@@ -173,17 +173,17 @@ if __name__ == "__main__":
         repr(i)
         == "{[None, 3] => 'My,', [3, 5] => 'Hello', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
     )
-    i[5.5:6] = "Cruel"
+    i[5.5:6] = "Cruel"  # pylint: disable=invalid-slice-index
     assert (
         repr(i)
         == "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 6] => 'Cruel', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
     )
-    i[6:6.5] = "And Harsh"
+    i[6:6.5] = "And Harsh"  # pylint: disable=invalid-slice-index
     assert (
         repr(i)
         == "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 6] => 'Cruel', [6, 6.5] => 'And Harsh', [6.5, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
     )
-    i[5.9:6.6] = None
+    i[5.9:6.6] = None  # pylint: disable=invalid-slice-index
     assert (
         repr(i)
         == "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 5.9000000000000004] => 'Cruel', [6.5999999999999996, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
@@ -222,9 +222,13 @@ if __name__ == "__main__":
         print("Test 3 skipped")
     else:
         i = intervalmap()
-        i[: datetime(2005, 10, 24)] = "A"
-        i[datetime(2005, 11, 11) : datetime(2005, 11, 17)] = "B"
-        i[datetime(2005, 11, 30) :] = "C"
+        i[: datetime(2005, 10, 24)] = "A"  # pylint: disable=invalid-slice-index
+        i[
+            datetime(2005, 11, 11) : datetime(  # pylint: disable=invalid-slice-index
+                2005, 11, 17
+            )
+        ] = "B"
+        i[datetime(2005, 11, 30) :] = "C"  # pylint: disable=invalid-slice-index
         assert i[datetime(2005, 9, 25)] == "A"
         assert i[datetime(2005, 10, 23)] == "A"
         assert i[datetime(2005, 10, 26)] == None
diff --git a/notes_table.py b/notes_table.py
index 550b65b99..236ae3b95 100644
--- a/notes_table.py
+++ b/notes_table.py
@@ -27,16 +27,18 @@
 
 """Calculs sur les notes et cache des resultats
 """
-from types import StringType
+from types import StringType, FloatType
 import pdb
 import inspect
 
+
 import scolars
 import sco_groups
 from notes_log import log, logCallStack
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 import sco_codes_parcours
+from sco_codes_parcours import DEF, UE_SPORT, UE_is_fondamentale, UE_is_professionnelle
 from sco_parcours_dut import formsemestre_get_etud_capitalisation
 from sco_parcours_dut import list_formsemestre_utilisateurs_uecap
 import sco_parcours_dut
@@ -46,6 +48,12 @@ import sco_moduleimpl
 import sco_evaluations
 import sco_compute_moy
 from sco_formulas import NoteVector
+from sco_exceptions import (
+    AccessDenied,
+    NoteProcessError,
+    ScoException,
+    ScoValueError,
+)
 
 # Support for old user-written "bonus" functions with 2 args:
 BONUS_TWO_ARGS = len(inspect.getargspec(CONFIG.compute_bonus)[0]) == 2
@@ -107,7 +115,7 @@ def comp_etud_sum_coef_modules_ue(context, formsemestre_id, etudid, ue_id):
 
     (nécessaire pour éviter appels récursifs de nt, qui peuvent boucler)
     """
-    infos = SimpleDictFetch(
+    infos = ndb.SimpleDictFetch(
         context,
         """SELECT mod.coefficient
     FROM notes_modules mod, notes_moduleimpl mi, notes_moduleimpl_inscription ins
@@ -1023,7 +1031,7 @@ class NotesTable:
         Si l'étudiant est défaillant, met un code DEF sur toutes les UE
         """
         cnx = self.context.GetDBConnexion()
-        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
         cursor.execute(
             "select etudid, code, assidu, compense_formsemestre_id, event_date from scolar_formsemestre_validation where formsemestre_id=%(formsemestre_id)s and ue_id is NULL;",
             {"formsemestre_id": self.formsemestre_id},
@@ -1040,7 +1048,7 @@ class NotesTable:
                 "code": code,
                 "assidu": assidu,
                 "compense_formsemestre_id": compense_formsemestre_id,
-                "event_date": DateISOtoDMY(event_date),
+                "event_date": ndb.DateISOtoDMY(event_date),
             }
 
         self.decisions_jury = decisions_jury
@@ -1089,7 +1097,7 @@ class NotesTable:
             decisions_jury_ues[etudid][ue_id] = {
                 "code": code,
                 "ects": ects,  # 0. si non UE validée ou si mode de calcul different (?)
-                "event_date": DateISOtoDMY(event_date),
+                "event_date": ndb.DateISOtoDMY(event_date),
             }
 
         self.decisions_jury_ues = decisions_jury_ues
@@ -1281,7 +1289,7 @@ class NotesTable:
         (ne compte que les notes en attente dans des évaluation avec coef. non nul).
         """
         cnx = self.context.GetDBConnexion()
-        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
         cursor.execute(
             "select n.* from notes_notes n, notes_evaluation e, notes_moduleimpl m, notes_moduleimpl_inscription i where n.etudid = %(etudid)s and n.value = %(code_attente)s and n.evaluation_id=e.evaluation_id and e.moduleimpl_id=m.moduleimpl_id and m.formsemestre_id=%(formsemestre_id)s and e.coefficient != 0 and m.moduleimpl_id=i.moduleimpl_id and i.etudid=%(etudid)s",
             {
diff --git a/pe_semestretag.py b/pe_semestretag.py
index 919110a15..7509ea908 100644
--- a/pe_semestretag.py
+++ b/pe_semestretag.py
@@ -113,7 +113,7 @@ class SemestreTag(pe_tagtable.TableTag):
         self.modimpls = [
             modimpl
             for modimpl in self.nt._modimpls
-            if modimpl["ue"]["type"] == sco_utils.UE_STANDARD
+            if modimpl["ue"]["type"] == sco_codes_parcours.UE_STANDARD
         ]  # la liste des modules (objet modimpl)
         # self._modimpl_ids = [modimpl['moduleimpl_id'] for modimpl in self._modimpls] # la liste de id des modules (modimpl_id)
         self.somme_coeffs = sum(
diff --git a/sco_abs_notification.py b/sco_abs_notification.py
index e26e3d123..ec4ecc7d8 100644
--- a/sco_abs_notification.py
+++ b/sco_abs_notification.py
@@ -39,7 +39,7 @@ from email.MIMEBase import MIMEBase  # pylint: disable=no-name-in-module,import-
 from email.Header import Header  # pylint: disable=no-name-in-module,import-error
 from email import Encoders  # pylint: disable=no-name-in-module,import-error
 
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 from scolog import logdb
@@ -57,8 +57,8 @@ def abs_notify(context, etudid, date):
     if not sem:
         return  # non inscrit a la date, pas de notification
 
-    debut_sem = DateDMYtoISO(sem["date_debut"])
-    fin_sem = DateDMYtoISO(sem["date_fin"])
+    debut_sem = ndb.DateDMYtoISO(sem["date_debut"])
+    fin_sem = ndb.DateDMYtoISO(sem["date_fin"])
     nbabs = context.CountAbs(etudid, debut=debut_sem, fin=fin_sem)
     nbabsjust = context.CountAbsJust(etudid, debut=debut_sem, fin=fin_sem)
 
@@ -111,12 +111,12 @@ def abs_notify_send(
     """Actually send the notification by email, and register it in database"""
     cnx = context.GetDBConnexion()
     log("abs_notify: sending notification to %s" % destinations)
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     for email in destinations:
         del msg["To"]
         msg["To"] = email
         context.sendEmail(msg)
-        SimpleQuery(
+        ndb.SimpleQuery(
             context,
             """insert into absences_notifications (etudid, email, nbabs, nbabsjust, formsemestre_id) values (%(etudid)s, %(email)s, %(nbabs)s, %(nbabsjust)s, %(formsemestre_id)s)""",
             vars(),
@@ -203,7 +203,7 @@ def etud_nbabs_last_notified(context, etudid, formsemestre_id=None):
     """nbabs lors de la dernière notification envoyée pour cet étudiant dans ce semestre
     ou sans semestre (ce dernier cas est nécessaire pour la transition au nouveau code)"""
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         """select * from absences_notifications where etudid = %(etudid)s and (formsemestre_id = %(formsemestre_id)s or formsemestre_id is NULL) order by notification_date desc""",
         vars(),
@@ -218,7 +218,7 @@ def etud_nbabs_last_notified(context, etudid, formsemestre_id=None):
 def user_nbdays_since_last_notif(context, email_addr, etudid):
     """nb days since last notification to this email, or None if no previous notification"""
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         """select * from absences_notifications where email = %(email_addr)s and etudid=%(etudid)s order by notification_date desc""",
         {"email_addr": email_addr, "etudid": etudid},
@@ -273,7 +273,7 @@ def retreive_current_formsemestre(context, etudid, cur_date):
     WHERE sem.formsemestre_id = i.formsemestre_id AND i.etudid=%(etudid)s
     AND (%(cur_date)s >= sem.date_debut) AND (%(cur_date)s <= sem.date_fin)"""
 
-    r = SimpleDictFetch(context, req, {"etudid": etudid, "cur_date": cur_date})
+    r = ndb.SimpleDictFetch(context, req, {"etudid": etudid, "cur_date": cur_date})
     if not r:
         return None
     # s'il y a plusieurs semestres, prend le premier (rarissime et non significatif):
@@ -286,5 +286,5 @@ def mod_with_evals_at_date(context, date_abs, etudid):
     req = """SELECT m.* FROM notes_moduleimpl m, notes_evaluation e, notes_moduleimpl_inscription i
     WHERE m.moduleimpl_id = e.moduleimpl_id AND e.moduleimpl_id = i.moduleimpl_id
     AND i.etudid = %(etudid)s AND e.jour = %(date_abs)s"""
-    r = SimpleDictFetch(context, req, {"etudid": etudid, "date_abs": date_abs})
+    r = ndb.SimpleDictFetch(context, req, {"etudid": etudid, "date_abs": date_abs})
     return r
diff --git a/sco_apogee_compare.py b/sco_apogee_compare.py
index 221e707af..5f36d13f3 100644
--- a/sco_apogee_compare.py
+++ b/sco_apogee_compare.py
@@ -50,6 +50,7 @@ from sco_utils import *
 from notes_log import log
 import sco_apogee_csv
 from gen_tables import GenTable
+from sco_exceptions import ScoValueError
 
 _help_txt = """
 <div class="help">
diff --git a/sco_archives.py b/sco_archives.py
index e328d4fb2..0a7f2b334 100644
--- a/sco_archives.py
+++ b/sco_archives.py
@@ -52,7 +52,7 @@ import shutil
 import glob
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 
 import sco_formsemestre
 import sco_pvjury
@@ -62,6 +62,9 @@ import sco_groups
 import sco_groups_view
 from sco_recapcomplet import make_formsemestre_recapcomplet
 import sco_bulletins_pdf
+from sco_exceptions import (
+    AccessDenied,
+)
 
 
 class BaseArchiver:
diff --git a/sco_archives_etud.py b/sco_archives_etud.py
index 01e2db6ae..f1cf2a835 100644
--- a/sco_archives_etud.py
+++ b/sco_archives_etud.py
@@ -38,6 +38,8 @@ import sco_groups
 import sco_trombino
 import sco_excel
 import sco_archives
+from sco_permissions import ScoEtudAddAnnotations
+from sco_exceptions import AccessDenied
 
 
 class EtudsArchiver(sco_archives.BaseArchiver):
diff --git a/sco_cost_formation.py b/sco_cost_formation.py
index 226938671..3bfc618b6 100644
--- a/sco_cost_formation.py
+++ b/sco_cost_formation.py
@@ -30,7 +30,7 @@
 
    (coût théorique en heures équivalent TD)
 """
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 from gen_tables import GenTable
diff --git a/sco_dept.py b/sco_dept.py
index f7c95c0ad..ce83313c7 100644
--- a/sco_dept.py
+++ b/sco_dept.py
@@ -27,7 +27,7 @@
 
 """Page accueil département (liste des semestres, etc)
 """
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 import sco_modalites
@@ -35,6 +35,7 @@ import sco_news
 import sco_up_to_date
 import sco_formsemestre
 from gen_tables import GenTable
+from sco_permissions import ScoEtudInscrit, ScoEditApo
 
 
 def index_html(context, REQUEST=None, showcodes=0, showsemtable=0):
diff --git a/sco_dump_db.py b/sco_dump_db.py
index 9cea74073..36dbf37db 100644
--- a/sco_dump_db.py
+++ b/sco_dump_db.py
@@ -60,9 +60,10 @@ from email.Header import Header  # pylint: disable=no-name-in-module,import-erro
 from email import Encoders  # pylint: disable=no-name-in-module,import-error
 
 
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
+from sco_exceptions import ScoValueError
 
 SCO_DUMP_LOCK = "/tmp/scodump.lock"
 
@@ -71,7 +72,7 @@ def sco_dump_and_send_db(context, REQUEST=None):
     """Dump base de données du département courant et l'envoie anonymisée pour debug"""
     H = [context.sco_header(REQUEST, page_title="Assistance technique")]
     # get currect (dept) DB name:
-    cursor = SimpleQuery(context, "SELECT current_database()", {})
+    cursor = ndb.SimpleQuery(context, "SELECT current_database()", {})
     db_name = cursor.fetchone()[0]
     ano_db_name = "ANO" + db_name
     # Lock
diff --git a/sco_edit_formation.py b/sco_edit_formation.py
index 96c318e35..f56655740 100644
--- a/sco_edit_formation.py
+++ b/sco_edit_formation.py
@@ -28,12 +28,13 @@
 """Ajout/Modification/Supression formations
 (portage from DTML)
 """
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 from TrivialFormulator import TrivialFormulator, TF
 import sco_codes_parcours
 import sco_formsemestre
+from sco_exceptions import ScoValueError
 
 
 def formation_delete(context, formation_id=None, dialog_confirmed=False, REQUEST=None):
@@ -202,7 +203,7 @@ def formation_edit(context, formation_id=None, create=False, REQUEST=None):
             "titre": tf[2]["titre"],
             "version": version,
         }
-        quote_dict(args)
+        ndb.quote_dict(args)
         others = context.formation_list(args=args)
         if others and ((len(others) > 1) or others[0]["formation_id"] != formation_id):
             return (
diff --git a/sco_edit_matiere.py b/sco_edit_matiere.py
index 2cdda8933..7b041805d 100644
--- a/sco_edit_matiere.py
+++ b/sco_edit_matiere.py
@@ -28,11 +28,12 @@
 """Ajout/Modification/Supression matieres
 (portage from DTML)
 """
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 from TrivialFormulator import TrivialFormulator, TF
 import sco_formsemestre
+from sco_exceptions import ScoValueError
 
 
 def matiere_create(context, ue_id=None, REQUEST=None):
@@ -202,7 +203,7 @@ associé.
         # changement d'UE ?
         if tf[2]["ue_id"] != F["ue_id"]:
             log("attaching mat %s to new UE %s" % (matiere_id, tf[2]["ue_id"]))
-            SimpleQuery(
+            ndb.SimpleQuery(
                 context,
                 "UPDATE notes_modules SET ue_id = %(ue_id)s WHERE matiere_id=%(matiere_id)s",
                 {"ue_id": tf[2]["ue_id"], "matiere_id": matiere_id},
diff --git a/sco_edit_module.py b/sco_edit_module.py
index 64b51b6b0..c7aa3f5c2 100644
--- a/sco_edit_module.py
+++ b/sco_edit_module.py
@@ -28,7 +28,7 @@
 """Ajout/Modification/Supression UE
 (portage from DTML)
 """
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 import sco_codes_parcours
@@ -36,6 +36,8 @@ from TrivialFormulator import TrivialFormulator, TF
 import sco_formsemestre
 import sco_edit_ue
 import sco_tag_module
+from sco_permissions import ScoChangeFormation
+from sco_exceptions import ScoValueError
 
 _MODULE_HELP = """<p class="help">
 Les modules sont décrits dans le programme pédagogique. Un module est pour ce 
@@ -227,7 +229,7 @@ def module_edit(context, module_id=None, REQUEST=None):
     unlocked = not context.module_is_locked(module_id)
     Fo = context.formation_list(args={"formation_id": Mod["formation_id"]})[0]
     parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
-    M = SimpleDictFetch(
+    M = ndb.SimpleDictFetch(
         context,
         "SELECT ue.acronyme, mat.* FROM notes_matieres mat, notes_ue ue WHERE mat.ue_id = ue.ue_id AND ue.formation_id = %(formation_id)s ORDER BY ue.numero, mat.numero",
         {"formation_id": Mod["formation_id"]},
diff --git a/sco_edit_ue.py b/sco_edit_ue.py
index 4fa626240..593cda1cb 100644
--- a/sco_edit_ue.py
+++ b/sco_edit_ue.py
@@ -28,7 +28,7 @@
 """Ajout/Modification/Supression UE
 
 """
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 from TrivialFormulator import TrivialFormulator, TF
@@ -38,6 +38,8 @@ import sco_formsemestre
 import sco_formsemestre_validation
 import sco_codes_parcours
 import sco_tag_module
+from sco_permissions import ScoChangeFormation, ScoEditFormationTags, ScoImplement
+from sco_exceptions import ScoValueError, ScoLockedFormError
 
 
 def ue_create(context, formation_id=None, REQUEST=None):
@@ -84,7 +86,7 @@ def ue_edit(context, ue_id=None, create=False, formation_id=None, REQUEST=None):
 
     ue_types = parcours.ALLOWED_UE_TYPES
     ue_types.sort()
-    ue_types_names = [UE_TYPE_NAME[k] for k in ue_types]
+    ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types]
     ue_types = [str(x) for x in ue_types]
 
     fw = [
@@ -424,7 +426,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
             cur_ue_semestre_id = UE["semestre_id"]
             if iue > 0:
                 H.append("</ul>")
-            if UE["semestre_id"] == UE_SEM_DEFAULT:
+            if UE["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
                 lab = "Pas d'indication de semestre:"
             else:
                 lab = "Semestre %s:" % UE["semestre_id"]
@@ -456,8 +458,11 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module
             % UE
         )
 
-        if UE["type"] != UE_STANDARD:
-            H.append('<span class="ue_type">%s</span>' % UE_TYPE_NAME[UE["type"]])
+        if UE["type"] != sco_codes_parcours.UE_STANDARD:
+            H.append(
+                '<span class="ue_type">%s</span>'
+                % sco_codes_parcours.UE_TYPE_NAME[UE["type"]]
+            )
         ue_editable = editable and not context.ue_is_locked(UE["ue_id"])
         if ue_editable:
             H.append(
diff --git a/sco_edt_cal.py b/sco_edt_cal.py
index c1b7554f7..b41bd34b3 100644
--- a/sco_edt_cal.py
+++ b/sco_edt_cal.py
@@ -36,6 +36,7 @@ XXX incompatible avec les ics HyperPlanning Paris 13 (était pour GPU).
 import urllib2
 import traceback
 import icalendar
+import pprint
 
 from sco_utils import *
 import sco_formsemestre
diff --git a/sco_etape_apogee.py b/sco_etape_apogee.py
index 1a60edcab..4755cfc8c 100644
--- a/sco_etape_apogee.py
+++ b/sco_etape_apogee.py
@@ -83,6 +83,7 @@ import sco_groups
 import sco_groups_view
 import sco_archives
 import sco_apogee_csv
+from sco_exceptions import ScoValueError
 
 
 class ApoCSVArchiver(sco_archives.BaseArchiver):
diff --git a/sco_etape_apogee_view.py b/sco_etape_apogee_view.py
index d03b5107b..78f444f2f 100644
--- a/sco_etape_apogee_view.py
+++ b/sco_etape_apogee_view.py
@@ -43,6 +43,7 @@ import sco_apogee_csv
 import sco_portal_apogee
 from sco_apogee_csv import APO_PORTAL_ENCODING, APO_INPUT_ENCODING
 import sco_archives
+from sco_exceptions import ScoValueError
 
 
 def apo_semset_maq_status(
diff --git a/sco_export_results.py b/sco_export_results.py
index d330a9a82..c71fc2a5e 100644
--- a/sco_export_results.py
+++ b/sco_export_results.py
@@ -27,6 +27,7 @@
 
 """Export d'une table avec les résultats de tous les étudiants
 """
+from types import ListType
 
 import scolars
 import sco_bac
@@ -35,7 +36,7 @@ import sco_parcours_dut
 import sco_codes_parcours
 from sco_codes_parcours import NO_SEMESTRE_ID
 import sco_excel
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from gen_tables import GenTable
 import sco_pvjury
@@ -202,7 +203,7 @@ def _build_results_list(context, dpv_by_sem, etuds_infos):
 
 def get_set_formsemestre_id_dates(context, start_date, end_date):
     """Ensemble des formsemestre_id entre ces dates"""
-    s = SimpleDictFetch(
+    s = ndb.SimpleDictFetch(
         context,
         "SELECT formsemestre_id FROM notes_formsemestre WHERE date_debut >= %(start_date)s AND date_fin <= %(end_date)s",
         {"start_date": start_date, "end_date": end_date},
@@ -224,9 +225,9 @@ def scodoc_table_results(
     if not isinstance(types_parcours, ListType):
         types_parcours = [types_parcours]
     if start_date:
-        start_date_iso = DateDMYtoISO(start_date)
+        start_date_iso = ndb.DateDMYtoISO(start_date)
     if end_date:
-        end_date_iso = DateDMYtoISO(end_date)
+        end_date_iso = ndb.DateDMYtoISO(end_date)
     types_parcours = [int(x) for x in types_parcours if x]
 
     if start_date and end_date:
diff --git a/sco_find_etud.py b/sco_find_etud.py
index d8137c4cf..f003128de 100644
--- a/sco_find_etud.py
+++ b/sco_find_etud.py
@@ -27,17 +27,19 @@
 
 """Recherche d'étudiants
 """
-
-from sco_utils import *
+from types import ListType
 import xml.dom.minidom
 
-from notesdb import *
+from sco_utils import *
+
+import notesdb as ndb
 from notes_log import log
 from gen_tables import GenTable
 
 import scolars
 import sco_formsemestre
 import sco_groups
+from sco_permissions import ScoView
 
 
 def form_search_etud(
@@ -251,7 +253,7 @@ def search_etud_by_name(context, term, REQUEST=None):
         data = []
     else:
         if may_be_nip:
-            r = SimpleDictFetch(
+            r = ndb.SimpleDictFetch(
                 context,
                 "SELECT nom, prenom, code_nip FROM identite WHERE code_nip LIKE %(beginning)s ORDER BY nom",
                 {"beginning": term + "%"},
@@ -265,7 +267,7 @@ def search_etud_by_name(context, term, REQUEST=None):
                 for x in r
             ]
         else:
-            r = SimpleDictFetch(
+            r = ndb.SimpleDictFetch(
                 context,
                 "SELECT nom, prenom FROM identite WHERE nom LIKE %(beginning)s ORDER BY nom",
                 {"beginning": term + "%"},
diff --git a/sco_formations.py b/sco_formations.py
index 3756cd1d5..87cc2c0ef 100644
--- a/sco_formations.py
+++ b/sco_formations.py
@@ -28,16 +28,18 @@
 """Import / Export de formations
 """
 from operator import itemgetter
-
-from sco_utils import *
 import xml.dom.minidom
 
-from notesdb import *
+from sco_utils import *
+
+import notesdb as ndb
 from notes_log import log
 import sco_codes_parcours
 import sco_formsemestre
 import sco_tag_module
 from gen_tables import GenTable
+from sco_exceptions import ScoValueError
+from sco_permissions import ScoChangeFormation
 
 
 def formation_export(
@@ -131,11 +133,11 @@ def formation_import_xml(
     F = D[1]
     F_quoted = F.copy()
     log("F=%s" % F)
-    quote_dict(F_quoted)
+    ndb.quote_dict(F_quoted)
     log("F_quoted=%s" % F_quoted)
     # find new version number
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     log(
         "select max(version) from notes_formations where acronyme=%(acronyme)s and titre=%(titre)s"
         % F_quoted
diff --git a/sco_formsemestre.py b/sco_formsemestre.py
index 6ad764e1a..826a9efd4 100644
--- a/sco_formsemestre.py
+++ b/sco_formsemestre.py
@@ -29,14 +29,15 @@
 """
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 from notes_log import log
 from gen_tables import GenTable
 
 import sco_codes_parcours
 from sco_codes_parcours import NO_SEMESTRE_ID
+from sco_exceptions import ScoValueError
 
-_formsemestreEditor = EditableTable(
+_formsemestreEditor = ndb.EditableTable(
     "notes_formsemestre",
     "formsemestre_id",
     (
@@ -60,16 +61,16 @@ _formsemestreEditor = EditableTable(
     ),
     sortkey="date_debut",
     output_formators={
-        "date_debut": DateISOtoDMY,
-        "date_fin": DateISOtoDMY,
+        "date_debut": ndb.DateISOtoDMY,
+        "date_fin": ndb.DateISOtoDMY,
         "gestion_compensation": str,
         "gestion_semestrielle": str,
         "etat": str,
         "bul_hide_xml": str,
     },
     input_formators={
-        "date_debut": DateDMYtoISO,
-        "date_fin": DateDMYtoISO,
+        "date_debut": ndb.DateDMYtoISO,
+        "date_fin": ndb.DateDMYtoISO,
         "gestion_compensation": int,
         "gestion_semestrielle": int,
         "etat": int,
@@ -144,9 +145,9 @@ def formsemestre_enrich(context, sem):
             sem["semestre_id"],
         )  # eg "DUT Informatique semestre 2"
 
-    sem["dateord"] = DateDMYtoISO(sem["date_debut"])
-    sem["date_debut_iso"] = DateDMYtoISO(sem["date_debut"])
-    sem["date_fin_iso"] = DateDMYtoISO(sem["date_fin"])
+    sem["dateord"] = ndb.DateDMYtoISO(sem["date_debut"])
+    sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
+    sem["date_fin_iso"] = ndb.DateDMYtoISO(sem["date_fin"])
     try:
         mois_debut, annee_debut = sem["date_debut"].split("/")[1:]
     except:
@@ -229,7 +230,7 @@ def read_formsemestre_responsables(context, formsemestre_id):
     """recupere liste des responsables de ce semestre
     :returns: liste de chaines
     """
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         "SELECT responsable_id FROM notes_formsemestre_responsables WHERE formsemestre_id = %(formsemestre_id)s",
         {"formsemestre_id": formsemestre_id},
@@ -243,7 +244,7 @@ def write_formsemestre_responsables(context, sem):
 
 # ----------------------  Coefs des UE
 
-_formsemestre_uecoef_editor = EditableTable(
+_formsemestre_uecoef_editor = ndb.EditableTable(
     "notes_formsemestre_uecoef",
     "formsemestre_uecoef_id",
     ("formsemestre_uecoef_id", "formsemestre_id", "ue_id", "coefficient"),
@@ -292,7 +293,7 @@ def read_formsemestre_etapes(context, formsemestre_id):
     """recupere liste des codes etapes associés à ce semestre
     :returns: liste d'instance de ApoEtapeVDI
     """
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         "SELECT etape_apo FROM notes_formsemestre_etapes WHERE formsemestre_id = %(formsemestre_id)s",
         {"formsemestre_id": formsemestre_id},
@@ -311,7 +312,7 @@ def _write_formsemestre_aux(context, sem, fieldname, valuename):
     if not "etapes" in sem:
         return
     cnx = context.GetDBConnexion(autocommit=False)
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     tablename = "notes_formsemestre_" + fieldname
     try:
         cursor.execute(
@@ -481,8 +482,8 @@ def sem_une_annee(context, sem):
 def sem_est_courant(context, sem):
     """Vrai si la date actuelle (now) est dans le semestre (les dates de début et fin sont incluses)"""
     now = time.strftime("%Y-%m-%d")
-    debut = DateDMYtoISO(sem["date_debut"])
-    fin = DateDMYtoISO(sem["date_fin"])
+    debut = ndb.DateDMYtoISO(sem["date_debut"])
+    fin = ndb.DateDMYtoISO(sem["date_fin"])
     return (debut <= now) and (now <= fin)
 
 
diff --git a/sco_formsemestre_custommenu.py b/sco_formsemestre_custommenu.py
index 5365aa5c0..bae26e39f 100644
--- a/sco_formsemestre_custommenu.py
+++ b/sco_formsemestre_custommenu.py
@@ -30,14 +30,14 @@
 
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 from notes_log import log
 from TrivialFormulator import TrivialFormulator, TF
 import sco_formsemestre
 import sco_formsemestre_status
 import sco_edt_cal
 
-_custommenuEditor = EditableTable(
+_custommenuEditor = ndb.EditableTable(
     "notes_formsemestre_custommenu",
     "custommenu_id",
     ("custommenu_id", "formsemestre_id", "title", "url", "idx"),
diff --git a/sco_formsemestre_edit.py b/sco_formsemestre_edit.py
index 7850202ea..27d64d114 100644
--- a/sco_formsemestre_edit.py
+++ b/sco_formsemestre_edit.py
@@ -28,7 +28,7 @@
 """Form choix modules / responsables et creation formsemestre
 """
 
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 import sco_groups
 from notes_log import log
@@ -43,6 +43,8 @@ import sco_modalites
 import sco_formsemestre
 import sco_moduleimpl
 from sco_formsemestre import ApoEtapeVDI
+from sco_permissions import ScoImplement
+from sco_exceptions import AccessDenied, ScoValueError
 
 
 def _default_sem_title(F):
@@ -176,7 +178,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
 
     # Liste des ID de semestres
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute("select semestre_id from notes_semestres")
     semestre_id_list = [str(x[0]) for x in cursor.fetchall()]
     semestre_id_labels = []
@@ -639,7 +641,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
     msg = ""
     if tf[0] == 1:
         # check dates
-        if DateDMYtoISO(tf[2]["date_debut"]) > DateDMYtoISO(tf[2]["date_fin"]):
+        if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
             msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
         if context.get_preference("always_require_apo_sem_codes") and not any(
             [tf[2]["etape_apo" + str(n)] for n in range(0, EDIT_NB_ETAPES + 1)]
@@ -942,7 +944,7 @@ def formsemestre_clone(context, formsemestre_id, REQUEST=None):
     msg = ""
     if tf[0] == 1:
         # check dates
-        if DateDMYtoISO(tf[2]["date_debut"]) > DateDMYtoISO(tf[2]["date_fin"]):
+        if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
             msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
     if tf[0] == 0 or msg:
         return "".join(H) + msg + tf[1] + context.sco_footer(REQUEST)
@@ -1293,7 +1295,7 @@ def formsemestre_has_decisions_or_compensations(context, formsemestre_id):
     """True if decision de jury dans ce semestre
     ou bien compensation de ce semestre par d'autre ssemestres.
     """
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         "SELECT v.* FROM scolar_formsemestre_validation v WHERE v.formsemestre_id = %(formsemestre_id)s OR v.compense_formsemestre_id = %(formsemestre_id)s",
         {"formsemestre_id": formsemestre_id},
@@ -1314,17 +1316,17 @@ def do_formsemestre_delete(context, formsemestre_id, REQUEST):
         # evaluations
         evals = context.do_evaluation_list(args={"moduleimpl_id": mod["moduleimpl_id"]})
         for e in evals:
-            SimpleQuery(
+            ndb.SimpleQuery(
                 context,
                 "DELETE FROM notes_notes WHERE evaluation_id=%(evaluation_id)s",
                 e,
             )
-            SimpleQuery(
+            ndb.SimpleQuery(
                 context,
                 "DELETE FROM notes_notes_log WHERE evaluation_id=%(evaluation_id)s",
                 e,
             )
-            SimpleQuery(
+            ndb.SimpleQuery(
                 context,
                 "DELETE FROM notes_evaluation WHERE evaluation_id=%(evaluation_id)s",
                 e,
@@ -1335,7 +1337,7 @@ def do_formsemestre_delete(context, formsemestre_id, REQUEST):
             context, mod["moduleimpl_id"], formsemestre_id=formsemestre_id
         )
     # --- Desinscription des etudiants
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     req = "DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=%(formsemestre_id)s"
     cursor.execute(req, {"formsemestre_id": formsemestre_id})
     # --- Suppression des evenements
diff --git a/sco_formsemestre_inscriptions.py b/sco_formsemestre_inscriptions.py
index 44f2d137b..e3c127e3d 100644
--- a/sco_formsemestre_inscriptions.py
+++ b/sco_formsemestre_inscriptions.py
@@ -28,8 +28,11 @@
 """Opérations d'inscriptions aux semestres et modules
 """
 
-from sco_utils import ScoEtudInscrit, log, ScoValueError, DictDefault
-from sco_utils import UE_STANDARD, UE_SPORT, UE_TYPE_NAME
+import sco_utils as scu
+from notes_log import log
+from sco_exceptions import ScoValueError
+from sco_permissions import ScoEtudInscrit
+from sco_codes_parcours import UE_STANDARD, UE_SPORT, UE_TYPE_NAME
 from notesdb import ScoDocCursor, DateISOtoDMY, DateDMYtoISO
 
 from TrivialFormulator import TrivialFormulator, TF
@@ -287,8 +290,10 @@ def formsemestre_inscription_option(context, etudid, formsemestre_id, REQUEST=No
     )
     inscr = sco_moduleimpl.do_moduleimpl_inscription_list(context, etudid=etudid)
     # Formulaire
-    modimpls_by_ue_ids = DictDefault(defaultvalue=[])  # ue_id : [ moduleimpl_id ]
-    modimpls_by_ue_names = DictDefault(defaultvalue=[])  # ue_id : [ moduleimpl_name ]
+    modimpls_by_ue_ids = scu.DictDefault(defaultvalue=[])  # ue_id : [ moduleimpl_id ]
+    modimpls_by_ue_names = scu.DictDefault(
+        defaultvalue=[]
+    )  # ue_id : [ moduleimpl_name ]
     ues = []
     ue_ids = set()
     initvalues = {}
diff --git a/sco_formsemestre_validation.py b/sco_formsemestre_validation.py
index 8ca807de4..71b01db24 100644
--- a/sco_formsemestre_validation.py
+++ b/sco_formsemestre_validation.py
@@ -29,7 +29,7 @@
 """
 import urllib, time, datetime
 
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 from scolog import logdb
@@ -1035,7 +1035,7 @@ def formsemestre_validation_suppress_etud(context, formsemestre_id, etudid):
     """Suppression des decisions de jury pour un etudiant."""
     log("formsemestre_validation_suppress_etud( %s, %s)" % (formsemestre_id, etudid))
     cnx = context.GetDBConnexion(autocommit=False)
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
     try:
         # -- Validation du semestre et des UEs
@@ -1246,7 +1246,7 @@ def do_formsemestre_validate_previous_ue(
 
 def _invalidate_etud_formation_caches(context, etudid, formation_id):
     "Invalide tous les semestres de cette formation où l'etudiant est inscrit..."
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         """SELECT sem.* 
         FROM notes_formsemestre sem, notes_formsemestre_inscription i
@@ -1264,7 +1264,7 @@ def _invalidate_etud_formation_caches(context, etudid, formation_id):
 
 def get_etud_ue_cap_html(context, etudid, formsemestre_id, ue_id, REQUEST=None):
     """Ramene bout de HTML pour pouvoir supprimer une validation de cette UE"""
-    valids = SimpleDictFetch(
+    valids = ndb.SimpleDictFetch(
         context,
         """SELECT SFV.* FROM scolar_formsemestre_validation SFV
         WHERE ue_id=%(ue_id)s AND etudid=%(etudid)s""",
@@ -1276,7 +1276,7 @@ def get_etud_ue_cap_html(context, etudid, formsemestre_id, ue_id, REQUEST=None):
         '<div class="existing_valids"><span>Validations existantes pour cette UE:</span><ul>'
     ]
     for valid in valids:
-        valid["event_date"] = DateISOtoDMY(valid["event_date"])
+        valid["event_date"] = ndb.DateISOtoDMY(valid["event_date"])
         if valid["moy_ue"] != None:
             valid["m"] = ", moyenne %(moy_ue)g/20" % valid
         else:
@@ -1301,7 +1301,7 @@ def etud_ue_suppress_validation(context, etudid, formsemestre_id, ue_id, REQUEST
     """Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
     log("etud_ue_suppress_validation( %s, %s, %s)" % (etudid, formsemestre_id, ue_id))
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         "DELETE FROM scolar_formsemestre_validation WHERE etudid=%(etudid)s and ue_id=%(ue_id)s",
         {"etudid": etudid, "ue_id": ue_id},
@@ -1327,7 +1327,7 @@ def check_formation_ues(context, formation_id):
     ue_multiples = {}  # { ue_id : [ liste des formsemestre ] }
     for ue in ues:
         # formsemestres utilisant cette ue ?
-        sems = SimpleDictFetch(
+        sems = ndb.SimpleDictFetch(
             context,
             """SELECT DISTINCT sem.* 
              FROM notes_formsemestre sem, notes_modules mod, notes_moduleimpl mi
diff --git a/sco_groups.py b/sco_groups.py
index 71a6456fa..ebf6501fc 100644
--- a/sco_groups.py
+++ b/sco_groups.py
@@ -34,7 +34,9 @@ Optimisation possible:
 
 """
 
-import re, sets
+import collections
+import re
+import sets
 import operator
 
 # XML generation package (apt-get install jaxml)
@@ -42,13 +44,16 @@ import jaxml
 import xml.dom.minidom
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 from notes_log import log
 from scolog import logdb
 from TrivialFormulator import TrivialFormulator, TF
 import sco_formsemestre
 import scolars
 import sco_parcours_dut
+import sco_codes_parcours
+from sco_permissions import ScoEtudChangeGroups
+from sco_exceptions import ScoException, AccessDenied, ScoValueError
 
 
 def can_change_groups(context, REQUEST, formsemestre_id):
@@ -76,7 +81,7 @@ def checkGroupName(
         raise ValueError("invalid group name: " + groupName)
 
 
-partitionEditor = EditableTable(
+partitionEditor = ndb.EditableTable(
     "partition",
     "partition_id",
     (
@@ -90,7 +95,7 @@ partitionEditor = EditableTable(
     ),
 )
 
-groupEditor = EditableTable(
+groupEditor = ndb.EditableTable(
     "group_descr", "group_id", ("group_id", "partition_id", "group_name")
 )
 
@@ -99,7 +104,7 @@ group_list = groupEditor.list
 
 def get_group(context, group_id):
     """Returns group object, with partition"""
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         "SELECT gd.*, p.* FROM group_descr gd, partition p WHERE gd.group_id=%(group_id)s AND p.partition_id = gd.partition_id",
         {"group_id": group_id},
@@ -114,15 +119,17 @@ def group_delete(context, group, force=False):
     # if not group['group_name'] and not force:
     #    raise ValueError('cannot suppress this group')
     # remove memberships:
-    SimpleQuery(
+    ndb.SimpleQuery(
         context, "DELETE FROM group_membership WHERE group_id=%(group_id)s", group
     )
     # delete group:
-    SimpleQuery(context, "DELETE FROM group_descr WHERE group_id=%(group_id)s", group)
+    ndb.SimpleQuery(
+        context, "DELETE FROM group_descr WHERE group_id=%(group_id)s", group
+    )
 
 
 def get_partition(context, partition_id):
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         "SELECT p.* FROM partition p WHERE p.partition_id = %(partition_id)s",
         {"partition_id": partition_id},
@@ -134,7 +141,7 @@ def get_partition(context, partition_id):
 
 def get_partitions_list(context, formsemestre_id, with_default=True):
     """Liste des partitions pour ce semestre (list of dicts)"""
-    partitions = SimpleDictFetch(
+    partitions = ndb.SimpleDictFetch(
         context,
         "SELECT * FROM partition WHERE formsemestre_id=%(formsemestre_id)s order by numero",
         {"formsemestre_id": formsemestre_id},
@@ -148,7 +155,7 @@ def get_partitions_list(context, formsemestre_id, with_default=True):
 
 def get_default_partition(context, formsemestre_id):
     """Get partition for 'all' students (this one always exists, with NULL name)"""
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         "SELECT * FROM partition WHERE formsemestre_id=%(formsemestre_id)s AND partition_name is NULL",
         {"formsemestre_id": formsemestre_id},
@@ -176,7 +183,7 @@ def get_formsemestre_groups(context, formsemestre_id, with_default=False):
 def get_partition_groups(context, partition):
     """List of groups in this partition (list of dicts).
     Some groups may be empty."""
-    return SimpleDictFetch(
+    return ndb.SimpleDictFetch(
         context,
         "SELECT gd.*, p.* FROM group_descr gd, partition p WHERE gd.partition_id=%(partition_id)s AND gd.partition_id=p.partition_id ORDER BY group_name",
         partition,
@@ -185,7 +192,7 @@ def get_partition_groups(context, partition):
 
 def get_default_group(context, formsemestre_id, fix_if_missing=False, REQUEST=None):
     """Returns group_id for default ('tous') group"""
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         "SELECT gd.group_id FROM group_descr gd, partition p WHERE p.formsemestre_id=%(formsemestre_id)s AND p.partition_name is NULL AND p.partition_id = gd.partition_id",
         {"formsemestre_id": formsemestre_id},
@@ -217,7 +224,7 @@ def get_default_group(context, formsemestre_id, fix_if_missing=False, REQUEST=No
 
 def get_sem_groups(context, formsemestre_id):
     """Returns groups for this sem (in all partitions)."""
-    return SimpleDictFetch(
+    return ndb.SimpleDictFetch(
         context,
         "SELECT gd.*, p.* FROM group_descr gd, partition p WHERE p.formsemestre_id=%(formsemestre_id)s AND p.partition_id = gd.partition_id",
         {"formsemestre_id": formsemestre_id},
@@ -233,7 +240,7 @@ def get_group_members(context, group_id, etat=None):
     if etat is not None:
         req += " and ins.etat = %(etat)s"
 
-    r = SimpleDictFetch(context, req, {"group_id": group_id, "etat": etat})
+    r = ndb.SimpleDictFetch(context, req, {"group_id": group_id, "etat": etat})
 
     for etud in r:
         scolars.format_etud_ident(etud)
@@ -294,7 +301,7 @@ def get_group_infos(context, group_id, etat=None):  # was _getlisteetud
             else:
                 t["etath"] = "(dem.)"
             nbdem += 1
-        elif t["etat"] == DEF:
+        elif t["etat"] == sco_codes_parcours.DEF:
             t["etath"] = "Défaillant"
         else:
             t["etath"] = t["etat"]
@@ -329,7 +336,7 @@ def get_etud_groups(context, etudid, sem, exclude_default=False):
     req = "SELECT p.*, g.* from group_descr g, partition p, group_membership gm WHERE gm.etudid=%(etudid)s and gm.group_id = g.group_id and g.partition_id = p.partition_id and p.formsemestre_id = %(formsemestre_id)s"
     if exclude_default:
         req += " and p.partition_name is not NULL"
-    groups = SimpleDictFetch(
+    groups = ndb.SimpleDictFetch(
         context,
         req + " ORDER BY p.numero",
         {"etudid": etudid, "formsemestre_id": sem["formsemestre_id"]},
@@ -357,7 +364,7 @@ def formsemestre_get_etud_groupnames(context, formsemestre_id, attr="group_name"
     """Recupere les groupes de tous les etudiants d'un semestre
     { etudid : { partition_id : group_name  }}  (attr=group_name or group_id)
     """
-    infos = SimpleDictFetch(
+    infos = ndb.SimpleDictFetch(
         context,
         "select i.etudid, p.partition_id, gd.group_name, gd.group_id from notes_formsemestre_inscription i, partition p, group_descr gd, group_membership gm where i.formsemestre_id=%(formsemestre_id)s and i.formsemestre_id=p.formsemestre_id and p.partition_id=gd.partition_id and gm.etudid=i.etudid and gm.group_id = gd.group_id and p.partition_name is not NULL",
         {"formsemestre_id": formsemestre_id},
@@ -380,7 +387,7 @@ def etud_add_group_infos(context, etud, sem, sep=" "):
         etud["groupes"] = ""
         return etud
 
-    infos = SimpleDictFetch(
+    infos = ndb.SimpleDictFetch(
         context,
         "SELECT p.partition_name, g.* from group_descr g, partition p, group_membership gm WHERE gm.etudid=%(etudid)s and gm.group_id = g.group_id and g.partition_id = p.partition_id and p.formsemestre_id = %(formsemestre_id)s ORDER BY p.numero",
         {"etudid": etud["etudid"], "formsemestre_id": sem["formsemestre_id"]},
@@ -407,7 +414,7 @@ def etud_add_group_infos(context, etud, sem, sep=" "):
 
 def get_etud_groups_in_partition(context, partition_id):
     """Returns { etudid : group }, with all students in this partition"""
-    infos = SimpleDictFetch(
+    infos = ndb.SimpleDictFetch(
         context,
         "SELECT gd.*, etudid from group_descr gd, group_membership gm where gd.partition_id = %(partition_id)s and gm.group_id = gd.group_id",
         {"partition_id": partition_id},
@@ -530,10 +537,10 @@ def set_group(context, etudid, group_id):
     Warning: don't check if group_id exists (the caller should check).
     """
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     args = {"etudid": etudid, "group_id": group_id}
     # déjà inscrit ?
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         "SELECT * FROM group_membership gm WHERE etudid=%(etudid)s and group_id=%(group_id)s",
         args,
@@ -542,7 +549,7 @@ def set_group(context, etudid, group_id):
     if len(r):
         return False
     # inscrit
-    SimpleQuery(
+    ndb.SimpleQuery(
         context,
         "INSERT INTO group_membership (etudid, group_id) VALUES (%(etudid)s, %(group_id)s)",
         args,
@@ -569,7 +576,7 @@ def change_etud_group_in_partition(
     else:
         partition = get_partition(context, group["partition_id"])
     # 1- Supprime membership dans cette partition
-    SimpleQuery(
+    ndb.SimpleQuery(
         context,
         """DELETE FROM group_membership WHERE group_membership_id IN 
         (SELECT gm.group_membership_id 
@@ -651,10 +658,10 @@ def setGroups(
                 )
         # Retire les anciens membres:
         cnx = context.GetDBConnexion()
-        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
         for etudid in old_members_set:
             log("removing %s from group %s" % (etudid, group_id))
-            SimpleQuery(
+            ndb.SimpleQuery(
                 context,
                 "DELETE FROM group_membership WHERE etudid=%(etudid)s and group_id=%(group_id)s",
                 {"etudid": etudid, "group_id": group_id},
@@ -1079,7 +1086,7 @@ def partition_set_name(context, partition_id, partition_name, REQUEST=None, redi
     formsemestre_id = partition["formsemestre_id"]
 
     # check unicity
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         "SELECT p.* FROM partition p WHERE p.partition_name = %(partition_name)s AND formsemestre_id = %(formsemestre_id)s",
         {"partition_name": partition_name, "formsemestre_id": formsemestre_id},
@@ -1374,7 +1381,7 @@ def do_evaluation_listeetuds_groups(
         req += " and Isem.etat='I'"
     req += r
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(req, {"evaluation_id": evaluation_id})
     # log('listeetuds_groups: getallstudents=%s  groups=%s' % (getallstudents,groups))
     # log('req=%s' % (req % { 'evaluation_id' : "'"+evaluation_id+"'" }))
@@ -1393,7 +1400,7 @@ def do_evaluation_listegroupes(context, evaluation_id, include_default=False):
     else:
         c = " AND p.partition_name is not NULL"
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         "SELECT DISTINCT gd.group_id FROM group_descr gd, group_membership gm, partition p, notes_moduleimpl m, notes_evaluation e WHERE gm.group_id = gd.group_id and gd.partition_id = p.partition_id and p.formsemestre_id = m.formsemestre_id and m.moduleimpl_id = e.moduleimpl_id and e.evaluation_id = %(evaluation_id)s"
         + c,
@@ -1406,7 +1413,7 @@ def do_evaluation_listegroupes(context, evaluation_id, include_default=False):
 
 def listgroups(context, group_ids):
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     groups = []
     for group_id in group_ids:
         cursor.execute(
diff --git a/sco_groups_edit.py b/sco_groups_edit.py
index c1d0b38ee..be2e1f301 100644
--- a/sco_groups_edit.py
+++ b/sco_groups_edit.py
@@ -31,10 +31,11 @@
 import re
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 from notes_log import log
 import sco_formsemestre
 import sco_groups
+from sco_exceptions import AccessDenied
 
 
 def affectGroups(context, partition_id, REQUEST=None):
diff --git a/sco_import_users.py b/sco_import_users.py
index e974dbcac..654f2bed6 100644
--- a/sco_import_users.py
+++ b/sco_import_users.py
@@ -35,12 +35,13 @@ from email.MIMEBase import MIMEBase  # pylint: disable=no-name-in-module,import-
 from email.Header import Header  # pylint: disable=no-name-in-module,import-error
 from email import Encoders  # pylint: disable=no-name-in-module,import-error
 
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 from TrivialFormulator import TrivialFormulator, TF
 import sco_news
 import sco_excel
+from sco_exceptions import AccessDenied, ScoValueError, ScoException
 
 TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept")
 
diff --git a/sco_inscr_passage.py b/sco_inscr_passage.py
index 0da58a1f9..5c65e3611 100644
--- a/sco_inscr_passage.py
+++ b/sco_inscr_passage.py
@@ -31,7 +31,7 @@
 
 from gen_tables import GenTable
 
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 import sco_codes_parcours
@@ -41,6 +41,7 @@ import sco_formsemestre_inscriptions
 import sco_formsemestre
 import sco_groups
 import scolars
+from sco_exceptions import ScoValueError
 
 
 def list_authorized_etuds_by_sem(context, sem, delai=274):
@@ -60,7 +61,9 @@ def list_authorized_etuds_by_sem(context, sem, delai=274):
             auth_used = False  # autorisation deja utilisée ?
             etud = context.getEtudInfo(etudid=e["etudid"], filled=True)[0]
             for isem in etud["sems"]:
-                if DateDMYtoISO(isem["date_debut"]) >= DateDMYtoISO(src["date_fin"]):
+                if ndb.DateDMYtoISO(isem["date_debut"]) >= ndb.DateDMYtoISO(
+                    src["date_fin"]
+                ):
                     auth_used = True
             if not auth_used:
                 candidats[e["etudid"]] = etud
@@ -134,8 +137,8 @@ def list_inscrits_date(context, sem):
     SAUF sem à la date de début de sem.
     """
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
-    sem["date_debut_iso"] = DateDMYtoISO(sem["date_debut"])
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
+    sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
     cursor.execute(
         """select I.etudid
                       from notes_formsemestre_inscription I, notes_formsemestre S
diff --git a/sco_liste_notes.py b/sco_liste_notes.py
index b130d78fb..35e5a87a0 100644
--- a/sco_liste_notes.py
+++ b/sco_liste_notes.py
@@ -27,8 +27,12 @@
 
 """Liste des notes d'une évaluation
 """
+import urllib
 
-from notesdb import *
+from htmlutils import histogram_notes
+
+
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 from TrivialFormulator import TrivialFormulator, TF
@@ -40,7 +44,6 @@ import sco_evaluations
 import htmlutils
 import sco_excel
 from gen_tables import GenTable
-from htmlutils import histogram_notes
 
 
 def do_evaluation_listenotes(context, REQUEST):
@@ -432,7 +435,7 @@ def _make_table_notes(
     if with_emails:
         gl = "&amp;with_emails%3Alist=yes" + gl
     if len(evals) == 1:
-        evalname = "%s-%s" % (Mod["code"], DateDMYtoISO(E["jour"]))
+        evalname = "%s-%s" % (Mod["code"], ndb.DateDMYtoISO(E["jour"]))
         hh = "%s, %s (%d étudiants)" % (E["description"], gr_title, len(etudids))
         filename = make_filename("notes_%s_%s" % (evalname, gr_title_filename))
         caption = hh
@@ -721,12 +724,12 @@ def evaluation_check_absences(context, evaluation_id):
     am, pm, demijournee = _eval_demijournee(E)
 
     # Liste les absences à ce moment:
-    A = context.Absences.ListeAbsJour(DateDMYtoISO(E["jour"]), am=am, pm=pm)
+    A = context.Absences.ListeAbsJour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
     As = set([x["etudid"] for x in A])  # ensemble des etudiants absents
-    NJ = context.Absences.ListeAbsNonJustJour(DateDMYtoISO(E["jour"]), am=am, pm=pm)
+    NJ = context.Absences.ListeAbsNonJustJour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
     NJs = set([x["etudid"] for x in NJ])  # ensemble des etudiants absents non justifies
     Just = context.Absences.ListeAbsJour(
-        DateDMYtoISO(E["jour"]), am=am, pm=pm, is_abs=None, is_just=True
+        ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm, is_abs=None, is_just=True
     )
     Justs = set([x["etudid"] for x in Just])  # ensemble des etudiants avec justif
 
diff --git a/sco_lycee.py b/sco_lycee.py
index d6272737f..fe60885ea 100644
--- a/sco_lycee.py
+++ b/sco_lycee.py
@@ -32,7 +32,6 @@
 
 import tempfile, urllib, re
 
-from notesdb import *
 from sco_utils import *
 from notes_log import log
 import scolars
diff --git a/sco_modalites.py b/sco_modalites.py
index 396184c67..c8ed69a4a 100644
--- a/sco_modalites.py
+++ b/sco_modalites.py
@@ -108,5 +108,5 @@ def do_modalite_edit(context, *args, **kw):
     "edit a modalite"
     cnx = context.GetDBConnexion()
     # check
-    m = do_modalite_list(context, {"form_modalite_id": args[0]["form_modalite_id"]})[0]
+    _ = do_modalite_list(context, {"form_modalite_id": args[0]["form_modalite_id"]})[0]
     _modaliteEditor.edit(cnx, *args, **kw)
diff --git a/sco_moduleimpl_inscriptions.py b/sco_moduleimpl_inscriptions.py
index 7815a715c..4ee9acd10 100644
--- a/sco_moduleimpl_inscriptions.py
+++ b/sco_moduleimpl_inscriptions.py
@@ -29,7 +29,7 @@
 """
 
 
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 from scolog import logdb
@@ -38,6 +38,7 @@ import sco_formsemestre
 import sco_moduleimpl
 import sco_groups
 import htmlutils
+from sco_permissions import ScoEtudInscrit
 
 
 def moduleimpl_inscriptions_edit(
@@ -481,7 +482,7 @@ def is_inscrit_ue(context, etudid, formsemestre_id, ue_id):
     """Modules de cette UE dans ce semestre
     auxquels l'étudiant est inscrit.
     """
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         """SELECT mod.*
     FROM notes_moduleimpl mi, notes_modules mod,
@@ -502,7 +503,7 @@ def is_inscrit_ue(context, etudid, formsemestre_id, ue_id):
 def do_etud_desinscrit_ue(context, etudid, formsemestre_id, ue_id, REQUEST=None):
     """Desincrit l'etudiant de tous les modules de cette UE dans ce semestre."""
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         """DELETE FROM notes_moduleimpl_inscription 
     WHERE moduleimpl_inscription_id IN (
@@ -543,7 +544,7 @@ def do_etud_inscrit_ue(context, etudid, formsemestre_id, ue_id, REQUEST=None):
         raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid)
 
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         """SELECT mi.moduleimpl_id 
       FROM notes_moduleimpl mi, notes_modules mod, notes_formsemestre sem
diff --git a/sco_news.py b/sco_news.py
index a5860880c..007a7f4d1 100644
--- a/sco_news.py
+++ b/sco_news.py
@@ -41,20 +41,20 @@ from email.Header import Header  # pylint: disable=no-name-in-module,import-erro
 from email import Encoders  # pylint: disable=no-name-in-module,import-error
 
 
-from notesdb import *  # pylint: disable=unused-wildcard-import
+import notesdb as ndb  # pylint: disable=unused-wildcard-import
 from notes_log import log
 import scolars
 from sco_utils import SCO_ENCODING, SCO_ANNONCES_WEBSITE
 import sco_formsemestre
 import sco_moduleimpl
 
-_scolar_news_editor = EditableTable(
+_scolar_news_editor = ndb.EditableTable(
     "scolar_news",
     "news_id",
     ("date", "authenticated_user", "type", "object", "text", "url"),
     sortkey="date desc",
-    output_formators={"date": DateISOtoDMY},
-    input_formators={"date": DateDMYtoISO},
+    output_formators={"date": ndb.DateISOtoDMY},
+    input_formators={"date": ndb.DateDMYtoISO},
     html_quote=False,  # no user supplied data, needed to store html links
 )
 
@@ -114,7 +114,7 @@ def scolar_news_summary(context, n=5):
     """
 
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute("select * from scolar_news order by date desc limit 100")
     selected_news = {}  # (type,object) : news dict
     news = cursor.dictfetchall()  # la plus récente d'abord
@@ -122,7 +122,7 @@ def scolar_news_summary(context, n=5):
     for r in reversed(news):  # la plus ancienne d'abord
         # si on a deja une news avec meme (type,object)
         # et du meme jour, on la remplace
-        dmy = DateISOtoDMY(r["date"])  # round
+        dmy = ndb.DateISOtoDMY(r["date"])  # round
         key = (r["type"], r["object"], dmy)
         selected_news[key] = r
 
diff --git a/sco_page_etud.py b/sco_page_etud.py
index 737b3ac2a..cbc7f8c87 100644
--- a/sco_page_etud.py
+++ b/sco_page_etud.py
@@ -32,7 +32,7 @@
 """
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 import scolars
 import sco_bac
 import sco_photos
@@ -43,9 +43,17 @@ import sco_formsemestre_status
 import htmlutils
 from sco_bulletins import etud_descr_situation_semestre
 import sco_parcours_dut
+import sco_codes_parcours
 from sco_formsemestre_validation import formsemestre_recap_parcours_table
 import sco_archives_etud
 import sco_report
+from sco_permissions import (
+    ScoEtudChangeGroups,
+    ScoEtudInscrit,
+    ScoImplement,
+    ScoEtudChangeAdr,
+)
+from sco_exceptions import ScoValueError
 
 
 def _menuScolarite(context, authuser, sem, etudid):
@@ -76,12 +84,12 @@ def _menuScolarite(context, authuser, sem, etudid):
         )
 
     # Note: seul un etudiant inscrit (I) peut devenir défaillant.
-    if ins["etat"] != DEF:
+    if ins["etat"] != sco_codes_parcours.DEF:
         def_title = "Déclarer défaillance"
         def_url = (
             "formDef?etudid=%(etudid)s&amp;formsemestre_id=%(formsemestre_id)s" % args
         )
-    elif ins["etat"] == DEF:
+    elif ins["etat"] == sco_codes_parcours.DEF:
         def_title = "Annuler la défaillance"
         def_url = (
             "doCancelDef?etudid=%(etudid)s&amp;formsemestre_id=%(formsemestre_id)s"
diff --git a/sco_parcours_dut.py b/sco_parcours_dut.py
index d4bd5bb88..3bdaca8e3 100644
--- a/sco_parcours_dut.py
+++ b/sco_parcours_dut.py
@@ -27,12 +27,13 @@
 
 """Semestres: gestion parcours DUT (Arreté du 13 août 2005)
 """
-from notesdb import *
+import notesdb as ndb
 from notes_log import log
 from scolog import logdb
 import sco_formsemestre
 from sco_codes_parcours import *
 from dutrules import DUTRules  # regles generees a partir du CSV
+from sco_exceptions import ScoValueError
 
 
 class DecisionSem:
@@ -607,7 +608,7 @@ class SituationEtudParcoursGeneric:
             )  # > modif decisions jury (sem, UE)
 
         # -- supprime autorisations venant de ce formsemestre
-        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
         try:
             cursor.execute(
                 """delete from scolar_autorisation_inscription
@@ -742,7 +743,7 @@ def int_or_null(s):
         return int(s)
 
 
-_scolar_formsemestre_validation_editor = EditableTable(
+_scolar_formsemestre_validation_editor = ndb.EditableTable(
     "scolar_formsemestre_validation",
     "formsemestre_validation_id",
     (
@@ -758,8 +759,8 @@ _scolar_formsemestre_validation_editor = EditableTable(
         "semestre_id",
         "is_external",
     ),
-    output_formators={"event_date": DateISOtoDMY, "assidu": str},
-    input_formators={"event_date": DateDMYtoISO, "assidu": int_or_null},
+    output_formators={"event_date": ndb.DateISOtoDMY, "assidu": str},
+    input_formators={"event_date": ndb.DateDMYtoISO, "assidu": int_or_null},
 )
 
 scolar_formsemestre_validation_create = _scolar_formsemestre_validation_editor.create
@@ -779,7 +780,7 @@ def formsemestre_validate_sem(
     "Ajoute ou change validation semestre"
     args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
     # delete existing
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     try:
         cursor.execute(
             """delete from scolar_formsemestre_validation
@@ -827,7 +828,7 @@ def formsemestre_update_validation_sem(
         "assidu": int(assidu),
     }
     log("formsemestre_update_validation_sem: %s" % args)
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     to_invalidate = []
 
     # enleve compensations si necessaire
@@ -945,7 +946,7 @@ def do_formsemestre_validate_ue(
         args["event_date"] = date
 
     # delete existing
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     try:
         cond = "etudid = %(etudid)s and ue_id=%(ue_id)s"
         if formsemestre_id:
@@ -983,7 +984,7 @@ def formsemestre_has_decisions(context, formsemestre_id):
 
 def etud_est_inscrit_ue(cnx, etudid, formsemestre_id, ue_id):
     """Vrai si l'étudiant est inscrit a au moins un module de cette UE dans ce semestre"""
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         """select mi.* from notes_moduleimpl mi, notes_modules mo, notes_ue ue, notes_moduleimpl_inscription i
     where i.etudid = %(etudid)s and i.moduleimpl_id=mi.moduleimpl_id
@@ -997,12 +998,12 @@ def etud_est_inscrit_ue(cnx, etudid, formsemestre_id, ue_id):
     return len(cursor.fetchall())
 
 
-_scolar_autorisation_inscription_editor = EditableTable(
+_scolar_autorisation_inscription_editor = ndb.EditableTable(
     "scolar_autorisation_inscription",
     "autorisation_inscription_id",
     ("etudid", "formation_code", "semestre_id", "date", "origin_formsemestre_id"),
-    output_formators={"date": DateISOtoDMY},
-    input_formators={"date": DateDMYtoISO},
+    output_formators={"date": ndb.DateISOtoDMY},
+    input_formators={"date": ndb.DateDMYtoISO},
 )
 scolar_autorisation_inscription_list = _scolar_autorisation_inscription_editor.list
 
@@ -1033,7 +1034,7 @@ def formsemestre_get_etud_capitalisation(context, sem, etudid):
                   } ]
     """
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         """select distinct SFV.*, ue.ue_code from notes_ue ue, notes_formations nf, notes_formations nf2,
     scolar_formsemestre_validation SFV, notes_formsemestre sem
@@ -1058,7 +1059,7 @@ def formsemestre_get_etud_capitalisation(context, sem, etudid):
             "etudid": etudid,
             "formation_id": sem["formation_id"],
             "semestre_id": sem["semestre_id"],
-            "date_debut": DateDMYtoISO(sem["date_debut"]),
+            "date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
         },
     )
 
@@ -1072,7 +1073,7 @@ def list_formsemestre_utilisateurs_uecap(context, formsemestre_id):
     cnx = context.GetDBConnexion()
     sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
     F = context.formation_list(args={"formation_id": sem["formation_id"]})[0]
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         """select sem.formsemestre_id
     from notes_formsemestre sem, notes_formations F
@@ -1086,7 +1087,7 @@ def list_formsemestre_utilisateurs_uecap(context, formsemestre_id):
             "formation_code": F["formation_code"],
             "semestre_id": sem["semestre_id"],
             "formsemestre_id": formsemestre_id,
-            "date_debut": DateDMYtoISO(sem["date_debut"]),
+            "date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
         },
     )
     return [x[0] for x in cursor.fetchall()]
diff --git a/sco_placement.py b/sco_placement.py
index 7917a60d4..646230b97 100644
--- a/sco_placement.py
+++ b/sco_placement.py
@@ -30,6 +30,8 @@
 Contribution M. Salomon, UFC / IUT DE BELFORT-MONTBÉLIARD, 2016
 
 """
+import urllib
+import random
 
 import sco_utils as scu
 import notesdb as ndb
@@ -43,7 +45,6 @@ import sco_saisie_notes
 import sco_excel
 from sco_excel import *
 from gen_tables import GenTable
-import random
 
 
 def do_placement_selectetuds(context, REQUEST):
diff --git a/sco_portal_apogee.py b/sco_portal_apogee.py
index a760362be..2aaae513b 100644
--- a/sco_portal_apogee.py
+++ b/sco_portal_apogee.py
@@ -35,11 +35,13 @@ import xml.sax.saxutils
 import xml.dom.minidom
 import datetime
 
-import sco_utils
-from sco_utils import ScoEtudInscrit, log, ScoValueError, DictDefault
-from sco_utils import SCO_TMPDIR, SCO_ENCODING
+import sco_utils as scu
+from sco_utils import SCO_ENCODING
+from sco_permissions import ScoEtudInscrit
+from sco_exceptions import ScoValueError
+from notes_log import log
 
-SCO_CACHE_ETAPE_FILENAME = os.path.join(SCO_TMPDIR, "last_etapes.xml")
+SCO_CACHE_ETAPE_FILENAME = os.path.join(scu.SCO_TMPDIR, "last_etapes.xml")
 
 
 def has_portal(context):
@@ -162,7 +164,7 @@ def get_inscrits_etape(context, code_etape, anneeapogee=None, ntrials=2):
     if portal_timeout > 0:
         actual_timeout = max(1, actual_timeout)
     for _ntrial in range(ntrials):
-        doc = sco_utils.query_portal(req, timeout=actual_timeout)
+        doc = scu.query_portal(req, timeout=actual_timeout)
         if doc:
             break
     if not doc:
@@ -203,7 +205,7 @@ def query_apogee_portal(context, **args):
             return []
     portal_timeout = context.get_preference("portal_timeout")
     req = etud_url + "?" + urllib.urlencode(args.items())
-    doc = sco_utils.query_portal(req, timeout=portal_timeout)  # sco_utils
+    doc = scu.query_portal(req, timeout=portal_timeout)  # sco_utils
     return xml_to_list_of_dicts(doc, req=req)
 
 
@@ -259,7 +261,7 @@ def get_infos_apogee_allaccents(context, nom, prenom):
     "essai recup infos avec differents codages des accents"
     if nom:
         unom = unicode(nom, SCO_ENCODING)
-        nom_noaccents = str(sco_utils.suppression_diacritics(unom))
+        nom_noaccents = str(scu.suppression_diacritics(unom))
         nom_utf8 = unom.encode("utf-8")
     else:
         nom_noaccents = nom
@@ -267,7 +269,7 @@ def get_infos_apogee_allaccents(context, nom, prenom):
 
     if prenom:
         uprenom = unicode(prenom, SCO_ENCODING)
-        prenom_noaccents = str(sco_utils.suppression_diacritics(uprenom))
+        prenom_noaccents = str(scu.suppression_diacritics(uprenom))
         prenom_utf8 = uprenom.encode("utf-8")
     else:
         prenom_noaccents = prenom
@@ -323,7 +325,7 @@ def get_etud_apogee(context, code_nip):
         return {}
     portal_timeout = context.get_preference("portal_timeout")
     req = etud_url + "?" + urllib.urlencode((("nip", code_nip),))
-    doc = sco_utils.query_portal(req, timeout=portal_timeout)
+    doc = scu.query_portal(req, timeout=portal_timeout)
     d = _normalize_apo_fields(xml_to_list_of_dicts(doc, req=req))
     if not d:
         return None
@@ -387,7 +389,7 @@ def get_etapes_apogee(context):
             "get_etapes_apogee: requesting '%s' with timeout=%s"
             % (etapes_url, portal_timeout)
         )
-        doc = sco_utils.query_portal(etapes_url, timeout=portal_timeout)
+        doc = scu.query_portal(etapes_url, timeout=portal_timeout)
         try:
             infos = _parse_etapes_from_xml(context, doc)
             # cache le resultat (utile si le portail repond de façon intermitente)
@@ -482,7 +484,7 @@ def _normalize_apo_fields(infolist):
     for infos in infolist:
         if infos.has_key("paiementinscription"):
             infos["paiementinscription"] = (
-                sco_utils.strlower(infos["paiementinscription"]) == "true"
+                scu.strlower(infos["paiementinscription"]) == "true"
             )
             if infos["paiementinscription"]:
                 infos["paiementinscription_str"] = "ok"
@@ -562,5 +564,5 @@ def get_maquette_apogee(context, etape="", annee_scolaire=""):
         + "?"
         + urllib.urlencode((("etape", etape), ("annee", annee_scolaire)))
     )
-    doc = sco_utils.query_portal(req, timeout=portal_timeout)
+    doc = scu.query_portal(req, timeout=portal_timeout)
     return doc
diff --git a/sco_poursuite_dut.py b/sco_poursuite_dut.py
index c845ea049..9eed9c7dd 100644
--- a/sco_poursuite_dut.py
+++ b/sco_poursuite_dut.py
@@ -29,8 +29,8 @@
 
 Recapitule tous les semestres validés dans une feuille excel.
 """
+import collections
 
-from notesdb import *
 from sco_utils import *
 from notes_log import log
 from gen_tables import GenTable
diff --git a/sco_preferences.py b/sco_preferences.py
index be6fbc162..2a66eaa88 100644
--- a/sco_preferences.py
+++ b/sco_preferences.py
@@ -29,10 +29,11 @@
 """
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 from TrivialFormulator import TrivialFormulator, TF
 import sco_formsemestre
 import sco_bulletins_generator
+from sco_exceptions import ScoValueError, ScoException
 
 """Global/Semestre Preferences for ScoDoc (version dec 2008)
 
@@ -1729,7 +1730,7 @@ PREFS_DICT = dict(PREFS)
 
 
 class sco_base_preferences:
-    _editor = EditableTable(
+    _editor = ndb.EditableTable(
         "sco_prefs",
         "pref_id",
         ("pref_id", "name", "value", "formsemestre_id"),
diff --git a/sco_prepajury.py b/sco_prepajury.py
index 16fe212f4..a70f366dd 100644
--- a/sco_prepajury.py
+++ b/sco_prepajury.py
@@ -28,7 +28,6 @@
 """Feuille excel pour preparation des jurys
 """
 
-from notesdb import *
 from sco_utils import *
 from notes_log import log
 import notes_table
diff --git a/sco_pvjury.py b/sco_pvjury.py
index d275a2b0d..ca5fe0ac4 100644
--- a/sco_pvjury.py
+++ b/sco_pvjury.py
@@ -36,7 +36,7 @@ import sco_parcours_dut
 import sco_codes_parcours
 from sco_codes_parcours import NO_SEMESTRE_ID
 import sco_excel
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from gen_tables import GenTable
 import sco_pvpdf
@@ -71,7 +71,7 @@ def _descr_decisions_ues(context, nt, etudid, decisions_ue, decision_sem):
     for ue_id in decisions_ue.keys():
         try:
             if decisions_ue[ue_id] and (
-                decisions_ue[ue_id]["code"] == ADM
+                decisions_ue[ue_id]["code"] == sco_codes_parcours.ADM
                 or (
                     CONFIG.CAPITALIZE_ALL_UES
                     and sco_codes_parcours.code_semestre_validant(decision_sem["code"])
@@ -310,13 +310,13 @@ def dict_pvjury(
 
         # Cherche la date de decision (sem ou UE) la plus récente:
         if d["decision_sem"]:
-            date = DateDMYtoISO(d["decision_sem"]["event_date"])
+            date = ndb.DateDMYtoISO(d["decision_sem"]["event_date"])
             if date > max_date:  # decision plus recente
                 max_date = date
         if d["decisions_ue"]:
             for dec_ue in d["decisions_ue"].values():
                 if dec_ue:
-                    date = DateDMYtoISO(dec_ue["event_date"])
+                    date = ndb.DateDMYtoISO(dec_ue["event_date"])
                     if date > max_date:  # decision plus recente
                         max_date = date
         # Code semestre precedent
@@ -343,7 +343,7 @@ def dict_pvjury(
         D[etudid] = d
 
     return {
-        "date": DateISOtoDMY(max_date),
+        "date": ndb.DateISOtoDMY(max_date),
         "formsemestre": sem,
         "has_prev": has_prev,
         "semestre_non_terminal": semestre_non_terminal,
@@ -434,7 +434,7 @@ def pvjury_table(
             "ue_cap": e["decisions_ue_descr"],
             "validation_parcours_code": "ADM" if e["validation_parcours"] else "AJ",
             "devenir": e["autorisations_descr"],
-            "observations": unquote(e["observation"]),
+            "observations": ndb.unquote(e["observation"]),
             "mention": e["mention"],
             "ects": str(e["sum_ects"]),
         }
diff --git a/sco_saisie_notes.py b/sco_saisie_notes.py
index 7284a0080..9602faea7 100644
--- a/sco_saisie_notes.py
+++ b/sco_saisie_notes.py
@@ -33,7 +33,6 @@ import time
 import datetime
 import psycopg2
 
-# from notesdb import *
 import sco_utils as scu
 from notes_log import log
 from TrivialFormulator import TrivialFormulator, TF
@@ -258,9 +257,7 @@ def do_evaluation_upload_xls(context, REQUEST):
             )
             raise InvalidNoteValue()
         # -- check values
-        L, invalids, withoutnotes, absents, tosuppress = _check_notes(
-            notes, E, M["module"]
-        )
+        L, invalids, withoutnotes, absents, _ = _check_notes(notes, E, M["module"])
         if len(invalids):
             diag.append(
                 "Erreur: la feuille contient %d notes invalides</p>" % len(invalids)
@@ -339,7 +336,7 @@ def do_evaluation_set_missing(
         if not NotesDB.has_key(etudid):  # pas de note
             notes.append((etudid, value))
     # Check value
-    L, invalids, withoutnotes, absents, tosuppress = _check_notes(notes, E, M["module"])
+    L, invalids, _, _, _ = _check_notes(notes, E, M["module"])
     diag = ""
     if len(invalids):
         diag = "Valeur %s invalide" % value
@@ -368,9 +365,7 @@ def do_evaluation_set_missing(
         )
     # ok
     comment = "Initialisation notes manquantes"
-    nb_changed, nb_suppress, existing_decisions = _notes_add(
-        context, authuser, evaluation_id, L, comment
-    )
+    nb_changed, _, _ = _notes_add(context, authuser, evaluation_id, L, comment)
     # news
     M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=E["moduleimpl_id"])[0]
     mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
@@ -479,7 +474,7 @@ def _notes_add(context, uid, evaluation_id, notes, comment=None, do_it=True):
         psycopg2.Timestamp, time.localtime()[:6]
     )  # datetime.datetime.now().isoformat()
     # Verifie inscription et valeur note
-    inscrits = {}.fromkeys(
+    _ = {}.fromkeys(
         sco_groups.do_evaluation_listeetuds_groups(
             context, evaluation_id, getallstudents=True, include_dems=True
         )
@@ -806,10 +801,10 @@ def feuille_saisie_notes(context, evaluation_id, group_ids=[], REQUEST=None):
     )
     groups = sco_groups.listgroups(context, groups_infos.group_ids)
     gr_title_filename = sco_groups.listgroups_filename(groups)
-    gr_title = sco_groups.listgroups_abbrev(groups)
+    # gr_title = sco_groups.listgroups_abbrev(groups)
     if None in [g["group_name"] for g in groups]:  # tous les etudiants
         getallstudents = True
-        gr_title = "tous"
+        # gr_title = "tous"
         gr_title_filename = "tous"
     else:
         getallstudents = False
@@ -1240,11 +1235,9 @@ def save_note(
     if not can_edit_notes(context, authuser, E["moduleimpl_id"]):
         result["status"] = "unauthorized"
     else:
-        L, invalids, withoutnotes, absents, tosuppress = _check_notes(
-            [(etudid, value)], E, Mod
-        )
+        L, _, _, _, _ = _check_notes([(etudid, value)], E, Mod)
         if L:
-            nbchanged, nbsuppress, existing_decisions = _notes_add(
+            nbchanged, _, existing_decisions = _notes_add(
                 context, authuser, evaluation_id, L, comment=comment, do_it=True
             )
             sco_news.add(
diff --git a/sco_semset.py b/sco_semset.py
index e655c5184..f5df60556 100644
--- a/sco_semset.py
+++ b/sco_semset.py
@@ -40,7 +40,7 @@ sem_set_list(context)
 """
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 from notes_log import log
 import sco_formsemestre
 from sco_formsemestre import ApoEtapeVDI
@@ -49,9 +49,10 @@ import sco_etape_apogee
 from sco_etape_bilan import EtapeBilan
 import sco_portal_apogee
 from gen_tables import GenTable
+from sco_exceptions import ScoValueError
 
 
-_semset_editor = EditableTable(
+_semset_editor = ndb.EditableTable(
     "notes_semset", "semset_id", ("semset_id", "title", "annee_scolaire", "sem_id")
 )
 
@@ -81,7 +82,7 @@ class SemSet(dict):
             self["title"] = L[0]["title"]
             self["annee_scolaire"] = L[0]["annee_scolaire"]
             self["sem_id"] = L[0]["sem_id"]
-            r = SimpleDictFetch(
+            r = ndb.SimpleDictFetch(
                 context,
                 "SELECT formsemestre_id FROM notes_semset_formsemestre WHERE semset_id = %(semset_id)s",
                 {"semset_id": semset_id},
@@ -150,7 +151,7 @@ class SemSet(dict):
                 % (formsemestre_id, self.semset_id)
             )
 
-        SimpleQuery(
+        ndb.SimpleQuery(
             self.context,
             "INSERT INTO notes_semset_formsemestre (formsemestre_id, semset_id) VALUES (%(formsemestre_id)s, %(semset_id)s)",
             {"formsemestre_id": formsemestre_id, "semset_id": self.semset_id},
@@ -158,7 +159,7 @@ class SemSet(dict):
         self.load_sems()  # update our list
 
     def remove(self, formsemestre_id):
-        SimpleQuery(
+        ndb.SimpleQuery(
             self.context,
             "DELETE FROM notes_semset_formsemestre WHERE semset_id=%(semset_id)s AND formsemestre_id=%(formsemestre_id)s",
             {"formsemestre_id": formsemestre_id, "semset_id": self.semset_id},
diff --git a/sco_synchro_etuds.py b/sco_synchro_etuds.py
index c9070a2b5..14fe953a3 100644
--- a/sco_synchro_etuds.py
+++ b/sco_synchro_etuds.py
@@ -31,9 +31,11 @@
 import time
 import pprint
 
-from sco_utils import ScoEtudInscrit, annee_scolaire_debut, log, ScoValueError
+import sco_utils as scu
+from sco_permissions import ScoEtudInscrit
+from sco_exceptions import ScoValueError
 from notesdb import ScoDocCursor
-
+from notes_log import log
 import sco_portal_apogee
 import sco_inscr_passage
 import scolars
@@ -111,7 +113,7 @@ def formsemestre_synchro_etuds(
 
     if anneeapogee == None:  # année d'inscription par défaut
         anneeapogee = str(
-            annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"])
+            scu.annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"])
         )
 
     if type(etuds) == type(""):
@@ -791,7 +793,9 @@ def formsemestre_import_etud_admission(
     # Essaie de recuperer les etudiants des étapes, car
     # la requete get_inscrits_etape est en général beaucoup plus
     # rapide que les requetes individuelles get_etud_apogee
-    anneeapogee = str(annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"]))
+    anneeapogee = str(
+        scu.annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"])
+    )
     apo_etuds = {}  # nip : etud apo
     for etape in sem["etapes"]:
         etudsapo = sco_portal_apogee.get_inscrits_etape(
diff --git a/sco_tag_module.py b/sco_tag_module.py
index e886ccba0..d1fe3fdc6 100644
--- a/sco_tag_module.py
+++ b/sco_tag_module.py
@@ -34,9 +34,13 @@
    Pour l'UI, voir https://goodies.pixabay.com/jquery/tag-editor/demo.html
 """
 
+import types
+
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 from notes_log import log
+from sco_exceptions import ScoValueError, AccessDenied
+from sco_permissions import ScoEditFormationTags, ScoChangeFormation
 
 # Opérations à implementer:
 #  + liste des modules des formations de code donné (formation_code) avec ce tag
@@ -66,7 +70,7 @@ class ScoTag:
         self.title = title.strip()
         if not self.title:
             raise ScoValueError("invalid empty tag")
-        r = SimpleDictFetch(
+        r = ndb.SimpleDictFetch(
             context,
             "SELECT * FROM " + self.tag_table + " WHERE title = %(title)s",
             {"title": self.title},
@@ -77,8 +81,10 @@ class ScoTag:
             # Create new tag:
             log("creating new tag: %s" % self.title)
             cnx = context.GetDBConnexion()
-            oid = DBInsertDict(cnx, self.tag_table, {"title": self.title}, commit=True)
-            self.tag_id = SimpleDictFetch(
+            oid = ndb.DBInsertDict(
+                cnx, self.tag_table, {"title": self.title}, commit=True
+            )
+            self.tag_id = ndb.SimpleDictFetch(
                 context,
                 "SELECT tag_id FROM " + self.tag_table + " WHERE oid=%(oid)s",
                 {"oid": oid},
@@ -94,7 +100,7 @@ class ScoTag:
         This object should not be used after this call !
         """
         args = {"tag_id": self.tag_id}
-        SimpleQuery(
+        ndb.SimpleQuery(
             self.context,
             "DELETE FROM " + self.tag_table + " t WHERE t.tag_id = %(tag_id)s",
             args,
@@ -103,7 +109,7 @@ class ScoTag:
     def tag_object(self, object_id):
         """Associate tag to given object"""
         args = {self.obj_colname: object_id, "tag_id": self.tag_id}
-        r = SimpleDictFetch(
+        r = ndb.SimpleDictFetch(
             self.context,
             "SELECT * FROM "
             + self.assoc_table
@@ -117,7 +123,7 @@ class ScoTag:
         if not r:
             log("tag %s with %s" % (object_id, self.title))
             cnx = self.context.GetDBConnexion()
-            DBInsertDict(cnx, self.assoc_table, args, commit=True)
+            ndb.DBInsertDict(cnx, self.assoc_table, args, commit=True)
 
     def remove_tag_from_object(self, object_id):
         """Remove tag from module.
@@ -126,7 +132,7 @@ class ScoTag:
         """
         log("removing tag %s from %s" % (self.title, object_id))
         args = {"object_id": object_id, "tag_id": self.tag_id}
-        SimpleQuery(
+        ndb.SimpleQuery(
             self.context,
             "DELETE FROM  "
             + self.assoc_table
@@ -135,7 +141,7 @@ class ScoTag:
             + " = %(object_id)s AND a.tag_id = %(tag_id)s",
             args,
         )
-        r = SimpleDictFetch(
+        r = ndb.SimpleDictFetch(
             self.context,
             """SELECT * FROM notes_modules_tags mt WHERE tag_id = %(tag_id)s
             """,
@@ -143,7 +149,7 @@ class ScoTag:
         )
         if not r:
             # tag no more used, delete
-            SimpleQuery(
+            ndb.SimpleQuery(
                 self.context,
                 """DELETE FROM notes_tags t WHERE t.tag_id = %(tag_id)s""",
                 args,
@@ -162,7 +168,7 @@ class ModuleTag(ScoTag):
         args = {"tag_id": self.tag_id}
         if not formation_code:
             # tous les modules de toutes les formations !
-            r = SimpleDictFetch(
+            r = ndb.SimpleDictFetch(
                 self.context,
                 "SELECT "
                 + self.obj_colname
@@ -174,7 +180,7 @@ class ModuleTag(ScoTag):
         else:
             args["formation_code"] = formation_code
 
-            r = SimpleDictFetch(
+            r = ndb.SimpleDictFetch(
                 self.context,
                 """SELECT mt.module_id 
                 FROM notes_modules_tags mt, notes_modules m, notes_formations f
@@ -197,7 +203,7 @@ def module_tag_search(context, term, REQUEST=None):
     if not ALPHANUM_EXP.match(term.decode(SCO_ENCODING)):
         data = []
     else:
-        r = SimpleDictFetch(
+        r = ndb.SimpleDictFetch(
             context,
             "SELECT title FROM notes_tags WHERE title LIKE %(term)s",
             {"term": term + "%"},
@@ -209,7 +215,7 @@ def module_tag_search(context, term, REQUEST=None):
 
 def module_tag_list(context, module_id=""):
     """les noms de tags associés à ce module"""
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         """SELECT t.title
           FROM notes_modules_tags mt, notes_tags t
@@ -237,7 +243,7 @@ def module_tag_set(context, module_id="", taglist=[], REQUEST=None):
     #
     if not taglist:
         taglist = []
-    elif type(taglist) == StringType:
+    elif type(taglist) == types.StringType:
         taglist = taglist.split(",")
     taglist = [t.strip() for t in taglist]
     log("module_tag_set: module_id=%s taglist=%s" % (module_id, taglist))
diff --git a/sco_trombino.py b/sco_trombino.py
index c4beb2296..09b0b96eb 100644
--- a/sco_trombino.py
+++ b/sco_trombino.py
@@ -38,6 +38,7 @@ import tempfile
 
 from notes_log import log
 from sco_utils import *
+from sco_exceptions import ScoValueError
 import scolars
 import sco_photos
 import sco_formsemestre
diff --git a/sco_ue_external.py b/sco_ue_external.py
index ba7bb931d..3906d10cf 100644
--- a/sco_ue_external.py
+++ b/sco_ue_external.py
@@ -54,7 +54,7 @@ Solution proposée (nov 2014):
 
 """
 
-from notesdb import *
+import notesdb as ndb
 from sco_utils import *
 from notes_log import log
 import sco_formsemestre
@@ -62,6 +62,8 @@ import sco_moduleimpl
 import sco_edit_ue
 import sco_saisie_notes
 import sco_codes_parcours
+from sco_permissions import ScoImplement
+from sco_exceptions import AccessDenied, ScoValueError
 
 
 def external_ue_create(
@@ -69,7 +71,7 @@ def external_ue_create(
     formsemestre_id,
     titre="",
     acronyme="",
-    ue_type=UE_STANDARD,
+    ue_type=sco_codes_parcours.UE_STANDARD,
     ects=0.0,
     REQUEST=None,
 ):
@@ -178,7 +180,7 @@ def get_existing_external_ue(context, formation_id):
 
 def get_external_moduleimpl_id(context, formsemestre_id, ue_id):
     "moduleimpl correspondant à l'UE externe indiquée de ce formsemestre"
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         """
     SELECT moduleimpl_id FROM notes_moduleimpl mi, notes_modules mo
@@ -235,7 +237,7 @@ def external_ue_create_form(context, formsemestre_id, etudid, REQUEST=None):
     parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
     ue_types = parcours.ALLOWED_UE_TYPES
     ue_types.sort()
-    ue_types_names = [UE_TYPE_NAME[k] for k in ue_types]
+    ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types]
     ue_types = [str(x) for x in ue_types]
 
     if existing_external_ue:
diff --git a/sco_undo_notes.py b/sco_undo_notes.py
index 43f6e7306..0e6df989f 100644
--- a/sco_undo_notes.py
+++ b/sco_undo_notes.py
@@ -49,7 +49,7 @@ import datetime
 from intervals import intervalmap
 
 from sco_utils import *
-from notesdb import *
+import notesdb as ndb
 from notes_log import log
 from gen_tables import GenTable
 import sco_formsemestre
@@ -166,7 +166,7 @@ def formsemestre_list_saisies_notes(
 ):
     """Table listant toutes les operations de saisies de notes, dans toutes les evaluations du semestre."""
     sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
-    r = SimpleDictFetch(
+    r = ndb.SimpleDictFetch(
         context,
         """select i.nom, n.*, mod.titre, e.description, e.jour from notes_notes n, notes_evaluation e, notes_moduleimpl m, notes_modules mod, identite i where m.moduleimpl_id = e.moduleimpl_id and m.module_id = mod.module_id and e.evaluation_id=n.evaluation_id and i.etudid=n.etudid and m.formsemestre_id=%(formsemestre_id)s order by date desc""",
         {"formsemestre_id": formsemestre_id},
@@ -212,7 +212,7 @@ def get_note_history(context, evaluation_id, etudid, REQUEST=None, fmt=""):
     [ { 'value', 'date', 'comment', 'uid' } ]
     """
     cnx = context.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
 
     # Valeur courante
     cursor.execute(
diff --git a/sco_utils.py b/sco_utils.py
index b7ff1e538..eb9f86ca8 100644
--- a/sco_utils.py
+++ b/sco_utils.py
@@ -28,30 +28,19 @@
 
 """ Common definitions
 """
-import pdb
-import os, sys, copy, re
-import pprint
-import traceback
-from types import (
-    StringType,
-    IntType,
-    FloatType,
-    UnicodeType,
-    ListType,
-    TupleType,
-    InstanceType,
-)
-import operator, bisect
-import collections
+import os
+import sys
+import copy
+import re
+
+import bisect
+import types
 import numbers
 import thread
-import urllib
 import urllib2
-import socket
 import xml.sax.saxutils
-import xml, xml.dom.minidom
-import time, datetime, cgi
-import mx
+import time
+import datetime
 
 try:
     import six
@@ -59,7 +48,7 @@ try:
     STRING_TYPES = six.string_types
 except ImportError:
     # fallback for very old ScoDoc instances
-    STRING_TYPES = StringType
+    STRING_TYPES = types.StringType
 
 from PIL import Image as PILImage
 
@@ -72,12 +61,13 @@ from VERSION import SCOVERSION
 import VERSION
 
 from SuppressAccents import suppression_diacritics
-from sco_exceptions import *
-from sco_permissions import *
+
+# from sco_exceptions import *
+# from sco_permissions import *
 from TrivialFormulator import TrivialFormulator, TF, tf_error_message
 from notes_log import log, logCallStack
 
-from sco_codes_parcours import *
+from sco_codes_parcours import NOTES_TOLERANCE, CODES_EXPL
 
 # ----- CALCUL ET PRESENTATION DES NOTES
 NOTES_PRECISION = 1e-4  # evite eventuelles erreurs d'arrondis
@@ -140,7 +130,7 @@ def fmt_note(val, note_max=None, keep_numeric=False):
         return "EXC"  # excuse, note neutralise
     if val == NOTES_ATTENTE:
         return "ATT"  # attente, note neutralisee
-    if type(val) == FloatType or type(val) == IntType:
+    if type(val) == types.FloatType or type(val) == types.IntType:
         if note_max != None and note_max > 0:
             val = val * 20.0 / note_max
         if keep_numeric:
@@ -382,7 +372,7 @@ def unescape_html_dict(d):
         indices = range(len(d))
     for k in indices:
         v = d[k]
-        if type(v) == StringType:
+        if type(v) == types.StringType:
             d[k] = unescape_html(v)
         elif isiterable(v):
             unescape_html_dict(v)
@@ -427,11 +417,11 @@ def simple_dictlist2xml(dictlist, doc=None, tagname=None, quote=False):
         raise ValueError("invalid empty tagname !")
     if not doc:
         doc = jaxml.XML_document(encoding=SCO_ENCODING)
-    scalar_types = [StringType, UnicodeType, IntType, FloatType]
+    scalar_types = [types.StringType, types.UnicodeType, types.IntType, types.FloatType]
     for d in dictlist:
         doc._push()
         if (
-            type(d) == InstanceType or type(d) in scalar_types
+            type(d) == types.InstanceType or type(d) in scalar_types
         ):  # pour ApoEtapeVDI et listes de chaines
             getattr(doc, tagname)(code=str(d))
         else:
@@ -448,7 +438,7 @@ def simple_dictlist2xml(dictlist, doc=None, tagname=None, quote=False):
                     [(k, v) for (k, v) in d.items() if type(v) in scalar_types]
                 )
             getattr(doc, tagname)(**d_scalar)
-            d_list = dict([(k, v) for (k, v) in d.items() if type(v) == ListType])
+            d_list = dict([(k, v) for (k, v) in d.items() if type(v) == types.ListType])
             if d_list:
                 for (k, v) in d_list.items():
                     simple_dictlist2xml(v, doc=doc, tagname=k, quote=quote)
@@ -580,7 +570,7 @@ def sendJSON(REQUEST, data):
 
 
 def sendXML(REQUEST, data, tagname=None, force_outer_xml_tag=True):
-    if type(data) != ListType:
+    if type(data) != types.ListType:
         data = [data]  # always list-of-dicts
     if force_outer_xml_tag:
         root_tagname = tagname + "_list"
@@ -776,7 +766,7 @@ def scodoc_html2txt(html):
 
 def is_valid_mail(email):
     """True if well-formed email address"""
-    return re.match("^.+@.+\..{2,3}$", email)
+    return re.match(r"^.+@.+\..{2,3}$", email)
 
 
 ICONSIZES = {}  # name : (width, height) cache image sizes
diff --git a/sco_zope.py b/sco_zope.py
index 73f581293..2de7484f5 100644
--- a/sco_zope.py
+++ b/sco_zope.py
@@ -27,6 +27,8 @@
 
 """Imports et configuration des composants Zope
 """
+# Avoid pylint warnings when linting without Zope
+# pylint: skip-file
 
 from OFS.SimpleItem import Item  # Basic zope object
 from OFS.PropertyManager import PropertyManager  # provide the 'Properties' tab with the
diff --git a/scolog.py b/scolog.py
index 35ce038c7..1ee852da5 100644
--- a/scolog.py
+++ b/scolog.py
@@ -28,7 +28,7 @@
 
 import pdb, os, sys
 from sco_exceptions import *
-from notesdb import *
+import notesdb as ndb
 from notes_log import retreive_request
 
 
@@ -46,8 +46,8 @@ def logdb(REQUEST=None, cnx=None, method=None, etudid=None, msg=None, commit=Tru
     else:
         args = {"authenticated_user": None, "remote_addr": None, "remote_host": None}
     args.update({"method": method, "etudid": etudid, "msg": msg})
-    quote_dict(args)
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    ndb.quote_dict(args)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         "insert into scolog (authenticated_user,remote_addr,remote_host,method,etudid,msg) values (%(authenticated_user)s,%(remote_addr)s,%(remote_host)s,%(method)s,%(etudid)s,%(msg)s)",
         args,
@@ -58,7 +58,7 @@ def logdb(REQUEST=None, cnx=None, method=None, etudid=None, msg=None, commit=Tru
 
 def loglist(cnx, method=None, authenticated_user=None):
     """List of events logged for these method and user"""
-    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
     cursor.execute(
         "select * from scolog where method=%(method)s and authenticated_user=%(authenticated_user)s",
         {"method": method, "authenticated_user": authenticated_user},