diff --git a/src/pretix/base/email.py b/src/pretix/base/email.py index 8dcda62cea..fc6f54ccfe 100644 --- a/src/pretix/base/email.py +++ b/src/pretix/base/email.py @@ -25,6 +25,7 @@ from datetime import timedelta from decimal import Decimal from itertools import groupby from smtplib import SMTPResponseException +from typing import TypeVar import css_inline from django.conf import settings @@ -49,23 +50,23 @@ from pretix.base.templatetags.rich_text import markdown_compile_email logger = logging.getLogger('pretix.base.email') +T = TypeVar("T", bound=EmailBackend) -class CustomSMTPBackend(EmailBackend): - def test(self, from_addr): - try: - self.open() - self.connection.ehlo_or_helo_if_needed() - (code, resp) = self.connection.mail(from_addr, []) - if code != 250: - logger.warn('Error testing mail settings, code %d, resp: %s' % (code, resp)) - raise SMTPResponseException(code, resp) - (code, resp) = self.connection.rcpt('testdummy@pretix.eu') - if (code != 250) and (code != 251): - logger.warn('Error testing mail settings, code %d, resp: %s' % (code, resp)) - raise SMTPResponseException(code, resp) - finally: - self.close() +def test_custom_smtp_backend(backend: T, from_addr: str) -> None: + try: + backend.open() + backend.connection.ehlo_or_helo_if_needed() + (code, resp) = backend.connection.mail(from_addr, []) + if code != 250: + logger.warning('Error testing mail settings, code %d, resp: %s' % (code, resp)) + raise SMTPResponseException(code, resp) + (code, resp) = backend.connection.rcpt('testdummy@pretix.eu') + if (code != 250) and (code != 251): + logger.warning('Error testing mail settings, code %d, resp: %s' % (code, resp)) + raise SMTPResponseException(code, resp) + finally: + backend.close() class BaseHTMLMailRenderer: diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 7c1a6b2a29..13d43a137c 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -670,16 +670,17 @@ class Event(EventMixin, LoggedModel): Returns an email server connection, either by using the system-wide connection or by returning a custom one based on the event's settings. """ - from pretix.base.email import CustomSMTPBackend if self.settings.smtp_use_custom or force_custom: - return CustomSMTPBackend(host=self.settings.smtp_host, - port=self.settings.smtp_port, - username=self.settings.smtp_username, - password=self.settings.smtp_password, - use_tls=self.settings.smtp_use_tls, - use_ssl=self.settings.smtp_use_ssl, - fail_silently=False, timeout=timeout) + return get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND, + host=self.settings.smtp_host, + port=self.settings.smtp_port, + username=self.settings.smtp_username, + password=self.settings.smtp_password, + use_tls=self.settings.smtp_use_tls, + use_ssl=self.settings.smtp_use_ssl, + fail_silently=False, + timeout=timeout) else: return get_connection(fail_silently=False) diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index f1f85a4b4b..7a842c7daa 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -36,6 +36,7 @@ import string from datetime import date, datetime, time import pytz +from django.conf import settings from django.core.mail import get_connection from django.core.validators import MinLengthValidator, RegexValidator from django.db import models @@ -195,16 +196,15 @@ class Organizer(LoggedModel): Returns an email server connection, either by using the system-wide connection or by returning a custom one based on the organizer's settings. """ - from pretix.base.email import CustomSMTPBackend - if self.settings.smtp_use_custom or force_custom: - return CustomSMTPBackend(host=self.settings.smtp_host, - port=self.settings.smtp_port, - username=self.settings.smtp_username, - password=self.settings.smtp_password, - use_tls=self.settings.smtp_use_tls, - use_ssl=self.settings.smtp_use_ssl, - fail_silently=False, timeout=timeout) + return get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND, + host=self.settings.smtp_host, + port=self.settings.smtp_port, + username=self.settings.smtp_username, + password=self.settings.smtp_password, + use_tls=self.settings.smtp_use_tls, + use_ssl=self.settings.smtp_use_ssl, + fail_silently=False, timeout=timeout) else: return get_connection(fail_silently=False) diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index e496aefc82..6eecc9d5bb 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -65,7 +65,9 @@ from i18nfield.utils import I18nJSONEncoder from pytz import timezone from pretix.base.channels import get_all_sales_channels -from pretix.base.email import get_available_placeholders +from pretix.base.email import ( + get_available_placeholders, test_custom_smtp_backend, +) from pretix.base.models import Event, LogEntry, Order, TaxRule, Voucher from pretix.base.models.event import EventMetaValue from pretix.base.services import tickets @@ -641,7 +643,7 @@ class MailSettings(EventSettingsViewMixin, EventSettingsFormView): if request.POST.get('test', '0').strip() == '1': backend = self.request.event.get_mail_backend(force_custom=True, timeout=10) try: - backend.test(self.request.event.settings.mail_from) + test_custom_smtp_backend(backend, self.request.event.settings.mail_from) except Exception as e: messages.warning(self.request, _('An error occurred while contacting the SMTP server: %s') % str(e)) else: diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py index dcc7850fea..0c7e4bc31d 100644 --- a/src/pretix/control/views/organizer.py +++ b/src/pretix/control/views/organizer.py @@ -64,6 +64,7 @@ from django.views.generic import ( from pretix.api.models import WebHook from pretix.base.auth import get_auth_backends from pretix.base.channels import get_all_sales_channels +from pretix.base.email import test_custom_smtp_backend from pretix.base.i18n import language from pretix.base.models import ( CachedFile, Customer, Device, Gate, GiftCard, Invoice, LogEntry, @@ -265,7 +266,7 @@ class OrganizerMailSettings(OrganizerSettingsFormView): if request.POST.get('test', '0').strip() == '1': backend = self.request.organizer.get_mail_backend(force_custom=True, timeout=10) try: - backend.test(self.request.organizer.settings.mail_from) + test_custom_smtp_backend(backend, self.request.organizer.settings.mail_from) except Exception as e: messages.warning(self.request, _('An error occurred while contacting the SMTP server: %s') % str(e)) else: diff --git a/src/pretix/settings.py b/src/pretix/settings.py index c848b1c3f0..c187f9f0c8 100644 --- a/src/pretix/settings.py +++ b/src/pretix/settings.py @@ -222,6 +222,7 @@ EMAIL_HOST_PASSWORD = config.get('mail', 'password', fallback='') EMAIL_USE_TLS = config.getboolean('mail', 'tls', fallback=False) EMAIL_USE_SSL = config.getboolean('mail', 'ssl', fallback=False) EMAIL_SUBJECT_PREFIX = '[pretix] ' +EMAIL_BACKEND = EMAIL_CUSTOM_SMTP_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' ADMINS = [('Admin', n) for n in config.get('mail', 'admins', fallback='').split(",") if n] diff --git a/src/pretix/testutils/settings.py b/src/pretix/testutils/settings.py index 7e3767f15a..5213739501 100644 --- a/src/pretix/testutils/settings.py +++ b/src/pretix/testutils/settings.py @@ -37,7 +37,7 @@ SITE_URL = "http://example.com" atexit.register(tmpdir.cleanup) -EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' +EMAIL_BACKEND = EMAIL_CUSTOM_SMTP_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' COMPRESS_ENABLED = COMPRESS_OFFLINE = False COMPRESS_CACHE_BACKEND = 'testcache' diff --git a/src/tests/base/test_mail.py b/src/tests/base/test_mail.py index cb8c325453..651bad7519 100644 --- a/src/tests/base/test_mail.py +++ b/src/tests/base/test_mail.py @@ -81,7 +81,34 @@ def test_send_mail_with_event_sender(env): assert len(djmail.outbox) == 1 assert djmail.outbox[0].to == [user.email] assert djmail.outbox[0].subject == 'Test subject' - assert djmail.outbox[0].from_email == 'Dummy ' + + +@pytest.mark.django_db +@pytest.mark.parametrize("smtp_use_custom", (True, False)) +def test_send_mail_custom_event_smtp(env, smtp_use_custom): + djmail.outbox = [] + event, user, organizer = env + event.settings.set("smtp_use_custom", smtp_use_custom) + + mail('dummy@dummy.dummy', 'Test subject', 'mailtest.txt', {}, event=event) + + assert len(djmail.outbox) == 1 + assert djmail.outbox[0].to == [user.email] + assert djmail.outbox[0].subject == 'Test subject' + + +@pytest.mark.django_db +@pytest.mark.parametrize("smtp_use_custom", (True, False)) +def test_send_mail_custom_organizer_smtp(env, smtp_use_custom): + djmail.outbox = [] + event, user, organizer = env + organizer.settings.set("smtp_use_custom", smtp_use_custom) + + mail('dummy@dummy.dummy', 'Test subject', 'mailtest.txt', {}, organizer=organizer) + + assert len(djmail.outbox) == 1 + assert djmail.outbox[0].to == [user.email] + assert djmail.outbox[0].subject == 'Test subject' @pytest.mark.django_db diff --git a/src/tests/control/test_events.py b/src/tests/control/test_events.py index 680802210d..ea755f4d7c 100644 --- a/src/tests/control/test_events.py +++ b/src/tests/control/test_events.py @@ -501,8 +501,7 @@ class EventsTest(SoupTest): def test_email_settings(self): with mocker_context() as mocker: - mocked = mocker.patch('pretix.base.email.CustomSMTPBackend.test') - + mocked = mocker.patch('pretix.control.views.event.test_custom_smtp_backend') doc = self.get_doc('/control/event/%s/%s/settings/email' % (self.orga1.slug, self.event1.slug)) data = extract_form_fields(doc.select("form")[0]) data['test'] = '1' diff --git a/src/tests/control/test_organizer.py b/src/tests/control/test_organizer.py index 29e9335501..ff8cda330e 100644 --- a/src/tests/control/test_organizer.py +++ b/src/tests/control/test_organizer.py @@ -27,6 +27,7 @@ from django_scopes import scopes_disabled from tests.base import SoupTest, extract_form_fields from pretix.base.models import Event, Organizer, Team, User +from pretix.testutils.mock import mocker_context @pytest.fixture @@ -97,3 +98,15 @@ class OrganizerTest(SoupTest): self.orga1.settings.flush() assert self.orga1.settings.primary_color == "#33c33c" assert called + + def test_email_settings(self): + with mocker_context() as mocker: + mocked = mocker.patch('pretix.control.views.organizer.test_custom_smtp_backend') + doc = self.get_doc('/control/organizer/%s/settings/email' % self.orga1.slug) + data = extract_form_fields(doc.select("form")[0]) + data['test'] = '1' + doc = self.post_doc('/control/organizer/%s/settings/email' % self.orga1.slug, + data, follow=True) + assert doc.select('.alert-success') + self.event1.settings.flush() + assert mocked.called