stash
This commit is contained in:
parent
1a3aab4d41
commit
f9a7abdbb1
6 changed files with 58 additions and 40 deletions
|
@ -1,15 +1,20 @@
|
||||||
from django.urls import re_path
|
from django.urls import re_path
|
||||||
from django.contrib.auth.decorators import permission_required
|
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.decorators import api_view, permission_classes
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.permissions import IsAuthenticated
|
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.models import Event, Container, Item, Comment
|
||||||
from inventory.serializers import EventSerializer, ContainerSerializer, ItemSerializer, SearchResultSerializer
|
from inventory.serializers import EventSerializer, ContainerSerializer, ItemSerializer, SearchResultSerializer, \
|
||||||
|
CommentSerializer
|
||||||
|
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
|
|
||||||
|
from notify_sessions.models import SystemEvent
|
||||||
|
|
||||||
|
|
||||||
class EventViewSet(viewsets.ModelViewSet):
|
class EventViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = EventSerializer
|
serializer_class = EventSerializer
|
||||||
|
@ -71,6 +76,31 @@ def item(request, event_slug):
|
||||||
return Response(status=400)
|
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'])
|
@api_view(['GET', 'PUT', 'DELETE', 'PATCH'])
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
def item_by_id(request, event_slug, id):
|
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/$', 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'),
|
||||||
|
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'),
|
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 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, Comment
|
||||||
from inventory.shared_serializers import BasicItemSerializer
|
from inventory.shared_serializers import BasicItemSerializer
|
||||||
from mail.models import EventAddress
|
from mail.models import EventAddress
|
||||||
from tickets.shared_serializers import BasicIssueSerializer
|
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]
|
dict['addresses'] = [EventAddress.objects.get_or_create(address=x)[0] for x in addresses]
|
||||||
return dict
|
return dict
|
||||||
|
|
||||||
|
|
||||||
class ContainerSerializer(serializers.ModelSerializer):
|
class ContainerSerializer(serializers.ModelSerializer):
|
||||||
itemCount = serializers.SerializerMethodField()
|
itemCount = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@ -37,6 +38,18 @@ class ContainerSerializer(serializers.ModelSerializer):
|
||||||
return len(instance.items)
|
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):
|
class ItemSerializer(BasicItemSerializer):
|
||||||
timeline = serializers.SerializerMethodField()
|
timeline = serializers.SerializerMethodField()
|
||||||
dataImage = serializers.CharField(write_only=True, required=False)
|
dataImage = serializers.CharField(write_only=True, required=False)
|
||||||
|
|
0
core/testdata.py
Normal file
0
core/testdata.py
Normal file
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="timeline-item-wrapper">
|
<div class="timeline-item-wrapper">
|
||||||
<Lightbox v-if="lightboxHash" :hash="lightboxHash" @close="closeLightboxModal()"/>
|
|
||||||
<div class="timeline-item-description">
|
<div class="timeline-item-description">
|
||||||
<i class="avatar | small">
|
<i class="avatar | small">
|
||||||
<font-awesome-icon icon="user"/>
|
<font-awesome-icon icon="user"/>
|
||||||
|
@ -23,7 +22,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer" v-if="item.attachments.length">
|
<div class="card-footer" v-if="item.attachments.length">
|
||||||
<ul>
|
<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"
|
<AuthenticatedImage :src="`/media/2/256/${attachment.hash}/`" :alt="attachment.name"
|
||||||
v-if="attachment.mime_type.startsWith('image/')" cached/>
|
v-if="attachment.mime_type.startsWith('image/')" cached/>
|
||||||
<AuthenticatedDataLink :href="`/media/2/${attachment.hash}/`" :download="attachment.name"
|
<AuthenticatedDataLink :href="`/media/2/${attachment.hash}/`" :download="attachment.name"
|
||||||
|
@ -59,16 +58,11 @@
|
||||||
|
|
||||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
||||||
import Lightbox from "@/components/Lightbox.vue";
|
import {mapMutations} from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TimelineMail',
|
name: 'TimelineMail',
|
||||||
components: {Lightbox, AuthenticatedImage, AuthenticatedDataLink},
|
components: {AuthenticatedImage, AuthenticatedDataLink},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
lightboxHash: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
'item': {
|
'item': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -85,12 +79,7 @@ export default {
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openLightboxModalWith(attachment) {
|
...mapMutations(['openLightboxModalWith'])
|
||||||
this.lightboxHash = attachment.hash;
|
|
||||||
},
|
|
||||||
closeLightboxModal() { // Closes the editing modal and discards the edited copy of the item.
|
|
||||||
this.lightboxHash = null;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="timeline-item-wrapper">
|
<div class="timeline-item-wrapper">
|
||||||
<Lightbox v-if="lightboxHash" :hash="lightboxHash" @close="closeLightboxModal()"/>
|
|
||||||
<div class="timeline-item-description">
|
<div class="timeline-item-description">
|
||||||
<i class="avatar | small">
|
<i class="avatar | small">
|
||||||
<font-awesome-icon icon="user"/>
|
<font-awesome-icon icon="user"/>
|
||||||
|
@ -19,7 +18,7 @@
|
||||||
<AuthenticatedImage v-if="item.item.file" cached
|
<AuthenticatedImage v-if="item.item.file" cached
|
||||||
:src="`/media/2/256/${item.item.file}/`"
|
:src="`/media/2/256/${item.item.file}/`"
|
||||||
class="d-block card-img-left"
|
class="d-block card-img-left"
|
||||||
@click="openLightboxModalWith(item.item)"
|
@click="openLightboxModalWith(item.item.file)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -78,16 +77,11 @@
|
||||||
|
|
||||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
||||||
import Lightbox from "@/components/Lightbox.vue";
|
import {mapMutations} from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TimelineRelatedItem',
|
name: 'TimelineRelatedItem',
|
||||||
components: {Lightbox, AuthenticatedImage, AuthenticatedDataLink},
|
components: {AuthenticatedImage, AuthenticatedDataLink},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
lightboxHash: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
'item': {
|
'item': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -104,13 +98,8 @@ export default {
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openLightboxModalWith(attachment) {
|
...mapMutations(['openLightboxModalWith'])
|
||||||
this.lightboxHash = attachment.hash;
|
}
|
||||||
},
|
|
||||||
closeLightboxModal() { // Closes the editing modal and discards the edited copy of the item.
|
|
||||||
this.lightboxHash = null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
|
||||||
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
|
||||||
import Lightbox from "@/components/Lightbox.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TimelineRelatedTicket',
|
name: 'TimelineRelatedTicket',
|
||||||
components: {Lightbox},
|
components: {},
|
||||||
props: {
|
props: {
|
||||||
'item': {
|
'item': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
Loading…
Add table
Reference in a new issue