add StorageLocation model
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
j3d1 2023-11-29 23:38:01 +01:00
parent 0d394d531b
commit 93335b2776
8 changed files with 179 additions and 8 deletions

1
.gitignore vendored
View file

@ -129,4 +129,5 @@ dmypy.json
.pyre/ .pyre/
staticfiles/ staticfiles/
userfiles/
testdata.py testdata.py

View file

@ -7,8 +7,8 @@ from rest_framework.response import Response
from authentication.models import ToolshedUser, KnownIdentity from authentication.models import ToolshedUser, KnownIdentity
from authentication.signature_auth import SignatureAuthentication from authentication.signature_auth import SignatureAuthentication
from toolshed.models import InventoryItem from toolshed.models import InventoryItem, StorageLocation
from toolshed.serializers import InventoryItemSerializer from toolshed.serializers import InventoryItemSerializer, StorageLocationSerializer
router = routers.SimpleRouter() router = routers.SimpleRouter()
@ -61,7 +61,19 @@ def search_inventory_items(request):
return Response({'error': 'No query provided.'}, status=400) return Response({'error': 'No query provided.'}, status=400)
class StorageLocationViewSet(viewsets.ModelViewSet):
serializer_class = StorageLocationSerializer
authentication_classes = [SignatureAuthentication]
permission_classes = [IsAuthenticated]
def get_queryset(self):
if type(self.request.user) == KnownIdentity and self.request.user.user.exists():
return StorageLocation.objects.filter(owner=self.request.user.user.get())
return StorageLocation.objects.none()
router.register(r'inventory_items', InventoryItemViewSet, basename='inventory_items') router.register(r'inventory_items', InventoryItemViewSet, basename='inventory_items')
router.register(r'storage_locations', StorageLocationViewSet, basename='storage_locations')
urlpatterns = router.urls + [ urlpatterns = router.urls + [
path('search/', search_inventory_items, name='search_inventory_items'), path('search/', search_inventory_items, name='search_inventory_items'),

View file

@ -0,0 +1,32 @@
# Generated by Django 4.2.2 on 2024-02-20 13:50
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('toolshed', '0003_inventoryitem_files_and_more'),
]
operations = [
migrations.CreateModel(
name='StorageLocation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='storage_locations', to='toolshed.category')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='storage_locations', to=settings.AUTH_USER_MODEL)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='toolshed.storagelocation')),
],
),
migrations.AddField(
model_name='inventoryitem',
name='storage_location',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_items', to='toolshed.storagelocation'),
),
]

View file

@ -62,6 +62,8 @@ class InventoryItem(SoftDeleteModel):
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') files = models.ManyToManyField(File, related_name='connected_items')
storage_location = models.ForeignKey('StorageLocation', on_delete=models.CASCADE, null=True, blank=True,
related_name='inventory_items')
def clean(self): def clean(self):
if (self.name is None or self.name == "") and self.files.count() == 0: if (self.name is None or self.name == "") and self.files.count() == 0:
@ -77,3 +79,16 @@ class ItemProperty(models.Model):
class ItemTag(models.Model): class ItemTag(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE) tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
inventory_item = models.ForeignKey(InventoryItem, on_delete=models.CASCADE) inventory_item = models.ForeignKey(InventoryItem, on_delete=models.CASCADE)
class StorageLocation(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='storage_locations')
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='storage_locations')
def __str__(self):
parent = str(self.parent) + "/" if self.parent else ""
return parent + self.name

View file

@ -3,7 +3,7 @@ from authentication.models import KnownIdentity, ToolshedUser, FriendRequestInco
from authentication.serializers import OwnerSerializer from authentication.serializers import OwnerSerializer
from files.models import File from files.models import File
from files.serializers import FileSerializer from files.serializers import FileSerializer
from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag, StorageLocation
class FriendSerializer(serializers.ModelSerializer): class FriendSerializer(serializers.ModelSerializer):
@ -11,7 +11,7 @@ class FriendSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = KnownIdentity model = KnownIdentity
fields = ['username', 'public_key'] fields = ['id', 'username', 'public_key']
def get_username(self, obj): def get_username(self, obj):
return obj.username + '@' + obj.domain return obj.username + '@' + obj.domain
@ -48,6 +48,23 @@ class CategorySerializer(serializers.ModelSerializer):
return Category.objects.get(name=data.split("/")[-1]) return Category.objects.get(name=data.split("/")[-1])
class StorageLocationSerializer(serializers.ModelSerializer):
owner = OwnerSerializer(read_only=True)
category = CategorySerializer(required=False, allow_null=True)
path = serializers.SerializerMethodField()
class Meta:
model = StorageLocation
fields = ['id', 'name', 'description', 'path', 'category', 'owner']
read_only_fields = ['path']
@staticmethod
def get_path(obj):
if obj.parent:
return StorageLocationSerializer.get_path(obj.parent) + "/" + obj.name
return obj.name
class ItemPropertySerializer(serializers.ModelSerializer): class ItemPropertySerializer(serializers.ModelSerializer):
property = PropertySerializer(read_only=True) property = PropertySerializer(read_only=True)
@ -74,7 +91,7 @@ class InventoryItemSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = InventoryItem model = InventoryItem
fields = ['id', 'name', 'description', 'owner', 'category', 'availability_policy', 'owned_quantity', 'owner', fields = ['id', 'name', 'description', 'owner', 'category', 'availability_policy', 'owned_quantity', 'owner',
'tags', 'properties', 'files'] 'tags', 'properties', 'files', 'storage_location']
def to_internal_value(self, data): def to_internal_value(self, data):
files = data.pop('files', []) files = data.pop('files', [])

View file

