add /files/ and /item_files/ API endpoints
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
j3d1 2023-07-07 19:28:17 +02:00
parent 3afeed81b3
commit d00d637dca
11 changed files with 430 additions and 33 deletions

View file

@ -0,0 +1,12 @@
from rest_framework import serializers
from authentication.models import ToolshedUser
class OwnerSerializer(serializers.ReadOnlyField):
class Meta:
model = ToolshedUser
fields = ['username', 'domain']
def to_representation(self, value):
return value.username + '@' + value.domain

View file

@ -35,5 +35,6 @@ urlpatterns = [
path('api/', include('toolshed.api.friend')), path('api/', include('toolshed.api.friend')),
path('api/', include('toolshed.api.inventory')), path('api/', include('toolshed.api.inventory')),
path('api/', include('toolshed.api.info')), path('api/', include('toolshed.api.info')),
path('api/', include('toolshed.api.files')),
path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='api-docs'), path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='api-docs'),
] ]

View file

@ -0,0 +1,20 @@
from django.core.files.base import ContentFile
from rest_framework import serializers
from files.models import File
class FileSerializer(serializers.Serializer):
data = serializers.CharField()
mime_type = serializers.CharField()
class Meta:
model = File
fields = ['data', 'mime_type']
read_only_fields = ['id', 'size', 'name']
def to_representation(self, instance):
return {'id': instance.id, 'name': instance.file.url, 'size': instance.file.size,
'mime_type': instance.mime_type}
def create(self, validated_data):
return File.objects.get_or_create(**validated_data)[0]

View file

@ -0,0 +1,74 @@
from django.urls import path
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from authentication.signature_auth import SignatureAuthenticationLocal
from files.models import File
from files.serializers import FileSerializer
from toolshed.models import InventoryItem
@api_view(['GET'])
@permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthenticationLocal])
def list_all_files(request, format=None): # /files/
files = File.objects.select_related().filter(connected_items__owner=request.user).distinct()
return Response(FileSerializer(files, many=True).data)
def get_item_files(request, item_id):
try:
item = InventoryItem.objects.get(id=item_id, owner=request.user)
files = item.files.all()
return Response(FileSerializer(files, many=True).data)
except InventoryItem.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
def post_item_file(request, item_id):
try:
item = InventoryItem.objects.get(id=item_id, owner=request.user)
serializer = FileSerializer(data=request.data)
if serializer.is_valid():
file = serializer.save()
item.files.add(file)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except InventoryItem.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
@api_view(['POST', 'GET'])
@permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthenticationLocal])
def item_files(request, item_id, format=None): # /item_files/
if request.method == 'GET':
return get_item_files(request, item_id)
elif request.method == 'POST':
return post_item_file(request, item_id)
@api_view(['DELETE'])
@permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthenticationLocal])
def delete_item_file(request, item_id, file_id, format=None): # /item_files/
try:
item = InventoryItem.objects.get(id=item_id, owner=request.user)
file = item.files.get(id=file_id)
item.files.remove(file_id)
if file.connected_items.count() == 0:
file.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except InventoryItem.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
except File.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
urlpatterns = [
path('files/', list_all_files),
path('item_files/<int:item_id>/', item_files),
path('item_files/<int:item_id>/<int:file_id>/', delete_item_file),
]

View file

@ -1,3 +1,4 @@
from django.db import transaction
from django.urls import path from django.urls import path
from rest_framework import routers, viewsets from rest_framework import routers, viewsets
from rest_framework.decorators import authentication_classes, api_view, permission_classes from rest_framework.decorators import authentication_classes, api_view, permission_classes
@ -35,11 +36,13 @@ class InventoryItemViewSet(viewsets.ModelViewSet):
return InventoryItem.objects.filter(owner=self.request.user.user.get()) return InventoryItem.objects.filter(owner=self.request.user.user.get())
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(owner=self.request.user.user.get()) with transaction.atomic():
serializer.save(owner=self.request.user.user.get()).clean()
def perform_update(self, serializer): def perform_update(self, serializer):
with transaction.atomic():
if serializer.instance.owner == self.request.user.user.get(): if serializer.instance.owner == self.request.user.user.get():
serializer.save() serializer.save().clean()
def perform_destroy(self, instance): def perform_destroy(self, instance):
if instance.owner == self.request.user.user.get(): if instance.owner == self.request.user.user.get():

View file

