fix embedded file upload

This commit is contained in:
j3d1 2023-11-20 20:09:58 +01:00
parent aa0bb9fd0d
commit 27589a09bd
4 changed files with 76 additions and 19 deletions

View file

@ -14,11 +14,19 @@ class FileManager(models.Manager):
if 'data' in kwargs and type(kwargs['data']) == str: if 'data' in kwargs and type(kwargs['data']) == str:
import base64 import base64
from hashlib import sha256 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') kwargs.pop('data')
content_hash = sha256(content).hexdigest() content_hash = sha256(content).hexdigest()
kwargs['file'] = ContentFile(content, content_hash) kwargs['file'] = ContentFile(content, content_hash)
kwargs['hash'] = content_hash kwargs['hash'] = content_hash
kwargs['mime_type'] = mime_type
else: else:
raise ValueError('data must be a base64 encoded string or file and hash must be provided') raise ValueError('data must be a base64 encoded string or file and hash must be provided')
try: try:
@ -30,11 +38,19 @@ class FileManager(models.Manager):
if 'data' in kwargs and type(kwargs['data']) == str: if 'data' in kwargs and type(kwargs['data']) == str:
import base64 import base64
from hashlib import sha256 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') kwargs.pop('data')
content_hash = sha256(content).hexdigest() content_hash = sha256(content).hexdigest()
kwargs['file'] = ContentFile(content, content_hash) kwargs['file'] = ContentFile(content, content_hash)
kwargs['hash'] = content_hash kwargs['hash'] = content_hash
kwargs['mime_type'] = mime_type
elif 'file' in kwargs and 'hash' in kwargs and type(kwargs['file']) == ContentFile: elif 'file' in kwargs and 'hash' in kwargs and type(kwargs['file']) == ContentFile:
pass pass
else: else:

View file

@ -15,7 +15,7 @@ class FileTestCase(TestCase):
def test_list_files(self): def test_list_files(self):
import base64 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') response = client.get('/api/1/files')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()[0]['hash'], item.hash) self.assertEqual(response.json()[0]['hash'], item.hash)
@ -23,7 +23,7 @@ class FileTestCase(TestCase):
def test_one_file(self): def test_one_file(self):
import base64 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}') response = client.get(f'/api/1/file/{item.hash}')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()['hash'], item.hash) self.assertEqual(response.json()['hash'], item.hash)
@ -33,15 +33,17 @@ class FileTestCase(TestCase):
import base64 import base64
Item.objects.create(container=self.box, event=self.event, description='1') Item.objects.create(container=self.box, event=self.event, description='1')
item = Item.objects.create(container=self.box, event=self.event, description='2') 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(response.status_code, 201)
self.assertEqual(len(response.json()['hash']), 64) self.assertEqual(len(response.json()['hash']), 64)
def test_delete_file(self): def test_delete_file(self):
import base64 import base64
item = Item.objects.create(container=self.box, event=self.event, description='1') 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.objects.create(item=item, data="data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8'))
file = File.objects.create(item=item, data=base64.b64encode(b"bar").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) self.assertEqual(len(File.objects.all()), 2)
response = client.delete(f'/api/1/file/{file.hash}') response = client.delete(f'/api/1/file/{file.hash}')
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)

View file

@ -43,13 +43,14 @@ class ContainerViewSet(viewsets.ModelViewSet):
class ItemSerializer(serializers.ModelSerializer): class ItemSerializer(serializers.ModelSerializer):
dataImage = serializers.CharField(write_only=True, required=False)
cid = serializers.SerializerMethodField() cid = serializers.SerializerMethodField()
box = serializers.SerializerMethodField() box = serializers.SerializerMethodField()
file = serializers.SerializerMethodField() file = serializers.SerializerMethodField()
class Meta: class Meta:
model = Item model = Item
fields = ['cid', 'box', 'uid', 'description', 'file'] fields = ['cid', 'box', 'uid', 'description', 'file', 'dataImage']
read_only_fields = ['uid'] read_only_fields = ['uid']
def get_cid(self, instance): def get_cid(self, instance):
@ -72,13 +73,15 @@ class ItemSerializer(serializers.ModelSerializer):
return super().to_internal_value(data) return super().to_internal_value(data)
def validate(self, attrs): def validate(self, attrs):
attrs.pop('dataImage', None)
return super().validate(attrs) return super().validate(attrs)
def create(self, validated_data): def create(self, validated_data):
if 'dataImage' in 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') validated_data.pop('dataImage')
item = Item.objects.create(**validated_data)
item.files.set([file])
return item
return Item.objects.create(**validated_data) return Item.objects.create(**validated_data)
def update(self, instance, validated_data): def update(self, instance, validated_data):
@ -87,8 +90,9 @@ class ItemSerializer(serializers.ModelSerializer):
validated_data['returned_at'] = datetime.now() validated_data['returned_at'] = datetime.now()
validated_data.pop('returned') validated_data.pop('returned')
if 'dataImage' in validated_data: 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') validated_data.pop('dataImage')
instance.files.add(file)
return super().update(instance, validated_data) return super().update(instance, validated_data)

View file

@ -22,12 +22,13 @@ class ItemTestCase(TestCase):
item = Item.objects.create(container=self.box, event=self.event, description='1') item = Item.objects.create(container=self.box, event=self.event, description='1')
response = client.get(f'/api/1/{self.event.slug}/item') response = client.get(f'/api/1/{self.event.slug}/item')
self.assertEqual(response.status_code, 200) 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): def test_members_with_file(self):
import base64 import base64
item = Item.objects.create(container=self.box, event=self.event, description='1') 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') response = client.get(f'/api/1/{self.event.slug}/item')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), self.assertEqual(response.json(),
@ -44,19 +45,35 @@ class ItemTestCase(TestCase):
def test_create_item(self): def test_create_item(self):
response = client.post(f'/api/1/{self.event.slug}/item', {'cid': self.box.cid, 'description': '1'}) 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.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(len(Item.objects.all()), 1)
self.assertEqual(Item.objects.all()[0].uid, 1) self.assertEqual(Item.objects.all()[0].uid, 1)
self.assertEqual(Item.objects.all()[0].description, '1') self.assertEqual(Item.objects.all()[0].description, '1')
self.assertEqual(Item.objects.all()[0].container.cid, self.box.cid) self.assertEqual(Item.objects.all()[0].container.cid, self.box.cid)
#def test_create_item_fail(self): def test_create_item_with_file(self):
# response = client.post(f'/api/1/{self.event.slug}/item/', {'cid': self.box.cid}) import base64
# self.assertEqual(response.status_code, 500) 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): def test_update_item(self):
item = Item.objects.create(container=self.box, event=self.event, description='1') 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.status_code, 200)
self.assertEqual(response.json(), self.assertEqual(response.json(),
{'uid': 1, 'description': '2', 'box': 'BOX', 'cid': self.box.cid, 'file': None}) {'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].description, '2')
self.assertEqual(Item.objects.all()[0].container.cid, self.box.cid) 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): def test_delete_item(self):
item = Item.objects.create(container=self.box, event=self.event, description='1') item = Item.objects.create(container=self.box, event=self.event, description='1')
Item.objects.create(container=self.box, event=self.event, description='2') Item.objects.create(container=self.box, event=self.event, description='2')
@ -95,4 +131,3 @@ class ItemTestCase(TestCase):
def test_item_nonexistent(self): def test_item_nonexistent(self):
response = client.get(f'/api/1/NOEVENT/item') response = client.get(f'/api/1/NOEVENT/item')
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)