Compare commits

..

16 commits

Author SHA1 Message Date
f00afb86d6 stash
All checks were successful
/ test (push) Successful in 57s
2024-11-21 02:16:00 +01:00
4f08a2c265 stash 2024-11-21 01:54:19 +01:00
f51c367ead stash 2024-11-21 01:52:18 +01:00
124713b98c save raw_mails as file 2024-11-21 01:52:18 +01:00
f9e8ce1178 stash 2024-11-21 01:52:18 +01:00
6a99e7420f stash 2024-11-21 01:52:18 +01:00
6a81b1f832 stash 2024-11-21 01:52:18 +01:00
b6a8e3acd4 stash 2024-11-21 01:52:14 +01:00
420d22bb43 stash 2024-11-21 01:51:39 +01:00
f785189304 stash 2024-11-21 01:47:12 +01:00
9320765bc5 stash 2024-11-21 01:47:12 +01:00
33d368b5f4 stash 2024-11-21 01:47:12 +01:00
7083f4f7b7 stash 2024-11-21 01:47:12 +01:00
75ea0f4b46 stash 2024-11-21 01:47:12 +01:00
fdf5ab8ad1 implement simple backend search for items and tickets
All checks were successful
/ test (push) Successful in 51s
/ deploy (push) Successful in 4m36s
2024-11-21 01:45:47 +01:00
f2647f0dbd add /matches endpoint ad return match information in tickets and /item endpoints
All checks were successful
/ test (push) Successful in 52s
/ deploy (push) Successful in 4m42s
2024-11-21 00:59:03 +01:00
12 changed files with 327 additions and 70 deletions

View file