@ -0,0 +1,34 @@
# Generated by Django 4.2.2 on 2023-07-07 17:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0001_initial'),
('toolshed', '0002_inventoryitem_itemtag_itemproperty_and_more'),
]
operations = [
migrations.AddField(
model_name='inventoryitem',
name='files',
field=models.ManyToManyField(related_name='connected_items', to='files.file'),
),
migrations.AlterField(
model_name='inventoryitem',
name='availability_policy',
field=models.CharField(default='private', max_length=255),
),
migrations.AlterField(
model_name='inventoryitem',
name='description',
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name='inventoryitem',
name='name',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View file

@ -1,8 +1,10 @@
from django.db import models from django.db import models
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django_softdelete.models import SoftDeleteModel from django_softdelete.models import SoftDeleteModel
from rest_framework.exceptions import ValidationError
from authentication.models import ToolshedUser, KnownIdentity from authentication.models import ToolshedUser, KnownIdentity
from files.models import File
class Category(SoftDeleteModel): class Category(SoftDeleteModel):
@ -28,7 +30,7 @@ class Property(models.Model):
unit_name_plural = models.CharField(max_length=255, null=True, blank=True) unit_name_plural = models.CharField(max_length=255, null=True, blank=True)
base2_prefix = models.BooleanField(default=False) base2_prefix = models.BooleanField(default=False)
dimensions = models.IntegerField(null=False, blank=False, default=1, validators=[MinValueValidator(1)]) dimensions = models.IntegerField(null=False, blank=False, default=1, validators=[MinValueValidator(1)])
origin = models.CharField(max_length=255) origin = models.CharField(max_length=255, null=False, blank=False)
class Meta: class Meta:
verbose_name_plural = 'properties' verbose_name_plural = 'properties'
@ -41,7 +43,7 @@ class Tag(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True) description = models.TextField(null=True, blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True, related_name='tags') category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True, related_name='tags')
origin = models.CharField(max_length=255) origin = models.CharField(max_length=255, null=False, blank=False)
def __str__(self): def __str__(self):
return self.name return self.name
@ -49,16 +51,21 @@ class Tag(models.Model):
class InventoryItem(SoftDeleteModel): class InventoryItem(SoftDeleteModel):
published = models.BooleanField(default=False) published = models.BooleanField(default=False)
name = models.CharField(max_length=255) name = models.CharField(max_length=255, null=True, blank=True)
description = models.TextField() description = models.TextField(null=True, blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True, category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True,
related_name='inventory_items') related_name='inventory_items')
availability_policy = models.CharField(max_length=255) availability_policy = models.CharField(max_length=255, default="private")
owned_quantity = models.IntegerField(default=1, validators=[MinValueValidator(0)]) owned_quantity = models.IntegerField(default=1, validators=[MinValueValidator(0)])
owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='inventory_items') owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='inventory_items')
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
tags = models.ManyToManyField('Tag', through='ItemTag', related_name='inventory_items') tags = models.ManyToManyField(Tag, through='ItemTag', related_name='inventory_items')
properties = models.ManyToManyField('Property', through='ItemProperty') properties = models.ManyToManyField(Property, through='ItemProperty')
files = models.ManyToManyField(File, related_name='connected_items')
def clean(self):
if (self.name is None or self.name == "") and self.files.count() == 0:
raise ValidationError("Name or at least one file must be set")
class ItemProperty(models.Model): class ItemProperty(models.Model):

View file

