mirror of https://gitlab.crans.org/bde/nk20
Optimize permissions, use memoization
This commit is contained in:
parent
3f5faa0b05
commit
5c9c0bbc2a
|
@ -50,7 +50,7 @@ def save_object(sender, instance, **kwargs):
|
||||||
if instance._meta.label_lower in EXCLUDED:
|
if instance._meta.label_lower in EXCLUDED:
|
||||||
return
|
return
|
||||||
|
|
||||||
if hasattr(instance, "_force_save"):
|
if hasattr(instance, "_no_log"):
|
||||||
return
|
return
|
||||||
|
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
|
@ -109,7 +109,7 @@ def delete_object(sender, instance, **kwargs):
|
||||||
if instance._meta.label_lower in EXCLUDED:
|
if instance._meta.label_lower in EXCLUDED:
|
||||||
return
|
return
|
||||||
|
|
||||||
if hasattr(instance, "_force_delete"):
|
if hasattr(instance, "_no_log"):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.db.models import Q, F
|
||||||
from note.models import Note, NoteUser, NoteClub, NoteSpecial
|
from note.models import Note, NoteUser, NoteClub, NoteSpecial
|
||||||
from note_kfet.middlewares import get_current_session
|
from note_kfet.middlewares import get_current_session
|
||||||
from member.models import Membership, Club
|
from member.models import Membership, Club
|
||||||
|
from .decorators import memoize
|
||||||
|
|
||||||
from .models import Permission
|
from .models import Permission
|
||||||
|
|
||||||
|
@ -22,6 +23,28 @@ class PermissionBackend(ModelBackend):
|
||||||
supports_anonymous_user = False
|
supports_anonymous_user = False
|
||||||
supports_inactive_user = False
|
supports_inactive_user = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@memoize
|
||||||
|
def get_raw_permissions(user, t):
|
||||||
|
"""
|
||||||
|
Query permissions of a certain type for a user, then memoize it.
|
||||||
|
:param user: The owner of the permissions
|
||||||
|
:param t: The type of the permissions: view, change, add or delete
|
||||||
|
:return: The queryset of the permissions of the user (memoized) grouped by clubs
|
||||||
|
"""
|
||||||
|
if isinstance(user, AnonymousUser):
|
||||||
|
# Unauthenticated users have no permissions
|
||||||
|
return Permission.objects.none()
|
||||||
|
|
||||||
|
return Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \
|
||||||
|
.filter(
|
||||||
|
rolepermissions__role__membership__user=user,
|
||||||
|
rolepermissions__role__membership__date_start__lte=datetime.date.today(),
|
||||||
|
rolepermissions__role__membership__date_end__gte=datetime.date.today(),
|
||||||
|
type=t,
|
||||||
|
mask__rank__lte=get_current_session().get("permission_mask", 0),
|
||||||
|
).distinct('club', 'pk')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def permissions(user, model, type):
|
def permissions(user, model, type):
|
||||||
"""
|
"""
|
||||||
|
@ -31,18 +54,16 @@ class PermissionBackend(ModelBackend):
|
||||||
:param type: The type of the permissions: view, change, add or delete
|
:param type: The type of the permissions: view, change, add or delete
|
||||||
:return: A generator of the requested permissions
|
:return: A generator of the requested permissions
|
||||||
"""
|
"""
|
||||||
for permission in Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \
|
clubs = {}
|
||||||
.filter(
|
|
||||||
rolepermissions__role__membership__user=user,
|
for permission in PermissionBackend.get_raw_permissions(user, type):
|
||||||
rolepermissions__role__membership__date_start__lte=datetime.date.today(),
|
if not isinstance(model.model_class()(), permission.model.model_class()) or not permission.club:
|
||||||
rolepermissions__role__membership__date_end__gte=datetime.date.today(),
|
|
||||||
model__app_label=model.app_label, # For polymorphic models, we don't filter on model type
|
|
||||||
type=type,
|
|
||||||
).all():
|
|
||||||
if not isinstance(model, permission.model.__class__) or not permission.club:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
club = Club.objects.get(pk=permission.club)
|
if permission.club not in clubs:
|
||||||
|
clubs[permission.club] = club = Club.objects.get(pk=permission.club)
|
||||||
|
else:
|
||||||
|
club = clubs[permission.club]
|
||||||
permission = permission.about(
|
permission = permission.about(
|
||||||
user=user,
|
user=user,
|
||||||
club=club,
|
club=club,
|
||||||
|
@ -56,10 +77,10 @@ class PermissionBackend(ModelBackend):
|
||||||
F=F,
|
F=F,
|
||||||
Q=Q
|
Q=Q
|
||||||
)
|
)
|
||||||
if permission.mask.rank <= get_current_session().get("permission_mask", 0):
|
|
||||||
yield permission
|
yield permission
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@memoize
|
||||||
def filter_queryset(user, model, t, field=None):
|
def filter_queryset(user, model, t, field=None):
|
||||||
"""
|
"""
|
||||||
Filter a queryset by considering the permissions of a given user.
|
Filter a queryset by considering the permissions of a given user.
|
||||||
|
@ -93,10 +114,15 @@ class PermissionBackend(ModelBackend):
|
||||||
query = query | perm.query
|
query = query | perm.query
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
@memoize
|
||||||
def has_perm(self, user_obj, perm, obj=None):
|
def has_perm(self, user_obj, perm, obj=None):
|
||||||
if user_obj is None or isinstance(user_obj, AnonymousUser):
|
if user_obj is None or isinstance(user_obj, AnonymousUser):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
sess = get_current_session()
|
||||||
|
if sess is not None and sess.session_key is None:
|
||||||
|
return Permission.objects.none()
|
||||||
|
|
||||||
if user_obj.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
|
if user_obj.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from functools import lru_cache
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from django.contrib.sessions.models import Session
|
||||||
|
from note_kfet.middlewares import get_current_session
|
||||||
|
|
||||||
|
|
||||||
|
def memoize(f):
|
||||||
|
"""
|
||||||
|
Memoize results and store in sessions
|
||||||
|
|
||||||
|
This decorator is useful for permissions: they are loaded once needed, then stored for next calls.
|
||||||
|
The storage is contained with sessions since it depends on the selected mask.
|
||||||
|
"""
|
||||||
|
sess_funs = {}
|
||||||
|
last_collect = time()
|
||||||
|
|
||||||
|
def collect():
|
||||||
|
"""
|
||||||
|
Clear cache of results when sessions are invalid, to flush useless data.
|
||||||
|
This function is called every minute.
|
||||||
|
"""
|
||||||
|
nonlocal sess_funs
|
||||||
|
|
||||||
|
new_sess_funs = {}
|
||||||
|
for sess_key in sess_funs:
|
||||||
|
if Session.objects.filter(session_key=sess_key).exists():
|
||||||
|
new_sess_funs[sess_key] = sess_funs[sess_key]
|
||||||
|
sess_funs = new_sess_funs
|
||||||
|
|
||||||
|
def func(*args, **kwargs):
|
||||||
|
nonlocal last_collect
|
||||||
|
|
||||||
|
if time() - last_collect > 60:
|
||||||
|
# Clear cache
|
||||||
|
collect()
|
||||||
|
last_collect = time()
|
||||||
|
|
||||||
|
# If there is no session, then we don't memoize anything.
|
||||||
|
sess = get_current_session()
|
||||||
|
if sess is None or sess.session_key is None:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
sess_key = sess.session_key
|
||||||
|
if sess_key not in sess_funs:
|
||||||
|
# lru_cache makes the job of memoization
|
||||||
|
# We store only the 512 latest data per session. It has to be enough.
|
||||||
|
sess_funs[sess_key] = lru_cache(512)(f)
|
||||||
|
return sess_funs[sess_key](*args, **kwargs)
|
||||||
|
|
||||||
|
func.func_name = f.__name__
|
||||||
|
|
||||||
|
return func
|
|
@ -45,7 +45,7 @@ class InstancedPermission:
|
||||||
else:
|
else:
|
||||||
oldpk = obj.pk
|
oldpk = obj.pk
|
||||||
# Ensure previous models are deleted
|
# Ensure previous models are deleted
|
||||||
self.model.model_class().objects.filter(pk=obj.pk).annotate(_force_delete=F("pk") + 1).delete()
|
self.model.model_class().objects.filter(pk=obj.pk).annotate(_force_delete=F("pk")).delete()
|
||||||
# Force insertion, no data verification, no trigger
|
# Force insertion, no data verification, no trigger
|
||||||
obj._force_save = True
|
obj._force_save = True
|
||||||
Model.save(obj, force_insert=True)
|
Model.save(obj, force_insert=True)
|
||||||
|
|
|
@ -27,7 +27,7 @@ def pre_save_object(sender, instance, **kwargs):
|
||||||
if instance._meta.label_lower in EXCLUDED:
|
if instance._meta.label_lower in EXCLUDED:
|
||||||
return
|
return
|
||||||
|
|
||||||
if hasattr(instance, "_no_log"):
|
if hasattr(instance, "_force_save"):
|
||||||
return
|
return
|
||||||
|
|
||||||
user = get_current_authenticated_user()
|
user = get_current_authenticated_user()
|
||||||
|
@ -74,7 +74,7 @@ def pre_delete_object(instance, **kwargs):
|
||||||
if instance._meta.label_lower in EXCLUDED:
|
if instance._meta.label_lower in EXCLUDED:
|
||||||
return
|
return
|
||||||
|
|
||||||
if hasattr(instance, "_no_log"):
|
if hasattr(instance, "_force_delete"):
|
||||||
return
|
return
|
||||||
|
|
||||||
user = get_current_authenticated_user()
|
user = get_current_authenticated_user()
|
||||||
|
|
|
@ -20,11 +20,8 @@ def not_empty_model_list(model_name):
|
||||||
return False
|
return False
|
||||||
elif user.is_superuser and session.get("permission_mask", 0) >= 42:
|
elif user.is_superuser and session.get("permission_mask", 0) >= 42:
|
||||||
return True
|
return True
|
||||||
if session.get("not_empty_model_list_" + model_name, None):
|
|
||||||
return session.get("not_empty_model_list_" + model_name, None) == 1
|
|
||||||
qs = model_list(model_name)
|
qs = model_list(model_name)
|
||||||
session["not_empty_model_list_" + model_name] = 1 if qs.exists() else 2
|
return qs.exists()
|
||||||
return session.get("not_empty_model_list_" + model_name) == 1
|
|
||||||
|
|
||||||
|
|
||||||
@stringfilter
|
@stringfilter
|
||||||
|
@ -38,11 +35,8 @@ def not_empty_model_change_list(model_name):
|
||||||
return False
|
return False
|
||||||
elif user.is_superuser and session.get("permission_mask", 0) >= 42:
|
elif user.is_superuser and session.get("permission_mask", 0) >= 42:
|
||||||
return True
|
return True
|
||||||
if session.get("not_empty_model_change_list_" + model_name, None):
|
|
||||||
return session.get("not_empty_model_change_list_" + model_name, None) == 1
|
|
||||||
qs = model_list(model_name, "change")
|
qs = model_list(model_name, "change")
|
||||||
session["not_empty_model_change_list_" + model_name] = 1 if qs.exists() else 2
|
return qs.exists()
|
||||||
return session.get("not_empty_model_change_list_" + model_name) == 1
|
|
||||||
|
|
||||||
|
|
||||||
@stringfilter
|
@stringfilter
|
||||||
|
|
Loading…
Reference in New Issue