Merge branch 'main' into pypi_release

This commit is contained in:
Christopher Broderick 2023-03-26 00:56:00 +00:00 committed by GitHub
commit b716e046b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 9 deletions

View File

@ -687,8 +687,8 @@ def extract_part_data(
name = f"part-{counter}{ext}" name = f"part-{counter}{ext}"
else: else:
name = f"part-{counter}_{name}" name = f"part-{counter}_{name}"
payload = part.as_string() if part.is_multipart() else part.get_payload(decode=True)
files.append(SimpleUploadedFile(name, part.get_payload(decode=True), mimetypes.guess_type(name)[0])) files.append(SimpleUploadedFile(name, payload, mimetypes.guess_type(name)[0]))
logger.debug("Found MIME attachment %s", name) logger.debug("Found MIME attachment %s", name)
return part_body, part_full_body return part_body, part_full_body

View File

@ -12,7 +12,7 @@ import helpdesk.email
from helpdesk.email import extract_part_data, object_from_message from helpdesk.email import extract_part_data, object_from_message
from helpdesk.exceptions import DeleteIgnoredTicketException, IgnoreTicketException from helpdesk.exceptions import DeleteIgnoredTicketException, IgnoreTicketException
from helpdesk.management.commands.get_email import Command from helpdesk.management.commands.get_email import Command
from helpdesk.models import FollowUp, FollowUpAttachment, IgnoreEmail, Queue, Ticket, TicketCC from helpdesk.models import Attachment, FollowUp, FollowUpAttachment, IgnoreEmail, Queue, Ticket, TicketCC
from helpdesk.tests import utils from helpdesk.tests import utils
import itertools import itertools
import logging import logging
@ -230,6 +230,29 @@ class GetEmailCommonTests(TestCase):
followup = ticket.followup_set.get() followup = ticket.followup_set.get()
self.assertEqual(1, followup.followupattachment_set.count()) self.assertEqual(1, followup.followupattachment_set.count())
def test_email_with_multipart_as_attachment(self):
"""
Is a multipart attachment to an email correctly saved as an attachment
"""
att_filename = 'email_attachment.eml'
message, _, _ = utils.generate_multipart_email(type_list=['plain', 'html'])
email_attachment, _, _ = utils.generate_multipart_email(type_list=['plain', 'html'])
att_content = email_attachment.as_string()
message.attach(utils.generate_file_mime_part(filename=att_filename, content=att_content))
object_from_message(message.as_string(), self.queue_public, self.logger)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(f'[test-1] {message.get("subject")} (Opened)', mail.outbox[0].subject)
ticket = Ticket.objects.get()
followup = ticket.followup_set.get()
att_retrieved: Attachment = followup.followupattachment_set.get()
self.assertTrue(att_retrieved.filename.endswith(att_filename), "Filename of attached multipart not detected: %s" % (att_retrieved.filename))
with att_retrieved.file.open('r') as f:
retrieved_content = f.read()
self.assertEquals(att_content, retrieved_content, "Retrieved attachment content different to original :\n\n%s\n\n%s" % (att_content, retrieved_content))
class GetEmailParametricTemplate(object): class GetEmailParametricTemplate(object):
"""TestCase that checks basic email functionality across methods and socks configs.""" """TestCase that checks basic email functionality across methods and socks configs."""

View File

@ -141,14 +141,15 @@ def generate_email_address(
# format email address for RFC 2822 and return # format email address for RFC 2822 and return
return email.utils.formataddr((real_name, email_address)), email_address, first_name, last_name return email.utils.formataddr((real_name, email_address)), email_address, first_name, last_name
def generate_file_mime_part(locale: str="en_US",filename: str = None) -> Message: def generate_file_mime_part(locale: str="en_US",filename: str = None, content: str = None) -> Message:
""" """
:param locale: change this to generate locale specific file name and attachment content :param locale: change this to generate locale specific file name and attachment content
:param filename: pass a file name if you want to specify a specific name otherwise a random name will be generated :param filename: pass a file name if you want to specify a specific name otherwise a random name will be generated
:param content: pass a string value if you want have specific content otherwise a random string will be generated
""" """
part = MIMEBase('application', 'octet-stream') part = MIMEBase('application', 'octet-stream')
part.set_payload(get_fake("text", locale=locale, min_length=1024)) part.set_payload(get_fake("text", locale=locale, min_length=1024) if content is None else content)
encoders.encode_base64(part) encoders.encode_base64(part)
if not filename: if not filename:
filename = get_fake("word", locale=locale, min_length=8) + ".txt" filename = get_fake("word", locale=locale, min_length=8) + ".txt"
@ -227,7 +228,7 @@ def generate_mime_part(locale: str="en_US",
return msg return msg
def generate_multipart_email(locale: str="en_US", def generate_multipart_email(locale: str="en_US",
type_list: typing.List[str]=["plain", "html", "attachment"], type_list: typing.List[str]=["plain", "html", "image"],
use_short_email: bool=False use_short_email: bool=False
) -> typing.Tuple[Message, typing.Tuple[str, str], typing.Tuple[str, str]]: ) -> typing.Tuple[Message, typing.Tuple[str, str], typing.Tuple[str, str]]:
""" """

View File

@ -10,7 +10,7 @@ from ..lib import format_time_spent
from ..templated_email import send_templated_mail from ..templated_email import send_templated_mail
from collections import defaultdict from collections import defaultdict
from copy import deepcopy from copy import deepcopy
from datetime import date, datetime, timedelta from datetime import datetime, timedelta
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
@ -24,7 +24,7 @@ from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonRespons
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.dateparse import parse_datetime from django.utils.dateparse import parse_date, parse_datetime
from django.utils.html import escape from django.utils.html import escape
from django.utils.timezone import make_aware from django.utils.timezone import make_aware
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -521,7 +521,8 @@ def get_due_date_from_request_or_ticket(
due_date = request.POST.get('due_date', None) or None due_date = request.POST.get('due_date', None) or None
if due_date is not None: if due_date is not None:
due_date = make_aware(parse_datetime(due_date)) parsed_date = parse_datetime(due_date) or datetime.combine(parse_date(due_date), time())
due_date = make_aware(parsed_date)
else: else:
due_date_year = int(request.POST.get('due_date_year', 0)) due_date_year = int(request.POST.get('due_date_year', 0))
due_date_month = int(request.POST.get('due_date_month', 0)) due_date_month = int(request.POST.get('due_date_month', 0))