@ -1,4 +1,4 @@
from toolshed.models import Category, Tag, Property, InventoryItem, ItemProperty from toolshed.models import Category, Tag, Property, InventoryItem, ItemProperty, StorageLocation
class CategoryTestMixin: class CategoryTestMixin:
@ -13,8 +13,10 @@ class CategoryTestMixin:
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'], origin='test') 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'], origin='test') 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') self.f['tag3'] = Tag.objects.create(name='tag3', origin='test')
@ -41,3 +43,13 @@ class InventoryTestMixin(CategoryTestMixin, TagTestMixin, PropertyTestMixin):
self.f['item2'].tags.add(self.f['tag2'], through_defaults={}) self.f['item2'].tags.add(self.f['tag2'], through_defaults={})
ItemProperty.objects.create(inventory_item=self.f['item2'], property=self.f['prop1'], value='value1').save() ItemProperty.objects.create(inventory_item=self.f['item2'], property=self.f['prop1'], value='value1').save()
ItemProperty.objects.create(inventory_item=self.f['item2'], property=self.f['prop2'], value='value2').save() ItemProperty.objects.create(inventory_item=self.f['item2'], property=self.f['prop2'], value='value2').save()
class LocationTestMixin:
def prepare_locations(self):
self.f['loc1'] = StorageLocation.objects.create(name='loc1', owner=self.f['local_user1'])
self.f['loc2'] = StorageLocation.objects.create(name='loc2', owner=self.f['local_user1'],
category=self.f['cat1'])
self.f['loc3'] = StorageLocation.objects.create(name='loc3', owner=self.f['local_user1'], parent=self.f['loc1'])
self.f['loc4'] = StorageLocation.objects.create(name='loc4', owner=self.f['local_user1'], parent=self.f['loc1'],
category=self.f['cat1'])

View file

@ -210,6 +210,17 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
}) })
self.assertEqual(reply.status_code, 400) self.assertEqual(reply.status_code, 400)
def test_post_request_missing_key_none(self):
befriender = self.f['ext_user1']
befriendee = self.f['local_user1']
reply = client.post('/api/friendrequests/', befriender, {
'befriender': str(befriender),
'befriendee': str(befriendee),
'befriender_key': None,
'secret': 'secret2'
})
self.assertEqual(reply.status_code, 400)
def test_post_request_breaking_key(self): def test_post_request_breaking_key(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = self.f['local_user1'] befriendee = self.f['local_user1']

View file

@ -0,0 +1,71 @@
from authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase
from files.tests import FilesTestMixin
from toolshed.models import InventoryItem, Category
from toolshed.tests import InventoryTestMixin, LocationTestMixin
client = SignatureAuthClient()
class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin, ToolshedTestCase):
def setUp(self):
super().setUp()
self.prepare_users()
self.prepare_categories()
self.prepare_tags()
self.prepare_properties()
self.prepare_locations()
self.prepare_inventory()
def test_locations(self):
self.assertEqual("loc1", str(self.f['loc1']))
self.assertEqual("loc1", self.f['loc1'].name)
self.assertEqual("loc2", str(self.f['loc2']))
self.assertEqual("loc2", self.f['loc2'].name)
self.assertEqual("loc1/loc3", str(self.f['loc3']))
self.assertEqual("loc3", self.f['loc3'].name)
self.assertEqual(self.f['loc1'], self.f['loc3'].parent)
self.assertEqual("loc1/loc4", str(self.f['loc4']))
self.assertEqual("loc4", self.f['loc4'].name)
self.assertEqual(self.f['loc1'], self.f['loc4'].parent)
def test_get_inventory(self):
reply = client.get('/api/inventory_items/', self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['name'], 'test1')
self.assertEqual(reply.json()[0]['description'], 'test')
self.assertEqual(reply.json()[0]['owned_quantity'], 1)
self.assertEqual(reply.json()[0]['tags'], [])
self.assertEqual(reply.json()[0]['properties'], [])
self.assertEqual(reply.json()[0]['category'], 'cat1')
self.assertEqual(reply.json()[0]['availability_policy'], 'friends')
self.assertEqual(reply.json()[1]['name'], 'test2')
self.assertEqual(reply.json()[1]['description'], 'test2')
self.assertEqual(reply.json()[1]['owned_quantity'], 1)
self.assertEqual(reply.json()[1]['tags'], ['tag1', 'tag2'])
self.assertEqual(reply.json()[1]['properties'],
[{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}])
self.assertEqual(reply.json()[1]['category'], 'cat1')
self.assertEqual(reply.json()[1]['availability_policy'], 'friends')
def test_get_inventory_item(self):
reply = client.get('/api/storage_locations/', self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 4)
self.assertEqual(reply.json()[0]['name'], 'loc1')
self.assertEqual(reply.json()[0]['description'], None)
self.assertEqual(reply.json()[0]['category'], None)
self.assertEqual(reply.json()[0]['path'], 'loc1')
self.assertEqual(reply.json()[1]['name'], 'loc2')
self.assertEqual(reply.json()[1]['description'], None)
self.assertEqual(reply.json()[1]['category'], 'cat1')
self.assertEqual(reply.json()[1]['path'], 'loc2')
self.assertEqual(reply.json()[2]['name'], 'loc3')
self.assertEqual(reply.json()[2]['description'], None)
self.assertEqual(reply.json()[2]['category'], None)
self.assertEqual(reply.json()[2]['path'], 'loc1/loc3')
self.assertEqual(reply.json()[3]['name'], 'loc4')
self.assertEqual(reply.json()[3]['description'], None)
self.assertEqual(reply.json()[3]['category'], 'cat1')
self.assertEqual(reply.json()[3]['path'], 'loc1/loc4')