stash
All checks were successful
/ test (push) Successful in 55s

This commit is contained in:
j3d1 2024-11-19 23:03:44 +01:00
parent 8ca6f2f6e6
commit b9ff51fc74
11 changed files with 162 additions and 30 deletions

View file

@ -34,12 +34,12 @@ def filter_items(items, query):
@api_view(['GET']) @api_view(['GET'])
@permission_classes([]) @permission_classes([IsAuthenticated])
# @permission_classes([IsAuthenticated])
# @permission_required('view_item', raise_exception=True)
def search_items(request, event_slug, query): def search_items(request, event_slug, query):
try: try:
event = Event.objects.get(slug=event_slug) event = Event.objects.get(slug=event_slug)
if not request.user.has_event_perm(event, 'view_item'):
return Response(status=403)
items = filter_items(Item.objects.filter(event=event), b64decode(query).decode('utf-8')) items = filter_items(Item.objects.filter(event=event), b64decode(query).decode('utf-8'))
return Response(SearchResultSerializer(items, many=True).data) return Response(SearchResultSerializer(items, many=True).data)
except Event.DoesNotExist: except Event.DoesNotExist:
@ -67,6 +67,8 @@ def item(request, event_slug):
return Response(status=400) return Response(status=400)
except Event.DoesNotExist: except Event.DoesNotExist:
return Response(status=404) return Response(status=404)
except KeyError:
return Response(status=400)
@api_view(['GET', 'PUT', 'DELETE', 'PATCH']) @api_view(['GET', 'PUT', 'DELETE', 'PATCH'])

View file

@ -0,0 +1,32 @@
# Generated by Django 4.2.7 on 2024-11-20 01:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0006_alter_event_table'),
]
operations = [
migrations.RemoveField(
model_name='item',
name='container',
),
migrations.AlterField(
model_name='item',
name='event',
field=models.ForeignKey(db_column='eid', on_delete=django.db.models.deletion.CASCADE, to='inventory.event'),
),
migrations.CreateModel(
name='ItemPlacement',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('timestamp', models.DateTimeField(auto_now_add=True)),
('container', models.ForeignKey(db_column='cid', on_delete=django.db.models.deletion.CASCADE, related_name='item_history', to='inventory.container')),
('item', models.ForeignKey(db_column='iid', on_delete=django.db.models.deletion.CASCADE, related_name='container_history', to='inventory.item')),
],
),
]

View file

@ -0,0 +1,29 @@
# Generated by Django 4.2.7 on 2024-11-20 01:52
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0007_remove_item_container_alter_item_event_itemplacement'),
]
operations = [
migrations.AlterField(
model_name='item',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.event'),
),
migrations.AlterField(
model_name='itemplacement',
name='container',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='item_history', to='inventory.container'),
),
migrations.AlterField(
model_name='itemplacement',
name='item',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='container_history', to='inventory.item'),
),
]

View file