@ -1,5 +1,9 @@
from rest_framework import serializers from rest_framework import serializers
from authentication.models import KnownIdentity, ToolshedUser from authentication.models import KnownIdentity, ToolshedUser
from authentication.serializers import OwnerSerializer
from files.models import File
from files.serializers import FileSerializer
from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag
@ -34,15 +38,6 @@ class CategorySerializer(serializers.ModelSerializer):
return Category.objects.get(name=data.split("/")[-1]) return Category.objects.get(name=data.split("/")[-1])
class InventoryItemOwnerSerializer(serializers.ReadOnlyField):
class Meta:
model = ToolshedUser
fields = '__all__'
def to_representation(self, value):
return value.username + '@' + value.domain
class ItemPropertySerializer(serializers.ModelSerializer): class ItemPropertySerializer(serializers.ModelSerializer):
property = PropertySerializer(read_only=True) property = PropertySerializer(read_only=True)
@ -60,7 +55,7 @@ class ItemPropertySerializer(serializers.ModelSerializer):
class InventoryItemSerializer(serializers.ModelSerializer): class InventoryItemSerializer(serializers.ModelSerializer):
owner = InventoryItemOwnerSerializer(read_only=True) owner = OwnerSerializer(read_only=True)
tags = serializers.SlugRelatedField(many=True, required=False, queryset=Tag.objects.all(), slug_field='name') tags = serializers.SlugRelatedField(many=True, required=False, queryset=Tag.objects.all(), slug_field='name')
properties = ItemPropertySerializer(many=True, required=False, source='itemproperty_set') properties = ItemPropertySerializer(many=True, required=False, source='itemproperty_set')
category = CategorySerializer(required=False, allow_null=True) category = CategorySerializer(required=False, allow_null=True)
@ -70,14 +65,34 @@ class InventoryItemSerializer(serializers.ModelSerializer):
fields = ['id', 'name', 'description', 'owner', 'category', 'availability_policy', 'owned_quantity', 'owner', fields = ['id', 'name', 'description', 'owner', 'category', 'availability_policy', 'owned_quantity', 'owner',
'tags', 'properties'] 'tags', 'properties']
def to_internal_value(self, data):
files = data.pop('files', [])
ret = super().to_internal_value(data)
ret['files'] = files
return ret
def create(self, validated_data): def create(self, validated_data):
tags = validated_data.pop('tags', []) tags = validated_data.pop('tags', [])
props = validated_data.pop('itemproperty_set', []) props = validated_data.pop('itemproperty_set', [])
files = validated_data.pop('files', [])
item = InventoryItem.objects.create(**validated_data) item = InventoryItem.objects.create(**validated_data)
for tag in tags: for tag in tags:
item.tags.add(tag, through_defaults={}) item.tags.add(tag, through_defaults={})
for prop in props: for prop in props:
ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value']) ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value'])
for file in files:
if type(file) == dict:
file_serializer = FileSerializer(data=file)
if file_serializer.is_valid():
file_serializer.save()
item.files.add(file_serializer.instance)
else:
raise serializers.ValidationError(file_serializer.errors)
elif type(file) == int:
if File.objects.filter(id=file).exists():
item.files.add(File.objects.get(id=file))
else:
raise serializers.ValidationError("File with id {} does not exist".format(file))
item.save() item.save()
return item return item

View file

@ -3,28 +3,28 @@ from toolshed.models import Category, Tag, Property, InventoryItem, ItemProperty
class CategoryTestMixin: class CategoryTestMixin:
def prepare_categories(self): def prepare_categories(self):
self.f['cat1'] = Category.objects.create(name='cat1') self.f['cat1'] = Category.objects.create(name='cat1', origin='test')
self.f['cat2'] = Category.objects.create(name='cat2') self.f['cat2'] = Category.objects.create(name='cat2', origin='test')
self.f['cat3'] = Category.objects.create(name='cat3') self.f['cat3'] = Category.objects.create(name='cat3', origin='test')
self.f['subcat1'] = Category.objects.create(name='subcat1', parent=self.f['cat1']) self.f['subcat1'] = Category.objects.create(name='subcat1', parent=self.f['cat1'], origin='test')
self.f['subcat2'] = Category.objects.create(name='subcat2', parent=self.f['cat1']) self.f['subcat2'] = Category.objects.create(name='subcat2', parent=self.f['cat1'], origin='test')
self.f['subcat3'] = Category.objects.create(name='subcat3', parent=self.f['subcat1']) self.f['subcat3'] = Category.objects.create(name='subcat3', parent=self.f['subcat1'], origin='test')
class TagTestMixin: class TagTestMixin:
def prepare_tags(self): def prepare_tags(self):
self.f['tag1'] = Tag.objects.create(name='tag1', description='tag1 description', category=self.f['cat1']) self.f['tag1'] = Tag.objects.create(name='tag1', description='tag1 description', category=self.f['cat1'], origin='test')
self.f['tag2'] = Tag.objects.create(name='tag2', description='tag2 description', category=self.f['cat1']) self.f['tag2'] = Tag.objects.create(name='tag2', description='tag2 description', category=self.f['cat1'], origin='test')
self.f['tag3'] = Tag.objects.create(name='tag3') self.f['tag3'] = Tag.objects.create(name='tag3', origin='test')
class PropertyTestMixin: class PropertyTestMixin:
def prepare_properties(self): def prepare_properties(self):
self.f['prop1'] = Property.objects.create(name='prop1') self.f['prop1'] = Property.objects.create(name='prop1', origin='test')
self.f['prop2'] = Property.objects.create( self.f['prop2'] = Property.objects.create(
name='prop2', description='prop2 description', category=self.f['cat1']) name='prop2', description='prop2 description', category=self.f['cat1'], origin='test')
self.f['prop3'] = Property.objects.create( self.f['prop3'] = Property.objects.create(
name='prop3', description='prop3 description', category=self.f['cat1']) name='prop3', description='prop3 description', category=self.f['cat1'], origin='test')
class InventoryTestMixin(CategoryTestMixin, TagTestMixin, PropertyTestMixin): class InventoryTestMixin(CategoryTestMixin, TagTestMixin, PropertyTestMixin):

