mirror of
https://github.com/etesync/server
synced 2024-12-29 10:48:07 +00:00
Rename stoken to etag and cstoken to stoken.
This conforms better with what people know from HTTP and properly differentiates from CSToken which is now renamed to stoken.
This commit is contained in:
parent
8eee280bbb
commit
9c63f8d674
@ -46,11 +46,11 @@ class Collection(models.Model):
|
|||||||
return self.main_item.content
|
return self.main_item.content
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stoken(self):
|
def etag(self):
|
||||||
return self.content.uid
|
return self.content.uid
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def cstoken(self):
|
def stoken(self):
|
||||||
last_revision = CollectionItemRevision.objects.filter(item__collection=self).last()
|
last_revision = CollectionItemRevision.objects.filter(item__collection=self).last()
|
||||||
if last_revision is None:
|
if last_revision is None:
|
||||||
# FIXME: what is the etag for None? Though if we use the revision for collection it should be shared anyway.
|
# FIXME: what is the etag for None? Though if we use the revision for collection it should be shared anyway.
|
||||||
@ -77,7 +77,7 @@ class CollectionItem(models.Model):
|
|||||||
return self.revisions.get(current=True)
|
return self.revisions.get(current=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stoken(self):
|
def etag(self):
|
||||||
return self.content.uid
|
return self.content.uid
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,17 +114,17 @@ class CollectionItemRevisionSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class CollectionItemSerializer(serializers.ModelSerializer):
|
class CollectionItemSerializer(serializers.ModelSerializer):
|
||||||
encryptionKey = BinaryBase64Field()
|
encryptionKey = BinaryBase64Field()
|
||||||
stoken = serializers.CharField(allow_null=True)
|
etag = serializers.CharField(allow_null=True)
|
||||||
content = CollectionItemRevisionSerializer(many=False)
|
content = CollectionItemRevisionSerializer(many=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.CollectionItem
|
model = models.CollectionItem
|
||||||
fields = ('uid', 'version', 'encryptionKey', 'content', 'stoken')
|
fields = ('uid', 'version', 'encryptionKey', 'content', 'etag')
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
"""Function that's called when this serializer creates an item"""
|
"""Function that's called when this serializer creates an item"""
|
||||||
validate_stoken = self.context.get('validate_stoken', False)
|
validate_etag = self.context.get('validate_etag', False)
|
||||||
stoken = validated_data.pop('stoken')
|
etag = validated_data.pop('etag')
|
||||||
revision_data = validated_data.pop('content')
|
revision_data = validated_data.pop('content')
|
||||||
uid = validated_data.pop('uid')
|
uid = validated_data.pop('uid')
|
||||||
|
|
||||||
@ -132,10 +132,10 @@ class CollectionItemSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
instance, created = Model.objects.get_or_create(uid=uid, defaults=validated_data)
|
instance, created = Model.objects.get_or_create(uid=uid, defaults=validated_data)
|
||||||
cur_stoken = instance.stoken if not created else None
|
cur_etag = instance.etag if not created else None
|
||||||
|
|
||||||
if validate_stoken and cur_stoken != stoken:
|
if validate_etag and cur_etag != etag:
|
||||||
raise serializers.ValidationError('Wrong stoken. Expected {} got {}'.format(cur_stoken, stoken))
|
raise serializers.ValidationError('Wrong etag. Expected {} got {}'.format(cur_etag, etag))
|
||||||
|
|
||||||
if not created:
|
if not created:
|
||||||
# We don't have to use select_for_update here because the unique constraint on current guards against
|
# We don't have to use select_for_update here because the unique constraint on current guards against
|
||||||
@ -154,39 +154,39 @@ class CollectionItemSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class CollectionItemDepSerializer(serializers.ModelSerializer):
|
class CollectionItemDepSerializer(serializers.ModelSerializer):
|
||||||
stoken = serializers.CharField()
|
etag = serializers.CharField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.CollectionItem
|
model = models.CollectionItem
|
||||||
fields = ('uid', 'stoken')
|
fields = ('uid', 'etag')
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
item = self.__class__.Meta.model.objects.get(uid=data['uid'])
|
item = self.__class__.Meta.model.objects.get(uid=data['uid'])
|
||||||
stoken = data['stoken']
|
etag = data['etag']
|
||||||
if item.stoken != stoken:
|
if item.etag != etag:
|
||||||
raise serializers.ValidationError('Wrong stoken. Expected {} got {}'.format(item.stoken, stoken))
|
raise serializers.ValidationError('Wrong etag. Expected {} got {}'.format(item.etag, etag))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class CollectionItemBulkGetSerializer(serializers.ModelSerializer):
|
class CollectionItemBulkGetSerializer(serializers.ModelSerializer):
|
||||||
stoken = serializers.CharField(required=False)
|
etag = serializers.CharField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.CollectionItem
|
model = models.CollectionItem
|
||||||
fields = ('uid', 'stoken')
|
fields = ('uid', 'etag')
|
||||||
|
|
||||||
|
|
||||||
class CollectionSerializer(serializers.ModelSerializer):
|
class CollectionSerializer(serializers.ModelSerializer):
|
||||||
encryptionKey = CollectionEncryptionKeyField()
|
encryptionKey = CollectionEncryptionKeyField()
|
||||||
accessLevel = serializers.SerializerMethodField('get_access_level_from_context')
|
accessLevel = serializers.SerializerMethodField('get_access_level_from_context')
|
||||||
cstoken = serializers.CharField(read_only=True)
|
stoken = serializers.CharField(read_only=True)
|
||||||
stoken = serializers.CharField(allow_null=True)
|
etag = serializers.CharField(allow_null=True)
|
||||||
content = CollectionItemRevisionSerializer(many=False)
|
content = CollectionItemRevisionSerializer(many=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Collection
|
model = models.Collection
|
||||||
fields = ('uid', 'version', 'accessLevel', 'encryptionKey', 'content', 'cstoken', 'stoken')
|
fields = ('uid', 'version', 'accessLevel', 'encryptionKey', 'content', 'stoken', 'etag')
|
||||||
|
|
||||||
def get_access_level_from_context(self, obj):
|
def get_access_level_from_context(self, obj):
|
||||||
request = self.context.get('request', None)
|
request = self.context.get('request', None)
|
||||||
@ -196,13 +196,13 @@ class CollectionSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
"""Function that's called when this serializer creates an item"""
|
"""Function that's called when this serializer creates an item"""
|
||||||
stoken = validated_data.pop('stoken')
|
etag = validated_data.pop('etag')
|
||||||
revision_data = validated_data.pop('content')
|
revision_data = validated_data.pop('content')
|
||||||
encryption_key = validated_data.pop('encryptionKey')
|
encryption_key = validated_data.pop('encryptionKey')
|
||||||
instance = self.__class__.Meta.model(**validated_data)
|
instance = self.__class__.Meta.model(**validated_data)
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if stoken is not None:
|
if etag is not None:
|
||||||
raise serializers.ValidationError('Stoken is not None')
|
raise serializers.ValidationError('Stoken is not None')
|
||||||
|
|
||||||
instance.save()
|
instance.save()
|
||||||
@ -221,12 +221,12 @@ class CollectionSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
"""Function that's called when this serializer is meant to update an item"""
|
"""Function that's called when this serializer is meant to update an item"""
|
||||||
stoken = validated_data.pop('stoken')
|
etag = validated_data.pop('etag')
|
||||||
revision_data = validated_data.pop('content')
|
revision_data = validated_data.pop('content')
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if stoken != instance.stoken:
|
if etag != instance.etag:
|
||||||
raise serializers.ValidationError('Wrong stoken. Expected {} got {}'.format(instance.stoken, stoken))
|
raise serializers.ValidationError('Wrong etag. Expected {} got {}'.format(instance.etag, etag))
|
||||||
|
|
||||||
main_item = instance.main_item
|
main_item = instance.main_item
|
||||||
# We don't have to use select_for_update here because the unique constraint on current guards against
|
# We don't have to use select_for_update here because the unique constraint on current guards against
|
||||||
|
@ -70,7 +70,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)
|
||||||
cstoken_id_field = None
|
stoken_id_field = None
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
serializer_class = self.serializer_class
|
serializer_class = self.serializer_class
|
||||||
@ -84,43 +84,43 @@ class BaseViewSet(viewsets.ModelViewSet):
|
|||||||
user = self.request.user
|
user = self.request.user
|
||||||
return queryset.filter(members__user=user)
|
return queryset.filter(members__user=user)
|
||||||
|
|
||||||
def get_cstoken_obj(self, request):
|
def get_stoken_obj(self, request):
|
||||||
cstoken = request.GET.get('cstoken', None)
|
stoken = request.GET.get('stoken', None)
|
||||||
|
|
||||||
if cstoken is not None:
|
if stoken is not None:
|
||||||
return get_object_or_404(Stoken.objects.all(), uid=cstoken)
|
return get_object_or_404(Stoken.objects.all(), uid=stoken)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def filter_by_cstoken(self, request, queryset):
|
def filter_by_stoken(self, request, queryset):
|
||||||
cstoken_id_field = self.cstoken_id_field + '__id'
|
stoken_id_field = self.stoken_id_field + '__id'
|
||||||
|
|
||||||
cstoken_rev = self.get_cstoken_obj(request)
|
stoken_rev = self.get_stoken_obj(request)
|
||||||
if cstoken_rev is not None:
|
if stoken_rev is not None:
|
||||||
filter_by = {cstoken_id_field + '__gt': cstoken_rev.id}
|
filter_by = {stoken_id_field + '__gt': stoken_rev.id}
|
||||||
queryset = queryset.filter(**filter_by)
|
queryset = queryset.filter(**filter_by)
|
||||||
|
|
||||||
return queryset, cstoken_rev
|
return queryset, stoken_rev
|
||||||
|
|
||||||
def get_queryset_cstoken(self, queryset):
|
def get_queryset_stoken(self, queryset):
|
||||||
cstoken_id_field = self.cstoken_id_field + '__id'
|
stoken_id_field = self.stoken_id_field + '__id'
|
||||||
|
|
||||||
new_cstoken_id = queryset.aggregate(cstoken_id=Max(cstoken_id_field))['cstoken_id']
|
new_stoken_id = queryset.aggregate(stoken_id=Max(stoken_id_field))['stoken_id']
|
||||||
new_cstoken = new_cstoken_id and Stoken.objects.get(id=new_cstoken_id).uid
|
new_stoken = new_stoken_id and Stoken.objects.get(id=new_stoken_id).uid
|
||||||
|
|
||||||
return queryset, new_cstoken
|
return queryset, new_stoken
|
||||||
|
|
||||||
def filter_by_cstoken_and_limit(self, request, queryset):
|
def filter_by_stoken_and_limit(self, request, queryset):
|
||||||
limit = int(request.GET.get('limit', 50))
|
limit = int(request.GET.get('limit', 50))
|
||||||
|
|
||||||
queryset, cstoken_rev = self.filter_by_cstoken(request, queryset)
|
queryset, stoken_rev = self.filter_by_stoken(request, queryset)
|
||||||
cstoken = cstoken_rev.uid if cstoken_rev is not None else None
|
stoken = stoken_rev.uid if stoken_rev is not None else None
|
||||||
|
|
||||||
queryset = queryset[:limit]
|
queryset = queryset[:limit]
|
||||||
queryset, new_cstoken = self.get_queryset_cstoken(queryset)
|
queryset, new_stoken = self.get_queryset_stoken(queryset)
|
||||||
new_cstoken = new_cstoken or cstoken
|
new_stoken = new_stoken or stoken
|
||||||
|
|
||||||
return queryset, new_cstoken
|
return queryset, new_stoken
|
||||||
|
|
||||||
|
|
||||||
class CollectionViewSet(BaseViewSet):
|
class CollectionViewSet(BaseViewSet):
|
||||||
@ -129,7 +129,7 @@ class CollectionViewSet(BaseViewSet):
|
|||||||
queryset = Collection.objects.all()
|
queryset = Collection.objects.all()
|
||||||
serializer_class = CollectionSerializer
|
serializer_class = CollectionSerializer
|
||||||
lookup_field = 'uid'
|
lookup_field = 'uid'
|
||||||
cstoken_id_field = 'items__revisions__stoken'
|
stoken_id_field = 'items__revisions__stoken'
|
||||||
|
|
||||||
def get_queryset(self, queryset=None):
|
def get_queryset(self, queryset=None):
|
||||||
if queryset is None:
|
if queryset is None:
|
||||||
@ -172,13 +172,13 @@ class CollectionViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def list(self, request):
|
def list(self, request):
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
queryset, new_cstoken = self.filter_by_cstoken_and_limit(request, queryset)
|
queryset, new_stoken = self.filter_by_stoken_and_limit(request, queryset)
|
||||||
|
|
||||||
serializer = self.serializer_class(queryset, context=self.get_serializer_context(), many=True)
|
serializer = self.serializer_class(queryset, context=self.get_serializer_context(), many=True)
|
||||||
|
|
||||||
ret = {
|
ret = {
|
||||||
'data': serializer.data,
|
'data': serializer.data,
|
||||||
'cstoken': new_cstoken,
|
'stoken': new_stoken,
|
||||||
}
|
}
|
||||||
return Response(ret)
|
return Response(ret)
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ class CollectionItemViewSet(BaseViewSet):
|
|||||||
queryset = CollectionItem.objects.all()
|
queryset = CollectionItem.objects.all()
|
||||||
serializer_class = CollectionItemSerializer
|
serializer_class = CollectionItemSerializer
|
||||||
lookup_field = 'uid'
|
lookup_field = 'uid'
|
||||||
cstoken_id_field = 'revisions__stoken'
|
stoken_id_field = 'revisions__stoken'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
collection_uid = self.kwargs['collection_uid']
|
collection_uid = self.kwargs['collection_uid']
|
||||||
@ -240,13 +240,13 @@ class CollectionItemViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def list(self, request, collection_uid=None):
|
def list(self, request, collection_uid=None):
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
queryset, new_cstoken = self.filter_by_cstoken_and_limit(request, queryset)
|
queryset, new_stoken = self.filter_by_stoken_and_limit(request, queryset)
|
||||||
|
|
||||||
serializer = self.serializer_class(queryset, context=self.get_serializer_context(), many=True)
|
serializer = self.serializer_class(queryset, context=self.get_serializer_context(), many=True)
|
||||||
|
|
||||||
ret = {
|
ret = {
|
||||||
'data': serializer.data,
|
'data': serializer.data,
|
||||||
'cstoken': new_cstoken,
|
'stoken': new_stoken,
|
||||||
}
|
}
|
||||||
return Response(ret)
|
return Response(ret)
|
||||||
|
|
||||||
@ -277,21 +277,21 @@ class CollectionItemViewSet(BaseViewSet):
|
|||||||
'detail': 'Request has too many items. Limit: {}'. format(item_limit)}
|
'detail': 'Request has too many items. Limit: {}'. format(item_limit)}
|
||||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
queryset, cstoken_rev = self.filter_by_cstoken(request, queryset)
|
queryset, stoken_rev = self.filter_by_stoken(request, queryset)
|
||||||
|
|
||||||
uids, stokens = zip(*[(item['uid'], item.get('stoken')) for item in serializer.validated_data])
|
uids, etags = zip(*[(item['uid'], item.get('etag')) for item in serializer.validated_data])
|
||||||
revs = CollectionItemRevision.objects.filter(uid__in=stokens, current=True)
|
revs = CollectionItemRevision.objects.filter(uid__in=etags, current=True)
|
||||||
queryset = queryset.filter(uid__in=uids).exclude(revisions__in=revs)
|
queryset = queryset.filter(uid__in=uids).exclude(revisions__in=revs)
|
||||||
|
|
||||||
queryset, new_cstoken = self.get_queryset_cstoken(queryset)
|
queryset, new_stoken = self.get_queryset_stoken(queryset)
|
||||||
cstoken = cstoken_rev and cstoken_rev.uid
|
stoken = stoken_rev and stoken_rev.uid
|
||||||
new_cstoken = new_cstoken or cstoken
|
new_stoken = new_stoken or stoken
|
||||||
|
|
||||||
serializer = self.get_serializer_class()(queryset, context=self.get_serializer_context(), many=True)
|
serializer = self.get_serializer_class()(queryset, context=self.get_serializer_context(), many=True)
|
||||||
|
|
||||||
ret = {
|
ret = {
|
||||||
'data': serializer.data,
|
'data': serializer.data,
|
||||||
'cstoken': new_cstoken,
|
'stoken': new_stoken,
|
||||||
}
|
}
|
||||||
return Response(ret)
|
return Response(ret)
|
||||||
|
|
||||||
@ -299,11 +299,11 @@ class CollectionItemViewSet(BaseViewSet):
|
|||||||
|
|
||||||
@action_decorator(detail=False, methods=['POST'])
|
@action_decorator(detail=False, methods=['POST'])
|
||||||
def batch(self, request, collection_uid=None):
|
def batch(self, request, collection_uid=None):
|
||||||
cstoken = request.GET.get('cstoken', None)
|
stoken = request.GET.get('stoken', None)
|
||||||
collection_object = get_object_or_404(self.get_collection_queryset(Collection.objects), uid=collection_uid)
|
collection_object = get_object_or_404(self.get_collection_queryset(Collection.objects), uid=collection_uid)
|
||||||
|
|
||||||
if cstoken is not None and cstoken != collection_object.cstoken:
|
if stoken is not None and stoken != collection_object.stoken:
|
||||||
content = {'code': 'stale_cstoken', 'detail': 'CSToken is too old'}
|
content = {'code': 'stale_stoken', 'detail': 'Stoken is too old'}
|
||||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
items = request.data.get('items')
|
items = request.data.get('items')
|
||||||
@ -331,18 +331,18 @@ class CollectionItemViewSet(BaseViewSet):
|
|||||||
|
|
||||||
@action_decorator(detail=False, methods=['POST'])
|
@action_decorator(detail=False, methods=['POST'])
|
||||||
def transaction(self, request, collection_uid=None):
|
def transaction(self, request, collection_uid=None):
|
||||||
cstoken = request.GET.get('cstoken', None)
|
stoken = request.GET.get('stoken', None)
|
||||||
collection_object = get_object_or_404(self.get_collection_queryset(Collection.objects), uid=collection_uid)
|
collection_object = get_object_or_404(self.get_collection_queryset(Collection.objects), uid=collection_uid)
|
||||||
|
|
||||||
if cstoken is not None and cstoken != collection_object.cstoken:
|
if stoken is not None and stoken != collection_object.stoken:
|
||||||
content = {'code': 'stale_cstoken', 'detail': 'CSToken is too old'}
|
content = {'code': 'stale_stoken', 'detail': 'Stoken is too old'}
|
||||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
items = request.data.get('items')
|
items = request.data.get('items')
|
||||||
deps = request.data.get('deps', None)
|
deps = request.data.get('deps', None)
|
||||||
# FIXME: It should just be one serializer
|
# FIXME: It should just be one serializer
|
||||||
context = self.get_serializer_context()
|
context = self.get_serializer_context()
|
||||||
context.update({'validate_stoken': True})
|
context.update({'validate_etag': True})
|
||||||
serializer = self.get_serializer_class()(data=items, context=context, many=True)
|
serializer = self.get_serializer_class()(data=items, context=context, many=True)
|
||||||
deps_serializer = CollectionItemDepSerializer(data=deps, context=context, many=True)
|
deps_serializer = CollectionItemDepSerializer(data=deps, context=context, many=True)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user