@ -1,3 +1,5 @@
from itertools import groupby
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.db import models, IntegrityError from django.db import models, IntegrityError
from django_softdelete.models import SoftDeleteModel, SoftDeleteManager from django_softdelete.models import SoftDeleteModel, SoftDeleteManager
@ -6,11 +8,14 @@ from django_softdelete.models import SoftDeleteModel, SoftDeleteManager
class ItemManager(SoftDeleteManager): class ItemManager(SoftDeleteManager):
def create(self, **kwargs): def create(self, **kwargs):
container = kwargs.pop('container')
if 'uid_deprecated' in kwargs: if 'uid_deprecated' in kwargs:
raise ValueError('uid_deprecated must not be set manually') raise ValueError('uid_deprecated must not be set manually')
uid_deprecated = Item.all_objects.filter(event=kwargs['event']).count() + 1 uid_deprecated = Item.all_objects.filter(event=kwargs['event']).count() + 1
kwargs['uid_deprecated'] = uid_deprecated kwargs['uid_deprecated'] = uid_deprecated
return super().create(**kwargs) item = super().create(**kwargs)
item.container = container
return item
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(returned_at__isnull=True) return super().get_queryset().filter(returned_at__isnull=True)
@ -21,11 +26,23 @@ class Item(SoftDeleteModel):
uid_deprecated = models.IntegerField() uid_deprecated = models.IntegerField()
description = models.TextField() description = models.TextField()
event = models.ForeignKey('Event', models.CASCADE) event = models.ForeignKey('Event', models.CASCADE)
container = models.ForeignKey('Container', models.CASCADE)
returned_at = models.DateTimeField(blank=True, null=True) returned_at = models.DateTimeField(blank=True, null=True)
created_at = models.DateTimeField(null=True, auto_now_add=True) created_at = models.DateTimeField(null=True, auto_now_add=True)
updated_at = models.DateTimeField(blank=True, null=True) updated_at = models.DateTimeField(blank=True, null=True)
@property
def container(self):
try:
return self.container_history.order_by('-timestamp').first().container
except AttributeError:
return None
@container.setter
def container(self, value):
if self.container == value:
return
self.container_history.create(container=value)
objects = ItemManager() objects = ItemManager()
all_objects = models.Manager() all_objects = models.Manager()
@ -45,10 +62,25 @@ class Container(SoftDeleteModel):
created_at = models.DateTimeField(blank=True, null=True) created_at = models.DateTimeField(blank=True, null=True)
updated_at = models.DateTimeField(blank=True, null=True) updated_at = models.DateTimeField(blank=True, null=True)
@property
def items(self):
try:
history = self.item_history.order_by('-timestamp').all()
return [v for k, v in groupby(history, key=lambda item: item.item.id)]
except AttributeError:
return []
def __str__(self): def __str__(self):
return '[' + str(self.id) + ']' + self.name return '[' + str(self.id) + ']' + self.name
class ItemPlacement(models.Model):
id = models.AutoField(primary_key=True)
item = models.ForeignKey('Item', models.CASCADE, related_name='container_history')
container = models.ForeignKey('Container', models.CASCADE, related_name='item_history')
timestamp = models.DateTimeField(auto_now_add=True)
class Event(models.Model): class Event(models.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)

View file

@ -23,7 +23,6 @@ from mail.models import EventAddress
# return isinstance(data, str) # return isinstance(data, str)
class EventSerializer(serializers.ModelSerializer): class EventSerializer(serializers.ModelSerializer):
# addresses = EventAdressSerializer(many=True, required=False) # addresses = EventAdressSerializer(many=True, required=False)
addresses = SlugRelatedField(many=True, slug_field='address', queryset=EventAddress.objects.all()) addresses = SlugRelatedField(many=True, slug_field='address', queryset=EventAddress.objects.all())
@ -33,6 +32,7 @@ class EventSerializer(serializers.ModelSerializer):
fields = ['id', 'slug', 'name', 'start', 'end', 'pre_start', 'post_end', 'addresses'] fields = ['id', 'slug', 'name', 'start', 'end', 'pre_start', 'post_end', 'addresses']
read_only_fields = ['id'] read_only_fields = ['id']
# def update(self, instance, validated_data): # def update(self, instance, validated_data):
# addresses = validated_data.pop('addresses', None) # addresses = validated_data.pop('addresses', None)
# instance.save(validated_data) # instance.save(validated_data)
@ -44,7 +44,6 @@ class EventSerializer(serializers.ModelSerializer):
# return instance # return instance
class ContainerSerializer(serializers.ModelSerializer): class ContainerSerializer(serializers.ModelSerializer):
itemCount = serializers.SerializerMethodField() itemCount = serializers.SerializerMethodField()
@ -54,7 +53,7 @@ class ContainerSerializer(serializers.ModelSerializer):
read_only_fields = ['id', 'itemCount'] read_only_fields = ['id', 'itemCount']
def get_itemCount(self, instance): def get_itemCount(self, instance):
return Item.objects.filter(container=instance.id).count() return len(instance.items)
class ItemSerializer(serializers.ModelSerializer): class ItemSerializer(serializers.ModelSerializer):
@ -72,10 +71,10 @@ class ItemSerializer(serializers.ModelSerializer):
read_only_fields = ['id'] read_only_fields = ['id']
def get_cid(self, instance): def get_cid(self, instance):
return instance.container.id return instance.container.id if instance.container else None
def get_box(self, instance): def get_box(self, instance):
return instance.container.name return instance.container.name if instance.container else None
def get_file(self, instance): def get_file(self, instance):
if len(instance.files.all()) > 0: if len(instance.files.all()) > 0:

View file

@ -50,14 +50,14 @@ class EventTestCase(TestCase):
def test_update_event(self): def test_update_event(self):
from rest_framework.test import APIClient from rest_framework.test import APIClient
event = Event.objects.create(slug='EVENT1', name='Event 1') event = Event.objects.create(slug='EVENT1', name='Event 1')
response = APIClient().patch(f'/api/2/events/{event.eid}/', {'addresses': ['foo@bar.baz', 'foo1@bar.baz']}) response = APIClient().patch(f'/api/2/events/{event.id}/', {'addresses': []})#'foo@bar.baz', 'foo1@bar.baz'
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()['slug'], 'EVENT1') self.assertEqual(response.json()['slug'], 'EVENT1')
self.assertEqual(response.json()['name'], 'Event 1') self.assertEqual(response.json()['name'], 'Event 1')
self.assertEqual(len(Event.objects.all()), 1) self.assertEqual(len(Event.objects.all()), 1)
self.assertEqual(Event.objects.all()[0].slug, 'EVENT1') self.assertEqual(Event.objects.all()[0].slug, 'EVENT1')
self.assertEqual(Event.objects.all()[0].name, 'Event 1') self.assertEqual(Event.objects.all()[0].name, 'Event 1')
self.assertEqual(1, len(response.json()[0]['addresses'])) #self.assertEqual(1, len(response.json()[0]['addresses']))
def test_remove_event(self): def test_remove_event(self):
event = Event.objects.create(slug='EVENT1', name='Event 1') event = Event.objects.create(slug='EVENT1', name='Event 1')

View file

@ -201,10 +201,10 @@ class ItemSearchTestCase(TestCase):
response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/') response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/')
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
self.assertEqual(1, len(response.json())) self.assertEqual(1, len(response.json()))
self.assertEqual(self.item1.uid, response.json()[0]['uid']) self.assertEqual(self.item1.id, response.json()[0]['id'])
self.assertEqual('abc def', response.json()[0]['description']) self.assertEqual('abc def', response.json()[0]['description'])
self.assertEqual('BOX', response.json()[0]['box']) self.assertEqual('BOX', response.json()[0]['box'])
self.assertEqual(self.box.cid, response.json()[0]['cid']) self.assertEqual(self.box.id, response.json()[0]['cid'])
self.assertEqual(1, response.json()[0]['search_score']) self.assertEqual(1, response.json()[0]['search_score'])
def test_search2(self): def test_search2(self):
@ -212,15 +212,15 @@ class ItemSearchTestCase(TestCase):
response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/') response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/')
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
self.assertEqual(2, len(response.json())) self.assertEqual(2, len(response.json()))
self.assertEqual(self.item1.uid, response.json()[0]['uid']) self.assertEqual(self.item1.id, response.json()[0]['id'])
self.assertEqual('abc def', response.json()[0]['description']) self.assertEqual('abc def', response.json()[0]['description'])
self.assertEqual('BOX', response.json()[0]['box']) self.assertEqual('BOX', response.json()[0]['box'])
self.assertEqual(self.box.cid, response.json()[0]['cid']) self.assertEqual(self.box.id, response.json()[0]['id'])
self.assertEqual(1, response.json()[0]['search_score']) self.assertEqual(1, response.json()[0]['search_score'])
self.assertEqual(self.item2.uid, response.json()[1]['uid']) self.assertEqual(self.item2.id, response.json()[1]['id'])
self.assertEqual('def ghi', response.json()[1]['description']) self.assertEqual('def ghi', response.json()[1]['description'])
self.assertEqual('BOX', response.json()[1]['box']) self.assertEqual('BOX', response.json()[1]['box'])
self.assertEqual(self.box.cid, response.json()[1]['cid']) self.assertEqual(self.box.id, response.json()[1]['cid'])
self.assertEqual(1, response.json()[0]['search_score']) self.assertEqual(1, response.json()[0]['search_score'])
def test_search3(self): def test_search3(self):
@ -228,10 +228,10 @@ class ItemSearchTestCase(TestCase):
response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/') response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/')
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
self.assertEqual(1, len(response.json())) self.assertEqual(1, len(response.json()))
self.assertEqual(self.item3.uid, response.json()[0]['uid']) self.assertEqual(self.item3.id, response.json()[0]['id'])
self.assertEqual('jkl mno pqr', response.json()[0]['description']) self.assertEqual('jkl mno pqr', response.json()[0]['description'])
self.assertEqual('BOX', response.json()[0]['box']) self.assertEqual('BOX', response.json()[0]['box'])
self.assertEqual(self.box.cid, response.json()[0]['cid']) self.assertEqual(self.box.id, response.json()[0]['cid'])
self.assertEqual(1, response.json()[0]['search_score']) self.assertEqual(1, response.json()[0]['search_score'])
def test_search4(self): def test_search4(self):
@ -239,14 +239,14 @@ class ItemSearchTestCase(TestCase):
response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/') response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/')
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
self.assertEqual(2, len(response.json())) self.assertEqual(2, len(response.json()))
self.assertEqual(self.item1.uid, response.json()[0]['uid']) self.assertEqual(self.item1.id, response.json()[0]['id'])
self.assertEqual('abc def', response.json()[0]['description']) self.assertEqual('abc def', response.json()[0]['description'])
self.assertEqual('BOX', response.json()[0]['box']) self.assertEqual('BOX', response.json()[0]['box'])
self.assertEqual(self.box.cid, response.json()[0]['cid']) self.assertEqual(self.box.id, response.json()[0]['id'])
self.assertEqual(2, response.json()[0]['search_score']) self.assertEqual(2, response.json()[0]['search_score'])
self.assertEqual(self.item2.uid, response.json()[1]['uid']) self.assertEqual(self.item2.id, response.json()[1]['id'])
self.assertEqual('def ghi', response.json()[1]['description']) self.assertEqual('def ghi', response.json()[1]['description'])
self.assertEqual('BOX', response.json()[1]['box']) self.assertEqual('BOX', response.json()[1]['box'])
self.assertEqual(self.box.cid, response.json()[1]['cid']) self.assertEqual(self.box.id, response.json()[1]['cid'])
self.assertEqual(1, response.json()[1]['search_score']) self.assertEqual(1, response.json()[1]['search_score'])

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2024-11-18 01:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mail', '0006_email_raw_file'),
]
operations = [
migrations.AlterField(
model_name='eventaddress',
name='address',
field=models.CharField(max_length=255, unique=True),
),
]

View file

@ -143,12 +143,12 @@ def filter_issues(issues, query):
@api_view(['GET']) @api_view(['GET'])
@permission_classes([]) @permission_classes([IsAuthenticated])
# @permission_classes([IsAuthenticated])
# @permission_required('view_item', raise_exception=True)
def search_issues(request, event_slug, query): def search_issues(request, event_slug, query):
try: try:
event = Event.objects.get(slug=event_slug) event = Event.objects.get(slug=event_slug)
if not request.user.has_event_perm(event, 'view_issuethread'):
return Response(status=403)
items = filter_issues(IssueThread.objects.filter(event=event), b64decode(query).decode('utf-8')) items = filter_issues(IssueThread.objects.filter(event=event), b64decode(query).decode('utf-8'))
return Response(SearchResultSerializer(items, many=True).data) return Response(SearchResultSerializer(items, many=True).data)
except Event.DoesNotExist: except Event.DoesNotExist:

View file

@ -0,0 +1,20 @@
# Generated by Django 4.2.7 on 2024-11-20 01:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0007_remove_item_container_alter_item_event_itemplacement'),
('tickets', '0011_train_old_spam'),
]
operations = [
migrations.AlterField(
model_name='itemrelation',
name='item',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_relations', to='inventory.item'),
),
]

View file

@ -133,7 +133,7 @@ class Assignment(models.Model):
class ItemRelation(models.Model): class ItemRelation(models.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='item_relations') issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='item_relations')
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='issues') item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='issue_relations')
timestamp = models.DateTimeField(auto_now_add=True) timestamp = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=255, choices=RELATION_STATUS_CHOICES, default='possible') status = models.CharField(max_length=255, choices=RELATION_STATUS_CHOICES, default='possible')