import logging from django.urls import re_path from django.contrib.auth.decorators import permission_required from rest_framework import routers, viewsets, serializers, status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from asgiref.sync import async_to_sync from channels.layers import get_channel_layer from core.settings import MAIL_DOMAIN from mail.models import Email from mail.protocol import send_smtp, make_reply, collect_references from notify_sessions.models import SystemEvent from tickets.models import IssueThread, Comment, STATE_CHOICES, StateChange class IssueSerializer(serializers.ModelSerializer): timeline = serializers.SerializerMethodField() class Meta: model = IssueThread fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity') read_only_fields = ('id', 'timeline', 'last_activity') @staticmethod def get_timeline(obj): timeline = [] for comment in obj.comments.all(): timeline.append({ 'type': 'comment', 'id': comment.id, 'timestamp': comment.timestamp, 'comment': comment.comment, }) for state_change in obj.state_changes.all(): timeline.append({ 'type': 'state', 'id': state_change.id, 'timestamp': state_change.timestamp, 'state': state_change.state, }) for email in obj.emails.all(): timeline.append({ 'type': 'mail', 'id': email.id, 'timestamp': email.timestamp, 'sender': email.sender, 'recipient': email.recipient, 'subject': email.subject, 'body': email.body, }) return sorted(timeline, key=lambda x: x['timestamp']) def update(self, instance, validated_data): if 'state' in validated_data: instance.state = validated_data['state'] instance.save() StateChange.objects.create( issue_thread=instance, state=validated_data['state'], ) return instance class IssueViewSet(viewsets.ModelViewSet): serializer_class = IssueSerializer queryset = IssueThread.objects.all() class CommentSerializer(serializers.ModelSerializer): class Meta: model = Comment fields = ('id', 'comment', 'timestamp', 'issue_thread') class CommentViewSet(viewsets.ModelViewSet): serializer_class = CommentSerializer queryset = Comment.objects.all() @api_view(['POST']) @permission_classes([IsAuthenticated]) @permission_required('tickets.add_issuethread', raise_exception=True) def reply(request, pk): issue = IssueThread.objects.get(pk=pk) # email = issue.reply(request.data['body']) # TODO evaluate if this is a useful abstraction references = collect_references(issue) most_recent = Email.objects.filter(issue_thread=issue, recipient__endswith='@' + MAIL_DOMAIN).order_by( '-timestamp').first() mail = Email.objects.create( issue_thread=issue, sender=most_recent.recipient, recipient=most_recent.sender, subject=f'Re: {most_recent.subject}', body=request.data['message'], in_reply_to=most_recent.reference, ) log = logging.getLogger('mail.log') async_to_sync(send_smtp)(make_reply(mail, references), log) return Response({'status': 'ok'}, status=status.HTTP_201_CREATED) @api_view(['POST']) @permission_classes([IsAuthenticated]) @permission_required('tickets.add_issuethread_manual', raise_exception=True) def manual_ticket(request): 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: return Response({'status': 'error', 'message': 'missing sender'}, status=status.HTTP_400_BAD_REQUEST) if 'recipient' not in request.data: return Response({'status': 'error', 'message': 'missing recipient'}, status=status.HTTP_400_BAD_REQUEST) if 'body' not in request.data: return Response({'status': 'error', 'message': 'missing body'}, status=status.HTTP_400_BAD_REQUEST) issue = IssueThread.objects.create( name=request.data['name'], manually_created=True, ) email = Email.objects.create( issue_thread=issue, sender=request.data['sender'], recipient=request.data['recipient'], subject=request.data['name'], body=request.data['body'], ) systemevent = SystemEvent.objects.create(type='email received', reference=email.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": "email received"} ) return Response(IssueSerializer(issue).data, status=status.HTTP_201_CREATED) class StateSerializer(serializers.Serializer): text = serializers.SerializerMethodField() value = serializers.SerializerMethodField() def get_text(self, obj): return obj['text'] def get_value(self, obj): return obj['value'] @api_view(['GET']) @permission_classes([IsAuthenticated]) def get_available_states(request): def get_state_choices(): for state in STATE_CHOICES: yield {'value': list(state)[0], 'text': list(state)[1]} return Response(get_state_choices()) router = routers.SimpleRouter() router.register(r'tickets', IssueViewSet, basename='issues') router.register(r'comments', CommentViewSet, basename='comments') urlpatterns = ([ re_path(r'^tickets/(?P\d+)/reply/$', reply, name='reply'), re_path(r'^tickets/manual/$', manual_ticket, name='manual_ticket'), re_path(r'^tickets/states/$', get_available_states, name='get_available_states'), ] + router.urls)