@ -114,10 +114,6 @@ router.register(r'boxes', ContainerViewSet, basename='boxes')
router.register(r'box', ContainerViewSet, basename='boxes') router.register(r'box', ContainerViewSet, basename='boxes')
urlpatterns = router.urls + [ urlpatterns = router.urls + [
# path('<event_slug>/items/', item),
# path('<event_slug>/items/<query>/', search_items),
# path('<event_slug>/item/', item),
# path('<event_slug>/item/<id>/', item_by_id),
re_path(r'^(?P<event_slug>[\w-]+)/items/$', item, name='item'), re_path(r'^(?P<event_slug>[\w-]+)/items/$', item, name='item'),
re_path(r'^(?P<event_slug>[\w-]+)/items/(?P<query>[-A-Za-z0-9+/]*={0,3})/$', search_items, name='search_items'), re_path(r'^(?P<event_slug>[\w-]+)/items/(?P<query>[-A-Za-z0-9+/]*={0,3})/$', search_items, name='search_items'),
re_path(r'^(?P<event_slug>[\w-]+)/item/$', item, name='item'), re_path(r'^(?P<event_slug>[\w-]+)/item/$', item, name='item'),

View file

@ -43,6 +43,13 @@ class Item(SoftDeleteModel):
return return
self.container_history.create(container=value) self.container_history.create(container=value)
@property
def related_issues(self):
groups = groupby(self.issue_relation_changes.all(), lambda rel: rel.issue_thread.id)
return [sorted(v, key=lambda r: r.timestamp)[0].issue_thread for k, v in groups]
objects = ItemManager() objects = ItemManager()
all_objects = models.Manager() all_objects = models.Manager()

View file

@ -4,7 +4,9 @@ from rest_framework.relations import SlugRelatedField
from files.models import File from files.models import File
from inventory.models import Event, Container, Item from inventory.models import Event, Container, Item
from inventory.shared_serializers import BasicItemSerializer
from mail.models import EventAddress from mail.models import EventAddress
from tickets.shared_serializers import BasicIssueSerializer
# class EventAdressSerializer(serializers.ModelSerializer): # class EventAdressSerializer(serializers.ModelSerializer):
@ -56,34 +58,15 @@ class ContainerSerializer(serializers.ModelSerializer):
return len(instance.items) return len(instance.items)
class ItemSerializer(serializers.ModelSerializer): class ItemSerializer(BasicItemSerializer):
dataImage = serializers.CharField(write_only=True, required=False) dataImage = serializers.CharField(write_only=True, required=False)
cid = serializers.SerializerMethodField() related_issues = BasicIssueSerializer(many=True, read_only=True)
box = serializers.SerializerMethodField()
file = serializers.SerializerMethodField()
returned = serializers.SerializerMethodField(required=False)
event = serializers.SlugRelatedField(slug_field='slug', queryset=Event.objects.all(),
allow_null=True, required=False)
class Meta: class Meta:
model = Item model = Item
fields = ['cid', 'box', 'id', 'description', 'file', 'dataImage', 'returned', 'event'] fields = ['cid', 'box', 'id', 'description', 'file', 'dataImage', 'returned', 'event', 'related_issues']
read_only_fields = ['id'] read_only_fields = ['id']
def get_cid(self, instance):
return instance.container.id if instance.container else None
def get_box(self, instance):
return instance.container.name if instance.container else None
def get_file(self, instance):
if len(instance.files.all()) > 0:
return instance.files.all().order_by('-created_at')[0].hash
return None
def get_returned(self, instance):
return instance.returned_at is not None
def to_internal_value(self, data): def to_internal_value(self, data):
container = None container = None
returned = False returned = False

View file

@ -0,0 +1,31 @@
from rest_framework import serializers
from inventory.models import Event, Item
class BasicItemSerializer(serializers.ModelSerializer):
cid = serializers.SerializerMethodField()
box = serializers.SerializerMethodField()
file = serializers.SerializerMethodField()
returned = serializers.SerializerMethodField(required=False)
event = serializers.SlugRelatedField(slug_field='slug', queryset=Event.objects.all(),
allow_null=True, required=False)
class Meta:
model = Item
fields = ['cid', 'box', 'id', 'description', 'file', 'returned', 'event']
read_only_fields = ['id']
def get_cid(self, instance):
return instance.container.id if instance.container else None
def get_box(self, instance):
return instance.container.name if instance.container else None
def get_file(self, instance):
if len(instance.files.all()) > 0:
return instance.files.all().order_by('-created_at')[0].hash
return None
def get_returned(self, instance):
return instance.returned_at is not None

View file

@ -30,9 +30,15 @@ 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 = self.client.get(f'/api/2/{self.event.slug}/item/') response = self.client.get(f'/api/2/{self.event.slug}/item/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), self.assertEqual(len(response.json()), 1)
[{'id': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.id, 'file': None, self.assertEqual(response.json()[0]['id'], item.id)
'returned': False, 'event': self.event.slug}]) self.assertEqual(response.json()[0]['description'], '1')
self.assertEqual(response.json()[0]['box'], 'BOX')
self.assertEqual(response.json()[0]['cid'], self.box.id)
self.assertEqual(response.json()[0]['file'], None)
self.assertEqual(response.json()[0]['returned'], False)
self.assertEqual(response.json()[0]['event'], self.event.slug)
self.assertEqual(len(response.json()[0]['related_issues']), 0)
def test_members_with_file(self): def test_members_with_file(self):
import base64 import base64
@ -40,9 +46,15 @@ class ItemTestCase(TestCase):
file = File.objects.create(item=item, data="data:text/plain;base64," + 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 = self.client.get(f'/api/2/{self.event.slug}/item/') response = self.client.get(f'/api/2/{self.event.slug}/item/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), self.assertEqual(len(response.json()), 1)
[{'id': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.id, 'file': file.hash, self.assertEqual(response.json()[0]['id'], item.id)
'returned': False, 'event': self.event.slug}]) self.assertEqual(response.json()[0]['description'], '1')
self.assertEqual(response.json()[0]['box'], 'BOX')
self.assertEqual(response.json()[0]['cid'], self.box.id)
self.assertEqual(response.json()[0]['file'], file.hash)
self.assertEqual(response.json()[0]['returned'], False)
self.assertEqual(response.json()[0]['event'], self.event.slug)
self.assertEqual(len(response.json()[0]['related_issues']), 0)
def test_multi_members(self): def test_multi_members(self):
Item.objects.create(container=self.box, event=self.event, description='1') Item.objects.create(container=self.box, event=self.event, description='1')
@ -55,9 +67,14 @@ class ItemTestCase(TestCase):
def test_create_item(self): def test_create_item(self):
response = self.client.post(f'/api/2/{self.event.slug}/item/', {'cid': self.box.id, 'description': '1'}) response = self.client.post(f'/api/2/{self.event.slug}/item/', {'cid': self.box.id, 'description': '1'})
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(response.json(), self.assertEqual(response.json()['id'], 1)
{'id': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.id, 'file': None, self.assertEqual(response.json()['description'], '1')
'returned': False, 'event': self.event.slug}) self.assertEqual(response.json()['box'], 'BOX')
self.assertEqual(response.json()['cid'], self.box.id)
self.assertEqual(response.json()['file'], None)
self.assertEqual(response.json()['returned'], False)
self.assertEqual(response.json()['event'], self.event.slug)
self.assertEqual(len(response.json()['related_issues']), 0)
self.assertEqual(len(Item.objects.all()), 1) self.assertEqual(len(Item.objects.all()), 1)
self.assertEqual(Item.objects.all()[0].id, 1) self.assertEqual(Item.objects.all()[0].id, 1)
self.assertEqual(Item.objects.all()[0].description, '1') self.assertEqual(Item.objects.all()[0].description, '1')
@ -94,9 +111,14 @@ class ItemTestCase(TestCase):
response = self.client.patch(f'/api/2/{self.event.slug}/item/{item.id}/', {'description': '2'}, response = self.client.patch(f'/api/2/{self.event.slug}/item/{item.id}/', {'description': '2'},
content_type='application/json') content_type='application/json')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), self.assertEqual(response.json()['id'], item.id)
{'id': 1, 'description': '2', 'box': 'BOX', 'cid': self.box.id, 'file': None, self.assertEqual(response.json()['description'], '2')
'returned': False, 'event': self.event.slug}) self.assertEqual(response.json()['box'], 'BOX')
self.assertEqual(response.json()['cid'], self.box.id)
self.assertEqual(response.json()['file'], None)
self.assertEqual(response.json()['returned'], False)
self.assertEqual(response.json()['event'], self.event.slug)
self.assertEqual(len(response.json()['related_issues']), 0)
self.assertEqual(len(Item.objects.all()), 1) self.assertEqual(len(Item.objects.all()), 1)
self.assertEqual(Item.objects.all()[0].id, 1) self.assertEqual(Item.objects.all()[0].id, 1)
self.assertEqual(Item.objects.all()[0].description, '2') self.assertEqual(Item.objects.all()[0].description, '2')
@ -215,7 +237,7 @@ class ItemSearchTestCase(TestCase):
self.assertEqual(self.item1.id, response.json()[0]['id']) 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.id, response.json()[0]['id']) 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'])
self.assertEqual(self.item2.id, response.json()[1]['id']) 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'])
@ -242,7 +264,7 @@ class ItemSearchTestCase(TestCase):
self.assertEqual(self.item1.id, response.json()[0]['id']) 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.id, response.json()[0]['id']) self.assertEqual(self.box.id, response.json()[0]['cid'])
self.assertEqual(2, response.json()[0]['search_score']) self.assertEqual(2, response.json()[0]['search_score'])
self.assertEqual(self.item2.id, response.json()[1]['id']) 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'])