View file

@ -0,0 +1,140 @@
from django.test import Client
from authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase
from files.tests import FilesTestMixin
from toolshed.models import File
from toolshed.tests import InventoryTestMixin
anonymous_client = Client()
client = SignatureAuthClient()
class FileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, ToolshedTestCase):
def setUp(self):
super().setUp()
self.prepare_users()
self.prepare_files()
self.prepare_categories()
self.prepare_tags()
self.prepare_properties()
self.prepare_inventory()
self.f['item1'].files.add(self.f['test_file1'])
self.f['item1'].files.add(self.f['test_file2'])
self.f['item2'].files.add(self.f['test_file1'])
def test_files_anonymous(self):
response = anonymous_client.get(f"/api/item_files/{self.f['item1'].id}/")
self.assertEqual(response.status_code, 403)
def test_list_all_files(self):
response = client.get(f"/api/files/", self.f['local_user1'])
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 2)
self.assertEqual(response.json()[0]['mime_type'], 'text/plain')
self.assertEqual(response.json()[0]['name'],
f"/media/{self.f['hash1'][:2]}/{self.f['hash1'][2:4]}/{self.f['hash1'][4:6]}/{self.f['hash1'][6:]}")
self.assertEqual(response.json()[1]['mime_type'], 'text/plain')
self.assertEqual(response.json()[1]['name'],
f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
def test_files(self):
response = client.get(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'])
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 2)
self.assertEqual(response.json()[0]['mime_type'], 'text/plain')
self.assertEqual(response.json()[0]['name'],
f"/media/{self.f['hash1'][:2]}/{self.f['hash1'][2:4]}/{self.f['hash1'][4:6]}/{self.f['hash1'][6:]}")
self.assertEqual(response.json()[1]['mime_type'], 'text/plain')
self.assertEqual(response.json()[1]['name'],
f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
def test_files_not_found(self):
response = client.get(f"/api/item_files/99999/", self.f['local_user1'])
self.assertEqual(response.status_code, 404)
def test_post_file(self):
response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'],
{'data': self.f['encoded_content4'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 201)
self.assertEqual(File.objects.count(), 4)
self.assertEqual(File.objects.last().mime_type, 'text/plain')
self.assertEqual(File.objects.last().file.read(), self.f['test_content4'])
self.assertEqual(File.objects.last().file.size, len(self.f['test_content4']))
self.assertEqual(File.objects.last().file.name,
f"{self.f['hash4'][:2]}/{self.f['hash4'][2:4]}/{self.f['hash4'][4:6]}/{self.f['hash4'][6:]}")
def test_post_file_duplicate(self):
self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2)
response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'],
{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 201)
self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 3)
self.assertEqual(self.f['item1'].files.last().file.read(), self.f['test_content3'])
self.assertEqual(self.f['item1'].files.last().file.size, len(self.f['test_content3']))
self.assertEqual(self.f['item1'].files.last().file.name,
f"{self.f['hash3'][:2]}/{self.f['hash3'][2:4]}/{self.f['hash3'][4:6]}/{self.f['hash3'][6:]}")
def test_post_file_invalid(self):
response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'],
{'data': self.f['encoded_content4']})
self.assertEqual(response.status_code, 400)
def test_post_file_not_found_item(self):
response = client.post(f"/api/item_files/99999/", self.f['local_user1'],
{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 404)
self.assertEqual(File.objects.count(), 3)
def test_post_file_not_authenticated(self):
response = anonymous_client.post(f"/api/item_files/{self.f['item1'].id}/",
{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 403)
self.assertEqual(File.objects.count(), 3)
def test_post_file_not_authorized(self):
response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user2'],
{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 404)
self.assertEqual(File.objects.count(), 3)
def test_delete_file(self):
response = client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/",
self.f['local_user1'])
self.assertEqual(response.status_code, 204)
self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 1)
def test_delete_file_last_use(self):
response = client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file2'].id}/",
self.f['local_user1'])
self.assertEqual(response.status_code, 204)
self.assertEqual(File.objects.count(), 2)
self.assertEqual(self.f['item1'].files.count(), 1)
def test_delete_file_not_found(self):
response = client.delete(f"/api/item_files/{self.f['item1'].id}/99999/", self.f['local_user1'])
self.assertEqual(response.status_code, 404)
self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2)
def test_delete_file_not_found_item(self):
response = client.delete(f"/api/item_files/99999/{self.f['test_file1'].id}/", self.f['local_user1'])
self.assertEqual(response.status_code, 404)
self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2)
def test_delete_file_not_owner(self):
response = client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/",
self.f['local_user2'])
self.assertEqual(response.status_code, 404)
self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2)
def test_delete_file_anonymous(self):
response = anonymous_client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/")
self.assertEqual(response.status_code, 403)
self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2)

View file

@ -1,11 +1,12 @@
from authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase from authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase
from files.tests import FilesTestMixin
from toolshed.models import InventoryItem, Category from toolshed.models import InventoryItem, Category
from toolshed.tests import InventoryTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin from toolshed.tests import InventoryTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin
client = SignatureAuthClient() client = SignatureAuthClient()
class InventoryTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@ -77,6 +78,15 @@ class InventoryTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
self.assertEqual([t for t in item.tags.all()], []) self.assertEqual([t for t in item.tags.all()], [])
self.assertEqual([p for p in item.properties.all()], []) self.assertEqual([p for p in item.properties.all()], [])
def test_post_new_item_empty(self):
reply = client.post('/api/inventory_items/', self.f['local_user1'], {
'availability_policy': 'friends',
'owned_quantity': 1,
'image': '',
})
self.assertEqual(reply.status_code, 400)
self.assertEqual(InventoryItem.objects.count(), 2)
def test_post_new_item3(self): def test_post_new_item3(self):
reply = client.post('/api/inventory_items/', self.f['local_user1'], { reply = client.post('/api/inventory_items/', self.f['local_user1'], {
'availability_policy': 'friends', 'availability_policy': 'friends',
@ -202,3 +212,84 @@ class InventoryTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
reply = client.get('/api/search/?query=test', self.f['ext_user1']) reply = client.get('/api/search/?query=test', self.f['ext_user1'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0) self.assertEqual(len(reply.json()), 0)
class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, ToolshedTestCase):
def setUp(self):
super().setUp()
self.prepare_users()
self.prepare_categories()
self.prepare_tags()
self.prepare_properties()
self.prepare_files()
self.prepare_inventory()
def test_post_item_with_file_id(self):
reply = client.post('/api/inventory_items/', self.f['local_user1'], {
'name': 'test4',
'description': 'test',
'category': 'cat1',
'owned_quantity': 1,
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
'files': [self.f['test_file1'].id]
})
self.assertEqual(reply.status_code, 201)
self.assertEqual(InventoryItem.objects.count(), 3)
item = InventoryItem.objects.get(id=3)
self.assertEqual(item.availability_policy, 'private')
self.assertEqual(item.category, Category.objects.get(name='cat1'))
self.assertEqual(item.name, 'test4')
self.assertEqual(item.description, 'test')
self.assertEqual(item.owned_quantity, 1)
self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag2']])
self.assertEqual([p for p in item.properties.all()], [self.f['prop1'], self.f['prop2']])
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value1', 'value2'])
self.assertEqual([f for f in item.files.all()], [self.f['test_file1']])
def test_post_item_with_encoded_file(self):
reply = client.post('/api/inventory_items/', self.f['local_user1'], {
'name': 'test4',
'description': 'test',
'category': 'cat1',
'owned_quantity': 1,
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
'files': [{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}]
})
self.assertEqual(reply.status_code, 201)
self.assertEqual(InventoryItem.objects.count(), 3)
item = InventoryItem.objects.get(id=3)
self.assertEqual(item.availability_policy, 'private')
self.assertEqual(item.category, Category.objects.get(name='cat1'))
self.assertEqual(item.name, 'test4')
self.assertEqual(item.description, 'test')
self.assertEqual(item.owned_quantity, 1)
self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag2']])
self.assertEqual([p for p in item.properties.all()], [self.f['prop1'], self.f['prop2']])
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value1', 'value2'])
self.assertEqual([f for f in item.files.all()], [self.f['test_file3']])
def test_post_item_with_file_id_fail(self):
reply = client.post('/api/inventory_items/', self.f['local_user1'], {
'name': 'test4',
'description': 'test',
'category': 'cat1',
'owned_quantity': 1,
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
'files': [99999]
})
self.assertEqual(reply.status_code, 400)
def test_post_item_with_encoded_file_fail(self):
reply = client.post('/api/inventory_items/', self.f['local_user1'], {
'name': 'test4',
'description': 'test',
'category': 'cat1',
'owned_quantity': 1,
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
'files': [{'data': self.f['encoded_content3']}]
})
self.assertEqual(reply.status_code, 400)