Collections: add support for collection types.

We also added the field for invitations, as it's needed for collections
to work.
master
Tom Hacohen 4 years ago
parent acd22b9b47
commit 5d8a92f000

@ -0,0 +1,29 @@
# Generated by Django 3.1.1 on 2020-10-13 13:36
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('django_etebase', '0030_auto_20200922_0832'),
]
operations = [
migrations.CreateModel(
name='CollectionType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uid', models.BinaryField(db_index=True, editable=True)),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='collectionmember',
name='collectionType',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='django_etebase.collectiontype'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 3.1.1 on 2020-10-13 14:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_etebase', '0031_auto_20201013_1336'),
]
operations = [
migrations.AlterField(
model_name='collectiontype',
name='uid',
field=models.BinaryField(db_index=True, editable=True, unique=True),
),
]

@ -30,6 +30,11 @@ from .exceptions import EtebaseValidationError
UidValidator = RegexValidator(regex=r'^[a-zA-Z0-9\-_]{20,}$', message='Not a valid UID') UidValidator = RegexValidator(regex=r'^[a-zA-Z0-9\-_]{20,}$', message='Not a valid UID')
class CollectionType(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
uid = models.BinaryField(editable=True, blank=False, null=False, db_index=True, unique=True)
class Collection(models.Model): class Collection(models.Model):
main_item = models.OneToOneField('CollectionItem', related_name='parent', null=True, on_delete=models.SET_NULL) main_item = models.OneToOneField('CollectionItem', related_name='parent', null=True, on_delete=models.SET_NULL)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
@ -162,6 +167,7 @@ class CollectionMember(models.Model):
collection = models.ForeignKey(Collection, related_name='members', on_delete=models.CASCADE) collection = models.ForeignKey(Collection, related_name='members', on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
encryptionKey = models.BinaryField(editable=True, blank=False, null=False) encryptionKey = models.BinaryField(editable=True, blank=False, null=False)
collectionType = models.ForeignKey(CollectionType, on_delete=models.PROTECT, null=True)
accessLevel = models.IntegerField( accessLevel = models.IntegerField(
choices=AccessLevels.choices, choices=AccessLevels.choices,
default=AccessLevels.READ_ONLY, default=AccessLevels.READ_ONLY,

@ -86,6 +86,15 @@ class CollectionEncryptionKeyField(BinaryBase64Field):
return None return None
class CollectionTypeField(BinaryBase64Field):
def get_attribute(self, instance):
request = self.context.get('request', None)
if request is not None:
collection_type = instance.members.get(user=request.user).collectionType
return collection_type and collection_type.uid
return None
class UserSlugRelatedField(serializers.SlugRelatedField): class UserSlugRelatedField(serializers.SlugRelatedField):
def get_queryset(self): def get_queryset(self):
view = self.context.get('view', None) view = self.context.get('view', None)
@ -256,8 +265,15 @@ class CollectionItemBulkGetSerializer(BetterErrorsMixin, serializers.ModelSerial
fields = ('uid', 'etag') fields = ('uid', 'etag')
class CollectionListMultiSerializer(BetterErrorsMixin, serializers.Serializer):
collectionTypes = serializers.ListField(
child=BinaryBase64Field()
)
class CollectionSerializer(BetterErrorsMixin, serializers.ModelSerializer): class CollectionSerializer(BetterErrorsMixin, serializers.ModelSerializer):
collectionKey = CollectionEncryptionKeyField() collectionKey = CollectionEncryptionKeyField()
collectionType = CollectionTypeField()
accessLevel = serializers.SerializerMethodField('get_access_level_from_context') accessLevel = serializers.SerializerMethodField('get_access_level_from_context')
stoken = serializers.CharField(read_only=True) stoken = serializers.CharField(read_only=True)
@ -265,7 +281,7 @@ class CollectionSerializer(BetterErrorsMixin, serializers.ModelSerializer):
class Meta: class Meta:
model = models.Collection model = models.Collection
fields = ('item', 'accessLevel', 'collectionKey', 'stoken') fields = ('item', 'accessLevel', 'collectionKey', 'collectionType', 'stoken')
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)
@ -276,6 +292,7 @@ class CollectionSerializer(BetterErrorsMixin, 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"""
collection_key = validated_data.pop('collectionKey') collection_key = validated_data.pop('collectionKey')
collection_type = validated_data.pop('collectionType')
main_item_data = validated_data.pop('main_item') main_item_data = validated_data.pop('main_item')
etag = main_item_data.pop('etag') etag = main_item_data.pop('etag')
@ -297,11 +314,16 @@ class CollectionSerializer(BetterErrorsMixin, serializers.ModelSerializer):
process_revisions_for_item(main_item, revision_data) process_revisions_for_item(main_item, revision_data)
user = validated_data.get('owner')
collection_type_obj, _ = models.CollectionType.objects.get_or_create(uid=collection_type, owner=user)
models.CollectionMember(collection=instance, models.CollectionMember(collection=instance,
stoken=models.Stoken.objects.create(), stoken=models.Stoken.objects.create(),
user=validated_data.get('owner'), user=user,
accessLevel=models.AccessLevels.ADMIN, accessLevel=models.AccessLevels.ADMIN,
encryptionKey=collection_key, encryptionKey=collection_key,
collectionType=collection_type_obj,
).save() ).save()
return instance return instance
@ -381,6 +403,7 @@ class CollectionInvitationSerializer(BetterErrorsMixin, serializers.ModelSeriali
class InvitationAcceptSerializer(BetterErrorsMixin, serializers.Serializer): class InvitationAcceptSerializer(BetterErrorsMixin, serializers.Serializer):
collectionType = BinaryBase64Field()
encryptionKey = BinaryBase64Field() encryptionKey = BinaryBase64Field()
def create(self, validated_data): def create(self, validated_data):
@ -388,13 +411,18 @@ class InvitationAcceptSerializer(BetterErrorsMixin, serializers.Serializer):
with transaction.atomic(): with transaction.atomic():
invitation = self.context['invitation'] invitation = self.context['invitation']
encryption_key = validated_data.get('encryptionKey') encryption_key = validated_data.get('encryptionKey')
collection_type = validated_data.pop('collectionType')
user = invitation.user
collection_type_obj, _ = models.CollectionType.objects.get_or_create(uid=collection_type, owner=user)
member = models.CollectionMember.objects.create( member = models.CollectionMember.objects.create(
collection=invitation.collection, collection=invitation.collection,
stoken=models.Stoken.objects.create(), stoken=models.Stoken.objects.create(),
user=invitation.user, user=user,
accessLevel=invitation.accessLevel, accessLevel=invitation.accessLevel,
encryptionKey=encryption_key, encryptionKey=encryption_key,
collectionType=collection_type_obj,
) )
models.CollectionMemberRemoved.objects.filter( models.CollectionMemberRemoved.objects.filter(

@ -66,6 +66,7 @@ from .serializers import (
CollectionItemDepSerializer, CollectionItemDepSerializer,
CollectionItemRevisionSerializer, CollectionItemRevisionSerializer,
CollectionItemChunkSerializer, CollectionItemChunkSerializer,
CollectionListMultiSerializer,
CollectionMemberSerializer, CollectionMemberSerializer,
CollectionInvitationSerializer, CollectionInvitationSerializer,
InvitationAcceptSerializer, InvitationAcceptSerializer,
@ -210,6 +211,21 @@ class CollectionViewSet(BaseViewSet):
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
queryset = self.get_queryset() queryset = self.get_queryset()
return self.list_common(request, queryset, *args, **kwargs)
@action_decorator(detail=False, methods=['POST'])
def list_multi(self, request, *args, **kwargs):
serializer = CollectionListMultiSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
collection_types = serializer.validated_data['collectionTypes']
queryset = self.get_queryset()
queryset = queryset.filter(members__collectionType__uid__in=collection_types)
return self.list_common(request, queryset, *args, **kwargs)
def list_common(self, request, queryset, *args, **kwargs):
result, new_stoken_obj, done = self.filter_by_stoken_and_limit(request, queryset) result, new_stoken_obj, done = self.filter_by_stoken_and_limit(request, queryset)
new_stoken = new_stoken_obj and new_stoken_obj.uid new_stoken = new_stoken_obj and new_stoken_obj.uid

Loading…
Cancel
Save