partition tickets by event
This commit is contained in:
parent
2c609427ec
commit
147fc9cf86
8 changed files with 85 additions and 20 deletions
|
@ -1,4 +1,4 @@
|
|||
from django.urls import path
|
||||
from django.urls import re_path
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from rest_framework import routers, viewsets
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
|
@ -40,6 +40,8 @@ def search_items(request, event_slug, query):
|
|||
@permission_classes([IsAuthenticated])
|
||||
def item(request, event_slug):
|
||||
try:
|
||||
event = None
|
||||
if event_slug != 'none':
|
||||
event = Event.objects.get(slug=event_slug)
|
||||
if request.method == 'GET':
|
||||
if not request.user.has_event_perm(event, 'view_item'):
|
||||
|
@ -99,8 +101,8 @@ router.register(r'boxes', ContainerViewSet, basename='boxes')
|
|||
router.register(r'box', ContainerViewSet, basename='boxes')
|
||||
|
||||
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/(?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/(?P<id>\d+)/$', item_by_id, name='item_by_id'),
|
||||
]
|
||||
|
|
|
@ -39,10 +39,12 @@ class ItemSerializer(serializers.ModelSerializer):
|
|||
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', 'uid', 'description', 'file', 'dataImage', 'returned']
|
||||
fields = ['cid', 'box', 'uid', 'description', 'file', 'dataImage', 'returned', 'event']
|
||||
read_only_fields = ['uid']
|
||||
|
||||
def get_cid(self, instance):
|
||||
|
|
|
@ -30,7 +30,7 @@ class ItemTestCase(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(),
|
||||
[{'uid': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.cid, 'file': None,
|
||||
'returned': False}])
|
||||
'returned': False, 'event': self.event.slug}])
|
||||
|
||||
def test_members_with_file(self):
|
||||
import base64
|
||||
|
@ -40,7 +40,7 @@ class ItemTestCase(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(),
|
||||
[{'uid': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.cid, 'file': file.hash,
|
||||
'returned': False}])
|
||||
'returned': False, 'event': self.event.slug}])
|
||||
|
||||
def test_multi_members(self):
|
||||
Item.objects.create(container=self.box, event=self.event, description='1')
|
||||
|
@ -55,7 +55,7 @@ class ItemTestCase(TestCase):
|
|||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.json(),
|
||||
{'uid': 1, 'description': '1', 'box': 'BOX', 'cid': self.box.cid, 'file': None,
|
||||
'returned': False})
|
||||
'returned': False, 'event': self.event.slug})
|
||||
self.assertEqual(len(Item.objects.all()), 1)
|
||||
self.assertEqual(Item.objects.all()[0].uid, 1)
|
||||
self.assertEqual(Item.objects.all()[0].description, '1')
|
||||
|
@ -86,7 +86,7 @@ class ItemTestCase(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(),
|
||||
{'uid': 1, 'description': '2', 'box': 'BOX', 'cid': self.box.cid, 'file': None,
|
||||
'returned': False})
|
||||
'returned': False, 'event': self.event.slug})
|
||||
self.assertEqual(len(Item.objects.all()), 1)
|
||||
self.assertEqual(Item.objects.all()[0].uid, 1)
|
||||
self.assertEqual(Item.objects.all()[0].description, '2')
|
||||
|
|
|
@ -56,7 +56,7 @@ def reply(request, pk):
|
|||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@permission_required('tickets.add_issuethread_manual', raise_exception=True)
|
||||
def manual_ticket(request):
|
||||
def manual_ticket(request, event_slug):
|
||||
if 'name' not in request.data:
|
||||
return Response({'status': 'error', 'message': 'missing name'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if 'sender' not in request.data:
|
||||
|
@ -66,8 +66,16 @@ def manual_ticket(request):
|
|||
if 'body' not in request.data:
|
||||
return Response({'status': 'error', 'message': 'missing body'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
event = None
|
||||
if event_slug != 'none':
|
||||
try:
|
||||
event = Event.objects.get(slug=event_slug)
|
||||
except:
|
||||
return Response({'status': 'error', 'message': 'invalid event'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
issue = IssueThread.objects.create(
|
||||
name=request.data['name'],
|
||||
event=event,
|
||||
manually_created=True,
|
||||
)
|
||||
email = Email.objects.create(
|
||||
|
@ -122,8 +130,8 @@ router.register(r'tickets', IssueViewSet, basename='issues')
|
|||
router.register(r'shipping_vouchers', ShippingVoucherViewSet, basename='shipping_vouchers')
|
||||
|
||||
urlpatterns = ([
|
||||
re_path(r'tickets/states/$', get_available_states, name='get_available_states'),
|
||||
re_path(r'^tickets/(?P<pk>\d+)/reply/$', reply, name='reply'),
|
||||
re_path(r'^tickets/(?P<pk>\d+)/comment/$', add_comment, name='add_comment'),
|
||||
re_path(r'^tickets/manual/$', manual_ticket, name='manual_ticket'),
|
||||
re_path(r'^tickets/states/$', get_available_states, name='get_available_states'),
|
||||
re_path(r'^(?P<event_slug>[\w-]+)/tickets/manual/$', manual_ticket, name='manual_ticket'),
|
||||
] + router.urls)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from authentication.models import ExtendedUser
|
||||
from inventory.models import Event
|
||||
from mail.api_v2 import AttachmentSerializer
|
||||
from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher
|
||||
from inventory.serializers import ItemSerializer
|
||||
|
@ -41,11 +42,13 @@ class IssueSerializer(serializers.ModelSerializer):
|
|||
last_activity = serializers.SerializerMethodField()
|
||||
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)
|
||||
related_items = ItemSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = IssueThread
|
||||
fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid', 'related_items')
|
||||
fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid', 'related_items', 'event')
|
||||
read_only_fields = ('id', 'timeline', 'last_activity', 'uuid', 'related_items')
|
||||
|
||||
def to_internal_value(self, data):
|
||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime, timedelta
|
|||
from django.test import TestCase, Client
|
||||
|
||||
from authentication.models import ExtendedUser
|
||||
from inventory.models import Event
|
||||
from mail.models import Email, EmailAttachment
|
||||
from tickets.models import IssueThread, StateChange, Comment
|
||||
from django.contrib.auth.models import Permission
|
||||
|
@ -16,6 +17,7 @@ class IssueApiTest(TestCase):
|
|||
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.token = AuthToken.objects.create(user=self.user)
|
||||
self.client = Client(headers={'Authorization': 'Token ' + self.token[1]})
|
||||
|
||||
|
@ -28,6 +30,7 @@ class IssueApiTest(TestCase):
|
|||
now = datetime.now()
|
||||
issue = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
mail1 = Email.objects.create(
|
||||
subject='test',
|
||||
|
@ -61,6 +64,7 @@ class IssueApiTest(TestCase):
|
|||
self.assertEqual(response.json()[0]['id'], 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'], issue.uuid)
|
||||
self.assertEqual(response.json()[0]['last_activity'], comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
|
||||
|
@ -93,12 +97,15 @@ class IssueApiTest(TestCase):
|
|||
now = datetime.now()
|
||||
issue1 = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
issue2 = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
issue3 = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
mail1 = Email.objects.create(
|
||||
subject='test',
|
||||
|
@ -118,8 +125,11 @@ class IssueApiTest(TestCase):
|
|||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(3, len(response.json()))
|
||||
self.assertEqual(issue1.id, response.json()[0]['id'])
|
||||
self.assertEqual("evt", response.json()[0]['event'])
|
||||
self.assertEqual(issue2.id, response.json()[1]['id'])
|
||||
self.assertEqual("evt", response.json()[1]['event'])
|
||||
self.assertEqual(issue3.id, response.json()[2]['id'])
|
||||
self.assertEqual("evt", response.json()[2]['event'])
|
||||
self.assertEqual(issue1.state_changes.first().timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
response.json()[0]['last_activity'])
|
||||
self.assertEqual(mail1.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
|
@ -153,6 +163,7 @@ class IssueApiTest(TestCase):
|
|||
now = datetime.now()
|
||||
issue = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
mail1 = Email.objects.create(
|
||||
subject='test',
|
||||
|
@ -189,6 +200,7 @@ class IssueApiTest(TestCase):
|
|||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(1, len(response.json()))
|
||||
self.assertEqual(issue.id, response.json()[0]['id'])
|
||||
self.assertEqual("evt", response.json()[0]['event'])
|
||||
self.assertEqual('pending_new', response.json()[0]['state'])
|
||||
self.assertEqual('test issue', response.json()[0]['name'])
|
||||
self.assertEqual(None, response.json()[0]['assigned_to'])
|
||||
|
@ -230,13 +242,14 @@ class IssueApiTest(TestCase):
|
|||
self.assertEqual(file2.hash, response.json()[0]['timeline'][1]['attachments'][1]['hash'])
|
||||
|
||||
def test_manual_creation(self):
|
||||
response = self.client.post('/api/2/tickets/manual/',
|
||||
response = self.client.post('/api/2/evt/tickets/manual/',
|
||||
{'name': 'test issue', 'sender': 'test', 'recipient': 'test', 'body': 'test'},
|
||||
content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.json()['state'], 'pending_new')
|
||||
self.assertEqual(response.json()['name'], 'test issue')
|
||||
self.assertEqual(response.json()['assigned_to'], None)
|
||||
self.assertEqual("evt", response.json()['event'])
|
||||
timeline = response.json()['timeline']
|
||||
self.assertEqual(len(timeline), 2)
|
||||
self.assertEqual(timeline[0]['type'], 'state')
|
||||
|
@ -247,9 +260,35 @@ class IssueApiTest(TestCase):
|
|||
self.assertEqual(timeline[1]['subject'], 'test issue')
|
||||
self.assertEqual(timeline[1]['body'], 'test')
|
||||
|
||||
def test_manual_creation_none(self):
|
||||
response = self.client.post('/api/2/none/tickets/manual/',
|
||||
{'name': 'test issue', 'sender': 'test', 'recipient': 'test', 'body': 'test'},
|
||||
content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.json()['state'], 'pending_new')
|
||||
self.assertEqual(response.json()['name'], 'test issue')
|
||||
self.assertEqual(response.json()['assigned_to'], None)
|
||||
self.assertEqual(None, response.json()['event'])
|
||||
timeline = response.json()['timeline']
|
||||
self.assertEqual(len(timeline), 2)
|
||||
self.assertEqual(timeline[0]['type'], 'state')
|
||||
self.assertEqual(timeline[0]['state'], 'pending_new')
|
||||
self.assertEqual(timeline[1]['type'], 'mail')
|
||||
self.assertEqual(timeline[1]['sender'], 'test')
|
||||
self.assertEqual(timeline[1]['recipient'], 'test')
|
||||
self.assertEqual(timeline[1]['subject'], 'test issue')
|
||||
self.assertEqual(timeline[1]['body'], 'test')
|
||||
|
||||
def test_manual_creation_invalid(self):
|
||||
response = self.client.post('/api/2/foobar/tickets/manual/',
|
||||
{'name': 'test issue', 'sender': 'test', 'recipient': 'test', 'body': 'test'},
|
||||
content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_post_comment_altenative(self):
|
||||
issue = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
response = self.client.post(f'/api/2/tickets/{issue.id}/comment/', {'comment': 'test'})
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
@ -260,6 +299,7 @@ class IssueApiTest(TestCase):
|
|||
def test_post_alt_comment_empty(self):
|
||||
issue = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
response = self.client.post(f'/api/2/tickets/{issue.id}/comment/', {'comment': ''})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
@ -267,6 +307,7 @@ class IssueApiTest(TestCase):
|
|||
def test_state_change(self):
|
||||
issue = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
response = self.client.patch(f'/api/2/tickets/{issue.id}/', {'state': 'pending_open'},
|
||||
content_type='application/json')
|
||||
|
@ -284,6 +325,7 @@ class IssueApiTest(TestCase):
|
|||
def test_state_change_invalid_state(self):
|
||||
issue = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
response = self.client.patch(f'/api/2/tickets/{issue.id}/', {'state': 'invalid'},
|
||||
content_type='application/json')
|
||||
|
@ -292,12 +334,14 @@ class IssueApiTest(TestCase):
|
|||
def test_assign_user(self):
|
||||
issue = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
event=self.event,
|
||||
)
|
||||
response = self.client.patch(f'/api/2/tickets/{issue.id}/', {'assigned_to': self.user.username},
|
||||
content_type='application/json')
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual('pending_new', response.json()['state'])
|
||||
self.assertEqual('test issue', response.json()['name'])
|
||||
self.assertEqual("evt", response.json()['event'])
|
||||
self.assertEqual(self.user.username, response.json()['assigned_to'])
|
||||
timeline = response.json()['timeline']
|
||||
self.assertEqual(2, len(timeline))
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
{{ getEventSlug }}
|
||||
</button>
|
||||
<div class="dropdown-menu bg-dark" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item text-light" href="#" v-for="(event, index) in events" v-bind:key="index"
|
||||
<a class="dropdown-item text-light" href="#" v-for="(event, index) in selectableEvents"
|
||||
v-bind:key="index"
|
||||
:class="{ active: event.slug === getEventSlug }" @click="changeEvent(event)">{{ event.slug }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -115,6 +116,9 @@ export default {
|
|||
computed: {
|
||||
...mapState(['events']),
|
||||
...mapGetters(['getEventSlug', 'getActiveView', "checkPermission", "hasPermissions", "layout", "route"]),
|
||||
selectableEvents() {
|
||||
return [{slug: 'all'}, ...this.events, {slug: 'none'}];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['changeEvent', 'changeView']),
|
||||
|
|
|
@ -21,7 +21,7 @@ const store = createStore({
|
|||
state_options: [],
|
||||
shippingVouchers: [],
|
||||
|
||||
lastEvent: '37C3',
|
||||
lastEvent: 'all',
|
||||
lastUsed: {},
|
||||
searchQuery: '',
|
||||
remember: false,
|
||||
|
@ -358,7 +358,8 @@ const store = createStore({
|
|||
async searchEventItems({commit, getters, state}, query) {
|
||||
const encoded_query = base64.encode(utf8.encode(query));
|
||||
|
||||
const {data, success} = await http.get(`/2/${getters.getEventSlug}/items/${encoded_query}/`, state.user.token);
|
||||
const {data, success} = await http.get(`/2/${getters.getEventSlug}/items/${encoded_query}/`,
|
||||
state.user.token);
|
||||
if (data && success)
|
||||
commit('replaceLoadedItems', data);
|
||||
},
|
||||
|
@ -410,7 +411,8 @@ const store = createStore({
|
|||
async searchEventTickets({commit, getters, state}, query) {
|
||||
const encoded_query = base64.encode(utf8.encode(query));
|
||||
|
||||
const {data, success} = await http.get(`/2/${getters.getEventSlug}/tickets/${encoded_query}/`, state.user.token);
|
||||
const {data, success} = await http.get(`/2/${getters.getEventSlug}/tickets/${encoded_query}/`,
|
||||
state.user.token);
|
||||
if (data && success)
|
||||
commit('replaceTickets', data);
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue