add /files/ and /item_files/ API endpoints
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
3afeed81b3
commit
d00d637dca
11 changed files with 430 additions and 33 deletions
12
backend/authentication/serializers.py
Normal file
12
backend/authentication/serializers.py
Normal 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
|
|
@ -35,5 +35,6 @@ urlpatterns = [
|
|||
path('api/', include('toolshed.api.friend')),
|
||||
path('api/', include('toolshed.api.inventory')),
|
||||
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'),
|
||||
]
|
||||
|
|
20
backend/files/serializers.py
Normal file
20
backend/files/serializers.py
Normal 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]
|
74
backend/toolshed/api/files.py
Normal file
74
backend/toolshed/api/files.py
Normal 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),
|
||||
]
|
|
@ -1,3 +1,4 @@
|
|||
from django.db import transaction
|
||||
from django.urls import path
|
||||
from rest_framework import routers, viewsets
|
||||
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())
|
||||
|
||||
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):
|
||||
if serializer.instance.owner == self.request.user.user.get():
|
||||
serializer.save()
|
||||
with transaction.atomic():
|
||||
if serializer.instance.owner == self.request.user.user.get():
|
||||
serializer.save().clean()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
if instance.owner == self.request.user.user.get():
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -1,8 +1,10 @@
|
|||
from django.db import models
|
||||
from django.core.validators import MinValueValidator
|
||||
from django_softdelete.models import SoftDeleteModel
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from authentication.models import ToolshedUser, KnownIdentity
|
||||
from files.models import File
|
||||
|
||||
|
||||
class Category(SoftDeleteModel):
|
||||
|
@ -28,7 +30,7 @@ class Property(models.Model):
|
|||
unit_name_plural = models.CharField(max_length=255, null=True, blank=True)
|
||||
base2_prefix = models.BooleanField(default=False)
|
||||
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:
|
||||
verbose_name_plural = 'properties'
|
||||
|
@ -41,7 +43,7 @@ class Tag(models.Model):
|
|||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(null=True, blank=True)
|
||||
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):
|
||||
return self.name
|
||||
|
@ -49,16 +51,21 @@ class Tag(models.Model):
|
|||
|
||||
class InventoryItem(SoftDeleteModel):
|
||||
published = models.BooleanField(default=False)
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField()
|
||||
name = models.CharField(max_length=255, 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='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)])
|
||||
owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='inventory_items')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
tags = models.ManyToManyField('Tag', through='ItemTag', related_name='inventory_items')
|
||||
properties = models.ManyToManyField('Property', through='ItemProperty')
|
||||
tags = models.ManyToManyField(Tag, through='ItemTag', related_name='inventory_items')
|
||||
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):
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -34,15 +38,6 @@ class CategorySerializer(serializers.ModelSerializer):
|
|||
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):
|
||||
property = PropertySerializer(read_only=True)
|
||||
|
||||
|
@ -60,7 +55,7 @@ class ItemPropertySerializer(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')
|
||||
properties = ItemPropertySerializer(many=True, required=False, source='itemproperty_set')
|
||||
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',
|
||||
'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):
|
||||
tags = validated_data.pop('tags', [])
|
||||
props = validated_data.pop('itemproperty_set', [])
|
||||
files = validated_data.pop('files', [])
|
||||
item = InventoryItem.objects.create(**validated_data)
|
||||
for tag in tags:
|
||||
item.tags.add(tag, through_defaults={})
|
||||
for prop in props:
|
||||
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()
|
||||
return item
|
||||
|
||||
|
|
|
@ -3,28 +3,28 @@ from toolshed.models import Category, Tag, Property, InventoryItem, ItemProperty
|
|||
|
||||
class CategoryTestMixin:
|
||||
def prepare_categories(self):
|
||||
self.f['cat1'] = Category.objects.create(name='cat1')
|
||||
self.f['cat2'] = Category.objects.create(name='cat2')
|
||||
self.f['cat3'] = Category.objects.create(name='cat3')
|
||||
self.f['subcat1'] = Category.objects.create(name='subcat1', parent=self.f['cat1'])
|
||||
self.f['subcat2'] = Category.objects.create(name='subcat2', parent=self.f['cat1'])
|
||||
self.f['subcat3'] = Category.objects.create(name='subcat3', parent=self.f['subcat1'])
|
||||
self.f['cat1'] = Category.objects.create(name='cat1', origin='test')
|
||||
self.f['cat2'] = Category.objects.create(name='cat2', origin='test')
|
||||
self.f['cat3'] = Category.objects.create(name='cat3', origin='test')
|
||||
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'], origin='test')
|
||||
self.f['subcat3'] = Category.objects.create(name='subcat3', parent=self.f['subcat1'], origin='test')
|
||||
|
||||
|
||||
class TagTestMixin:
|
||||
def prepare_tags(self):
|
||||
self.f['tag1'] = Tag.objects.create(name='tag1', description='tag1 description', category=self.f['cat1'])
|
||||
self.f['tag2'] = Tag.objects.create(name='tag2', description='tag2 description', category=self.f['cat1'])
|
||||
self.f['tag3'] = Tag.objects.create(name='tag3')
|
||||
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'], origin='test')
|
||||
self.f['tag3'] = Tag.objects.create(name='tag3', origin='test')
|
||||
|
||||
|
||||
class PropertyTestMixin:
|
||||
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(
|
||||
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(
|
||||
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):
|
||||
|
|
140
backend/toolshed/tests/test_files.py
Normal file
140
backend/toolshed/tests/test_files.py
Normal 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)
|
|
@ -1,11 +1,12 @@
|
|||
from authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase
|
||||
from files.tests import FilesTestMixin
|
||||
from toolshed.models import InventoryItem, Category
|
||||
from toolshed.tests import InventoryTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin
|
||||
|
||||
client = SignatureAuthClient()
|
||||
|
||||
|
||||
class InventoryTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
|
||||
class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -77,6 +78,15 @@ class InventoryTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
|
|||
self.assertEqual([t for t in item.tags.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):
|
||||
reply = client.post('/api/inventory_items/', self.f['local_user1'], {
|
||||
'availability_policy': 'friends',
|
||||
|
@ -202,3 +212,84 @@ class InventoryTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
|
|||
reply = client.get('/api/search/?query=test', self.f['ext_user1'])
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
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)
|
Loading…
Reference in a new issue