From 67fb714ddba00af185f43cdadc47069c7c559958 Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Thu, 20 Feb 2020 13:56:16 +0200 Subject: [PATCH] More progress. --- .../migrations/0005_auto_20200220_1123.py | 29 +++++++++++ .../migrations/0006_auto_20200220_1137.py | 49 +++++++++++++++++++ .../migrations/0007_auto_20200220_1144.py | 28 +++++++++++ django_etesync/models.py | 40 ++++++++++----- django_etesync/serializers.py | 24 ++++++--- django_etesync/views.py | 38 ++++++++++++++ 6 files changed, 189 insertions(+), 19 deletions(-) create mode 100644 django_etesync/migrations/0005_auto_20200220_1123.py create mode 100644 django_etesync/migrations/0006_auto_20200220_1137.py create mode 100644 django_etesync/migrations/0007_auto_20200220_1144.py diff --git a/django_etesync/migrations/0005_auto_20200220_1123.py b/django_etesync/migrations/0005_auto_20200220_1123.py new file mode 100644 index 0000000..88c9ea6 --- /dev/null +++ b/django_etesync/migrations/0005_auto_20200220_1123.py @@ -0,0 +1,29 @@ +# Generated by Django 3.0.3 on 2020-02-20 11:23 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_etesync', '0004_auto_20200220_1029'), + ] + + operations = [ + migrations.RemoveField( + model_name='collectionitemchunk', + name='items', + ), + migrations.AddField( + model_name='collectionitem', + name='chunks', + field=models.ManyToManyField(related_name='items', to='django_etesync.CollectionItemChunk'), + ), + migrations.AddField( + model_name='collectionitemchunk', + name='collection', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='chunks', to='django_etesync.Collection'), + preserve_default=False, + ), + ] diff --git a/django_etesync/migrations/0006_auto_20200220_1137.py b/django_etesync/migrations/0006_auto_20200220_1137.py new file mode 100644 index 0000000..efc421e --- /dev/null +++ b/django_etesync/migrations/0006_auto_20200220_1137.py @@ -0,0 +1,49 @@ +# Generated by Django 3.0.3 on 2020-02-20 11:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_etesync', '0005_auto_20200220_1123'), + ] + + operations = [ + migrations.RemoveField( + model_name='collectionitem', + name='chunks', + ), + migrations.RemoveField( + model_name='collectionitem', + name='current', + ), + migrations.RemoveField( + model_name='collectionitem', + name='encryptionKey', + ), + migrations.RemoveField( + model_name='collectionitem', + name='hmac', + ), + migrations.RemoveField( + model_name='collectionitem', + name='version', + ), + migrations.CreateModel( + name='CollectionItemSnapshot', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('version', models.PositiveSmallIntegerField()), + ('encryptionKey', models.BinaryField(editable=True)), + ('hmac', models.CharField(max_length=50)), + ('current', models.BooleanField(db_index=True, default=True)), + ('chunks', models.ManyToManyField(related_name='items', to='django_etesync.CollectionItemChunk')), + ('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='snapshots', to='django_etesync.CollectionItem')), + ], + options={ + 'unique_together': {('item', 'current')}, + }, + ), + ] diff --git a/django_etesync/migrations/0007_auto_20200220_1144.py b/django_etesync/migrations/0007_auto_20200220_1144.py new file mode 100644 index 0000000..3ebf55b --- /dev/null +++ b/django_etesync/migrations/0007_auto_20200220_1144.py @@ -0,0 +1,28 @@ +# Generated by Django 3.0.3 on 2020-02-20 11:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_etesync', '0006_auto_20200220_1137'), + ] + + operations = [ + migrations.AddField( + model_name='collectionitemchunk', + name='item', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='chunks', to='django_etesync.CollectionItem'), + preserve_default=False, + ), + migrations.AlterUniqueTogether( + name='collectionitemchunk', + unique_together={('item', 'order')}, + ), + migrations.RemoveField( + model_name='collectionitemchunk', + name='collection', + ), + ] diff --git a/django_etesync/models.py b/django_etesync/models.py index f773495..1bd2090 100644 --- a/django_etesync/models.py +++ b/django_etesync/models.py @@ -35,19 +35,18 @@ class Collection(models.Model): def __str__(self): return self.uid - @cached_property - def current_items(self): - return self.items.filter(current=True) + +def chunk_directory_path(instance, filename): + col = instance.itemSnapshot.item.collection + user_id = col.owner.id + return Path('user_{}'.format(user_id), col.uid, instance.uid) + class CollectionItem(models.Model): uid = models.CharField(db_index=True, blank=False, null=False, max_length=44, validators=[UidValidator]) - version = models.PositiveSmallIntegerField() - encryptionKey = models.BinaryField(editable=True, blank=False, null=False) collection = models.ForeignKey(Collection, related_name='items', on_delete=models.CASCADE) - hmac = models.CharField(max_length=50, blank=False, null=False) - current = models.BooleanField(db_index=True, default=True) class Meta: unique_together = ('uid', 'collection') @@ -55,22 +54,37 @@ class CollectionItem(models.Model): def __str__(self): return self.uid - -def chunk_directory_path(instance, filename): - col = instance.itemSnapshot.item.collection - user_id = col.owner.id - return Path('user_{}'.format(user_id), col.uid, instance.uid) + @cached_property + def content(self): + return self.snapshots.get(current=True) class CollectionItemChunk(models.Model): uid = models.CharField(db_index=True, blank=False, null=False, max_length=44, validators=[UidValidator]) - items = models.ManyToManyField(CollectionItem, related_name='chunks') + item = models.ForeignKey(CollectionItem, related_name='chunks', on_delete=models.CASCADE) order = models.CharField(max_length=100, blank=False, null=False) # We probably just want to implement this manually because we can have more than one pointing to a file. chunkFile = models.FileField(upload_to=chunk_directory_path) class Meta: + unique_together = ('item', 'order') ordering = ['order'] def __str__(self): return self.uid + + +class CollectionItemSnapshot(models.Model): + version = models.PositiveSmallIntegerField() + encryptionKey = models.BinaryField(editable=True, blank=False, null=False) + item = models.ForeignKey(CollectionItem, related_name='snapshots', on_delete=models.CASCADE) + chunks = models.ManyToManyField(CollectionItemChunk, related_name='items') + hmac = models.CharField(max_length=50, blank=False, null=False) + current = models.BooleanField(db_index=True, default=True) + + class Meta: + unique_together = ('item', 'current') + + def __str__(self): + return '{} {} current={}'.format(self.item.uid, self.id, self.current) + diff --git a/django_etesync/serializers.py b/django_etesync/serializers.py index 15034c2..f86d5e1 100644 --- a/django_etesync/serializers.py +++ b/django_etesync/serializers.py @@ -64,7 +64,7 @@ class CollectionItemChunkSerializer(serializers.ModelSerializer): fields = ('uid', ) -class CollectionItemSerializer(serializers.ModelSerializer): +class CollectionItemSnapshotSerializer(serializers.ModelSerializer): encryptionKey = BinaryBase64Field() chunks = serializers.SlugRelatedField( slug_field='uid', @@ -73,18 +73,30 @@ class CollectionItemSerializer(serializers.ModelSerializer): ) class Meta: - model = models.CollectionItem - fields = ('uid', 'version', 'encryptionKey', 'chunks', 'hmac') + model = models.CollectionItemSnapshot + fields = ('version', 'encryptionKey', 'chunks', 'hmac') -class CollectionItemInlineSerializer(CollectionItemSerializer): +class CollectionItemSnapshotInlineSerializer(CollectionItemSnapshotSerializer): chunksData = serializers.SerializerMethodField('get_inline_chunks_from_context') - class Meta(CollectionItemSerializer.Meta): - fields = CollectionItemSerializer.Meta.fields + ('chunksData', ) + class Meta(CollectionItemSnapshotSerializer.Meta): + fields = CollectionItemSnapshotSerializer.Meta.fields + ('chunksData', ) def get_inline_chunks_from_context(self, obj): request = self.context.get('request', None) if request is not None: return ['SomeInlineData', 'Somemoredata'] return 'readOnly' + + +class CollectionItemSerializer(serializers.ModelSerializer): + content = CollectionItemSnapshotSerializer(read_only=True, many=False) + + class Meta: + model = models.CollectionItem + fields = ('uid', 'content') + + +class CollectionItemInlineSerializer(CollectionItemSerializer): + content = CollectionItemSnapshotInlineSerializer(read_only=True, many=False) diff --git a/django_etesync/views.py b/django_etesync/views.py index 0c28974..57136a4 100644 --- a/django_etesync/views.py +++ b/django_etesync/views.py @@ -24,6 +24,7 @@ from django.views.decorators.http import require_POST from rest_framework import status from rest_framework import viewsets +from rest_framework import parsers from rest_framework.response import Response from . import app_settings, paginators @@ -143,3 +144,40 @@ class CollectionItemViewSet(BaseViewSet): def partial_update(self, request, collection_uid=None, uid=None): # FIXME: implement, or should it be implemented elsewhere? return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) + + +class CollectionItemChunkViewSet(viewsets.ViewSet): + allowed_methods = ['GET', 'POST'] + authentication_classes = BaseViewSet.authentication_classes + permission_classes = BaseViewSet.permission_classes + parser_classes = (parsers.MultiPartParser, ) + lookup_field = 'uid' + + def create(self, request, collection_uid=None): + # FIXME: we are potentially not getting the correct queryset + collection_object = Collection.objects.get(uid=collection_uid) + + many = isinstance(request.data, list) + serializer = self.serializer_class(data=request.data, many=many) + if serializer.is_valid(): + try: + serializer.save(collection=collection_object) + except IntegrityError: + content = {'code': 'integrity_error'} + return Response(content, status=status.HTTP_400_BAD_REQUEST) + + return Response({}, status=status.HTTP_201_CREATED) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def destroy(self, request, collection_uid=None, uid=None): + # FIXME: implement + return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) + + def update(self, request, collection_uid=None, uid=None): + # FIXME: implement, or should it be implemented elsewhere? + return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) + + def partial_update(self, request, collection_uid=None, uid=None): + # FIXME: implement, or should it be implemented elsewhere? + return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)