Skip to content

Commit

Permalink
limit deleted objects
Browse files Browse the repository at this point in the history
  • Loading branch information
vsemionov committed Aug 25, 2017
1 parent b1566e8 commit 81e3f59
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 10 deletions.
51 changes: 44 additions & 7 deletions api/core/limit.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.conf import settings
from django.db import transaction
from django.db.models import Subquery
from rest_framework import exceptions, status

from .mixin import ViewSetMixin
Expand All @@ -13,23 +14,36 @@ class LimitExceededError(exceptions.APIException):

# this mixin depends on NestedModelMixin
class LimitedModelMixin(ViewSetMixin):
parent_key_filter = ()

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.check_limits = False

def _check_limits(self, parent):
child_type = self.queryset.model

@staticmethod
def _get_limit(parent, child_type, deleted):
parent_limits = settings.API_LIMITS.get(parent._meta.label)
if not parent_limits:
return
return None

child_limit = parent_limits.get(child_type._meta.label)
if not child_limit:
return None

limit = child_limit[int(deleted)]
if not limit:
return None

return limit

limit = parent_limits.get(child_type._meta.label)
def _check_active_limits(self, parent):
child_type = self.queryset.model

limit = self._get_limit(parent, child_type, False)
if not limit:
return

child_set_name = child_type._meta.default_related_name
child_set_name = child_type._meta.model_name + '_set'
child_set = getattr(parent, child_set_name)

if util.is_deletable(child_type):
Expand All @@ -39,6 +53,22 @@ def _check_limits(self, parent):
raise LimitExceededError('exceeded limit of %d %s per %s' %
(limit, child_type._meta.verbose_name_plural, parent._meta.verbose_name))

def _check_deleted_limits(self):
child_type = self.queryset.model

limit = self._get_limit(self.parent_model, child_type, True)
if not limit:
return

field, kwarg = self.parent_key_filter
filter_kwargs = {field: self.kwargs[kwarg]}
filter_kwargs['deleted'] = True

queryset = child_type.objects.filter(**filter_kwargs)

delete_ids = Subquery(queryset.order_by('-updated', '-id')[limit:].values('id'))
queryset.filter(id__in=delete_ids).delete()

def get_parent_queryset(self):
queryset = super().get_parent_queryset()

Expand All @@ -51,10 +81,17 @@ def get_parent(self):
parent = super().get_parent()

if self.check_limits:
self._check_limits(parent)
self._check_active_limits(parent)

return parent

def destroy(self, request, *args, **kwargs):
response = super().destroy(request, *args, **kwargs)

self._check_deleted_limits()

return response

@transaction.atomic(savepoint=False)
def perform_create(self, serializer):
self.check_limits = True
Expand Down
2 changes: 2 additions & 0 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class UserChildViewSet(NestedViewSet):
safe_parent = True
object_filters = {'user_id': 'user_username'}
parent_filters = {'username': 'user_username'}
parent_key_filter = ('user_id', 'user_username')


class NotebookViewSet(UserChildViewSet):
Expand Down Expand Up @@ -104,6 +105,7 @@ class NoteViewSet(NestedViewSet):
'user_id': 'user_username',
'ext_id': 'notebook_ext_id'
}
parent_key_filter = ('notebook_id', 'notebook_ext_id')

get_hyperlinked_serializer_class = staticmethod(links.create_hyperlinked_note_serializer_class)

Expand Down
6 changes: 3 additions & 3 deletions boomerang/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,11 @@

API_LIMITS = {
'auth.User': {
'api.Notebook': 8,
'api.Task': 250,
'api.Notebook': (8, 64),
'api.Task': (250, 1000),
},
'api.Notebook': {
'api.Note': 125,
'api.Note': (125, 500),
},
}
API_MAX_PAGE_SIZE = 100
Expand Down

0 comments on commit 81e3f59

Please sign in to comment.