diff --git a/core/files/models.py b/core/files/models.py index aece630..d417790 100644 --- a/core/files/models.py +++ b/core/files/models.py @@ -14,11 +14,19 @@ class FileManager(models.Manager): if 'data' in kwargs and type(kwargs['data']) == str: import base64 from hashlib import sha256 - content = base64.b64decode(kwargs['data'], validate=True) + raw = kwargs['data'] + if not raw.startswith('data:'): + raise ValueError('data must be a base64 encoded string or file and hash must be provided') + raw = raw.split(';base64,') + if len(raw) != 2: + raise ValueError('data must be a base64 encoded string or file and hash must be provided') + mime_type = raw[0].split(':')[1] + content = base64.b64decode(raw[1], validate=True) kwargs.pop('data') content_hash = sha256(content).hexdigest() kwargs['file'] = ContentFile(content, content_hash) kwargs['hash'] = content_hash + kwargs['mime_type'] = mime_type else: raise ValueError('data must be a base64 encoded string or file and hash must be provided') try: @@ -30,11 +38,19 @@ class FileManager(models.Manager): if 'data' in kwargs and type(kwargs['data']) == str: import base64 from hashlib import sha256 - content = base64.b64decode(kwargs['data'], validate=True) + raw = kwargs['data'] + if not raw.startswith('data:'): + raise ValueError('data must be a base64 encoded string or file and hash must be provided') + raw = raw.split(';base64,') + if len(raw) != 2: + raise ValueError('data must be a base64 encoded string or file and hash must be provided') + mime_type = raw[0].split(':')[1] + content = base64.b64decode(raw[1], validate=True) kwargs.pop('data') content_hash = sha256(content).hexdigest() kwargs['file'] = ContentFile(content, content_hash) kwargs['hash'] = content_hash + kwargs['mime_type'] = mime_type elif 'file' in kwargs and 'hash' in kwargs and type(kwargs['file']) == ContentFile: pass else: diff --git a/core/files/tests.py b/core/files/tests.py index ca475fb..fd05b95 100644 --- a/core/files/tests.py +++ b/core/files/tests.py @@ -15,7 +15,7 @@ class FileTestCase(TestCase): def test_list_files(self): import base64 - item = File.objects.create(data=base64.b64encode(b"foo").decode('utf-8')) + item = File.objects.create(data="data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')) response = client.get('/api/1/files') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()[0]['hash'], item.hash) @@ -23,7 +23,7 @@ class FileTestCase(TestCase): def test_one_file(self): import base64 - item = File.objects.create(data=base64.b64encode(b"foo").decode('utf-8')) + item = File.objects.create(data="data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')) response = client.get(f'/api/1/file/{item.hash}') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['hash'], item.hash) @@ -33,15 +33,17 @@ class FileTestCase(TestCase): import base64 Item.objects.create(container=self.box, event=self.event, description='1') item = Item.objects.create(container=self.box, event=self.event, description='2') - response = client.post('/api/1/file', {'data': base64.b64encode(b"foo").decode('utf-8')}, content_type='application/json') + response = client.post('/api/1/file', + {'data': "data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')}, + content_type='application/json') self.assertEqual(response.status_code, 201) self.assertEqual(len(response.json()['hash']), 64) def test_delete_file(self): import base64 item = Item.objects.create(container=self.box, event=self.event, description='1') - File.objects.create(item=item, data=base64.b64encode(b"foo").decode('utf-8')) - file = File.objects.create(item=item, data=base64.b64encode(b"bar").decode('utf-8')) + File.objects.create(item=item, data="data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')) + file = File.objects.create(item=item, data="data:text/plain;base64," + base64.b64encode(b"bar").decode('utf-8')) self.assertEqual(len(File.objects.all()), 2) response = client.delete(f'/api/1/file/{file.hash}') self.assertEqual(response.status_code, 204) diff --git a/core/inventory/api_v1.py b/core/inventory/api_v1.py index 3f3ece7..52c6ed0 100644 --- a/core/inventory/api_v1.py +++ b/core/inventory/api_v1.py @@ -43,13 +43,14 @@ class ContainerViewSet(viewsets.ModelViewSet): class ItemSerializer(serializers.ModelSerializer): + dataImage = serializers.CharField(write_only=True, required=False) cid = serializers.SerializerMethodField() box = serializers.SerializerMethodField() file = serializers.SerializerMethodField() class Meta: model = Item - fields = ['cid', 'box', 'uid', 'description', 'file'] + fields = ['cid', 'box', 'uid', 'description', 'file', 'dataImage'] read_only_fields = ['uid'] def get_cid(self, instance): @@ -72,13 +73,15 @@ class ItemSerializer(serializers.ModelSerializer): return super().to_internal_value(data) def validate(self, attrs): - attrs.pop('dataImage', None) return super().validate(attrs) def create(self, validated_data): if 'dataImage' in validated_data: - file = File.objects.create(data=validated_data['dataImage'], iid=validated_data['iid']) + file = File.objects.create(data=validated_data['dataImage']) validated_data.pop('dataImage') + item = Item.objects.create(**validated_data) + item.files.set([file]) + return item return Item.objects.create(**validated_data) def update(self, instance, validated_data): @@ -87,8 +90,9 @@ class ItemSerializer(serializers.ModelSerializer): validated_data['returned_at'] = datetime.now() validated_data.pop('returned') if 'dataImage' in validated_data: - file = File.objects.create(data=validated_data['dataImage'], iid=instance.iid) + file = File.objects.create(data=validated_data['dataImage']) validated_data.pop('dataImage') + instance.files.add(file) return super().update(instance, validated_data) diff --git a/core/inventory/tests/test_items.py b/core/inventory/tests/test_items.py index 942b9ae..e13bcba 100644 --- a/core/inventory/tests/test_items.py +++ b/core/inventory/tests/test_items.py @@ -22,12 +22,13 @@ class ItemTestCase(TestCase): item = Item.objects.create(container=self.box, event=self.event, description='1') response = client.get(f'/api/1/{self.event.slug}/item') self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), [{'uid': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.cid, 'file': None}]) + self.assertEqual(response.json(), + [{'uid': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.cid, 'file': None}]) def test_members_with_file(self): import base64 item = Item.objects.create(container=self.box, event=self.event, description='1') - file = File.objects.create(item=item, data=base64.b64encode(b"foo").decode('utf-8')) + file = File.objects.create(item=item, data="data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')) response = client.get(f'/api/1/{self.event.slug}/item') self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), @@ -44,19 +45,35 @@ class ItemTestCase(TestCase): def test_create_item(self): response = client.post(f'/api/1/{self.event.slug}/item', {'cid': self.box.cid, 'description': '1'}) self.assertEqual(response.status_code, 201) - self.assertEqual(response.json(), {'uid': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.cid, 'file': None}) + self.assertEqual(response.json(), + {'uid': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.cid, 'file': None}) self.assertEqual(len(Item.objects.all()), 1) self.assertEqual(Item.objects.all()[0].uid, 1) self.assertEqual(Item.objects.all()[0].description, '1') self.assertEqual(Item.objects.all()[0].container.cid, self.box.cid) - #def test_create_item_fail(self): - # response = client.post(f'/api/1/{self.event.slug}/item/', {'cid': self.box.cid}) - # self.assertEqual(response.status_code, 500) + def test_create_item_with_file(self): + import base64 + response = client.post(f'/api/1/{self.event.slug}/item', + {'cid': self.box.cid, 'description': '1', + 'dataImage': "data:text/plain;base64," + base64.b64encode(b"foo").decode( + 'utf-8')}, content_type='application/json') + self.assertEqual(response.status_code, 201) + self.assertEqual(response.json()['uid'], 1) + self.assertEqual(response.json()['description'], '1') + self.assertEqual(response.json()['box'], 'BOX') + self.assertEqual(response.json()['cid'], self.box.cid) + self.assertEqual(len(response.json()['file']), 64) + self.assertEqual(len(Item.objects.all()), 1) + self.assertEqual(Item.objects.all()[0].uid, 1) + self.assertEqual(Item.objects.all()[0].description, '1') + self.assertEqual(Item.objects.all()[0].container.cid, self.box.cid) + self.assertEqual(len(File.objects.all()), 1) def test_update_item(self): item = Item.objects.create(container=self.box, event=self.event, description='1') - response = client.put(f'/api/1/{self.event.slug}/item/{item.uid}', {'description': '2'}, content_type='application/json') + response = client.put(f'/api/1/{self.event.slug}/item/{item.uid}', {'description': '2'}, + content_type='application/json') self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {'uid': 1, 'description': '2', 'box': 'BOX', 'cid': self.box.cid, 'file': None}) @@ -65,6 +82,25 @@ class ItemTestCase(TestCase): self.assertEqual(Item.objects.all()[0].description, '2') self.assertEqual(Item.objects.all()[0].container.cid, self.box.cid) + def test_update_item_with_file(self): + import base64 + item = Item.objects.create(container=self.box, event=self.event, description='1') + response = client.put(f'/api/1/{self.event.slug}/item/{item.uid}', + {'description': '2', + 'dataImage': "data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')}, + content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['uid'], 1) + self.assertEqual(response.json()['description'], '2') + self.assertEqual(response.json()['box'], 'BOX') + self.assertEqual(response.json()['cid'], self.box.cid) + self.assertEqual(len(response.json()['file']), 64) + self.assertEqual(len(Item.objects.all()), 1) + self.assertEqual(Item.objects.all()[0].uid, 1) + self.assertEqual(Item.objects.all()[0].description, '2') + self.assertEqual(Item.objects.all()[0].container.cid, self.box.cid) + self.assertEqual(len(File.objects.all()), 1) + def test_delete_item(self): item = Item.objects.create(container=self.box, event=self.event, description='1') Item.objects.create(container=self.box, event=self.event, description='2') @@ -95,4 +131,3 @@ class ItemTestCase(TestCase): def test_item_nonexistent(self): response = client.get(f'/api/1/NOEVENT/item') self.assertEqual(response.status_code, 404) -