View file

@ -1,4 +1,3 @@
import logging
from base64 import b64decode from base64 import b64decode
from django.urls import re_path from django.urls import re_path
@ -15,8 +14,9 @@ from inventory.models import Event
from mail.models import Email from mail.models import Email
from mail.protocol import send_smtp, make_reply, collect_references from mail.protocol import send_smtp, make_reply, collect_references
from notify_sessions.models import SystemEvent from notify_sessions.models import SystemEvent
from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher, ItemRelation
from tickets.serializers import IssueSerializer, CommentSerializer, ShippingVoucherSerializer, SearchResultSerializer from tickets.serializers import IssueSerializer, CommentSerializer, ShippingVoucherSerializer, SearchResultSerializer
from tickets.shared_serializers import RelationSerializer
class IssueViewSet(viewsets.ModelViewSet): class IssueViewSet(viewsets.ModelViewSet):
@ -24,6 +24,11 @@ class IssueViewSet(viewsets.ModelViewSet):
queryset = IssueThread.objects.all() queryset = IssueThread.objects.all()
class RelationViewSet(viewsets.ModelViewSet):
serializer_class = RelationSerializer
queryset = ItemRelation.objects.all()
class CommentViewSet(viewsets.ModelViewSet): class CommentViewSet(viewsets.ModelViewSet):
serializer_class = CommentSerializer serializer_class = CommentSerializer
queryset = Comment.objects.all() queryset = Comment.objects.all()
@ -157,10 +162,9 @@ def search_issues(request, event_slug, query):
router = routers.SimpleRouter() router = routers.SimpleRouter()
router.register(r'tickets', IssueViewSet, basename='issues') router.register(r'tickets', IssueViewSet, basename='issues')
# router.register(r'comments', CommentViewSet, basename='comments') router.register(r'matches', RelationViewSet, basename='matches')
router.register(r'shipping_vouchers', ShippingVoucherViewSet, basename='shipping_vouchers') router.register(r'shipping_vouchers', ShippingVoucherViewSet, basename='shipping_vouchers')
# [-A-Za-z0-9+/]*={0,3} # [-A-Za-z0-9+/]*={0,3}
urlpatterns = ([ urlpatterns = ([
re_path(r'tickets/states/$', get_available_states, name='get_available_states'), re_path(r'tickets/states/$', get_available_states, name='get_available_states'),
@ -168,5 +172,5 @@ urlpatterns = ([
re_path(r'^tickets/(?P<pk>\d+)/comment/$', add_comment, name='add_comment'), re_path(r'^tickets/(?P<pk>\d+)/comment/$', add_comment, name='add_comment'),
re_path(r'^(?P<event_slug>[\w-]+)/tickets/manual/$', manual_ticket, name='manual_ticket'), re_path(r'^(?P<event_slug>[\w-]+)/tickets/manual/$', manual_ticket, name='manual_ticket'),
re_path(r'^(?P<event_slug>[\w-]+)/tickets/(?P<query>[-A-Za-z0-9+/]*={0,3})/$', search_issues, re_path(r'^(?P<event_slug>[\w-]+)/tickets/(?P<query>[-A-Za-z0-9+/]*={0,3})/$', search_issues,
name='search_issues'), name='search_issues'),
] + router.urls) ] + router.urls)

View file

@ -0,0 +1,29 @@
# Generated by Django 4.2.7 on 2024-11-20 23:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0008_alter_item_event_alter_itemplacement_container_and_more'),
('tickets', '0012_alter_itemrelation_item'),
]
operations = [
migrations.RemoveField(
model_name='issuethread',
name='related_items',
),
migrations.AlterField(
model_name='itemrelation',
name='issue_thread',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='item_relation_changes', to='tickets.issuethread'),
),
migrations.AlterField(
model_name='itemrelation',
name='item',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_relation_changes', to='inventory.item'),
),
]

View file

@ -1,3 +1,5 @@
from itertools import groupby
from django.utils import timezone from django.utils import timezone
from django.db import models from django.db import models
from django_softdelete.models import SoftDeleteModel from django_softdelete.models import SoftDeleteModel
@ -43,7 +45,6 @@ class IssueThread(SoftDeleteModel):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
event = models.ForeignKey(Event, null=True, on_delete=models.SET_NULL, related_name='issue_threads') event = models.ForeignKey(Event, null=True, on_delete=models.SET_NULL, related_name='issue_threads')
manually_created = models.BooleanField(default=False) manually_created = models.BooleanField(default=False)
related_items = models.ManyToManyField(Item, through='ItemRelation')
def short_uuid(self): def short_uuid(self):
return self.uuid[:8] return self.uuid[:8]
@ -76,6 +77,11 @@ class IssueThread(SoftDeleteModel):
return return
self.assignments.create(assigned_to=value) self.assignments.create(assigned_to=value)
@property
def related_items(self):
groups = groupby(self.item_relation_changes.all(), lambda rel: rel.item.id)
return [sorted(v, key=lambda r: r.timestamp)[0].item for k, v in groups]
def __str__(self): def __str__(self):
return '[' + str(self.id) + '][' + self.short_uuid() + '] ' + self.name return '[' + str(self.id) + '][' + self.short_uuid() + '] ' + self.name
@ -132,8 +138,8 @@ 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_relation_changes')
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='issue_relations') item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='issue_relation_changes')
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')

View file

@ -2,9 +2,10 @@ from rest_framework import serializers
from authentication.models import ExtendedUser from authentication.models import ExtendedUser
from inventory.models import Event from inventory.models import Event
from inventory.shared_serializers import BasicItemSerializer
from mail.api_v2 import AttachmentSerializer from mail.api_v2 import AttachmentSerializer
from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher
from inventory.serializers import ItemSerializer from tickets.shared_serializers import BasicIssueSerializer
class CommentSerializer(serializers.ModelSerializer): class CommentSerializer(serializers.ModelSerializer):
@ -37,14 +38,10 @@ class ShippingVoucherSerializer(serializers.ModelSerializer):
read_only_fields = ('id', 'timestamp', 'used_at') read_only_fields = ('id', 'timestamp', 'used_at')
class IssueSerializer(serializers.ModelSerializer): class IssueSerializer(BasicIssueSerializer):
timeline = serializers.SerializerMethodField() timeline = serializers.SerializerMethodField()
last_activity = serializers.SerializerMethodField() last_activity = serializers.SerializerMethodField()
assigned_to = serializers.SlugRelatedField(slug_field='username', queryset=ExtendedUser.objects.all(), related_items = BasicItemSerializer(many=True, read_only=True)
allow_null=True, required=False)
event = serializers.SlugRelatedField(slug_field='slug', queryset=Event.objects.all(),
allow_null=True, required=False)
related_items = ItemSerializer(many=True, read_only=True)
class Meta: class Meta:
model = IssueThread model = IssueThread
@ -55,8 +52,6 @@ class IssueSerializer(serializers.ModelSerializer):
ret = super().to_internal_value(data) ret = super().to_internal_value(data)
if 'state' in data: if 'state' in data:
ret['state'] = data['state'] ret['state'] = data['state']
# if 'assigned_to' in data:
# ret['assigned_to'] = data['assigned_to']
return ret return ret
def validate(self, attrs): def validate(self, attrs):
@ -74,8 +69,8 @@ class IssueSerializer(serializers.ModelSerializer):
last_mail = self.emails.order_by('-timestamp').first().timestamp if self.emails.count() > 0 else None last_mail = self.emails.order_by('-timestamp').first().timestamp if self.emails.count() > 0 else None
last_assignment = self.assignments.order_by('-timestamp').first().timestamp if \ last_assignment = self.assignments.order_by('-timestamp').first().timestamp if \
self.assignments.count() > 0 else None self.assignments.count() > 0 else None
last_relation = self.item_relations.order_by('-timestamp').first().timestamp if \ last_relation = self.item_relation_changes.order_by('-timestamp').first().timestamp if \
self.item_relations.count() > 0 else None self.item_relation_changes.count() > 0 else None
args = [x for x in [last_state_change, last_comment, last_mail, last_assignment, last_relation] if args = [x for x in [last_state_change, last_comment, last_mail, last_assignment, last_relation] if
x is not None] x is not None]
return max(args) return max(args)
@ -117,13 +112,13 @@ class IssueSerializer(serializers.ModelSerializer):
'timestamp': assignment.timestamp, 'timestamp': assignment.timestamp,
'assigned_to': assignment.assigned_to.username, 'assigned_to': assignment.assigned_to.username,
}) })
for relation in obj.item_relations.all(): for relation in (obj.item_relation_changes.all()):
timeline.append({ timeline.append({
'type': 'item_relation', 'type': 'item_relation',
'id': relation.id, 'id': relation.id,
'status': relation.status, 'status': relation.status,
'timestamp': relation.timestamp, 'timestamp': relation.timestamp,
'item': ItemSerializer(relation.item).data, 'item': BasicItemSerializer(relation.item).data,
}) })
for shipping_voucher in obj.shipping_vouchers.all(): for shipping_voucher in obj.shipping_vouchers.all():
timeline.append({ timeline.append({
@ -135,9 +130,6 @@ class IssueSerializer(serializers.ModelSerializer):
}) })
return sorted(timeline, key=lambda x: x['timestamp']) return sorted(timeline, key=lambda x: x['timestamp'])
def get_queryset(self):
return IssueThread.objects.all().order_by('-last_activity')
class SearchResultSerializer(serializers.Serializer): class SearchResultSerializer(serializers.Serializer):
search_score = serializers.IntegerField() search_score = serializers.IntegerField()

View file

@ -0,0 +1,23 @@
from rest_framework import serializers
from authentication.models import ExtendedUser
from inventory.models import Event
from tickets.models import IssueThread, ItemRelation
class RelationSerializer(serializers.ModelSerializer):
class Meta:
model = ItemRelation
fields = ('id', 'status', 'timestamp', 'item', 'issue_thread')
class BasicIssueSerializer(serializers.ModelSerializer):
assigned_to = serializers.SlugRelatedField(slug_field='username', queryset=ExtendedUser.objects.all(),
allow_null=True, required=False)
event = serializers.SlugRelatedField(slug_field='slug', queryset=Event.objects.all(),
allow_null=True, required=False)
class Meta:
model = IssueThread
fields = ('id', 'name', 'state', 'assigned_to', 'uuid', 'event')
read_only_fields = ('id', 'timeline', 'last_activity', 'uuid', 'related_items')

View file

@ -0,0 +1,149 @@
from datetime import datetime, timedelta
from django.test import TestCase, Client
from authentication.models import ExtendedUser
from inventory.models import Event, Container, Item
from mail.models import Email, EmailAttachment
from tickets.models import IssueThread, StateChange, Comment, ItemRelation
from django.contrib.auth.models import Permission
from knox.models import AuthToken
from base64 import b64encode
class IssueItemMatchApiTest(TestCase):
def setUp(self):
super().setUp()
self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test')
self.user.user_permissions.add(*Permission.objects.all())
self.user.save()
self.event = Event.objects.create(slug='evt')
self.box = Container.objects.create(name='box1')
self.item = Item.objects.create(container=self.box, description="foo", event=self.event)
self.token = AuthToken.objects.create(user=self.user)
self.client = Client(headers={'Authorization': 'Token ' + self.token[1]})
now = datetime.now()
self.issue = IssueThread.objects.create(
name="test issue",
event=self.event,
)
self.mail1 = Email.objects.create(
subject='test',
body='test',
sender='test',
recipient='test',
issue_thread=self.issue,
timestamp=now,
)
self.comment = Comment.objects.create(
issue_thread=self.issue,
comment="test",
timestamp=now + timedelta(seconds=3),
)
self.match = ItemRelation.objects.create(
issue_thread=self.issue,
item=self.item,
timestamp=now + timedelta(seconds=5),
)
def test_issues(self):
response = self.client.get('/api/2/tickets/')
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 1)
self.assertEqual(response.json()[0]['id'], self.issue.id)
self.assertEqual(response.json()[0]['name'], "test issue")
self.assertEqual(response.json()[0]['state'], "pending_new")
self.assertEqual(response.json()[0]['event'], "evt")
self.assertEqual(response.json()[0]['assigned_to'], None)
self.assertEqual(response.json()[0]['uuid'], self.issue.uuid)
self.assertEqual(response.json()[0]['last_activity'], self.match.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
self.assertEqual(len(response.json()[0]['timeline']), 4)
self.assertEqual(response.json()[0]['timeline'][0]['type'], 'state')
self.assertEqual(response.json()[0]['timeline'][1]['type'], 'mail')
self.assertEqual(response.json()[0]['timeline'][2]['type'], 'comment')
self.assertEqual(response.json()[0]['timeline'][1]['id'], self.mail1.id)
self.assertEqual(response.json()[0]['timeline'][2]['id'], self.comment.id)
self.assertEqual(response.json()[0]['timeline'][0]['state'], 'pending_new')
self.assertEqual(response.json()[0]['timeline'][1]['sender'], 'test')
self.assertEqual(response.json()[0]['timeline'][1]['recipient'], 'test')
self.assertEqual(response.json()[0]['timeline'][1]['subject'], 'test')
self.assertEqual(response.json()[0]['timeline'][1]['body'], 'test')
self.assertEqual(response.json()[0]['timeline'][1]['timestamp'],
self.mail1.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
self.assertEqual(response.json()[0]['timeline'][2]['comment'], 'test')
self.assertEqual(response.json()[0]['timeline'][2]['timestamp'],
self.comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
self.assertEqual(response.json()[0]['timeline'][3]['status'], 'possible')
self.assertEqual(response.json()[0]['timeline'][3]['timestamp'],
self.match.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
self.assertEqual(response.json()[0]['timeline'][3]['item']['description'], "foo")
self.assertEqual(response.json()[0]['timeline'][3]['item']['event'], "evt")
self.assertEqual(response.json()[0]['timeline'][3]['item']['box'], "box1")
self.assertEqual(response.json()[0]['related_items'][0]['description'], "foo")
self.assertEqual(response.json()[0]['related_items'][0]['event'], "evt")
self.assertEqual(response.json()[0]['related_items'][0]['box'], "box1")
def test_members(self):
response = self.client.get(f'/api/2/{self.event.slug}/item/')
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 1)
self.assertEqual(response.json()[0]['id'], self.item.id)
self.assertEqual(response.json()[0]['description'], 'foo')
self.assertEqual(response.json()[0]['box'], 'box1')
self.assertEqual(response.json()[0]['cid'], self.box.id)
self.assertEqual(response.json()[0]['file'], None)
self.assertEqual(response.json()[0]['returned'], False)
self.assertEqual(response.json()[0]['event'], self.event.slug)
self.assertEqual(len(response.json()[0]['related_issues']), 1)
self.assertEqual(response.json()[0]['related_issues'][0]['id'], self.issue.id)
self.assertEqual(response.json()[0]['related_issues'][0]['name'], "test issue")
def test_add_match(self):
response = self.client.get('/api/2/matches/')
self.assertEqual(1, len(response.json()))
item = Item.objects.create(container=self.box, event=self.event, description='1')
issue = IssueThread.objects.create(name="test issue", event=self.event)
response = self.client.post(f'/api/2/matches/',
{'item': item.id, 'issue_thread': issue.id},
content_type='application/json')
self.assertEqual(response.status_code, 201)
response = self.client.get('/api/2/matches/')
self.assertEqual(2, len(response.json()))
response = self.client.get('/api/2/tickets/')
self.assertEqual(4, len(response.json()[0]['timeline']))
self.assertEqual('item_relation', response.json()[0]['timeline'][3]['type'])
self.assertEqual('possible', response.json()[0]['timeline'][3]['status'])
self.assertEqual(1, len(response.json()[0]['related_items']))
def test_change_match_state(self):
response = self.client.get('/api/2/matches/')
self.assertEqual(1, len(response.json()))
response = self.client.get('/api/2/tickets/')
self.assertEqual(4, len(response.json()[0]['timeline']))
self.assertEqual('item_relation', response.json()[0]['timeline'][3]['type'])
self.assertEqual('possible', response.json()[0]['timeline'][3]['status'])
self.assertEqual(1, len(response.json()[0]['related_items']))
response = self.client.post(f'/api/2/matches/',
{'item': self.item.id, 'issue_thread': self.issue.id, 'status': 'confirmed'},
content_type='application/json')
self.assertEqual(response.status_code, 201)
self.assertEqual(response.json()['status'], 'confirmed')
self.assertEqual(response.json()['id'], 2)
response = self.client.get('/api/2/matches/')
self.assertEqual(2, len(response.json()))
response = self.client.get('/api/2/tickets/')
self.assertEqual(5, len(response.json()[0]['timeline']))
self.assertEqual('item_relation', response.json()[0]['timeline'][3]['type'])
self.assertEqual('possible', response.json()[0]['timeline'][3]['status'])
self.assertEqual('item_relation', response.json()[0]['timeline'][4]['type'])
self.assertEqual('confirmed', response.json()[0]['timeline'][4]['status'])
self.assertEqual(1, len(response.json()[0]['related_items']))

View file

@ -3,9 +3,9 @@ from datetime import datetime, timedelta
from django.test import TestCase, Client from django.test import TestCase, Client
from authentication.models import ExtendedUser from authentication.models import ExtendedUser
from inventory.models import Event from inventory.models import Event, Container, Item
from mail.models import Email, EmailAttachment from mail.models import Email, EmailAttachment
from tickets.models import IssueThread, StateChange, Comment from tickets.models import IssueThread, StateChange, Comment, ItemRelation
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from knox.models import AuthToken from knox.models import AuthToken
@ -20,6 +20,8 @@ class IssueApiTest(TestCase):
self.user.user_permissions.add(*Permission.objects.all()) self.user.user_permissions.add(*Permission.objects.all())
self.user.save() self.user.save()
self.event = Event.objects.create(slug='evt') self.event = Event.objects.create(slug='evt')
self.box = Container.objects.create(name='box1')
self.item = Item.objects.create(container=self.box, description="foo", event=self.event)
self.token = AuthToken.objects.create(user=self.user) self.token = AuthToken.objects.create(user=self.user)
self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) self.client = Client(headers={'Authorization': 'Token ' + self.token[1]})
@ -56,6 +58,11 @@ class IssueApiTest(TestCase):
comment="test", comment="test",
timestamp=now + timedelta(seconds=3), timestamp=now + timedelta(seconds=3),
) )
match = ItemRelation.objects.create(
issue_thread=issue,
item = self.item,
timestamp=now + timedelta(seconds=5),
)
self.assertEqual('pending_new', issue.state) self.assertEqual('pending_new', issue.state)
self.assertEqual('test issue', issue.name) self.assertEqual('test issue', issue.name)
self.assertEqual(None, issue.assigned_to) self.assertEqual(None, issue.assigned_to)
@ -69,8 +76,8 @@ class IssueApiTest(TestCase):
self.assertEqual(response.json()[0]['event'], "evt") self.assertEqual(response.json()[0]['event'], "evt")
self.assertEqual(response.json()[0]['assigned_to'], None) self.assertEqual(response.json()[0]['assigned_to'], None)
self.assertEqual(response.json()[0]['uuid'], issue.uuid) self.assertEqual(response.json()[0]['uuid'], issue.uuid)
self.assertEqual(response.json()[0]['last_activity'], comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ')) self.assertEqual(response.json()[0]['last_activity'], match.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
self.assertEqual(len(response.json()[0]['timeline']), 4) self.assertEqual(len(response.json()[0]['timeline']), 5)
self.assertEqual(response.json()[0]['timeline'][0]['type'], 'state') self.assertEqual(response.json()[0]['timeline'][0]['type'], 'state')
self.assertEqual(response.json()[0]['timeline'][1]['type'], 'mail') self.assertEqual(response.json()[0]['timeline'][1]['type'], 'mail')
self.assertEqual(response.json()[0]['timeline'][2]['type'], 'mail') self.assertEqual(response.json()[0]['timeline'][2]['type'], 'mail')
@ -94,6 +101,15 @@ class IssueApiTest(TestCase):
self.assertEqual(response.json()[0]['timeline'][3]['comment'], 'test') self.assertEqual(response.json()[0]['timeline'][3]['comment'], 'test')
self.assertEqual(response.json()[0]['timeline'][3]['timestamp'], self.assertEqual(response.json()[0]['timeline'][3]['timestamp'],
comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ')) comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
self.assertEqual(response.json()[0]['timeline'][4]['status'], 'possible')
self.assertEqual(response.json()[0]['timeline'][4]['timestamp'],
match.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
self.assertEqual(response.json()[0]['timeline'][4]['item']['description'], "foo")
self.assertEqual(response.json()[0]['timeline'][4]['item']['event'], "evt")
self.assertEqual(response.json()[0]['timeline'][4]['item']['box'], "box1")
self.assertEqual(response.json()[0]['related_items'][0]['description'], "foo")
self.assertEqual(response.json()[0]['related_items'][0]['event'], "evt")
self.assertEqual(response.json()[0]['related_items'][0]['box'], "box1")
def test_issues_incomplete_timeline(self): def test_issues_incomplete_timeline(self):
now = datetime.now() now = datetime.now()
@ -287,8 +303,7 @@ class IssueApiTest(TestCase):
content_type='application/json') content_type='application/json')
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
# def test_post_comment(self):
#def test_post_comment(self):
# issue = IssueThread.objects.create( # issue = IssueThread.objects.create(
# name="test issue", # name="test issue",
# ) # )