mirror of
https://github.com/pretix/pretix.git
synced 2026-04-28 00:02:37 +00:00
Sendmail: Abstract away to allow more types of recipients (#2994)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -520,20 +520,20 @@ def base_placeholders(sender, **kwargs):
|
||||
lambda event: (event if not event.has_subevents or not event.subevents.exists() else event.subevents.first()).get_date_from_display()
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url_remove', ['waiting_list_entry', 'event'],
|
||||
lambda waiting_list_entry, event: build_absolute_uri(
|
||||
'url_remove', ['waiting_list_voucher', 'event'],
|
||||
lambda waiting_list_voucher, event: build_absolute_uri(
|
||||
event, 'presale:event.waitinglist.remove'
|
||||
) + '?voucher=' + waiting_list_entry.voucher.code,
|
||||
) + '?voucher=' + waiting_list_voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.waitinglist.remove',
|
||||
) + '?voucher=68CYU2H6ZTP3WLK5',
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url', ['waiting_list_entry', 'event'],
|
||||
lambda waiting_list_entry, event: build_absolute_uri(
|
||||
'url', ['waiting_list_voucher', 'event'],
|
||||
lambda waiting_list_voucher, event: build_absolute_uri(
|
||||
event, 'presale:event.redeem'
|
||||
) + '?voucher=' + waiting_list_entry.voucher.code,
|
||||
) + '?voucher=' + waiting_list_voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.redeem',
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from datetime import timedelta
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.db import models, transaction
|
||||
@@ -27,14 +28,16 @@ from django.db.models import F, Q, Sum
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django_scopes import ScopedManager
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Voucher
|
||||
from pretix.base.services.mail import mail
|
||||
from pretix.base.models import User, Voucher
|
||||
from pretix.base.services.mail import SendMailException, mail, render_mail
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
|
||||
from ...helpers.format import format_map
|
||||
from .base import LoggedModel
|
||||
from .event import Event, SubEvent
|
||||
from .items import Item, ItemVariation
|
||||
@@ -213,15 +216,74 @@ class WaitingListEntry(LoggedModel):
|
||||
self.voucher = v
|
||||
self.save()
|
||||
|
||||
self.send_mail(
|
||||
self.event.settings.mail_subject_waiting_list,
|
||||
self.event.settings.mail_text_waiting_list,
|
||||
get_email_context(
|
||||
event=self.event,
|
||||
waiting_list_entry=self,
|
||||
waiting_list_voucher=v,
|
||||
event_or_subevent=self.subevent or self.event,
|
||||
),
|
||||
user=user,
|
||||
auth=auth,
|
||||
)
|
||||
|
||||
def send_mail(self, subject: Union[str, LazyI18nString], template: Union[str, LazyI18nString],
|
||||
context: Dict[str, Any]=None, log_entry_type: str='pretix.waitinglist.email.sent',
|
||||
user: User=None, headers: dict=None, sender: str=None, auth=None, auto_email=True,
|
||||
attach_other_files: list=None, attach_cached_files: list=None):
|
||||
"""
|
||||
Sends an email to the entry's contact address.
|
||||
|
||||
* Call ``pretix.base.services.mail.mail`` with useful values for the ``event``, ``locale``, and ``recipient``
|
||||
parameters.
|
||||
|
||||
* Create a ``LogEntry`` with the email contents.
|
||||
|
||||
:param subject: Subject of the email
|
||||
:param template: LazyI18nString or template filename, see ``pretix.base.services.mail.mail`` for more details
|
||||
:param context: Dictionary to use for rendering the template
|
||||
:param log_entry_type: Key to be used for the log entry
|
||||
:param user: Administrative user who triggered this mail to be sent
|
||||
:param headers: Dictionary with additional mail headers
|
||||
:param sender: Custom email sender.
|
||||
"""
|
||||
if not self.email:
|
||||
return
|
||||
|
||||
for k, v in self.event.meta_data.items():
|
||||
context['meta_' + k] = v
|
||||
|
||||
with language(self.locale, self.event.settings.region):
|
||||
mail(
|
||||
self.email,
|
||||
self.event.settings.mail_subject_waiting_list,
|
||||
self.event.settings.mail_text_waiting_list,
|
||||
get_email_context(event=self.event, waiting_list_entry=self),
|
||||
self.event,
|
||||
locale=self.locale
|
||||
)
|
||||
recipient = self.email
|
||||
|
||||
try:
|
||||
email_content = render_mail(template, context)
|
||||
subject = format_map(subject, context)
|
||||
mail(
|
||||
recipient, subject, template, context,
|
||||
self.event,
|
||||
self.locale,
|
||||
headers=headers,
|
||||
sender=sender,
|
||||
auto_email=auto_email,
|
||||
attach_other_files=attach_other_files,
|
||||
attach_cached_files=attach_cached_files,
|
||||
)
|
||||
except SendMailException:
|
||||
raise
|
||||
else:
|
||||
self.log_action(
|
||||
log_entry_type,
|
||||
user=user,
|
||||
auth=auth,
|
||||
data={
|
||||
'subject': subject,
|
||||
'message': email_content,
|
||||
'recipient': recipient,
|
||||
}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def clean_itemvar(event, item, variation):
|
||||
|
||||
@@ -1209,8 +1209,8 @@ class MailSettingsForm(SettingsForm):
|
||||
'mail_text_resend_link': ['event', 'order'],
|
||||
'mail_subject_resend_link': ['event', 'order'],
|
||||
'mail_subject_resend_link_attendee': ['event', 'order'],
|
||||
'mail_text_waiting_list': ['event', 'waiting_list_entry'],
|
||||
'mail_subject_waiting_list': ['event', 'waiting_list_entry'],
|
||||
'mail_text_waiting_list': ['event', 'waiting_list_entry', 'waiting_list_voucher'],
|
||||
'mail_subject_waiting_list': ['event', 'waiting_list_entry', 'waiting_list_voucher'],
|
||||
'mail_text_resend_all_links': ['event', 'orders'],
|
||||
'mail_subject_resend_all_links': ['event', 'orders'],
|
||||
'mail_attach_ical_description': ['event', 'event_or_subevent'],
|
||||
|
||||
@@ -70,14 +70,7 @@ class FormPlaceholderMixin:
|
||||
)
|
||||
|
||||
|
||||
class MailForm(FormPlaceholderMixin, forms.Form):
|
||||
recipients = forms.ChoiceField(
|
||||
label=_('Send email to'),
|
||||
widget=forms.RadioSelect,
|
||||
initial='orders',
|
||||
choices=[]
|
||||
)
|
||||
sendto = forms.MultipleChoiceField() # overridden later
|
||||
class BaseMailForm(FormPlaceholderMixin, forms.Form):
|
||||
subject = forms.CharField(label=_("Subject"))
|
||||
message = forms.CharField(label=_("Message"))
|
||||
attachment = CachedFileField(
|
||||
@@ -91,12 +84,100 @@ class MailForm(FormPlaceholderMixin, forms.Form):
|
||||
help_text=_('Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs '
|
||||
'of no more than 2 MB in size.'),
|
||||
max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT
|
||||
) # TODO i18n
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
event = self.event = kwargs.pop('event')
|
||||
context_parameters = kwargs.pop('context_parameters')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['subject'] = I18nFormField(
|
||||
label=_('Subject'),
|
||||
widget=I18nTextInput, required=True,
|
||||
locales=event.settings.get('locales'),
|
||||
)
|
||||
self.fields['message'] = I18nFormField(
|
||||
label=_('Message'),
|
||||
widget=I18nTextarea, required=True,
|
||||
locales=event.settings.get('locales'),
|
||||
)
|
||||
self._set_field_placeholders('subject', context_parameters)
|
||||
self._set_field_placeholders('message', context_parameters)
|
||||
|
||||
|
||||
class WaitinglistMailForm(BaseMailForm):
|
||||
items = forms.ModelMultipleChoiceField(
|
||||
widget=forms.CheckboxSelectMultiple(
|
||||
attrs={'class': 'scrolling-multiple-choice'}
|
||||
),
|
||||
label=_('Only send to people who bought'),
|
||||
label=pgettext_lazy('sendmail_form', 'Waiting for'),
|
||||
required=True,
|
||||
queryset=Item.objects.none()
|
||||
)
|
||||
subevent = forms.ModelChoiceField(
|
||||
SubEvent.objects.none(),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to a specific event date'),
|
||||
required=False,
|
||||
empty_label=pgettext_lazy('subevent', 'All dates')
|
||||
)
|
||||
subevents_from = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to event dates starting at or after'),
|
||||
required=False,
|
||||
)
|
||||
subevents_to = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to event dates starting before'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
if d.get('subevent') and (d.get('subevents_from') or d.get('subevents_to')):
|
||||
raise ValidationError(pgettext_lazy('subevent', 'Please either select a specific date or a date range, not both.'))
|
||||
if bool(d.get('subevents_from')) != bool(d.get('subevents_to')):
|
||||
raise ValidationError(pgettext_lazy('subevent', 'If you set a date range, please set both a start and an end.'))
|
||||
return d
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
event = self.event = kwargs['event']
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['items'].queryset = event.items.all()
|
||||
if not self.initial.get('items'):
|
||||
self.initial['items'] = event.items.all()
|
||||
|
||||
if event.has_subevents:
|
||||
self.fields['subevent'].queryset = event.subevents.all()
|
||||
self.fields['subevent'].widget = Select2(
|
||||
attrs={
|
||||
'data-model-select2': 'event',
|
||||
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': pgettext_lazy('subevent', 'Date')
|
||||
}
|
||||
)
|
||||
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
|
||||
else:
|
||||
del self.fields['subevent']
|
||||
del self.fields['subevents_from']
|
||||
del self.fields['subevents_to']
|
||||
|
||||
|
||||
class OrderMailForm(BaseMailForm):
|
||||
recipients = forms.ChoiceField(
|
||||
label=pgettext_lazy('sendmail_from', 'Send to'),
|
||||
widget=forms.RadioSelect,
|
||||
initial='orders',
|
||||
choices=[]
|
||||
)
|
||||
sendto = forms.MultipleChoiceField() # overridden later
|
||||
items = forms.ModelMultipleChoiceField(
|
||||
widget=forms.CheckboxSelectMultiple(
|
||||
attrs={'class': 'scrolling-multiple-choice'}
|
||||
),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to products'),
|
||||
required=True,
|
||||
queryset=Item.objects.none()
|
||||
)
|
||||
@@ -105,31 +186,31 @@ class MailForm(FormPlaceholderMixin, forms.Form):
|
||||
required=False
|
||||
)
|
||||
checkin_lists = SafeModelMultipleChoiceField(queryset=CheckinList.objects.none(), required=False) # overridden later
|
||||
not_checked_in = forms.BooleanField(label=_("Send to customers not checked in"), required=False)
|
||||
not_checked_in = forms.BooleanField(label=pgettext_lazy('sendmail_from', 'Restrict to recipients without check-in'), required=False)
|
||||
subevent = forms.ModelChoiceField(
|
||||
SubEvent.objects.none(),
|
||||
label=_('Only send to customers of'),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to a specific event date'),
|
||||
required=False,
|
||||
empty_label=pgettext_lazy('subevent', 'All dates')
|
||||
)
|
||||
subevents_from = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(),
|
||||
label=pgettext_lazy('subevent', 'Only send to customers of dates starting at or after'),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to event dates starting at or after'),
|
||||
required=False,
|
||||
)
|
||||
subevents_to = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(),
|
||||
label=pgettext_lazy('subevent', 'Only send to customers of dates starting before'),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to event dates starting before'),
|
||||
required=False,
|
||||
)
|
||||
created_from = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(),
|
||||
label=pgettext_lazy('subevent', 'Only send to customers with orders created after'),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to orders created at or after'),
|
||||
required=False,
|
||||
)
|
||||
created_to = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(),
|
||||
label=pgettext_lazy('subevent', 'Only send to customers with orders created before'),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to orders created before'),
|
||||
required=False,
|
||||
)
|
||||
attach_tickets = forms.BooleanField(
|
||||
@@ -147,7 +228,7 @@ class MailForm(FormPlaceholderMixin, forms.Form):
|
||||
return d
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
event = self.event = kwargs.pop('event')
|
||||
event = self.event = kwargs['event']
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
recp_choices = [
|
||||
@@ -161,18 +242,6 @@ class MailForm(FormPlaceholderMixin, forms.Form):
|
||||
]
|
||||
self.fields['recipients'].choices = recp_choices
|
||||
|
||||
self.fields['subject'] = I18nFormField(
|
||||
label=_('Subject'),
|
||||
widget=I18nTextInput, required=True,
|
||||
locales=event.settings.get('locales'),
|
||||
)
|
||||
self.fields['message'] = I18nFormField(
|
||||
label=_('Message'),
|
||||
widget=I18nTextarea, required=True,
|
||||
locales=event.settings.get('locales'),
|
||||
)
|
||||
self._set_field_placeholders('subject', ['event', 'order', 'position_or_address'])
|
||||
self._set_field_placeholders('message', ['event', 'order', 'position_or_address'])
|
||||
choices = [(e, l) for e, l in Order.STATUS_CHOICE if e != 'n']
|
||||
choices.insert(0, ('na', _('payment pending (except unapproved)')))
|
||||
choices.insert(0, ('pa', _('approval pending')))
|
||||
@@ -181,7 +250,7 @@ class MailForm(FormPlaceholderMixin, forms.Form):
|
||||
('overdue', _('pending with payment overdue'))
|
||||
)
|
||||
self.fields['sendto'] = forms.MultipleChoiceField(
|
||||
label=_("Send to customers with order status"),
|
||||
label=pgettext_lazy('sendmail_from', 'Restrict to orders with status'),
|
||||
widget=forms.CheckboxSelectMultiple(
|
||||
attrs={'class': 'scrolling-multiple-choice no-search'}
|
||||
),
|
||||
@@ -205,11 +274,11 @@ class MailForm(FormPlaceholderMixin, forms.Form):
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': _('Send to customers checked in on list'),
|
||||
'data-placeholder': pgettext_lazy('sendmail_from', 'Restrict to recipients with check-in on list')
|
||||
}
|
||||
)
|
||||
self.fields['checkin_lists'].widget.choices = self.fields['checkin_lists'].choices
|
||||
self.fields['checkin_lists'].label = _('Send to customers checked in on list')
|
||||
self.fields['checkin_lists'].label = pgettext_lazy('sendmail_from', 'Restrict to recipients with check-in on list')
|
||||
|
||||
if event.has_subevents:
|
||||
self.fields['subevent'].queryset = event.subevents.all()
|
||||
|
||||
@@ -48,10 +48,11 @@ from django_scopes import scope, scopes_disabled
|
||||
|
||||
from pretix.base.models import SubEvent
|
||||
from pretix.base.signals import (
|
||||
event_copy_data, logentry_display, periodic_task,
|
||||
EventPluginSignal, event_copy_data, logentry_display, periodic_task,
|
||||
)
|
||||
from pretix.control.signals import nav_event
|
||||
from pretix.plugins.sendmail.models import ScheduledMail
|
||||
from pretix.plugins.sendmail.views import OrderSendView, WaitinglistSendView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -91,7 +92,7 @@ def control_nav_import(sender, request=None, **kwargs):
|
||||
'event': request.event.slug,
|
||||
'organizer': request.event.organizer.slug,
|
||||
}),
|
||||
'active': (url.namespace == 'plugins:sendmail' and url.url_name == 'send'),
|
||||
'active': (url.namespace == 'plugins:sendmail' and url.url_name.startswith('send')),
|
||||
},
|
||||
{
|
||||
'label': _('Automated emails'),
|
||||
@@ -117,7 +118,8 @@ def control_nav_import(sender, request=None, **kwargs):
|
||||
@receiver(signal=logentry_display)
|
||||
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
plains = {
|
||||
'pretix.plugins.sendmail.sent': _('Email was sent'),
|
||||
'pretix.plugins.sendmail.sent': _('Mass email was sent to customers or attendees.'),
|
||||
'pretix.plugins.sendmail.sent.waitinglist': _('Mass email was sent to waiting list entries.'),
|
||||
'pretix.plugins.sendmail.order.email.sent': _('The order received a mass email.'),
|
||||
'pretix.plugins.sendmail.order.email.sent.attendee': _('A ticket holder of this order received a mass email.'),
|
||||
'pretix.plugins.sendmail.rule.added': _('An email rule was created'),
|
||||
@@ -219,3 +221,17 @@ def sendmail_copy_data_receiver(sender, other, item_map, **kwargs):
|
||||
r.save()
|
||||
if limit_products:
|
||||
r.limit_products.add(*[item_map[p.id] for p in limit_products if p.id in item_map])
|
||||
|
||||
|
||||
sendmail_view_classes = EventPluginSignal()
|
||||
"""
|
||||
This signal allows you to register subclasses of ``pretix.plugins.sendmail.views.BaseSenderView`` that should be
|
||||
discovered by this plugin.
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword will contain the event.
|
||||
"""
|
||||
|
||||
|
||||
@receiver(signal=sendmail_view_classes, dispatch_uid="sendmail_register_sendmail_view_classes")
|
||||
def register_view_classes(sender, **kwargs):
|
||||
return [OrderSendView, WaitinglistSendView]
|
||||
|
||||
@@ -44,12 +44,12 @@ from pretix.helpers.format import format_map
|
||||
|
||||
|
||||
@app.task(base=ProfiledEventTask, acks_late=True)
|
||||
def send_mails(event: Event, user: int, subject: dict, message: dict, orders: list, items: list,
|
||||
recipients: str, filter_checkins: bool, not_checked_in: bool, checkin_lists: list,
|
||||
attachments: list = None, attach_tickets: bool = False) -> None:
|
||||
def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict, objects: list, items: list,
|
||||
recipients: str, filter_checkins: bool, not_checked_in: bool, checkin_lists: list,
|
||||
attachments: list = None, attach_tickets: bool = False) -> None:
|
||||
failures = []
|
||||
user = User.objects.get(pk=user) if user else None
|
||||
orders = Order.objects.filter(pk__in=orders, event=event)
|
||||
orders = Order.objects.filter(pk__in=objects, event=event)
|
||||
subject = LazyI18nString(subject)
|
||||
message = LazyI18nString(message)
|
||||
|
||||
@@ -138,7 +138,7 @@ def send_mails(event: Event, user: int, subject: dict, message: dict, orders: li
|
||||
locale=o.locale,
|
||||
order=o,
|
||||
attach_tickets=attach_tickets,
|
||||
attach_cached_files=attachments
|
||||
attach_cached_files=attachments,
|
||||
)
|
||||
o.log_action(
|
||||
'pretix.plugins.sendmail.order.email.sent',
|
||||
@@ -151,3 +151,27 @@ def send_mails(event: Event, user: int, subject: dict, message: dict, orders: li
|
||||
)
|
||||
except SendMailException:
|
||||
failures.append(o.email)
|
||||
|
||||
|
||||
@app.task(base=ProfiledEventTask, acks_late=True)
|
||||
def send_mails_to_waitinglist(event: Event, user: int, subject: dict, message: dict, objects: list,
|
||||
attachments: list = None) -> None:
|
||||
user = User.objects.get(pk=user) if user else None
|
||||
entries = event.waitinglistentries.filter(pk__in=objects).select_related(
|
||||
'subevent'
|
||||
)
|
||||
subject = LazyI18nString(subject)
|
||||
message = LazyI18nString(message)
|
||||
|
||||
for e in entries:
|
||||
e.send_mail(
|
||||
subject,
|
||||
message,
|
||||
get_email_context(
|
||||
event=e.event,
|
||||
waiting_list_entry=e,
|
||||
event_or_subevent=e.subevent or e.event,
|
||||
),
|
||||
user=user,
|
||||
attach_cached_files=attachments,
|
||||
)
|
||||
|
||||
@@ -22,33 +22,7 @@
|
||||
{% if log.display %}
|
||||
<br/><span class="fa fa-comment-o fa-fw"></span> {{ log.display }}
|
||||
{% endif %}
|
||||
<br/><span class="fa fa-tag fa-fw"></span> {% trans "Sent to orders:" %}
|
||||
{% for status in log.parsed_data.sendto %}
|
||||
{{ status }}{% if forloop.revcounter > 1 %},{% endif %}
|
||||
{% endfor %}
|
||||
{% if log.pdata.items %}
|
||||
<br/><span class="fa fa-shopping-cart fa-fw"></span> {{ log.pdata.items|join:", " }}
|
||||
{% endif %}
|
||||
{% if log.pdata.filter_checkins %}
|
||||
{% if log.pdata.not_checked_in %}
|
||||
<br/><span class="fa fa-check-square-o fa-fw"></span> {% trans "All customers not checked in" %}
|
||||
{% endif %}
|
||||
{% if log.pdata.checkin_lists %}
|
||||
<br/><span class="fa fa-check-square-o fa-fw"></span> {{ log.pdata.checkin_lists|join:", " }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if log.pdata.subevent_obj %}
|
||||
<br/><span class="fa fa-calendar fa-fw"></span> {{ log.pdata.subevent_obj }}
|
||||
{% elif log.pdata.subevents_from %}
|
||||
<br/><span class="fa fa-calendar fa-fw"></span> {{ log.pdata.subevents_from }} – {{ log.pdata.subevents_to }}
|
||||
{% endif %}
|
||||
{% if log.pdata.recipients == "attendees" %}
|
||||
<br/><span class="fa fa-envelope fa-fw"></span> {% trans "Attendee contact addresses" %}
|
||||
{% elif log.pdata.recipients == "both" %}
|
||||
<br/><span class="fa fa-envelope fa-fw"></span> {% trans "All contact addresses" %}
|
||||
{% else%}
|
||||
<br/><span class="fa fa-envelope fa-fw"></span> {% trans "Order contact addresses" %}
|
||||
{% endif %}
|
||||
{{ log.view.rendered_data }}
|
||||
</p>
|
||||
<p>
|
||||
{% for locale, value in log.pdata.locales.items %}
|
||||
@@ -58,7 +32,7 @@
|
||||
<pre>{{ value.message|linebreaksbr }}</pre>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<a href="{% url 'plugins:sendmail:send' organizer=request.event.organizer.slug event=request.event.slug %}?from_log={{ log.id }}">{% trans "Send a new email based on this" %}</a>
|
||||
<a href="{{ log.view.url }}?from_log={{ log.id }}">{% trans "Send a new email based on this" %}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{% load i18n %}
|
||||
<br/><span class="fa fa-tag fa-fw"></span> {% trans "Sent to orders:" %}
|
||||
{% for status in log.parsed_data.sendto %}
|
||||
{{ status }}{% if forloop.revcounter > 1 %},{% endif %}
|
||||
{% endfor %}
|
||||
{% if log.pdata.items %}
|
||||
<br/><span class="fa fa-shopping-cart fa-fw"></span> {{ log.pdata.items|join:", " }}
|
||||
{% endif %}
|
||||
{% if log.pdata.filter_checkins %}
|
||||
{% if log.pdata.not_checked_in %}
|
||||
<br/><span class="fa fa-check-square-o fa-fw"></span> {% trans "All customers not checked in" %}
|
||||
{% endif %}
|
||||
{% if log.pdata.checkin_lists %}
|
||||
<br/><span class="fa fa-check-square-o fa-fw"></span> {{ log.pdata.checkin_lists|join:", " }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if log.pdata.subevent_obj %}
|
||||
<br/><span class="fa fa-calendar fa-fw"></span> {{ log.pdata.subevent_obj }}
|
||||
{% elif log.pdata.subevents_from %}
|
||||
<br/><span class="fa fa-calendar fa-fw"></span> {{ log.pdata.subevents_from }} – {{ log.pdata.subevents_to }}
|
||||
{% endif %}
|
||||
{% if log.pdata.recipients == "attendees" %}
|
||||
<br/><span class="fa fa-envelope fa-fw"></span> {% trans "Attendee contact addresses" %}
|
||||
{% elif log.pdata.recipients == "both" %}
|
||||
<br/><span class="fa fa-envelope fa-fw"></span> {% trans "All contact addresses" %}
|
||||
{% else%}
|
||||
<br/><span class="fa fa-envelope fa-fw"></span> {% trans "Order contact addresses" %}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% load i18n %}
|
||||
{% if log.pdata.items %}
|
||||
<br/><span class="fa fa-shopping-cart fa-fw"></span> {{ log.pdata.items|join:", " }}
|
||||
{% endif %}
|
||||
{% if log.pdata.subevent_obj %}
|
||||
<br/><span class="fa fa-calendar fa-fw"></span> {{ log.pdata.subevent_obj }}
|
||||
{% elif log.pdata.subevents_from %}
|
||||
<br/><span class="fa fa-calendar fa-fw"></span> {{ log.pdata.subevents_from }} – {{ log.pdata.subevents_to }}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,16 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Send out emails" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Send out emails" %}</h1>
|
||||
<div class="list-group large-link-group">
|
||||
{% for view in views %}
|
||||
<a class="list-group-item" href="{{ view.url }}">
|
||||
<h4>{{ view.title }}</h4>
|
||||
<p>
|
||||
{{ view.description }}
|
||||
</p>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -4,7 +4,10 @@
|
||||
{% load humanize %}
|
||||
{% block title %}{% trans "Send out emails" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Send out emails" %}</h1>
|
||||
<h1>
|
||||
{% trans "Send out emails" %}
|
||||
<small>{{ view_title }}</small>
|
||||
</h1>
|
||||
{% block inner %}
|
||||
<form class="form-horizontal" method="post" action="" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
@@ -20,40 +23,16 @@
|
||||
{% bootstrap_form_errors form %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Recipients" %}</legend>
|
||||
{% bootstrap_field form.recipients layout='horizontal' %}
|
||||
{% bootstrap_field form.sendto layout='horizontal' %}
|
||||
{% if form.subevent %}
|
||||
{% bootstrap_field form.subevent layout='horizontal' %}
|
||||
{% bootstrap_field form.subevents_from layout='horizontal' %}
|
||||
{% bootstrap_field form.subevents_to layout='horizontal' %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.created_from layout='horizontal' %}
|
||||
{% bootstrap_field form.created_to layout='horizontal' %}
|
||||
{% bootstrap_field form.items layout='horizontal' %}
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-md-offset-3">
|
||||
<div class="panel-group">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<label data-toggle="collapse" data-target="#checkin_filter">
|
||||
{{ form.filter_checkins }} {{ form.filter_checkins.label }}
|
||||
</label>
|
||||
</div>
|
||||
<div id="checkin_filter" class="panel-body panel-collapse collapse {% if form.filter_checkins.value %} in {% else %} out {% endif %}">
|
||||
{% bootstrap_field form.not_checked_in layout='horizontal' %}
|
||||
{% bootstrap_field form.checkin_lists layout='horizontal' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include form_fragment_name with form=form %}
|
||||
</fieldset>
|
||||
<fieldset {% if is_preview %}class="hidden"{% endif %}>
|
||||
<legend>{% trans "Content" %}</legend>
|
||||
{% bootstrap_field form.subject layout='horizontal' %}
|
||||
{% bootstrap_field form.message layout='horizontal' %}
|
||||
{% bootstrap_field form.attachment layout='horizontal' %}
|
||||
{% bootstrap_field form.attach_tickets layout='horizontal' %}
|
||||
{% if form.attach_tickets %}
|
||||
{% bootstrap_field form.attach_tickets layout='horizontal' %}
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
{% if is_preview %}
|
||||
<fieldset>
|
||||
@@ -97,12 +76,7 @@
|
||||
</button>
|
||||
<button type="submit" class="btn btn-danger btn-save" name="action" value="send">
|
||||
<span class="fa fa-send" aria-hidden="true"></span>
|
||||
{% trans "Send" %}
|
||||
({% blocktrans trimmed with number=order_count|default_if_none:0|intcomma count c=order_count|default_if_none:0 %}
|
||||
{{ number }} matching order
|
||||
{% plural %}
|
||||
{{ number }} matching orders
|
||||
{% endblocktrans %})
|
||||
{% trans "Send" %} ({{ match_size }})
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% bootstrap_field form.recipients layout='horizontal' %}
|
||||
{% bootstrap_field form.sendto layout='horizontal' %}
|
||||
{% if form.subevent %}
|
||||
{% bootstrap_field form.subevent layout='horizontal' %}
|
||||
{% bootstrap_field form.subevents_from layout='horizontal' %}
|
||||
{% bootstrap_field form.subevents_to layout='horizontal' %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.created_from layout='horizontal' %}
|
||||
{% bootstrap_field form.created_to layout='horizontal' %}
|
||||
{% bootstrap_field form.items layout='horizontal' %}
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-md-offset-3">
|
||||
<div class="panel-group">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<label data-toggle="collapse" data-target="#checkin_filter">
|
||||
{{ form.filter_checkins }} {{ form.filter_checkins.label }}
|
||||
</label>
|
||||
</div>
|
||||
<div id="checkin_filter" class="panel-body panel-collapse collapse {% if form.filter_checkins.value %} in {% else %} out {% endif %}">
|
||||
{% bootstrap_field form.not_checked_in layout='horizontal' %}
|
||||
{% bootstrap_field form.checkin_lists layout='horizontal' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% bootstrap_field form.items layout='horizontal' %}
|
||||
{% if form.subevent %}
|
||||
{% bootstrap_field form.subevent layout='horizontal' %}
|
||||
{% bootstrap_field form.subevents_from layout='horizontal' %}
|
||||
{% bootstrap_field form.subevents_to layout='horizontal' %}
|
||||
{% endif %}
|
||||
@@ -40,8 +40,12 @@ from . import views
|
||||
from .api import RuleViewSet
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/$', views.SenderView.as_view(),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/$', views.IndexView.as_view(),
|
||||
name='send'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/orders/$', views.OrderSendView.as_view(),
|
||||
name='send.orders'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/waitinglist/$', views.WaitinglistSendView.as_view(),
|
||||
name='send.waitinglist'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/history/', views.EmailHistoryView.as_view(),
|
||||
name='history'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/rules/create', views.CreateRule.as_view(),
|
||||
|
||||
@@ -38,15 +38,17 @@ import logging
|
||||
import bleach
|
||||
import dateutil
|
||||
from django.contrib import messages
|
||||
from django.contrib.humanize.templatetags.humanize import intcomma
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Exists, Max, Min, OuterRef, Q
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import DeleteView, FormView, ListView
|
||||
from django.utils.translation import gettext_lazy as _, ngettext
|
||||
from django.views.generic import DeleteView, FormView, ListView, TemplateView
|
||||
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.i18n import LazyI18nString, language
|
||||
@@ -55,7 +57,9 @@ from pretix.base.models.event import SubEvent
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import CreateView, PaginationMixin, UpdateView
|
||||
from pretix.plugins.sendmail.tasks import send_mails
|
||||
from pretix.plugins.sendmail.tasks import (
|
||||
send_mails_to_orders, send_mails_to_waitinglist,
|
||||
)
|
||||
|
||||
from ...helpers.format import format_map
|
||||
from . import forms
|
||||
@@ -64,59 +68,104 @@ from .models import Rule, ScheduledMail
|
||||
logger = logging.getLogger('pretix.plugins.sendmail')
|
||||
|
||||
|
||||
class SenderView(EventPermissionRequiredMixin, FormView):
|
||||
class IndexView(EventPermissionRequiredMixin, TemplateView):
|
||||
template_name = 'pretixplugins/sendmail/index.html'
|
||||
permission = 'can_change_orders'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
from .signals import sendmail_view_classes
|
||||
classes = []
|
||||
for recv, resp in sendmail_view_classes.send(self.request.event):
|
||||
if isinstance(resp, (list, tuple)):
|
||||
classes += resp
|
||||
else:
|
||||
classes.append(resp)
|
||||
return super().get_context_data(**kwargs, views=[
|
||||
{
|
||||
'title': cls.TITLE,
|
||||
'description': cls.DESCRIPTION,
|
||||
'url': cls.get_url(self.request.event)
|
||||
} for cls in classes
|
||||
])
|
||||
|
||||
|
||||
class BaseSenderView(EventPermissionRequiredMixin, FormView):
|
||||
# These parameters usually SHOULD NOT be overridden
|
||||
template_name = 'pretixplugins/sendmail/send_form.html'
|
||||
permission = 'can_change_orders'
|
||||
form_class = forms.MailForm
|
||||
|
||||
# These parameters MUST be overridden by subclasses
|
||||
form_fragment_name = None
|
||||
context_parameters = ['event']
|
||||
task = None
|
||||
|
||||
# These parameters MUST be overriden by subclasses in a way that allows static access
|
||||
|
||||
ACTION_TYPE = None
|
||||
TITLE = ""
|
||||
DESCRIPTION = ""
|
||||
|
||||
# The following methods MUST be overridden by subclasses
|
||||
|
||||
@staticmethod
|
||||
def get_url(self, event):
|
||||
"""Returns the URL for this view for a given event."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_object_queryset(self, form):
|
||||
"""Returns a queryset of objects that will become recipients."""
|
||||
return Order.objects.none()
|
||||
|
||||
def describe_match_size(self, cnt):
|
||||
"""Returns a short human-readable description of the recipient set, such as '3 attendees'."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def show_history_meta_data(cls, logentry, _cache_store):
|
||||
"""Returns an HTML component for the history view."""
|
||||
raise NotImplementedError
|
||||
|
||||
# The following methods MAY be overridden by subclasses
|
||||
|
||||
def initial_from_logentry(self, logentry):
|
||||
return {
|
||||
'message': LazyI18nString(logentry.parsed_data['message']),
|
||||
'subject': LazyI18nString(logentry.parsed_data['subject']),
|
||||
}
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.get_full_path()
|
||||
|
||||
def get_task_kwargs(self, form, objects):
|
||||
kwargs = {
|
||||
'event': self.request.event.pk,
|
||||
'user': self.request.user.pk,
|
||||
'subject': form.cleaned_data['subject'].data,
|
||||
'message': form.cleaned_data['message'].data,
|
||||
'objects': [o.pk for o in objects],
|
||||
}
|
||||
attachment = form.cleaned_data.get('attachment')
|
||||
if attachment is not None and attachment is not False:
|
||||
kwargs['attachments'] = [form.cleaned_data['attachment'].id]
|
||||
return kwargs
|
||||
|
||||
# The following methods SHOULD NOT Be overridden by subclasses, but in some cases it may be necessary
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['event'] = self.request.event
|
||||
kwargs['context_parameters'] = self.context_parameters
|
||||
if 'from_log' in self.request.GET:
|
||||
try:
|
||||
from_log_id = self.request.GET.get('from_log')
|
||||
logentry = LogEntry.objects.get(
|
||||
id=from_log_id,
|
||||
event=self.request.event,
|
||||
action_type='pretix.plugins.sendmail.sent'
|
||||
action_type=self.ACTION_TYPE
|
||||
)
|
||||
kwargs['initial'] = {
|
||||
'recipients': logentry.parsed_data.get('recipients', 'orders'),
|
||||
'message': LazyI18nString(logentry.parsed_data['message']),
|
||||
'subject': LazyI18nString(logentry.parsed_data['subject']),
|
||||
'sendto': logentry.parsed_data['sendto'],
|
||||
**self.initial_from_logentry(logentry),
|
||||
}
|
||||
if 'items' in logentry.parsed_data:
|
||||
kwargs['initial']['items'] = self.request.event.items.filter(
|
||||
id__in=[a['id'] for a in logentry.parsed_data['items']]
|
||||
)
|
||||
elif logentry.parsed_data.get('item'):
|
||||
kwargs['initial']['items'] = self.request.event.items.filter(
|
||||
id=logentry.parsed_data['item']['id']
|
||||
)
|
||||
if 'checkin_lists' in logentry.parsed_data:
|
||||
kwargs['initial']['checkin_lists'] = self.request.event.checkin_lists.filter(
|
||||
id__in=[c['id'] for c in logentry.parsed_data['checkin_lists']]
|
||||
)
|
||||
kwargs['initial']['filter_checkins'] = logentry.parsed_data.get('filter_checkins', False)
|
||||
kwargs['initial']['not_checked_in'] = logentry.parsed_data.get('not_checked_in', False)
|
||||
if logentry.parsed_data.get('subevents_from'):
|
||||
kwargs['initial']['subevents_from'] = dateutil.parser.parse(logentry.parsed_data['subevents_from'])
|
||||
if logentry.parsed_data.get('subevents_to'):
|
||||
kwargs['initial']['subevents_to'] = dateutil.parser.parse(logentry.parsed_data['subevents_to'])
|
||||
if logentry.parsed_data.get('created_from'):
|
||||
kwargs['initial']['created_from'] = dateutil.parser.parse(logentry.parsed_data['created_from'])
|
||||
if logentry.parsed_data.get('created_to'):
|
||||
kwargs['initial']['created_to'] = dateutil.parser.parse(logentry.parsed_data['created_to'])
|
||||
if logentry.parsed_data.get('attach_tickets'):
|
||||
kwargs['initial']['attach_tickets'] = logentry.parsed_data['attach_tickets']
|
||||
if logentry.parsed_data.get('subevent'):
|
||||
try:
|
||||
kwargs['initial']['subevent'] = self.request.event.subevents.get(
|
||||
pk=logentry.parsed_data['subevent']['id']
|
||||
)
|
||||
except SubEvent.DoesNotExist:
|
||||
pass
|
||||
except LogEntry.DoesNotExist:
|
||||
raise Http404(_('You supplied an invalid log entry ID'))
|
||||
return kwargs
|
||||
@@ -126,6 +175,170 @@ class SenderView(EventPermissionRequiredMixin, FormView):
|
||||
return super().form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
objects = self.get_object_queryset(form)
|
||||
ocnt = objects.count()
|
||||
|
||||
self.output = {}
|
||||
if not ocnt:
|
||||
messages.error(self.request, _('There are no matching recipients for your selection.'))
|
||||
self.request.POST = self.request.POST.copy()
|
||||
self.request.POST.pop("action", "")
|
||||
return self.get(self.request, *self.args, **self.kwargs)
|
||||
|
||||
if self.request.POST.get("action") != "send":
|
||||
for l in self.request.event.settings.locales:
|
||||
with language(l, self.request.event.settings.region):
|
||||
context_dict = {}
|
||||
for k, v in get_available_placeholders(self.request.event, self.context_parameters).items():
|
||||
context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
v.render_sample(self.request.event)
|
||||
)
|
||||
|
||||
subject = bleach.clean(form.cleaned_data['subject'].localize(l), tags=[])
|
||||
preview_subject = format_map(subject, context_dict)
|
||||
message = form.cleaned_data['message'].localize(l)
|
||||
preview_text = markdown_compile_email(format_map(message, context_dict))
|
||||
|
||||
self.output[l] = {
|
||||
'subject': _('Subject: {subject}').format(subject=preview_subject),
|
||||
'html': preview_text,
|
||||
'attachment': form.cleaned_data.get('attachment')
|
||||
}
|
||||
|
||||
self.object_count = ocnt
|
||||
return self.get(self.request, *self.args, **self.kwargs)
|
||||
|
||||
self.task.apply_async(
|
||||
kwargs=self.get_task_kwargs(form, objects)
|
||||
)
|
||||
self.request.event.log_action(
|
||||
self.ACTION_TYPE,
|
||||
user=self.request.user,
|
||||
data=dict(form.cleaned_data)
|
||||
)
|
||||
messages.success(self.request, _('Your message has been queued and will be sent to the contact addresses of %s '
|
||||
'in the next few minutes.') % self.describe_match_size(len(objects)))
|
||||
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
ctx = super().get_context_data(*args, **kwargs)
|
||||
ctx['output'] = getattr(self, 'output', None)
|
||||
ctx['match_size'] = self.describe_match_size(getattr(self, 'object_count', None))
|
||||
ctx['form_fragment_name'] = self.form_fragment_name
|
||||
ctx['is_preview'] = self.request.method == 'POST' and self.request.POST.get('action') == 'preview' and ctx['form'].is_valid()
|
||||
ctx['view_title'] = self.TITLE
|
||||
return ctx
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
f = super().get_form(form_class)
|
||||
if self.request.method == 'POST' and self.request.POST.get('action') == 'preview':
|
||||
if f.is_valid():
|
||||
for fname, field in f.fields.items():
|
||||
field.widget.attrs['disabled'] = 'disabled'
|
||||
return f
|
||||
|
||||
|
||||
class OrderSendView(BaseSenderView):
|
||||
form_class = forms.OrderMailForm
|
||||
form_fragment_name = "pretixplugins/sendmail/send_form_fragment_orders.html"
|
||||
context_parameters = ['event', 'order', 'position_or_address']
|
||||
task = send_mails_to_orders
|
||||
|
||||
ACTION_TYPE = 'pretix.plugins.sendmail.sent'
|
||||
TITLE = _("Orders or attendees")
|
||||
DESCRIPTION = _("Send an email to every customer, or to every person a ticket has been "
|
||||
"purchased for, or a combination of both.")
|
||||
|
||||
@classmethod
|
||||
def show_history_meta_data(cls, logentry, _cache_store):
|
||||
if 'itemcache' not in _cache_store:
|
||||
_cache_store['itemcache'] = {
|
||||
i.pk: str(i) for i in logentry.event.items.all()
|
||||
}
|
||||
if 'checkin_list_cache' not in _cache_store:
|
||||
_cache_store['checkin_list_cache'] = {
|
||||
i.pk: str(i) for i in logentry.event.checkin_lists.all()
|
||||
}
|
||||
if 'status' not in _cache_store:
|
||||
status = dict(Order.STATUS_CHOICE)
|
||||
status['overdue'] = _('pending with payment overdue')
|
||||
status['na'] = _('payment pending (except unapproved)')
|
||||
status['pa'] = _('approval pending')
|
||||
status['r'] = status['c']
|
||||
_cache_store['status'] = status
|
||||
|
||||
tpl = get_template('pretixplugins/sendmail/history_fragment_orders.html')
|
||||
logentry.pdata['sendto'] = [
|
||||
_cache_store['status'][s] for s in logentry.pdata['sendto']
|
||||
]
|
||||
logentry.pdata['items'] = [
|
||||
_cache_store['itemcache'].get(i['id'], '?') for i in logentry.pdata.get('items', [])
|
||||
]
|
||||
logentry.pdata['checkin_lists'] = [
|
||||
_cache_store['checkin_list_cache'].get(i['id'], '?')
|
||||
for i in logentry.pdata.get('checkin_lists', []) if i['id'] in _cache_store['checkin_list_cache']
|
||||
]
|
||||
if logentry.pdata.get('subevent'):
|
||||
try:
|
||||
logentry.pdata['subevent_obj'] = logentry.event.subevents.get(pk=logentry.pdata['subevent']['id'])
|
||||
except SubEvent.DoesNotExist:
|
||||
pass
|
||||
return tpl.render({
|
||||
'log': logentry,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def get_url(cls, event):
|
||||
return reverse(
|
||||
'plugins:sendmail:send.orders',
|
||||
kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
}
|
||||
)
|
||||
|
||||
def initial_from_logentry(self, logentry: LogEntry):
|
||||
initial = super().initial_from_logentry(logentry)
|
||||
if 'recipients' in logentry.parsed_data:
|
||||
initial['recipients'] = logentry.parsed_data.get('recipients', 'orders')
|
||||
if 'sendto' in logentry.parsed_data:
|
||||
initial['sendto'] = logentry.parsed_data.get('recipients', 'sendto')
|
||||
if 'items' in logentry.parsed_data:
|
||||
initial['items'] = self.request.event.items.filter(
|
||||
id__in=[a['id'] for a in logentry.parsed_data['items']]
|
||||
)
|
||||
elif logentry.parsed_data.get('item'):
|
||||
initial['items'] = self.request.event.items.filter(
|
||||
id=logentry.parsed_data['item']['id']
|
||||
)
|
||||
if 'checkin_lists' in logentry.parsed_data:
|
||||
initial['checkin_lists'] = self.request.event.checkin_lists.filter(
|
||||
id__in=[c['id'] for c in logentry.parsed_data['checkin_lists']]
|
||||
)
|
||||
initial['filter_checkins'] = logentry.parsed_data.get('filter_checkins', False)
|
||||
initial['not_checked_in'] = logentry.parsed_data.get('not_checked_in', False)
|
||||
if logentry.parsed_data.get('subevents_from'):
|
||||
initial['subevents_from'] = dateutil.parser.parse(logentry.parsed_data['subevents_from'])
|
||||
if logentry.parsed_data.get('subevents_to'):
|
||||
initial['subevents_to'] = dateutil.parser.parse(logentry.parsed_data['subevents_to'])
|
||||
if logentry.parsed_data.get('created_from'):
|
||||
initial['created_from'] = dateutil.parser.parse(logentry.parsed_data['created_from'])
|
||||
if logentry.parsed_data.get('created_to'):
|
||||
initial['created_to'] = dateutil.parser.parse(logentry.parsed_data['created_to'])
|
||||
if logentry.parsed_data.get('attach_tickets'):
|
||||
initial['attach_tickets'] = logentry.parsed_data['attach_tickets']
|
||||
if logentry.parsed_data.get('subevent'):
|
||||
try:
|
||||
initial['subevent'] = self.request.event.subevents.get(
|
||||
pk=logentry.parsed_data['subevent']['id']
|
||||
)
|
||||
except SubEvent.DoesNotExist:
|
||||
pass
|
||||
return initial
|
||||
|
||||
def get_object_queryset(self, form):
|
||||
qs = Order.objects.filter(event=self.request.event)
|
||||
statusq = Q(status__in=form.cleaned_data['sendto'])
|
||||
if 'overdue' in form.cleaned_data['sendto']:
|
||||
@@ -189,88 +402,111 @@ class SenderView(EventPermissionRequiredMixin, FormView):
|
||||
if form.cleaned_data.get('created_to'):
|
||||
opq = opq.filter(order__datetime__lt=form.cleaned_data.get('created_to'))
|
||||
|
||||
orders = orders.annotate(match_pos=Exists(opq)).filter(match_pos=True).distinct()
|
||||
return orders.annotate(match_pos=Exists(opq)).filter(match_pos=True).distinct()
|
||||
|
||||
ocnt = orders.count()
|
||||
def describe_match_size(self, cnt):
|
||||
return ngettext(
|
||||
'%(number)s matching order',
|
||||
'%(number)s matching orders',
|
||||
cnt or 0,
|
||||
) % {
|
||||
'number': intcomma(cnt or 0),
|
||||
}
|
||||
|
||||
self.output = {}
|
||||
if not ocnt:
|
||||
messages.error(self.request, _('There are no orders matching this selection.'))
|
||||
self.request.POST = self.request.POST.copy()
|
||||
self.request.POST.pop("action", "")
|
||||
return self.get(self.request, *self.args, **self.kwargs)
|
||||
|
||||
if self.request.POST.get("action") != "send":
|
||||
for l in self.request.event.settings.locales:
|
||||
with language(l, self.request.event.settings.region):
|
||||
context_dict = {}
|
||||
for k, v in get_available_placeholders(self.request.event, ['event', 'order',
|
||||
'position_or_address']).items():
|
||||
context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
v.render_sample(self.request.event)
|
||||
)
|
||||
|
||||
subject = bleach.clean(form.cleaned_data['subject'].localize(l), tags=[])
|
||||
preview_subject = format_map(subject, context_dict)
|
||||
message = form.cleaned_data['message'].localize(l)
|
||||
preview_text = markdown_compile_email(format_map(message, context_dict))
|
||||
|
||||
self.output[l] = {
|
||||
'subject': _('Subject: {subject}').format(subject=preview_subject),
|
||||
'html': preview_text,
|
||||
'attachment': form.cleaned_data.get('attachment')
|
||||
}
|
||||
|
||||
self.order_count = ocnt
|
||||
return self.get(self.request, *self.args, **self.kwargs)
|
||||
|
||||
kwargs = {
|
||||
def get_task_kwargs(self, form, objects):
|
||||
kwargs = super().get_task_kwargs(form, objects)
|
||||
kwargs.update({
|
||||
'recipients': form.cleaned_data['recipients'],
|
||||
'event': self.request.event.pk,
|
||||
'user': self.request.user.pk,
|
||||
'subject': form.cleaned_data['subject'].data,
|
||||
'message': form.cleaned_data['message'].data,
|
||||
'orders': [o.pk for o in orders],
|
||||
'items': [i.pk for i in form.cleaned_data.get('items')],
|
||||
'not_checked_in': form.cleaned_data.get('not_checked_in'),
|
||||
'checkin_lists': [i.pk for i in form.cleaned_data.get('checkin_lists')],
|
||||
'filter_checkins': form.cleaned_data.get('filter_checkins'),
|
||||
'attach_tickets': form.cleaned_data.get('attach_tickets'),
|
||||
})
|
||||
return kwargs
|
||||
|
||||
|
||||
class WaitinglistSendView(BaseSenderView):
|
||||
form_class = forms.WaitinglistMailForm
|
||||
form_fragment_name = "pretixplugins/sendmail/send_form_fragment_waitinglist.html"
|
||||
context_parameters = ['event', 'waiting_list_entry', 'event_or_subevent']
|
||||
task = send_mails_to_waitinglist
|
||||
|
||||
ACTION_TYPE = 'pretix.plugins.sendmail.sent.waitinglist'
|
||||
TITLE = _("Waiting list")
|
||||
DESCRIPTION = _("Send an email to every person currently waiting to receive a voucher through the waiting "
|
||||
"list feature.")
|
||||
|
||||
@classmethod
|
||||
def show_history_meta_data(cls, logentry, _cache_store):
|
||||
if 'itemcache' not in _cache_store:
|
||||
_cache_store['itemcache'] = {
|
||||
i.pk: str(i) for i in logentry.event.items.all()
|
||||
}
|
||||
|
||||
tpl = get_template('pretixplugins/sendmail/history_fragment_waitinglist.html')
|
||||
logentry.pdata['items'] = [
|
||||
_cache_store['itemcache'].get(i['id'], '?') for i in logentry.pdata.get('items', [])
|
||||
]
|
||||
if logentry.pdata.get('subevent'):
|
||||
try:
|
||||
logentry.pdata['subevent_obj'] = logentry.event.subevents.get(pk=logentry.pdata['subevent']['id'])
|
||||
except SubEvent.DoesNotExist:
|
||||
pass
|
||||
return tpl.render({
|
||||
'log': logentry,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def get_url(cls, event):
|
||||
return reverse(
|
||||
'plugins:sendmail:send.waitinglist',
|
||||
kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
}
|
||||
)
|
||||
|
||||
def initial_from_logentry(self, logentry: LogEntry):
|
||||
initial = super().initial_from_logentry(logentry)
|
||||
if 'items' in logentry.parsed_data:
|
||||
initial['items'] = self.request.event.items.filter(
|
||||
id__in=[a['id'] for a in logentry.parsed_data['items']]
|
||||
)
|
||||
if logentry.parsed_data.get('subevents_from'):
|
||||
initial['subevents_from'] = dateutil.parser.parse(logentry.parsed_data['subevents_from'])
|
||||
if logentry.parsed_data.get('subevents_to'):
|
||||
initial['subevents_to'] = dateutil.parser.parse(logentry.parsed_data['subevents_to'])
|
||||
if logentry.parsed_data.get('subevent'):
|
||||
try:
|
||||
initial['subevent'] = self.request.event.subevents.get(
|
||||
pk=logentry.parsed_data['subevent']['id']
|
||||
)
|
||||
except SubEvent.DoesNotExist:
|
||||
pass
|
||||
return initial
|
||||
|
||||
def get_object_queryset(self, form):
|
||||
qs = self.request.event.waitinglistentries.filter(voucher__isnull=True)
|
||||
|
||||
qs = qs.filter(item__in=[i.pk for i in form.cleaned_data.get('items')])
|
||||
if form.cleaned_data.get('subevent'):
|
||||
qs = qs.filter(subevent=form.cleaned_data.get('subevent'))
|
||||
if form.cleaned_data.get('subevents_from'):
|
||||
qs = qs.filter(subevent__date_from__gte=form.cleaned_data.get('subevents_from'))
|
||||
if form.cleaned_data.get('subevents_to'):
|
||||
qs = qs.filter(subevent__date_from__lt=form.cleaned_data.get('subevents_to'))
|
||||
|
||||
return qs
|
||||
|
||||
def describe_match_size(self, cnt):
|
||||
return ngettext(
|
||||
'%(number)s waiting list entry',
|
||||
'%(number)s waiting list entries',
|
||||
cnt or 0,
|
||||
) % {
|
||||
'number': intcomma(cnt or 0),
|
||||
}
|
||||
attachment = form.cleaned_data.get('attachment')
|
||||
if attachment is not None and attachment is not False:
|
||||
kwargs['attachments'] = [form.cleaned_data['attachment'].id]
|
||||
|
||||
send_mails.apply_async(
|
||||
kwargs=kwargs
|
||||
)
|
||||
self.request.event.log_action('pretix.plugins.sendmail.sent',
|
||||
user=self.request.user,
|
||||
data=dict(form.cleaned_data))
|
||||
messages.success(self.request, _('Your message has been queued and will be sent to the contact addresses of %d '
|
||||
'orders in the next few minutes.') % len(orders))
|
||||
|
||||
return redirect(
|
||||
'plugins:sendmail:send',
|
||||
event=self.request.event.slug,
|
||||
organizer=self.request.event.organizer.slug
|
||||
)
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
ctx = super().get_context_data(*args, **kwargs)
|
||||
ctx['output'] = getattr(self, 'output', None)
|
||||
ctx['order_count'] = getattr(self, 'order_count', None)
|
||||
ctx['is_preview'] = self.request.method == 'POST' and self.request.POST.get('action') == 'preview' and ctx['form'].is_valid()
|
||||
return ctx
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
f = super().get_form(form_class)
|
||||
if self.request.method == 'POST' and self.request.POST.get('action') == 'preview':
|
||||
if f.is_valid():
|
||||
for fname, field in f.fields.items():
|
||||
field.widget.attrs['disabled'] = 'disabled'
|
||||
return f
|
||||
|
||||
|
||||
class EmailHistoryView(EventPermissionRequiredMixin, ListView):
|
||||
@@ -280,27 +516,30 @@ class EmailHistoryView(EventPermissionRequiredMixin, ListView):
|
||||
context_object_name = 'logs'
|
||||
paginate_by = 5
|
||||
|
||||
@cached_property
|
||||
def type_map(self):
|
||||
from .signals import sendmail_view_classes
|
||||
classes = []
|
||||
for recv, resp in sendmail_view_classes.send(self.request.event):
|
||||
if isinstance(resp, (list, tuple)):
|
||||
classes += resp
|
||||
else:
|
||||
classes.append(resp)
|
||||
return {
|
||||
cls.ACTION_TYPE: cls
|
||||
for cls in classes
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
qs = LogEntry.objects.filter(
|
||||
event=self.request.event,
|
||||
action_type='pretix.plugins.sendmail.sent'
|
||||
action_type__in=self.type_map.keys(),
|
||||
).select_related('event', 'user')
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
|
||||
itemcache = {
|
||||
i.pk: str(i) for i in self.request.event.items.all()
|
||||
}
|
||||
checkin_list_cache = {
|
||||
i.pk: str(i) for i in self.request.event.checkin_lists.all()
|
||||
}
|
||||
status = dict(Order.STATUS_CHOICE)
|
||||
status['overdue'] = _('pending with payment overdue')
|
||||
status['na'] = _('payment pending (except unapproved)')
|
||||
status['pa'] = _('approval pending')
|
||||
status['r'] = status['c']
|
||||
_cache = {}
|
||||
for log in ctx['logs']:
|
||||
log.pdata = log.parsed_data
|
||||
log.pdata['locales'] = {}
|
||||
@@ -309,21 +548,11 @@ class EmailHistoryView(EventPermissionRequiredMixin, ListView):
|
||||
'message': msg,
|
||||
'subject': log.pdata['subject'][locale]
|
||||
}
|
||||
log.pdata['sendto'] = [
|
||||
status[s] for s in log.pdata['sendto']
|
||||
]
|
||||
log.pdata['items'] = [
|
||||
itemcache.get(i['id'], '?') for i in log.pdata.get('items', [])
|
||||
]
|
||||
log.pdata['checkin_lists'] = [
|
||||
checkin_list_cache.get(i['id'], '?')
|
||||
for i in log.pdata.get('checkin_lists', []) if i['id'] in checkin_list_cache
|
||||
]
|
||||
if log.pdata.get('subevent'):
|
||||
try:
|
||||
log.pdata['subevent_obj'] = self.request.event.subevents.get(pk=log.pdata['subevent']['id'])
|
||||
except SubEvent.DoesNotExist:
|
||||
pass
|
||||
log.view = {
|
||||
'url': self.type_map[log.action_type].get_url(self.request.event),
|
||||
'title': self.type_map[log.action_type].TITLE,
|
||||
'rendered_data': self.type_map[log.action_type].show_history_meta_data(log, _cache)
|
||||
}
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
@@ -792,6 +792,27 @@ tbody th {
|
||||
background: $table-bg-hover;
|
||||
}
|
||||
|
||||
.large-link-group {
|
||||
a.list-group-item {
|
||||
&::before {
|
||||
font-family: FontAwesome;
|
||||
font-size: 30px;
|
||||
content: $fa-var-chevron-right;
|
||||
float: right;
|
||||
color: $link-color;
|
||||
}
|
||||
h4 {
|
||||
color: $link-color;
|
||||
}
|
||||
&:hover &::before {
|
||||
color: $link-hover-color;
|
||||
}
|
||||
&:hover h4 {
|
||||
color: $link-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.withoutjs {
|
||||
display: none !important;
|
||||
|
||||
@@ -68,13 +68,21 @@ def subevent(event):
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
se = event.subevents.create(name='se1', date_from=now())
|
||||
|
||||
return se
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def waitinglistentry(event, item):
|
||||
return event.waitinglistentries.create(
|
||||
item=item,
|
||||
created=now(),
|
||||
email='john@example.org',
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_view(logged_in_client, sendmail_url, expected=200):
|
||||
response = logged_in_client.get(sendmail_url)
|
||||
response = logged_in_client.get(sendmail_url + 'orders/')
|
||||
|
||||
assert response.status_code == expected
|
||||
|
||||
@@ -82,7 +90,7 @@ def test_sendmail_view(logged_in_client, sendmail_url, expected=200):
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_simple_case(logged_in_client, sendmail_url, event, order, pos):
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'orders',
|
||||
@@ -109,7 +117,7 @@ def test_sendmail_simple_case(logged_in_client, sendmail_url, event, order, pos)
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_email_not_sent_if_order_not_match(logged_in_client, sendmail_url, event, order, pos):
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'p',
|
||||
'action': 'send',
|
||||
'recipients': 'orders',
|
||||
@@ -126,7 +134,7 @@ def test_sendmail_email_not_sent_if_order_not_match(logged_in_client, sendmail_u
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_preview(logged_in_client, sendmail_url, event, order, pos):
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'recipients': 'orders',
|
||||
'items': pos.item_id,
|
||||
@@ -144,7 +152,7 @@ def test_sendmail_preview(logged_in_client, sendmail_url, event, order, pos):
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_invalid_data(logged_in_client, sendmail_url, event, order, pos):
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'orders',
|
||||
@@ -172,7 +180,7 @@ def test_sendmail_multi_locales(logged_in_client, sendmail_url, event, item):
|
||||
locale='de')
|
||||
OrderPosition.objects.create(order=o, item=item, price=13)
|
||||
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'p',
|
||||
'action': 'send',
|
||||
'recipients': 'orders',
|
||||
@@ -211,7 +219,7 @@ def test_sendmail_subevents(logged_in_client, sendmail_url, event, order, pos):
|
||||
op.save()
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'orders',
|
||||
@@ -226,7 +234,7 @@ def test_sendmail_subevents(logged_in_client, sendmail_url, event, order, pos):
|
||||
assert len(djmail.outbox) == 1
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'orders',
|
||||
@@ -248,7 +256,7 @@ def test_sendmail_subevents(logged_in_client, sendmail_url, event, order, pos):
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_placeholder(logged_in_client, sendmail_url, event, order, pos):
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'recipients': 'orders',
|
||||
'items': pos.item_id,
|
||||
@@ -272,7 +280,7 @@ def test_sendmail_attendee_mails(logged_in_client, sendmail_url, event, order, p
|
||||
p.save()
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
@@ -297,7 +305,7 @@ def test_sendmail_both_mails(logged_in_client, sendmail_url, event, order, pos):
|
||||
p.save()
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'both',
|
||||
@@ -325,7 +333,7 @@ def test_sendmail_both_but_same_address(logged_in_client, sendmail_url, event, o
|
||||
p.save()
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'both',
|
||||
@@ -350,7 +358,7 @@ def test_sendmail_attendee_fallback(logged_in_client, sendmail_url, event, order
|
||||
p.save()
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
@@ -380,7 +388,7 @@ def test_sendmail_attendee_product_filter(logged_in_client, sendmail_url, event,
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
@@ -409,7 +417,7 @@ def test_sendmail_attendee_checkin_filter(logged_in_client, sendmail_url, event,
|
||||
Checkin.objects.create(position=pos2, list=chkl2)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
@@ -428,7 +436,7 @@ def test_sendmail_attendee_checkin_filter(logged_in_client, sendmail_url, event,
|
||||
assert '/order/' not in djmail.outbox[0].body
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
@@ -447,7 +455,7 @@ def test_sendmail_attendee_checkin_filter(logged_in_client, sendmail_url, event,
|
||||
assert '/order/' not in djmail.outbox[0].body
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
@@ -467,7 +475,7 @@ def test_sendmail_attendee_checkin_filter(logged_in_client, sendmail_url, event,
|
||||
|
||||
# Test that filtering is ignored if filter_checkins is not set
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
@@ -489,7 +497,7 @@ def test_sendmail_attendee_checkin_filter(logged_in_client, sendmail_url, event,
|
||||
|
||||
# Test that filtering is ignored if filter_checkins is not set
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
@@ -508,3 +516,28 @@ def test_sendmail_attendee_checkin_filter(logged_in_client, sendmail_url, event,
|
||||
assert '/order/' not in djmail.outbox[1].body
|
||||
to_emails = set(*zip(*[mail.to for mail in djmail.outbox]))
|
||||
assert to_emails == {'attendee1@dummy.test', 'attendee2@dummy.test'}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_waitinglist_sendmail_simple_case(logged_in_client, sendmail_url, event, waitinglistentry):
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'waitinglist/',
|
||||
{'action': 'send',
|
||||
'items': waitinglistentry.item_id,
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == [waitinglistentry.email]
|
||||
assert djmail.outbox[0].subject == 'Test subject'
|
||||
assert 'This is a test file for sending mails.' in djmail.outbox[0].body
|
||||
|
||||
url = sendmail_url + 'history/'
|
||||
response = logged_in_client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'Test subject' in response.rendered_content
|
||||
|
||||
Reference in New Issue
Block a user