Collection: also save the collection UID on the model itself.
This enables us to have db-constraints for making sure that UIDs are unique, as well as being more efficient for lookups (which are very common). The UID should always be the same as the main_item.uid, though that's easily enforced as neither of them is allowed to change.
This commit is contained in:
parent
057b908565
commit
baa42d040d
19
django_etebase/migrations/0033_collection_uid.py
Normal file
19
django_etebase/migrations/0033_collection_uid.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.1.1 on 2020-12-14 11:21
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_etebase', '0032_auto_20201013_1409'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='collection',
|
||||
name='uid',
|
||||
field=models.CharField(db_index=True, max_length=43, null=True, validators=[django.core.validators.RegexValidator(message='Not a valid UID', regex='^[a-zA-Z0-9\\-_]{20,}$')]),
|
||||
),
|
||||
]
|
22
django_etebase/migrations/0034_auto_20201214_1124.py
Normal file
22
django_etebase/migrations/0034_auto_20201214_1124.py
Normal file
@ -0,0 +1,22 @@
|
||||
# Generated by Django 3.1.1 on 2020-12-14 11:24
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def update_collection_uid(apps, schema_editor):
|
||||
Collection = apps.get_model("django_etebase", "Collection")
|
||||
|
||||
for collection in Collection.objects.all():
|
||||
collection.uid = collection.main_item.uid
|
||||
collection.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("django_etebase", "0033_collection_uid"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_collection_uid),
|
||||
]
|
19
django_etebase/migrations/0035_auto_20201214_1126.py
Normal file
19
django_etebase/migrations/0035_auto_20201214_1126.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.1.1 on 2020-12-14 11:26
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_etebase', '0034_auto_20201214_1124'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='collection',
|
||||
name='uid',
|
||||
field=models.CharField(db_index=True, max_length=43, validators=[django.core.validators.RegexValidator(message='Not a valid UID', regex='^[a-zA-Z0-9\\-_]{20,}$')]),
|
||||
),
|
||||
]
|
19
django_etebase/migrations/0036_auto_20201214_1128.py
Normal file
19
django_etebase/migrations/0036_auto_20201214_1128.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.1.1 on 2020-12-14 11:28
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_etebase', '0035_auto_20201214_1126'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='collection',
|
||||
name='uid',
|
||||
field=models.CharField(db_index=True, max_length=43, unique=True, validators=[django.core.validators.RegexValidator(message='Not a valid UID', regex='^[a-zA-Z0-9\\-_]{20,}$')]),
|
||||
),
|
||||
]
|
@ -43,6 +43,8 @@ class CollectionType(models.Model):
|
||||
|
||||
class Collection(models.Model):
|
||||
main_item = models.OneToOneField("CollectionItem", related_name="parent", null=True, on_delete=models.SET_NULL)
|
||||
# The same as main_item.uid, we just also save it here so we have DB constraints for uniqueness (and efficiency)
|
||||
uid = models.CharField(db_index=True, unique=True, blank=False, max_length=43, validators=[UidValidator])
|
||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
|
||||
stoken_annotation = stoken_annotation_builder(["items__revisions__stoken", "members__stoken"])
|
||||
@ -50,10 +52,6 @@ class Collection(models.Model):
|
||||
def __str__(self):
|
||||
return self.uid
|
||||
|
||||
@cached_property
|
||||
def uid(self):
|
||||
return self.main_item.uid
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
return self.main_item.content
|
||||
@ -76,20 +74,6 @@ class Collection(models.Model):
|
||||
|
||||
return Stoken.objects.get(id=stoken_id).uid
|
||||
|
||||
def validate_unique(self, exclude=None):
|
||||
super().validate_unique(exclude=exclude)
|
||||
if exclude is None or "main_item" in exclude:
|
||||
return
|
||||
|
||||
if (
|
||||
self.__class__.objects.filter(owner=self.owner, main_item__uid=self.main_item.uid)
|
||||
.exclude(id=self.id)
|
||||
.exists()
|
||||
):
|
||||
raise EtebaseValidationError(
|
||||
"unique_uid", "Collection with this uid already exists", status_code=status.HTTP_409_CONFLICT
|
||||
)
|
||||
|
||||
|
||||
class CollectionItem(models.Model):
|
||||
uid = models.CharField(db_index=True, blank=False, max_length=43, validators=[UidValidator])
|
||||
|
@ -313,13 +313,13 @@ class CollectionSerializer(BetterErrorsMixin, serializers.ModelSerializer):
|
||||
|
||||
user = validated_data.get("owner")
|
||||
main_item_data = validated_data.pop("main_item")
|
||||
uid = main_item_data.get("uid")
|
||||
etag = main_item_data.pop("etag")
|
||||
revision_data = main_item_data.pop("content")
|
||||
|
||||
instance = self.__class__.Meta.model(**validated_data)
|
||||
instance = self.__class__.Meta.model(uid=uid, **validated_data)
|
||||
|
||||
with transaction.atomic():
|
||||
_ = self.__class__.Meta.model.objects.select_for_update().filter(owner=user)
|
||||
if etag is not None:
|
||||
raise EtebaseValidationError("bad_etag", "etag is not null")
|
||||
|
||||
|
@ -174,7 +174,7 @@ class CollectionViewSet(BaseViewSet):
|
||||
permission_classes = BaseViewSet.permission_classes + (permissions.IsCollectionAdminOrReadOnly,)
|
||||
queryset = Collection.objects.all()
|
||||
serializer_class = CollectionSerializer
|
||||
lookup_field = "main_item__uid"
|
||||
lookup_field = "uid"
|
||||
lookup_url_kwarg = "uid"
|
||||
stoken_annotation = Collection.stoken_annotation
|
||||
|
||||
@ -246,7 +246,7 @@ class CollectionViewSet(BaseViewSet):
|
||||
# can point to the most recent collection change rather than most recent removed membership.
|
||||
remed_qs = remed_qs.filter(stoken__id__lte=new_stoken_obj.id)
|
||||
|
||||
remed = remed_qs.values_list("collection__main_item__uid", flat=True)
|
||||
remed = remed_qs.values_list("collection__uid", flat=True)
|
||||
if len(remed) > 0:
|
||||
ret["removedMemberships"] = [{"uid": x} for x in remed]
|
||||
|
||||
@ -264,7 +264,7 @@ class CollectionItemViewSet(BaseViewSet):
|
||||
def get_queryset(self):
|
||||
collection_uid = self.kwargs["collection_uid"]
|
||||
try:
|
||||
collection = self.get_collection_queryset(Collection.objects).get(main_item__uid=collection_uid)
|
||||
collection = self.get_collection_queryset(Collection.objects).get(uid=collection_uid)
|
||||
except Collection.DoesNotExist:
|
||||
raise Http404("Collection does not exist")
|
||||
# XXX Potentially add this for performance: .prefetch_related('revisions__chunks')
|
||||
@ -312,7 +312,7 @@ class CollectionItemViewSet(BaseViewSet):
|
||||
|
||||
@action_decorator(detail=True, methods=["GET"])
|
||||
def revision(self, request, collection_uid=None, uid=None, *args, **kwargs):
|
||||
col = get_object_or_404(self.get_collection_queryset(Collection.objects), main_item__uid=collection_uid)
|
||||
col = get_object_or_404(self.get_collection_queryset(Collection.objects), uid=collection_uid)
|
||||
item = get_object_or_404(col.items, uid=uid)
|
||||
|
||||
limit = int(request.GET.get("limit", 50))
|
||||
@ -386,7 +386,7 @@ class CollectionItemViewSet(BaseViewSet):
|
||||
with transaction.atomic(): # We need this for locking on the collection object
|
||||
collection_object = get_object_or_404(
|
||||
self.get_collection_queryset(Collection.objects).select_for_update(), # Lock writes on the collection
|
||||
main_item__uid=collection_uid,
|
||||
uid=collection_uid,
|
||||
)
|
||||
|
||||
if stoken is not None and stoken != collection_object.stoken:
|
||||
@ -435,7 +435,7 @@ class CollectionItemChunkViewSet(viewsets.ViewSet):
|
||||
return queryset.filter(members__user=user)
|
||||
|
||||
def update(self, request, *args, collection_uid=None, collection_item_uid=None, uid=None, **kwargs):
|
||||
col = get_object_or_404(self.get_collection_queryset(), main_item__uid=collection_uid)
|
||||
col = get_object_or_404(self.get_collection_queryset(), uid=collection_uid)
|
||||
# IGNORED FOR NOW: col_it = get_object_or_404(col.items, uid=collection_item_uid)
|
||||
|
||||
data = {
|
||||
@ -459,7 +459,7 @@ class CollectionItemChunkViewSet(viewsets.ViewSet):
|
||||
import os
|
||||
from django.views.static import serve
|
||||
|
||||
col = get_object_or_404(self.get_collection_queryset(), main_item__uid=collection_uid)
|
||||
col = get_object_or_404(self.get_collection_queryset(), uid=collection_uid)
|
||||
# IGNORED FOR NOW: col_it = get_object_or_404(col.items, uid=collection_item_uid)
|
||||
chunk = get_object_or_404(col.chunks, uid=uid)
|
||||
|
||||
@ -487,7 +487,7 @@ class CollectionMemberViewSet(BaseViewSet):
|
||||
def get_queryset(self, queryset=None):
|
||||
collection_uid = self.kwargs["collection_uid"]
|
||||
try:
|
||||
collection = self.get_collection_queryset(Collection.objects).get(main_item__uid=collection_uid)
|
||||
collection = self.get_collection_queryset(Collection.objects).get(uid=collection_uid)
|
||||
except Collection.DoesNotExist:
|
||||
raise Http404("Collection does not exist")
|
||||
|
||||
@ -525,7 +525,7 @@ class CollectionMemberViewSet(BaseViewSet):
|
||||
@action_decorator(detail=False, methods=["POST"], permission_classes=our_base_permission_classes)
|
||||
def leave(self, request, collection_uid=None, *args, **kwargs):
|
||||
collection_uid = self.kwargs["collection_uid"]
|
||||
col = get_object_or_404(self.get_collection_queryset(Collection.objects), main_item__uid=collection_uid)
|
||||
col = get_object_or_404(self.get_collection_queryset(Collection.objects), uid=collection_uid)
|
||||
|
||||
member = col.members.get(user=request.user)
|
||||
self.perform_destroy(member)
|
||||
@ -584,7 +584,7 @@ class InvitationOutgoingViewSet(InvitationBaseViewSet):
|
||||
collection_uid = serializer.validated_data.get("collection", {}).get("uid")
|
||||
|
||||
try:
|
||||
collection = self.get_collection_queryset(Collection.objects).get(main_item__uid=collection_uid)
|
||||
collection = self.get_collection_queryset(Collection.objects).get(uid=collection_uid)
|
||||
except Collection.DoesNotExist:
|
||||
raise Http404("Collection does not exist")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user