diff --git a/README.md b/README.md index f3dba3b90..11b492913 100644 --- a/README.md +++ b/README.md @@ -111,13 +111,21 @@ Pour créer un utilisateur "super admin", c'est à dire admin dans tous les dép ## Lancement serveur (développement, sur VM Linux) +En tant qu'utilisateur `www-data` (pour avoir accès aux bases départements de ScoDoc7): + + 1. Lancer memcached: + + memcached + + 2. Dans un autre terminal, lancer le serveur: + export FLASK_APP=scodoc.py export FLASK_ENV=development flask run --host=0.0.0.0 -## Tests +## Tests unitaires - pytest tests/unit/test_users.py + pytest tests/unit # TODO diff --git a/app/scodoc/sco_core.py b/app/scodoc/sco_core.py deleted file mode 100644 index 119415d57..000000000 --- a/app/scodoc/sco_core.py +++ /dev/null @@ -1,196 +0,0 @@ -# -*- mode: python -*- -# -*- coding: utf-8 -*- - -"""Gestion des caches - XXX à déplacer dans sco_cache ? - - Ré-écriture en cours pour ScoDoc8, utilise flask_caching et memcached - - ScoDoc est maintenant multiprocessus / mono-thread, avec un cache en mémoire partagé. -""" - -import time -import six.moves._thread - - -from scodoc_manager import sco_mgr -import app.scodoc.sco_utils as scu -from app.scodoc.notes_log import log -from app.scodoc.sco_exceptions import NoteProcessError -from app.scodoc import sco_cache - - -# -# Cache global: chaque instance, repérée par sa connexion db, a un cache -# qui est recréé à la demande -# -NOTES_CACHE_INST = {} # { db_cnx_string : CacheNotesTable instance } - -# XXX OBSOLETE... XXX - - -class CacheNotesTable(object): - """gestion rudimentaire de cache pour les NotesTables""" - - def __init__(self): - log("new CacheTable (id=%s)" % id(self)) - # - self.lock = six.moves._thread.allocate_lock() - self.owner_thread = None # thread owning this cache - self.nref = 0 - # Cache des NotesTables - self.cache = {} # { formsemestre_id : NoteTable instance } - # Cache des classeur PDF (bulletins) - self.pdfcache = {} # { (formsemestre_id, version) : (filename, pdfdoc) } - # Listeners: - self.listeners = scu.DictDefault( - defaultvalue={} - ) # {formsemestre_id : {listener_id : callback }} - - def acquire(self): # OBSOLETE - "If this thread does not own the cache, acquire the lock" - if six.moves._thread.get_ident() != self.owner_thread: - if self.lock.locked(): - log( - "acquire: ident=%s waiting for lock" % six.moves._thread.get_ident() - ) # XXX debug - self.lock.acquire() - self.owner_thread = six.moves._thread.get_ident() - if self.owner_thread is None: # bug catching - log("WARNING: None thread id !") - self.nref += 1 - # log('nref=%d' % self.nref) - - def release(self): # OBSOLETE - "Release the lock" - cur_owner_thread = self.owner_thread - # log('release: ident=%s (nref=%d)' % (thread.get_ident(), self.nref)) - self.nref -= 1 - if self.nref == 0: - self.lock.release() - self.owner_thread = None - # Debug: - if six.moves._thread.get_ident() != cur_owner_thread: - log( - "WARNING: release: ident=%s != owner=%s nref=%d" - % (six.moves._thread.get_ident(), cur_owner_thread, self.nref) - ) - raise NoteProcessError("problem with notes cache") - - def get_NotesTable(self, context, formsemestre_id): # > - from app.scodoc import notes_table - - try: - self.acquire() - if formsemestre_id in self.cache: - # log('cache hit %s (id=%s, thread=%s)' - # % (formsemestre_id, id(self), thread.get_ident())) - return self.cache[formsemestre_id] - else: - t0 = time.time() - nt = notes_table.NotesTable(context, formsemestre_id) - dt = time.time() - t0 - self.cache[formsemestre_id] = nt - log( - "caching formsemestre_id=%s (id=%s) (%gs)" - % (formsemestre_id, id(self), dt) - ) - return nt - finally: - self.release() - - def inval_cache(self, context, formsemestre_id=None, pdfonly=False): # > - "expire cache pour un semestre (ou tous si pas d'argument)" - from app.scodoc import sco_parcours_dut - - XXX - - def store_bulletins_pdf(self, formsemestre_id, version, filename, pdfdoc): - "cache pdf data" - log( - "caching PDF formsemestre_id=%s version=%s (id=%s)" - % (formsemestre_id, version, id(self)) - ) - try: - self.acquire() - self.pdfcache[(formsemestre_id, version)] = (filename, pdfdoc) - finally: - self.release() - - def get_bulletins_pdf(self, formsemestre_id, version): - "returns cached PDF, or None if not in the cache" - try: - self.acquire() - if not hasattr(self, "pdfcache"): - self.pdfcache = {} # fix for old zope instances... - r = self.pdfcache.get((formsemestre_id, version), None) - if r: - log( - "get_bulletins_pdf(%s): cache hit %s (id=%s, thread=%s)" - % ( - version, - formsemestre_id, - id(self), - six.moves._thread.get_ident(), - ) - ) - return r - finally: - self.release() - - def add_listener(self, callback, formsemestre_id, listener_id): - """Add a "listener": a function called each time a formsemestre is modified""" - self.listeners[formsemestre_id][listener_id] = callback - - def remove_listener(self, formsemestre_id, listener_id): # UNUSED - """Remove a listener. - May raise exception if does not exists. - """ - del self.listeners[formsemestre_id][listener_id] - - def _call_listeners(self, formsemestre_id): - for listener_id, callback in self.listeners[formsemestre_id].items(): - callback(listener_id) - - def _call_all_listeners(self): - for formsemestre_id in self.listeners: - self._call_listeners(formsemestre_id) - - -def get_notes_cache(context): - "returns CacheNotesTable instance for us" - u = sco_mgr.get_db_uri() # identifie le dept de facon unique - if u not in NOTES_CACHE_INST: - log("getNotesCache: creating cache for %s" % u) - NOTES_CACHE_INST[u] = CacheNotesTable() - return NOTES_CACHE_INST[u] - - -def inval_cache( - context, formsemestre_id=None, pdfonly=False, formsemestre_id_list=None -): # > - "expire cache pour un semestre (ou tous si pas d'argument)" - if formsemestre_id_list: - for formsemestre_id in formsemestre_id_list: - get_notes_cache(context).inval_cache( - context, formsemestre_id=formsemestre_id, pdfonly=pdfonly - ) - # Affecte aussi cache inscriptions - get_formsemestre_inscription_cache(context).inval_cache(key=formsemestre_id) - else: - get_notes_cache(context).inval_cache( - context, formsemestre_id=formsemestre_id, pdfonly=pdfonly - ) - # Affecte aussi cache inscriptions - get_formsemestre_inscription_cache(context).inval_cache(key=formsemestre_id) - - -# Cache inscriptions semestres -def get_formsemestre_inscription_cache(context): - u = sco_mgr.get_db_uri() - if u in CACHE_formsemestre_inscription: - return CACHE_formsemestre_inscription[u] - else: - log("get_formsemestre_inscription_cache: new simpleCache") - CACHE_formsemestre_inscription[u] = sco_cache.simpleCache() - return CACHE_formsemestre_inscription[u] \ No newline at end of file diff --git a/app/scodoc/sco_tag_module.py b/app/scodoc/sco_tag_module.py index 55c18bdcc..46c5569dd 100644 --- a/app/scodoc/sco_tag_module.py +++ b/app/scodoc/sco_tag_module.py @@ -33,8 +33,7 @@ Pour l'UI, voir https://goodies.pixabay.com/jquery/tag-editor/demo.html """ - -import types +import http import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb @@ -235,7 +234,7 @@ def module_tag_set(context, module_id="", taglist=[], REQUEST=None): a string with tag names separated by commas ("un;deux") or a list of strings (["un", "deux"]) """ - # We check permission here to allow old Admins (withn only ScoChangeFormation perm) + # We check permission here to allow old Admins (with only ScoChangeFormation perm) if REQUEST: # called from Web authuser = REQUEST.AUTHENTICATED_USER tag_editable = authuser.has_permission( @@ -246,7 +245,7 @@ def module_tag_set(context, module_id="", taglist=[], REQUEST=None): # if not taglist: taglist = [] - elif isinstance(taglist, bytes): + elif isinstance(taglist, str): taglist = taglist.split(",") taglist = [t.strip() for t in taglist] log("module_tag_set: module_id=%s taglist=%s" % (module_id, taglist)) @@ -267,6 +266,8 @@ def module_tag_set(context, module_id="", taglist=[], REQUEST=None): t = ModuleTag(context, tagname) t.remove_tag_from_object(module_id) + return "", http.HTTPStatus.NO_CONTENT + def get_etud_tagged_modules(context, etudid, tagname): """Liste d'infos sur les modules de ce semestre avec ce tag. diff --git a/app/scodoc/sco_users.py b/app/scodoc/sco_users.py index 0d9249fda..29267c603 100644 --- a/app/scodoc/sco_users.py +++ b/app/scodoc/sco_users.py @@ -251,13 +251,14 @@ def _user_list(user_name): def user_info(user_name=None, user=None): """Dict avec infos sur l'utilisateur (qui peut ne pas etre dans notre base). - Si user_name est specifie, interroge la BD. Sinon, user doit etre un dict. + Si user_name est specifie, interroge la BD. Sinon, user doit etre une instance + de User. """ if user_name: info = _user_list(user_name) else: - info = [user.copy()] - user_name = user["user_name"] + info = user.to_dict() + user_name = user.user_name if not info: # special case: user is not in our database @@ -391,10 +392,10 @@ def check_modif_user(edit, user_name="", nom="", prenom="", email="", roles=[]): if not email: return False, "vous devriez indiquer le mail de l'utilisateur créé !" # ce login existe ? - users = _user_list(user_name) - if edit and not users: # safety net, le user_name ne devrait pas changer + user = _user_list(user_name) + if edit and not user: # safety net, le user_name ne devrait pas changer return False, "identifiant %s inexistant" % user_name - if not edit and users: + if not edit and user: return False, "identifiant %s déjà utilisé" % user_name # Des noms/prénoms semblables existent ? diff --git a/app/views/notes.py b/app/views/notes.py index d6935fec2..5e5d8ba54 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -366,7 +366,10 @@ sco_publish( sco_publish("/module_list", sco_edit_module.module_list, Permission.ScoView) sco_publish("/module_tag_search", sco_tag_module.module_tag_search, Permission.ScoView) sco_publish( - "/module_tag_set", sco_tag_module.module_tag_set, Permission.ScoEditFormationTags + "/module_tag_set", + sco_tag_module.module_tag_set, + Permission.ScoEditFormationTags, + methods=["GET", "POST"], ) # @@ -796,7 +799,7 @@ def edit_moduleimpl_resp(context, REQUEST, moduleimpl_id): ] help_str = """
Taper le début du nom de l'enseignant.
""" # Liste des enseignants avec forme pour affichage / saisie avec suggestion - userlist = [sco_users.user_info(u) for u in sco_users.get_user_list()] + userlist = [sco_users.user_info(user=u) for u in sco_users.get_user_list()] login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" for u in userlist: login2display[u["user_name"]] = u["nomplogin"] diff --git a/requirements-2.7.txt b/requirements-2.7.txt deleted file mode 100644 index 4ce146c0b..000000000 --- a/requirements-2.7.txt +++ /dev/null @@ -1,58 +0,0 @@ -alembic==1.5.5 -astroid==1.6.6 -attrdict==2.0.1 -Babel==2.9.0 -backports.functools-lru-cache==1.6.4 -blinker==1.4 -certifi==2021.5.30 -chardet==4.0.0 -click==7.1.2 -configparser==4.0.2 -cracklib==2.9.3 -dnspython==1.16.0 -dominate==2.6.0 -email-validator==1.1.2 -enum34==1.1.10 -Flask==1.1.4 -Flask-Babel==2.0.0 -Flask-Bootstrap==3.3.7.1 -Flask-Login==0.5.0 -Flask-Mail==0.9.1 -Flask-Migrate==2.7.0 -Flask-Moment==0.11.0 -Flask-SQLAlchemy==2.4.4 -Flask-WTF==0.14.3 -futures==3.3.0 -icalendar==4.0.7 -idna==2.10 -isort==4.3.21 -itsdangerous==1.1.0 -Jinja2==2.11.2 -lazy-object-proxy==1.6.0 -Mako==1.1.4 -MarkupSafe==1.1.1 -mccabe==0.6.1 -modernize==0.7 -Pillow==6.2.2 -pkg-resources==0.0.0 -psycopg2==2.8.6 -pyExcelerator==0.6.3a0 -PyJWT==1.7.1 -pylint==1.9.5 -PyRSS2Gen==1.1 -python-dateutil==2.8.1 -python-dotenv==0.15.0 -python-editor==1.0.4 -pytz==2021.1 -reportlab==3.5.59 -requests==2.25.1 -singledispatch==3.6.2 -six==1.15.0 -SQLAlchemy==1.3.23 -stripogram==1.5 -typing==3.7.4.3 -urllib3==1.26.5 -visitor==0.1.3 -Werkzeug==1.0.1 -wrapt==1.12.1 -WTForms==2.3.3