forked from ScoDoc/ScoDoc
bug fixes
This commit is contained in:
parent
5d521b9cfa
commit
be868497ff
12
README.md
12
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)
|
## 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_APP=scodoc.py
|
||||||
export FLASK_ENV=development
|
export FLASK_ENV=development
|
||||||
flask run --host=0.0.0.0
|
flask run --host=0.0.0.0
|
||||||
|
|
||||||
## Tests
|
## Tests unitaires
|
||||||
|
|
||||||
pytest tests/unit/test_users.py
|
pytest tests/unit
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
@ -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]
|
|
@ -33,8 +33,7 @@
|
|||||||
|
|
||||||
Pour l'UI, voir https://goodies.pixabay.com/jquery/tag-editor/demo.html
|
Pour l'UI, voir https://goodies.pixabay.com/jquery/tag-editor/demo.html
|
||||||
"""
|
"""
|
||||||
|
import http
|
||||||
import types
|
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
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")
|
a string with tag names separated by commas ("un;deux")
|
||||||
or a list of strings (["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
|
if REQUEST: # called from Web
|
||||||
authuser = REQUEST.AUTHENTICATED_USER
|
authuser = REQUEST.AUTHENTICATED_USER
|
||||||
tag_editable = authuser.has_permission(
|
tag_editable = authuser.has_permission(
|
||||||
@ -246,7 +245,7 @@ def module_tag_set(context, module_id="", taglist=[], REQUEST=None):
|
|||||||
#
|
#
|
||||||
if not taglist:
|
if not taglist:
|
||||||
taglist = []
|
taglist = []
|
||||||
elif isinstance(taglist, bytes):
|
elif isinstance(taglist, str):
|
||||||
taglist = taglist.split(",")
|
taglist = taglist.split(",")
|
||||||
taglist = [t.strip() for t in taglist]
|
taglist = [t.strip() for t in taglist]
|
||||||
log("module_tag_set: module_id=%s taglist=%s" % (module_id, 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 = ModuleTag(context, tagname)
|
||||||
t.remove_tag_from_object(module_id)
|
t.remove_tag_from_object(module_id)
|
||||||
|
|
||||||
|
return "", http.HTTPStatus.NO_CONTENT
|
||||||
|
|
||||||
|
|
||||||
def get_etud_tagged_modules(context, etudid, tagname):
|
def get_etud_tagged_modules(context, etudid, tagname):
|
||||||
"""Liste d'infos sur les modules de ce semestre avec ce tag.
|
"""Liste d'infos sur les modules de ce semestre avec ce tag.
|
||||||
|
@ -251,13 +251,14 @@ def _user_list(user_name):
|
|||||||
|
|
||||||
def user_info(user_name=None, user=None):
|
def user_info(user_name=None, user=None):
|
||||||
"""Dict avec infos sur l'utilisateur (qui peut ne pas etre dans notre base).
|
"""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:
|
if user_name:
|
||||||
info = _user_list(user_name)
|
info = _user_list(user_name)
|
||||||
else:
|
else:
|
||||||
info = [user.copy()]
|
info = user.to_dict()
|
||||||
user_name = user["user_name"]
|
user_name = user.user_name
|
||||||
|
|
||||||
if not info:
|
if not info:
|
||||||
# special case: user is not in our database
|
# 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:
|
if not email:
|
||||||
return False, "vous devriez indiquer le mail de l'utilisateur créé !"
|
return False, "vous devriez indiquer le mail de l'utilisateur créé !"
|
||||||
# ce login existe ?
|
# ce login existe ?
|
||||||
users = _user_list(user_name)
|
user = _user_list(user_name)
|
||||||
if edit and not users: # safety net, le user_name ne devrait pas changer
|
if edit and not user: # safety net, le user_name ne devrait pas changer
|
||||||
return False, "identifiant %s inexistant" % user_name
|
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
|
return False, "identifiant %s déjà utilisé" % user_name
|
||||||
|
|
||||||
# Des noms/prénoms semblables existent ?
|
# Des noms/prénoms semblables existent ?
|
||||||
|
@ -366,7 +366,10 @@ sco_publish(
|
|||||||
sco_publish("/module_list", sco_edit_module.module_list, Permission.ScoView)
|
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_search", sco_tag_module.module_tag_search, Permission.ScoView)
|
||||||
sco_publish(
|
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 = """<p class="help">Taper le début du nom de l'enseignant.</p>"""
|
help_str = """<p class="help">Taper le début du nom de l'enseignant.</p>"""
|
||||||
# Liste des enseignants avec forme pour affichage / saisie avec suggestion
|
# 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)"
|
login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)"
|
||||||
for u in userlist:
|
for u in userlist:
|
||||||
login2display[u["user_name"]] = u["nomplogin"]
|
login2display[u["user_name"]] = u["nomplogin"]
|
||||||
|
@ -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
|
|
Loading…
Reference in New Issue
Block a user