Collections: use the member stokens for filtering based on stoken

While at it, also generalised the stoken handling to be generic and
extendible.
This commit is contained in:
Tom Hacohen 2020-05-27 12:13:54 +03:00
parent 1f18f4e50b
commit d93a5d3f06
2 changed files with 21 additions and 17 deletions

View File

@ -17,6 +17,7 @@ from pathlib import Path
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.db.models import Q
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
@ -51,12 +52,14 @@ class Collection(models.Model):
@cached_property @cached_property
def stoken(self): def stoken(self):
last_revision = CollectionItemRevision.objects.filter(item__collection=self).last() stoken = Stoken.objects.filter(
if last_revision is None: Q(collectionitemrevision__item__collection=self) | Q(collectionmember__collection=self)
# FIXME: what is the etag for None? Though if we use the revision for collection it should be shared anyway. ).order_by('id').last()
return None
return last_revision.stoken.uid if stoken is None:
raise Exception('stoken is None. Should never happen')
return stoken.uid
class CollectionItem(models.Model): class CollectionItem(models.Model):

View File

@ -13,12 +13,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import json import json
from functools import reduce
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction, IntegrityError from django.db import transaction, IntegrityError
from django.db.models import Max from django.db.models import Max, Q
from django.db.models.functions import Greatest
from django.http import HttpResponseBadRequest, HttpResponse, Http404 from django.http import HttpResponseBadRequest, HttpResponse, Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@ -70,7 +72,7 @@ User = get_user_model()
class BaseViewSet(viewsets.ModelViewSet): class BaseViewSet(viewsets.ModelViewSet):
authentication_classes = tuple(app_settings.API_AUTHENTICATORS) authentication_classes = tuple(app_settings.API_AUTHENTICATORS)
permission_classes = tuple(app_settings.API_PERMISSIONS) permission_classes = tuple(app_settings.API_PERMISSIONS)
stoken_id_field = None stoken_id_fields = None
def get_serializer_class(self): def get_serializer_class(self):
serializer_class = self.serializer_class serializer_class = self.serializer_class
@ -93,20 +95,19 @@ class BaseViewSet(viewsets.ModelViewSet):
return None return None
def filter_by_stoken(self, request, queryset): def filter_by_stoken(self, request, queryset):
stoken_id_field = self.stoken_id_field + '__id'
stoken_rev = self.get_stoken_obj(request) stoken_rev = self.get_stoken_obj(request)
if stoken_rev is not None: if stoken_rev is not None:
filter_by = {stoken_id_field + '__gt': stoken_rev.id} filter_by_map = map(lambda x: Q(**{x + '__gt': stoken_rev.id}), self.stoken_id_fields)
queryset = queryset.filter(**filter_by) filter_by = reduce(lambda x, y: x | y, filter_by_map)
queryset = queryset.filter(filter_by).distinct()
return queryset, stoken_rev return queryset, stoken_rev
def get_queryset_stoken(self, queryset): def get_queryset_stoken(self, queryset):
stoken_id_field = self.stoken_id_field + '__id' aggr_fields = {x: Max(x) for x in self.stoken_id_fields}
aggr = queryset.aggregate(**aggr_fields)
new_stoken_id = queryset.aggregate(stoken_id=Max(stoken_id_field))['stoken_id'] maxid = max(map(lambda x: x or -1, aggr.values()))
new_stoken = new_stoken_id and Stoken.objects.get(id=new_stoken_id).uid new_stoken = (maxid >= 0) and Stoken.objects.get(id=maxid).uid
return queryset, new_stoken return queryset, new_stoken
@ -129,7 +130,7 @@ class CollectionViewSet(BaseViewSet):
queryset = Collection.objects.all() queryset = Collection.objects.all()
serializer_class = CollectionSerializer serializer_class = CollectionSerializer
lookup_field = 'uid' lookup_field = 'uid'
stoken_id_field = 'items__revisions__stoken' stoken_id_fields = ['items__revisions__stoken__id', 'members__stoken__id']
def get_queryset(self, queryset=None): def get_queryset(self, queryset=None):
if queryset is None: if queryset is None:
@ -189,7 +190,7 @@ class CollectionItemViewSet(BaseViewSet):
queryset = CollectionItem.objects.all() queryset = CollectionItem.objects.all()
serializer_class = CollectionItemSerializer serializer_class = CollectionItemSerializer
lookup_field = 'uid' lookup_field = 'uid'
stoken_id_field = 'revisions__stoken' stoken_id_fields = ['revisions__stoken__id']
def get_queryset(self): def get_queryset(self):
collection_uid = self.kwargs['collection_uid'] collection_uid = self.kwargs['collection_uid']