Compare commits
2 commits
1a3aab4d41
...
c8a75b5670
Author | SHA1 | Date | |
---|---|---|---|
c8a75b5670 | |||
f9a7abdbb1 |
8 changed files with 147 additions and 41 deletions
|
@ -1,15 +1,20 @@
|
|||
from django.urls import re_path
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from rest_framework import routers, viewsets
|
||||
from rest_framework import routers, viewsets, status
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
|
||||
from inventory.models import Event, Container, Item
|
||||
from inventory.serializers import EventSerializer, ContainerSerializer, ItemSerializer, SearchResultSerializer
|
||||
from inventory.models import Event, Container, Item, Comment
|
||||
from inventory.serializers import EventSerializer, ContainerSerializer, ItemSerializer, SearchResultSerializer, \
|
||||
CommentSerializer
|
||||
|
||||
from base64 import b64decode
|
||||
|
||||
from notify_sessions.models import SystemEvent
|
||||
|
||||
|
||||
class EventViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = EventSerializer
|
||||
|
@ -71,6 +76,31 @@ def item(request, event_slug):
|
|||
return Response(status=400)
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@permission_required('tickets.add_comment', raise_exception=True)
|
||||
def add_comment(request, event_slug, id):
|
||||
event = None
|
||||
if event_slug != 'none':
|
||||
event = Event.objects.get(slug=event_slug)
|
||||
item = Item.objects.get(event=event, id=id)
|
||||
if not request.user.has_event_perm(event, 'view_item'):
|
||||
return Response(status=403)
|
||||
if 'comment' not in request.data or request.data['comment'] == '':
|
||||
return Response({'status': 'error', 'message': 'missing comment'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
comment = Comment.objects.create(
|
||||
item=item,
|
||||
comment=request.data['comment'],
|
||||
)
|
||||
systemevent = SystemEvent.objects.create(type='comment added', reference=comment.id)
|
||||
channel_layer = get_channel_layer()
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
'general', {"type": "generic.event", "name": "send_message_to_frontend", "event_id": systemevent.id,
|
||||
"message": "comment added"}
|
||||
)
|
||||
return Response(CommentSerializer(comment).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
@api_view(['GET', 'PUT', 'DELETE', 'PATCH'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def item_by_id(request, event_slug, id):
|
||||
|
@ -117,5 +147,6 @@ urlpatterns = router.urls + [
|
|||
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+)/comment/$', add_comment, name='add_comment'),
|
||||
re_path(r'^(?P<event_slug>[\w-]+)/item/(?P<id>\d+)/$', item_by_id, name='item_by_id'),
|
||||
]
|
||||
|
|
|
@ -3,7 +3,7 @@ from rest_framework import serializers
|
|||
from rest_framework.relations import SlugRelatedField
|
||||
|
||||
from files.models import File
|
||||
from inventory.models import Event, Container, Item
|
||||
from inventory.models import Event, Container, Item, Comment
|
||||
from inventory.shared_serializers import BasicItemSerializer
|
||||
from mail.models import EventAddress
|
||||
from tickets.shared_serializers import BasicIssueSerializer
|
||||
|
@ -25,6 +25,7 @@ class EventSerializer(serializers.ModelSerializer):
|
|||
dict['addresses'] = [EventAddress.objects.get_or_create(address=x)[0] for x in addresses]
|
||||
return dict
|
||||
|
||||
|
||||
class ContainerSerializer(serializers.ModelSerializer):
|
||||
itemCount = serializers.SerializerMethodField()
|
||||
|
||||
|
@ -37,6 +38,18 @@ class ContainerSerializer(serializers.ModelSerializer):
|
|||
return len(instance.items)
|
||||
|
||||
|
||||
class CommentSerializer(serializers.ModelSerializer):
|
||||
|
||||
def validate(self, attrs):
|
||||
if 'comment' not in attrs or attrs['comment'] == '':
|
||||
raise serializers.ValidationError('comment cannot be empty')
|
||||
return attrs
|
||||
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ('id', 'comment', 'timestamp', 'item')
|
||||
|
||||
|
||||
class ItemSerializer(BasicItemSerializer):
|
||||
timeline = serializers.SerializerMethodField()
|
||||
dataImage = serializers.CharField(write_only=True, required=False)
|
||||
|
|
0
core/testdata.py
Normal file
0
core/testdata.py
Normal file
|
@ -3,7 +3,7 @@ services:
|
|||
build:
|
||||
context: ../../core
|
||||
dockerfile: ../deploy/dev/Dockerfile.backend
|
||||
command: bash -c 'python manage.py migrate && python manage.py runserver 0.0.0.0:8000'
|
||||
command: bash -c 'python manage.py runserver 0.0.0.0:8000'
|
||||
environment:
|
||||
- HTTP_HOST=core
|
||||
- DB_HOST=db
|
||||
|
@ -13,6 +13,7 @@ services:
|
|||
- DB_PASSWORD=system3
|
||||
volumes:
|
||||
- ../../core:/code
|
||||
- ./testdata.py:/code/testdata.py
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
|
|
87
deploy/dev/testdata.py
Normal file
87
deploy/dev/testdata.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
import os
|
||||
|
||||
|
||||
def setup():
|
||||
from authentication.models import ExtendedUser, EventPermission
|
||||
from inventory.models import Event
|
||||
from django.contrib.auth.models import Permission, Group
|
||||
permissions = ['add_item', 'view_item', 'view_file', 'delete_item', 'change_item']
|
||||
if not ExtendedUser.objects.filter(username='admin').exists():
|
||||
admin = ExtendedUser.objects.create_superuser('admin', 'admin@example.com', 'admin')
|
||||
admin.set_password('admin')
|
||||
admin.user_permissions.add(*Permission.objects.all())
|
||||
admin.save()
|
||||
|
||||
if not ExtendedUser.objects.filter(username='testuser').exists():
|
||||
testuser = ExtendedUser.objects.create_user('testuser', 'testuser@example.com', 'testuser')
|
||||
testuser.set_password('testuser')
|
||||
testuser.user_permissions.add(*Permission.objects.all())
|
||||
testuser.save()
|
||||
|
||||
team = Group.objects.get(name='Team')
|
||||
team.permissions.add(
|
||||
*Permission.objects.all()
|
||||
)
|
||||
|
||||
if not ExtendedUser.objects.filter(username='testuser2').exists():
|
||||
testuser2 = ExtendedUser.objects.create_user('testuser2', 'testuser2@example.com', 'testuser2')
|
||||
testuser2.set_password('testuser2')
|
||||
testuser2.groups.add(team)
|
||||
testuser2.save()
|
||||
|
||||
# hbug = ExtendedUser.objects.get(username='hbug')
|
||||
|
||||
if not Event.objects.filter(slug='TEST1').exists():
|
||||
event1 = Event.objects.create(name='first test event', slug='TEST1',
|
||||
start='2023-12-18 00:00:00.000000', end='2023-12-27 00:00:00.000000',
|
||||
pre_start='2023-12-31 00:00:00.000000', post_end='2024-01-04 00:00:00.000000')
|
||||
|
||||
if not Event.objects.filter(slug='TEST2').exists():
|
||||
event2 = Event.objects.create(name='second test event', slug='TEST2',
|
||||
start='2024-12-18 00:00:00.000000', end='2024-12-27 00:00:00.000000',
|
||||
pre_start='2024-12-31 00:00:00.000000', post_end='2025-01-04 00:00:00.000000')
|
||||
|
||||
# for permission in permissions:
|
||||
# EventPermission.objects.create(event=event_37c3, user=hbug,
|
||||
# permission=Permission.objects.get(codename=permission))
|
||||
|
||||
# from tickets.models import IssueThread
|
||||
#
|
||||
# from mail.models import Email
|
||||
#
|
||||
# issue_thread = IssueThread.objects.create(
|
||||
# name="test",
|
||||
# event=Event.objects.get(slug='TEST1')
|
||||
# )
|
||||
# mail1 = Email.objects.create(
|
||||
# subject='test subject',
|
||||
# body='test',
|
||||
# sender='test1@test',
|
||||
# recipient='test2@test',
|
||||
# issue_thread=issue_thread,
|
||||
# )
|
||||
# mail1_reply = Email.objects.create(
|
||||
# subject='Message received',
|
||||
# body='Thank you for your message.',
|
||||
# sender='test2@test',
|
||||
# recipient='test1@test',
|
||||
# in_reply_to=mail1.reference,
|
||||
# issue_thread=issue_thread,
|
||||
# )
|
||||
|
||||
|
||||
def main():
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
|
||||
import django
|
||||
|
||||
django.setup()
|
||||
|
||||
from django.core.management import call_command
|
||||
call_command('migrate')
|
||||
|
||||
setup()
|
||||
print('testdata initialised')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div class="timeline-item-wrapper">
|
||||
<Lightbox v-if="lightboxHash" :hash="lightboxHash" @close="closeLightboxModal()"/>
|
||||
<div class="timeline-item-description">
|
||||
<i class="avatar | small">
|
||||
<font-awesome-icon icon="user"/>
|
||||
|
@ -23,7 +22,7 @@
|
|||
</div>
|
||||
<div class="card-footer" v-if="item.attachments.length">
|
||||
<ul>
|
||||
<li v-for="attachment in item.attachments" @click="openLightboxModalWith(attachment)">
|
||||
<li v-for="attachment in item.attachments" @click="openLightboxModalWith(attachment.hash)">
|
||||
<AuthenticatedImage :src="`/media/2/256/${attachment.hash}/`" :alt="attachment.name"
|
||||
v-if="attachment.mime_type.startsWith('image/')" cached/>
|
||||
<AuthenticatedDataLink :href="`/media/2/${attachment.hash}/`" :download="attachment.name"
|
||||
|
@ -59,16 +58,11 @@
|
|||
|
||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
||||
import Lightbox from "@/components/Lightbox.vue";
|
||||
import {mapMutations} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'TimelineMail',
|
||||
components: {Lightbox, AuthenticatedImage, AuthenticatedDataLink},
|
||||
data() {
|
||||
return {
|
||||
lightboxHash: null,
|
||||
}
|
||||
},
|
||||
components: {AuthenticatedImage, AuthenticatedDataLink},
|
||||
props: {
|
||||
'item': {
|
||||
type: Object,
|
||||
|
@ -85,12 +79,7 @@ export default {
|
|||
|
||||
},
|
||||
methods: {
|
||||
openLightboxModalWith(attachment) {
|
||||
this.lightboxHash = attachment.hash;
|
||||
},
|
||||
closeLightboxModal() { // Closes the editing modal and discards the edited copy of the item.
|
||||
this.lightboxHash = null;
|
||||
},
|
||||
...mapMutations(['openLightboxModalWith'])
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div class="timeline-item-wrapper">
|
||||
<Lightbox v-if="lightboxHash" :hash="lightboxHash" @close="closeLightboxModal()"/>
|
||||
<div class="timeline-item-description">
|
||||
<i class="avatar | small">
|
||||
<font-awesome-icon icon="user"/>
|
||||
|
@ -19,7 +18,7 @@
|
|||
<AuthenticatedImage v-if="item.item.file" cached
|
||||
:src="`/media/2/256/${item.item.file}/`"
|
||||
class="d-block card-img-left"
|
||||
@click="openLightboxModalWith(item.item)"
|
||||
@click="openLightboxModalWith(item.item.file)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
|
@ -78,16 +77,11 @@
|
|||
|
||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
||||
import Lightbox from "@/components/Lightbox.vue";
|
||||
import {mapMutations} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'TimelineRelatedItem',
|
||||
components: {Lightbox, AuthenticatedImage, AuthenticatedDataLink},
|
||||
data() {
|
||||
return {
|
||||
lightboxHash: null,
|
||||
}
|
||||
},
|
||||
components: {AuthenticatedImage, AuthenticatedDataLink},
|
||||
props: {
|
||||
'item': {
|
||||
type: Object,
|
||||
|
@ -104,13 +98,8 @@ export default {
|
|||
|
||||
},
|
||||
methods: {
|
||||
openLightboxModalWith(attachment) {
|
||||
this.lightboxHash = attachment.hash;
|
||||
},
|
||||
closeLightboxModal() { // Closes the editing modal and discards the edited copy of the item.
|
||||
this.lightboxHash = null;
|
||||
},
|
||||
},
|
||||
...mapMutations(['openLightboxModalWith'])
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -19,13 +19,9 @@
|
|||
|
||||
<script>
|
||||
|
||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
||||
import Lightbox from "@/components/Lightbox.vue";
|
||||
|
||||
export default {
|
||||
name: 'TimelineRelatedTicket',
|
||||
components: {Lightbox},
|
||||
components: {},
|
||||
props: {
|
||||
'item': {
|
||||
type: Object,
|
||||
|
|
Loading…
Add table
Reference in a new issue