Implement item_create, batch and transaction.

master
Tom Hacohen 4 years ago
parent 249c3dc2be
commit b2fe30ac26

@ -14,7 +14,7 @@ from pydantic import BaseModel
from django_etebase import models from django_etebase import models
from django_etebase.models import Collection, AccessLevels, CollectionMember from django_etebase.models import Collection, AccessLevels, CollectionMember
from .authentication import get_authenticated_user from .authentication import get_authenticated_user
from .exceptions import ValidationError from .exceptions import ValidationError, transform_validation_error
from .msgpack import MsgpackRoute, MsgpackResponse from .msgpack import MsgpackRoute, MsgpackResponse
from .stoken_handler import filter_by_stoken_and_limit from .stoken_handler import filter_by_stoken_and_limit
@ -79,7 +79,7 @@ class CollectionItemOut(CollectionItemCommon):
version=obj.version, version=obj.version,
encryptionKey=obj.encryptionKey, encryptionKey=obj.encryptionKey,
etag=obj.etag, etag=obj.etag,
content=CollectionItemRevisionOut.from_orm_context(obj.content, context), content=CollectionItemRevision.from_orm_context(obj.content, context),
) )
@ -125,11 +125,26 @@ class ItemDepIn(BaseModel):
etag: str etag: str
uid: str uid: str
def validate_db(self):
item = models.CollectionItem.objects.get(uid=self.uid)
etag = self.etag
if item.etag != etag:
raise ValidationError(
"wrong_etag",
"Wrong etag. Expected {} got {}".format(item.etag, etag),
status_code=status.HTTP_409_CONFLICT,
)
class ItemBatchIn(BaseModel): class ItemBatchIn(BaseModel):
items: t.List[CollectionItemIn] items: t.List[CollectionItemIn]
deps: t.Optional[ItemDepIn] deps: t.Optional[ItemDepIn]
def validate_db(self):
if self.deps is not None:
for key, _value in self.deps:
getattr(self.deps, key).validate_db()
@sync_to_async @sync_to_async
def list_common( def list_common(
@ -172,7 +187,7 @@ async def collection_list(
pass pass
def process_revisions_for_item(item: models.CollectionItem, revision_data: CollectionItemRevisionOut): def process_revisions_for_item(item: models.CollectionItem, revision_data: CollectionItemRevision):
chunks_objs = [] chunks_objs = []
revision = models.CollectionItemRevision(**revision_data.dict(exclude={"chunks"}), item=item) revision = models.CollectionItemRevision(**revision_data.dict(exclude={"chunks"}), item=item)
@ -250,16 +265,60 @@ def get_collection(uid: str, user: User = Depends(get_authenticated_user), prefe
return MsgpackResponse(ret) return MsgpackResponse(ret)
def item_bulk_common(data: ItemBatchIn, user: User, stoken: str, uid: str, validate_etag: bool): def item_create(item_model: CollectionItemIn, validate_etag: bool):
"""Function that's called when this serializer creates an item"""
etag = item_model.etag
revision_data = item_model.content
uid = item_model.uid
Model = models.CollectionItem
with transaction.atomic():
instance, created = Model.objects.get_or_create(
uid=uid, defaults=item_model.dict(exclude={"uid", "etag", "content"})
)
cur_etag = instance.etag if not created else None
# If we are trying to update an up to date item, abort early and consider it a success
if cur_etag == revision_data.uid:
return instance
if validate_etag and cur_etag != etag:
raise ValidationError(
"wrong_etag",
"Wrong etag. Expected {} got {}".format(cur_etag, etag),
status_code=status.HTTP_409_CONFLICT,
)
if not created:
# We don't have to use select_for_update here because the unique constraint on current guards against
# the race condition. But it's a good idea because it'll lock and wait rather than fail.
current_revision = instance.revisions.filter(current=True).select_for_update().first()
current_revision.current = None
current_revision.save()
try:
process_revisions_for_item(instance, revision_data)
except django_exceptions.ValidationError as e:
transform_validation_error("content", e)
return instance
def item_bulk_common(data: ItemBatchIn, user: User, stoken: t.Optional[str], uid: str, validate_etag: bool):
queryset = get_collection_queryset(user, default_queryset) queryset = get_collection_queryset(user, default_queryset)
with transaction.atomic(): # We need this for locking the collection object with transaction.atomic(): # We need this for locking the collection object
collection_object = queryset.select_for_update().get(uid=uid) collection_object = queryset.select_for_update().get(uid=uid)
if stoken is not None and stoken != collection_object.stoken: if stoken is not None and stoken != collection_object.stoken:
raise ValidationError("stale_stoken", "Stoken is too old", status_code=status.HTTP_409_CONFLICT) raise ValidationError("stale_stoken", "Stoken is too old", status_code=status.HTTP_409_CONFLICT)
# XXX-TOM: make sure we return compatible errors
data.validate_db()
for item in data.items:
item_create(item, validate_etag)
def item_create(): return MsgpackResponse({})
pass #
@collection_router.post("/{uid}/item/transaction/") @collection_router.post("/{uid}/item/transaction/")

Loading…
Cancel
Save