show mail attachments in frontend
This commit is contained in:
parent
d1626d1777
commit
04f774404a
11 changed files with 252 additions and 23 deletions
|
@ -11,6 +11,7 @@ from rest_framework.response import Response
|
|||
|
||||
from core.settings import MEDIA_ROOT
|
||||
from files.models import File
|
||||
from mail.models import EmailAttachment
|
||||
|
||||
|
||||
@swagger_auto_schema(method='GET', auto_schema=None)
|
||||
|
@ -21,7 +22,11 @@ def media_urls(request, hash):
|
|||
if request.META.get('HTTP_IF_NONE_MATCH') and request.META.get('HTTP_IF_NONE_MATCH') == hash:
|
||||
return HttpResponse(status=status.HTTP_304_NOT_MODIFIED)
|
||||
|
||||
file = File.objects.get(hash=hash)
|
||||
file = File.objects.filter(hash=hash).first()
|
||||
attachment = EmailAttachment.objects.filter(hash=hash).first()
|
||||
file = file if file else attachment
|
||||
if not file:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
hash_path = file.file
|
||||
return HttpResponse(status=status.HTTP_200_OK,
|
||||
content_type=file.mime_type,
|
||||
|
@ -33,9 +38,10 @@ def media_urls(request, hash):
|
|||
'Age': 0,
|
||||
'ETag': file.hash,
|
||||
})
|
||||
|
||||
except File.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
except EmailAttachment.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
@swagger_auto_schema(method='GET', auto_schema=None)
|
||||
|
@ -47,7 +53,11 @@ def thumbnail_urls(request, size, hash):
|
|||
if request.META.get('HTTP_IF_NONE_MATCH') and request.META.get('HTTP_IF_NONE_MATCH') == hash + "_" + str(size):
|
||||
return HttpResponse(status=status.HTTP_304_NOT_MODIFIED)
|
||||
try:
|
||||
file = File.objects.get(hash=hash)
|
||||
file = File.objects.filter(hash=hash).first()
|
||||
attachment = EmailAttachment.objects.filter(hash=hash).first()
|
||||
file = file if file else attachment
|
||||
if not file:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
hash_path = file.file
|
||||
if not os.path.exists(MEDIA_ROOT + f'/thumbnails/{size}/{hash_path}'):
|
||||
from PIL import Image
|
||||
|
@ -72,6 +82,8 @@ def thumbnail_urls(request, size, hash):
|
|||
|
||||
except File.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
except EmailAttachment.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -77,6 +77,13 @@ class AbstractFile(models.Model):
|
|||
|
||||
objects = FileManager()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
from django.utils import timezone
|
||||
if not self.created_at:
|
||||
self.created_at = timezone.now()
|
||||
self.updated_at = timezone.now()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
from rest_framework import routers, viewsets, serializers
|
||||
|
||||
from mail.models import Email
|
||||
from mail.models import Email, EmailAttachment
|
||||
|
||||
|
||||
class AttachmentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = EmailAttachment
|
||||
fields = ['hash', 'mime_type', 'name']
|
||||
|
||||
|
||||
class EmailSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -26,7 +26,7 @@ class Migration(migrations.Migration):
|
|||
def generate_email_attachments(apps, schema_editor):
|
||||
for email in Email.objects.all():
|
||||
raw = email.raw
|
||||
if raw is None:
|
||||
if raw is None or raw == '':
|
||||
continue
|
||||
parsed, body, attachments = parse_email_body(raw.encode('utf-8'), NullLogger())
|
||||
email.attachments.clear()
|
||||
|
|
|
@ -92,8 +92,8 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
|||
session = mock.Mock()
|
||||
envelope = Envelope()
|
||||
envelope.mail_from = 'test1@test'
|
||||
envelope.rcpt_tos = ['test2@test']
|
||||
envelope.content = b'Subject: test\nFrom: test3@test\nTo: test4@test\nMessage-ID: <1@test>\n\ntest'
|
||||
envelope.rcpt_tos = ['test2@localhost']
|
||||
envelope.content = b'Subject: test\nFrom: test3@test\nTo: test4@localhost\nMessage-ID: <1@test>\n\ntest'
|
||||
result = async_to_sync(handler.handle_DATA)(server, session, envelope)
|
||||
self.assertEqual(result, '250 Message accepted for delivery')
|
||||
self.assertEqual(len(Email.objects.all()), 2)
|
||||
|
@ -101,14 +101,14 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
|||
aiosmtplib.send.assert_called_once()
|
||||
self.assertEqual('test', Email.objects.all()[0].subject)
|
||||
self.assertEqual('test1@test', Email.objects.all()[0].sender)
|
||||
self.assertEqual('test2@test', Email.objects.all()[0].recipient)
|
||||
self.assertEqual('test2@localhost', Email.objects.all()[0].recipient)
|
||||
self.assertEqual('test', Email.objects.all()[0].body)
|
||||
self.assertEqual(IssueThread.objects.all()[0], Email.objects.all()[0].issue_thread)
|
||||
self.assertEqual('<1@test>', Email.objects.all()[0].reference)
|
||||
self.assertEqual(None, Email.objects.all()[0].in_reply_to)
|
||||
self.assertEqual(expected_auto_reply_subject.format('test', IssueThread.objects.all()[0].short_uuid()),
|
||||
Email.objects.all()[1].subject)
|
||||
self.assertEqual('test2@test', Email.objects.all()[1].sender)
|
||||
self.assertEqual('test2@localhost', Email.objects.all()[1].sender)
|
||||
self.assertEqual('test1@test', Email.objects.all()[1].recipient)
|
||||
self.assertEqual(expected_auto_reply.format(IssueThread.objects.all()[0].short_uuid()),
|
||||
Email.objects.all()[1].body)
|
||||
|
@ -284,19 +284,19 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
|||
|
||||
def test_mail_reply(self):
|
||||
issue_thread = IssueThread.objects.create(
|
||||
name="test",
|
||||
name="test subject",
|
||||
)
|
||||
mail1 = Email.objects.create(
|
||||
subject='test subject',
|
||||
body='test',
|
||||
sender='test1@test',
|
||||
recipient='test2@' + MAIL_DOMAIN,
|
||||
recipient='test2@localhost',
|
||||
issue_thread=issue_thread,
|
||||
)
|
||||
mail1_reply = Email.objects.create(
|
||||
subject='Re: test subject',
|
||||
subject='Message received',
|
||||
body='Thank you for your message.',
|
||||
sender='test2@' + MAIL_DOMAIN,
|
||||
sender='test2@localhost',
|
||||
recipient='test1@test',
|
||||
in_reply_to=mail1.reference,
|
||||
issue_thread=issue_thread,
|
||||
|
@ -310,8 +310,22 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
|||
self.assertEqual(len(Email.objects.all()), 3)
|
||||
self.assertEqual(len(IssueThread.objects.all()), 1)
|
||||
aiosmtplib.send.assert_called_once()
|
||||
self.assertEqual(Email.objects.all()[2].subject, 'Re: test subject')
|
||||
self.assertEqual(Email.objects.all()[2].sender, 'test2@' + MAIL_DOMAIN)
|
||||
self.assertEqual(Email.objects.all()[0].subject, 'test subject')
|
||||
self.assertEqual(Email.objects.all()[0].sender, 'test1@test')
|
||||
self.assertEqual(Email.objects.all()[0].recipient, 'test2@localhost')
|
||||
self.assertEqual(Email.objects.all()[0].body, 'test')
|
||||
self.assertEqual(Email.objects.all()[0].issue_thread, issue_thread)
|
||||
self.assertEqual(Email.objects.all()[0].reference, mail1.reference)
|
||||
self.assertEqual(Email.objects.all()[1].subject, 'Message received')
|
||||
self.assertEqual(Email.objects.all()[1].sender, 'test2@localhost')
|
||||
self.assertEqual(Email.objects.all()[1].recipient, 'test1@test')
|
||||
self.assertEqual(Email.objects.all()[1].body, 'Thank you for your message.')
|
||||
self.assertEqual(Email.objects.all()[1].issue_thread, issue_thread)
|
||||
self.assertTrue(Email.objects.all()[1].reference.startswith("<"))
|
||||
self.assertTrue(Email.objects.all()[1].reference.endswith("@localhost>"))
|
||||
self.assertEqual(Email.objects.all()[1].in_reply_to, mail1.reference)
|
||||
self.assertEqual(Email.objects.all()[2].subject, 'Re: test subject [#{0}]'.format(issue_thread.short_uuid()))
|
||||
self.assertEqual(Email.objects.all()[2].sender, 'test2@localhost')
|
||||
self.assertEqual(Email.objects.all()[2].recipient, 'test1@test')
|
||||
self.assertEqual(Email.objects.all()[2].body, 'test')
|
||||
self.assertEqual(Email.objects.all()[2].issue_thread, issue_thread)
|
||||
|
@ -633,25 +647,26 @@ dGVzdGltYWdl
|
|||
|
||||
def test_mail_plus_issue_thread(self):
|
||||
issue_thread = IssueThread.objects.create(
|
||||
name="test",
|
||||
name="test subject",
|
||||
)
|
||||
mail1 = Email.objects.create(
|
||||
subject='test subject',
|
||||
body='test',
|
||||
sender='test1@test',
|
||||
recipient='test2@test',
|
||||
recipient='test2@localhost',
|
||||
issue_thread=issue_thread,
|
||||
)
|
||||
mail1_reply = Email.objects.create(
|
||||
subject='Message received',
|
||||
body='Thank you for your message.',
|
||||
sender='test2@test',
|
||||
sender='test2@localhost',
|
||||
recipient='test1@test',
|
||||
in_reply_to=mail1.reference,
|
||||
issue_thread=issue_thread,
|
||||
)
|
||||
from aiosmtpd.smtp import Envelope
|
||||
from asgiref.sync import async_to_sync
|
||||
from email.message import EmailMessage
|
||||
import aiosmtplib
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
@ -677,4 +692,23 @@ dGVzdGltYWdl
|
|||
self.assertEqual(Email.objects.all()[2].body, 'bar')
|
||||
self.assertEqual(Email.objects.all()[2].issue_thread, issue_thread)
|
||||
self.assertEqual(Email.objects.all()[2].reference, '<3@test>')
|
||||
self.assertEqual('test', IssueThread.objects.all()[0].name)
|
||||
self.assertEqual('test subject', IssueThread.objects.all()[0].name)
|
||||
response = self.client.post(f'/api/2/tickets/{issue_thread.id}/reply/', {
|
||||
'message': 'test'
|
||||
})
|
||||
aiosmtplib.send.assert_called_once();
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(4, len(Email.objects.all()))
|
||||
self.assertEqual(4, len(Email.objects.filter(issue_thread=issue_thread)))
|
||||
self.assertEqual(1, len(IssueThread.objects.all()))
|
||||
self.assertEqual(Email.objects.all()[3].subject, 'Re: test subject [#{0}]'.format(issue_thread.short_uuid()))
|
||||
self.assertEqual(Email.objects.all()[3].sender, 'test2@localhost')
|
||||
self.assertEqual(Email.objects.all()[3].recipient, 'test1@test')
|
||||
self.assertEqual(Email.objects.all()[3].body, 'test')
|
||||
self.assertEqual(Email.objects.all()[3].issue_thread, issue_thread)
|
||||
self.assertTrue(Email.objects.all()[3].reference.startswith("<"))
|
||||
self.assertTrue(Email.objects.all()[3].reference.endswith("@localhost>"))
|
||||
self.assertEqual(Email.objects.all()[3].in_reply_to, mail1.reference)
|
||||
self.assertEqual('test subject', IssueThread.objects.all()[0].name)
|
||||
self.assertEqual('pending_new', IssueThread.objects.all()[0].state)
|
||||
self.assertEqual(None, IssueThread.objects.all()[0].assigned_to)
|
||||
|
|
|
@ -10,6 +10,7 @@ from asgiref.sync import async_to_sync
|
|||
from channels.layers import get_channel_layer
|
||||
|
||||
from core.settings import MAIL_DOMAIN
|
||||
from mail.api_v2 import AttachmentSerializer
|
||||
from mail.models import Email
|
||||
from mail.protocol import send_smtp, make_reply, collect_references
|
||||
from notify_sessions.models import SystemEvent
|
||||
|
@ -75,6 +76,7 @@ class IssueSerializer(serializers.ModelSerializer):
|
|||
'recipient': email.recipient,
|
||||
'subject': email.subject,
|
||||
'body': email.body,
|
||||
'attachments': AttachmentSerializer(email.attachments.all(), many=True).data,
|
||||
})
|
||||
return sorted(timeline, key=lambda x: x['timestamp'])
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from datetime import datetime, timedelta
|
|||
from django.test import TestCase, Client
|
||||
|
||||
from authentication.models import ExtendedUser
|
||||
from mail.models import Email
|
||||
from mail.models import Email, EmailAttachment
|
||||
from tickets.models import IssueThread, StateChange, Comment
|
||||
from django.contrib.auth.models import Permission
|
||||
from knox.models import AuthToken
|
||||
|
@ -147,6 +147,88 @@ class IssueApiTest(TestCase):
|
|||
self.assertEqual(comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
response.json()[2]['timeline'][1]['timestamp'])
|
||||
|
||||
def test_issues_with_files(self):
|
||||
from django.core.files.base import ContentFile
|
||||
from hashlib import sha256
|
||||
now = datetime.now()
|
||||
issue = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
)
|
||||
mail1 = Email.objects.create(
|
||||
subject='test',
|
||||
body='test',
|
||||
sender='test',
|
||||
recipient='test',
|
||||
issue_thread=issue,
|
||||
timestamp=now,
|
||||
)
|
||||
mail2 = Email.objects.create(
|
||||
subject='test',
|
||||
body='test',
|
||||
sender='test',
|
||||
recipient='test',
|
||||
issue_thread=issue,
|
||||
in_reply_to=mail1.reference,
|
||||
timestamp=now + timedelta(seconds=2),
|
||||
)
|
||||
comment = Comment.objects.create(
|
||||
issue_thread=issue,
|
||||
comment="test",
|
||||
timestamp=now + timedelta(seconds=3),
|
||||
)
|
||||
file1 = EmailAttachment.objects.create(
|
||||
name='file1', mime_type='text/plain', file=ContentFile(b"foo1", "f1"),
|
||||
hash=sha256(b"foo1").hexdigest(), email=mail1
|
||||
)
|
||||
file2 = EmailAttachment.objects.create(
|
||||
name='file2', mime_type='text/plain', file=ContentFile(b"foo2", "f2"),
|
||||
hash=sha256(b"foo2").hexdigest(), email=mail1
|
||||
)
|
||||
|
||||
response = self.client.get('/api/2/tickets/')
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(1, len(response.json()))
|
||||
self.assertEqual(issue.id, response.json()[0]['id'])
|
||||
self.assertEqual('pending_new', response.json()[0]['state'])
|
||||
self.assertEqual('test issue', response.json()[0]['name'])
|
||||
self.assertEqual(None, response.json()[0]['assigned_to'])
|
||||
self.assertEqual(36, len(response.json()[0]['uuid']))
|
||||
self.assertEqual(comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
response.json()[0]['last_activity'])
|
||||
self.assertEqual(4, len(response.json()[0]['timeline']))
|
||||
self.assertEqual('state', response.json()[0]['timeline'][0]['type'])
|
||||
self.assertEqual('mail', response.json()[0]['timeline'][1]['type'])
|
||||
self.assertEqual('mail', response.json()[0]['timeline'][2]['type'])
|
||||
self.assertEqual('comment', response.json()[0]['timeline'][3]['type'])
|
||||
self.assertEqual(mail1.id, response.json()[0]['timeline'][1]['id'])
|
||||
self.assertEqual(mail2.id, response.json()[0]['timeline'][2]['id'])
|
||||
self.assertEqual(comment.id, response.json()[0]['timeline'][3]['id'])
|
||||
self.assertEqual('pending_new', response.json()[0]['timeline'][0]['state'])
|
||||
self.assertEqual('test', response.json()[0]['timeline'][1]['sender'])
|
||||
self.assertEqual('test', response.json()[0]['timeline'][1]['recipient'])
|
||||
self.assertEqual('test', response.json()[0]['timeline'][1]['subject'])
|
||||
self.assertEqual('test', response.json()[0]['timeline'][1]['body'])
|
||||
self.assertEqual(mail1.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
response.json()[0]['timeline'][1]['timestamp'])
|
||||
self.assertEqual('test', response.json()[0]['timeline'][2]['sender'])
|
||||
self.assertEqual('test', response.json()[0]['timeline'][2]['recipient'])
|
||||
|
||||
self.assertEqual('test', response.json()[0]['timeline'][2]['subject'])
|
||||
self.assertEqual('test', response.json()[0]['timeline'][2]['body'])
|
||||
self.assertEqual(mail2.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
response.json()[0]['timeline'][2]['timestamp'])
|
||||
self.assertEqual('test', response.json()[0]['timeline'][3]['comment'])
|
||||
self.assertEqual(comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
response.json()[0]['timeline'][3]['timestamp'])
|
||||
self.assertEqual(2, len(response.json()[0]['timeline'][1]['attachments']))
|
||||
self.assertEqual(0, len(response.json()[0]['timeline'][2]['attachments']))
|
||||
self.assertEqual('file1', response.json()[0]['timeline'][1]['attachments'][0]['name'])
|
||||
self.assertEqual('file2', response.json()[0]['timeline'][1]['attachments'][1]['name'])
|
||||
self.assertEqual('text/plain', response.json()[0]['timeline'][1]['attachments'][0]['mime_type'])
|
||||
self.assertEqual('text/plain', response.json()[0]['timeline'][1]['attachments'][1]['mime_type'])
|
||||
self.assertEqual(file1.hash, response.json()[0]['timeline'][1]['attachments'][0]['hash'])
|
||||
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/',
|
||||
{'name': 'test issue', 'sender': 'test', 'recipient': 'test', 'body': 'test'},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue