More optimisation

This commit is contained in:
Yohann D'ANELLO 2020-03-20 01:46:59 +01:00
parent f80cb635d3
commit 6fc43e651e
6 changed files with 75 additions and 47 deletions

View File

@ -5,15 +5,19 @@ from django.contrib.contenttypes.models import ContentType
from member.backends import PermissionBackend
from rest_framework import viewsets
from note_kfet.middlewares import get_current_authenticated_user
class ReadProtectedModelViewSet(viewsets.ModelViewSet):
"""
Protect a ModelViewSet by filtering the objects that the user cannot see.
"""
def get_queryset(self):
model = ContentType.objects.get_for_model(self.serializer_class.Meta.model)
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, model, "view"))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
user = get_current_authenticated_user()
self.queryset = model.objects.filter(PermissionBackend.filter_queryset(user, model, "view"))
class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet):
@ -21,6 +25,8 @@ class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet):
Protect a ReadOnlyModelViewSet by filtering the objects that the user cannot see.
"""
def get_queryset(self):
model = ContentType.objects.get_for_model(self.serializer_class.Meta.model)
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, model, "view"))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
user = get_current_authenticated_user()
self.queryset = model.objects.filter(PermissionBackend.filter_queryset(user, model, "view"))

View File

@ -19,18 +19,16 @@ class PermissionBackend(ModelBackend):
@staticmethod
def permissions(user, model, type):
for membership in Membership.objects.filter(user=user).all():
if not membership.valid() or membership.roles is None:
continue
for permission in Permission.objects.filter(
rolepermissions__role=membership.roles,
for permission in Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \
.filter(
rolepermissions__role__membership__user=user,
model__app_label=model.app_label, # For polymorphic models, we don't filter on model type
type=type
type=type,
).all():
club = Club.objects.get(pk=permission.club)
permission = permission.about(
user=user,
club=membership.club,
club=club,
User=User,
Club=Club,
Membership=Membership,
@ -55,6 +53,9 @@ class PermissionBackend(ModelBackend):
:return: A query that corresponds to the filter to give to a queryset
"""
from time import time
ti = time()
if user.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
# Superusers have all rights
return Q()
@ -64,11 +65,13 @@ class PermissionBackend(ModelBackend):
# Never satisfied
query = Q(pk=-1)
for perm in PermissionBackend.permissions(user, model, t):
perms = PermissionBackend.permissions(user, model, t)
for perm in perms:
if perm.field and field != perm.field:
continue
if perm.type != t or perm.model != model:
continue
perm.update_query()
query = query | perm.query
return query

View File

@ -6,7 +6,6 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter, SearchFilter
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from member.backends import PermissionBackend
from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \
TransactionTemplateSerializer, TransactionPolymorphicSerializer
from ..models.notes import Note, Alias
@ -30,7 +29,7 @@ class NotePolymorphicViewSet(ReadOnlyProtectedModelViewSet):
Parse query and apply filters.
:return: The filtered set of requested notes
"""
queryset = super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Note, "view"))
queryset = super().get_queryset()
alias = self.request.query_params.get("alias", ".*")
queryset = queryset.filter(
@ -57,7 +56,7 @@ class AliasViewSet(ReadProtectedModelViewSet):
:return: The filtered set of requested aliases
"""
queryset = super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
queryset = super().get_queryset()
alias = self.request.query_params.get("alias", ".*")
queryset = queryset.filter(

View File

@ -14,12 +14,14 @@ from django.utils.translation import gettext_lazy as _
class InstancedPermission:
def __init__(self, model, query, type, field, mask):
def __init__(self, model, query, type, field, mask, **kwargs):
self.model = model
self.query = query
self.raw_query = query
self.query = None
self.type = type
self.field = field
self.mask = mask
self.kwargs = kwargs
def applies(self, obj, permission_type, field_name=None):
"""
@ -33,6 +35,8 @@ class InstancedPermission:
if self.type == 'add':
if permission_type == self.type:
self.update_query()
# Don't increase indexes
obj.pk = 0
# Force insertion, no data verification, no trigger
@ -45,10 +49,16 @@ class InstancedPermission:
if permission_type == self.type:
if self.field and field_name != self.field:
return False
self.update_query()
return obj in self.model.model_class().objects.filter(self.query).all()
else:
return False
def update_query(self):
if not self.query:
# noinspection PyProtectedMember
self.query = Permission._about(self.raw_query, **self.kwargs)
def __repr__(self):
if self.field:
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
@ -178,7 +188,8 @@ class Permission(models.Model):
field = getattr(field, value[i])
return field
def _about(self, query, **kwargs):
@staticmethod
def _about(query, **kwargs):
if len(query) == 0:
# The query is either [] or {} and
# applies to all objects of the model
@ -186,11 +197,11 @@ class Permission(models.Model):
return Q(pk=F("pk"))
if isinstance(query, list):
if query[0] == 'AND':
return functools.reduce(operator.and_, [self._about(query, **kwargs) for query in query[1:]])
return functools.reduce(operator.and_, [Permission._about(query, **kwargs) for query in query[1:]])
elif query[0] == 'OR':
return functools.reduce(operator.or_, [self._about(query, **kwargs) for query in query[1:]])
return functools.reduce(operator.or_, [Permission._about(query, **kwargs) for query in query[1:]])
elif query[0] == 'NOT':
return ~self._about(query[1], **kwargs)
return ~Permission._about(query[1], **kwargs)
elif isinstance(query, dict):
q_kwargs = {}
for key in query:
@ -206,7 +217,7 @@ class Permission(models.Model):
return Q(**q_kwargs)
else:
# TODO: find a better way to crash here
raise Exception("query {} is wrong".format(self.query))
raise Exception("query {} is wrong".format(query))
def about(self, **kwargs):
"""
@ -214,8 +225,8 @@ class Permission(models.Model):
replaced by their values and the query interpreted
"""
query = json.loads(self.query)
query = self._about(query, **kwargs)
return InstancedPermission(self.model, query, self.type, self.field, self.mask)
# query = self._about(query, **kwargs)
return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
def __str__(self):
if self.field:

View File

@ -13,27 +13,35 @@ from member.backends import PermissionBackend
@stringfilter
def not_empty_model_list(model_name):
user = get_current_authenticated_user()
session = get_current_session()
if user is None:
return False
elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
elif user.is_superuser and session.get("permission_mask", 0) >= 42:
return True
if session.get("not_empty_model_list_" + model_name, None):
return session.get("not_empty_model_list_" + model_name, None) == 1
spl = model_name.split(".")
ct = ContentType.objects.get(app_label=spl[0], model=spl[1])
qs = ct.model_class().objects.filter(PermissionBackend.filter_queryset(user, ct, "view"))
return qs.exists()
session["not_empty_model_list_" + model_name] = 1 if qs.exists() else 2
return session.get("not_empty_model_list_" + model_name) == 1
@stringfilter
def not_empty_model_change_list(model_name):
user = get_current_authenticated_user()
session = get_current_session()
if user is None:
return False
elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
elif user.is_superuser and session.get("permission_mask", 0) >= 42:
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
spl = model_name.split(".")
ct = ContentType.objects.get(app_label=spl[0], model=spl[1])
qs = ct.model_class().objects.filter(PermissionBackend.filter_queryset(user, ct, "change"))
return qs.exists()
session["not_empty_model_change_list_" + model_name] = 1 if qs.exists() else 2
return session.get("not_empty_model_change_list_" + model_name) == 1
register = template.Library()

View File

@ -199,6 +199,7 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes
// When the user click on an alias, the associated note is added to the emitters
alias_obj.click(function () {
field.val("");
old_pattern = "";
// If the note is already an emitter, we increase the quantity
var disp = null;
notes_display.forEach(function (d) {