Fixed #42 -- Configurable e-mail texts

This commit is contained in:
Raphael Michel
2015-12-13 17:33:38 +01:00
parent 1e72c58219
commit d8ca0d527e
15 changed files with 1224 additions and 900 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -52,11 +52,13 @@ class SettingsForm(forms.Form):
self.obj = kwargs.pop('obj')
kwargs['initial'] = self.obj.settings.freeze()
super().__init__(*args, **kwargs)
for k, field in self.fields.items():
if isinstance(field, I18nFormField):
field.widget.enabled_langcodes = self.obj.settings.get('locales')
def save(self):
for name, field in self.fields.items():
value = self.cleaned_data[name]
if isinstance(value, UploadedFile):
if isinstance(self.obj, Event):
fname = '%s/%s/%s.%s' % (

View File

@@ -7,8 +7,10 @@ from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Model, QuerySet, TextField
from django.forms import BaseModelFormSet
from django.utils import translation
from django.utils.formats import date_format, number_format
from django.utils.safestring import mark_safe
from typing import Dict, List
from django.utils.translation import ugettext
from typing import Dict, List, Union, Optional
class LazyI18nString:
@@ -16,7 +18,7 @@ class LazyI18nString:
This represents an internationalized string that is/was/will be stored in the database.
"""
def __init__(self, data: Dict[str, str]):
def __init__(self, data: Optional[Union[str, Dict[str, str]]]):
"""
Input data should be a dictionary which maps language codes to content.
"""
@@ -62,6 +64,32 @@ class LazyI18nString:
def __lt__(self, other) -> bool: # NOQA
return str(self) < str(other)
def __format__(self, format_spec):
return self.__str__()
class LazyGettextProxy:
def __init__(self, lazygettext):
self.lazygettext = lazygettext
def __getitem__(self, item):
lng = translation.get_language()
translation.activate(item)
s = str(ugettext(self.lazygettext))
translation.activate(lng)
return s
def __contains__(self, item):
return True
def __str__(self):
return str(self.lazygettext)
@classmethod
def from_gettext(cls, lazygettext):
l = LazyI18nString({})
l.data = cls.LazyGettextProxy(lazygettext)
return l
class I18nWidget(forms.MultiWidget):
"""
@@ -89,7 +117,9 @@ class I18nWidget(forms.MultiWidget):
for lng in self.langcodes:
data.append(
value.data[lng]
if value is not None and isinstance(value.data, dict) and lng in value.data
if value is not None and (
isinstance(value.data, dict) or isinstance(value.data, LazyI18nString.LazyGettextProxy)
) and lng in value.data
else None
)
if value and not isinstance(value.data, dict):
@@ -278,3 +308,26 @@ class I18nFormSet(BaseModelFormSet):
)
self.add_fields(form, None)
return form
class LazyDate:
def __init__(self, value):
self.value = value
def __format__(self, format_spec):
return self.__str__()
def __str__(self):
return date_format(self.value, "SHORT_DATE_FORMAT")
class LazyNumber:
def __init__(self, value, decimal_pos=2):
self.value = value
self.decimal_pos = decimal_pos
def __format__(self, format_spec):
return self.__str__()
def __str__(self):
return number_format(self.value, decimal_pos=self.decimal_pos)

View File

@@ -13,6 +13,12 @@ from pretix.base.models import Event
logger = logging.getLogger('pretix.base.mail')
class TolerantDict(dict):
def __missing__(self, key):
return key
def mail(email: str, subject: str, template: str,
context: Dict[str, Any]=None, event: Event=None, locale: str=None):
"""
@@ -22,9 +28,11 @@ def mail(email: str, subject: str, template: str,
:param subject: The e-mail subject. Should be localized.
:param template: The filename of a template to be used. It will
be rendered with the recipient's locale. Alternatively, you
can pass a LazyI18nString and leave ``context`` empty
can pass a LazyI18nString and ``context`` will be used
for a Python .format() call.
:param context: The context for rendering the template.
:param event: The event, used for determining the sender of the e-mail
:param locale: The locale used while rendering the template
:return: ``False`` on obvious failures, like the user having to e-mail
address, ``True`` otherwise. ``True`` does not necessarily mean that
@@ -37,6 +45,8 @@ def mail(email: str, subject: str, template: str,
if isinstance(template, LazyI18nString):
body = str(template)
if context:
body = body.format_map(TolerantDict(context))
else:
tpl = get_template(template)
body = tpl.render(context)

View File

@@ -4,6 +4,7 @@ from django.conf import settings
from django.db import transaction
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from pretix.base.i18n import LazyDate, LazyNumber
from typing import List
from pretix.base.models import (
@@ -71,10 +72,9 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date
mail(
order.email, _('Payment received for your order: %(code)s') % {'code': order.code},
'pretixpresale/email/order_paid.txt',
order.event.settings.mail_text_order_paid,
{
'order': order,
'event': order.event,
'event': order.event.name,
'url': build_absolute_uri(order.event, 'presale:event.order', kwargs={
'order': order.code,
'secret': order.secret
@@ -217,15 +217,17 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
mail(
order.email, _('Your order: %(code)s') % {'code': order.code},
'pretixpresale/email/order_placed.txt',
event.settings.mail_text_order_placed,
{
'order': order,
'event': event,
'total': LazyNumber(order.total),
'currency': event.currency,
'date': LazyDate(order.expires),
'event': event.name,
'url': build_absolute_uri(event, 'presale:event.order', kwargs={
'order': order.code,
'secret': order.secret
}),
'payment': pprov.order_pending_mail_render(order)
'paymentinfo': str(pprov.order_pending_mail_render(order))
},
event, locale=order.locale
)

View File

@@ -7,7 +7,10 @@ from django.conf import settings
from django.core.files import File
from django.core.files.storage import default_storage
from django.db.models import Model
from typing import Any, Callable, Dict, Optional, TypeVar, Union
from django.utils.translation import ugettext_noop
from typing import Any, Dict, Optional
from pretix.base.i18n import LazyI18nString
DEFAULTS = {
'max_items_per_order': {
@@ -93,6 +96,33 @@ DEFAULTS = {
'mail_from': {
'default': settings.MAIL_FROM,
'type': str
},
'mail_text_order_placed': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
we successfully received your order for {event} with a total value
of {total} {currency}. Please complete your payment before {date}.
{paymentinfo}
You can change your order details and view the status of your order at
{url}
Best regards,
Your {event} team"""))
},
'mail_text_order_paid': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
we successfully received your payment for {event}. Thank you!
You can change your order details and view the status of your order at
{url}
Best regards,
Your {event} team"""))
}
}
@@ -158,6 +188,11 @@ class SettingsProxy:
return dateutil.parser.parse(value).date()
elif as_type == time:
return dateutil.parser.parse(value).time()
elif as_type == LazyI18nString and not isinstance(value, LazyI18nString):
try:
return LazyI18nString(json.loads(value))
except ValueError:
return LazyI18nString(str(value))
elif as_type is not None and issubclass(as_type, Model):
return as_type.objects.get(pk=value)
return value
@@ -174,6 +209,8 @@ class SettingsProxy:
return value.isoformat()
elif isinstance(value, Model):
return value.pk
elif isinstance(value, LazyI18nString):
return json.dumps(value.data)
elif isinstance(value, File):
return 'file://' + value.name

View File

@@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from pytz import common_timezones
from pretix.base.forms import I18nModelForm, SettingsForm
from pretix.base.i18n import I18nFormField, I18nTextarea
from pretix.base.models import Event
@@ -198,6 +199,21 @@ class ProviderForm(SettingsForm):
self.add_error(k, _('This field is required.'))
class MailSettingsForm(SettingsForm):
mail_text_order_placed = I18nFormField(
label=_("Placed order"),
required=False,
widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {total}, {currency}, {date}, {paymentinfo}, {url}")
)
mail_text_order_paid = I18nFormField(
label=_("Paid order"),
required=False,
widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {url}")
)
class TicketSettingsForm(SettingsForm):
ticket_download = forms.BooleanField(
label=_("Use feature"),

View File

@@ -32,16 +32,22 @@
</li>
<li>
<a href="{% url 'control:event.settings.plugins' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if "event.settings.plugins" == url_name %}class="active"{% endif %} >
{% if "event.settings.plugins" == url_name %}class="active"{% endif %}>
{% trans "Plugins" %}
</a>
</li>
<li>
<a href="{% url 'control:event.settings.tickets' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if "event.settings.tickets" == url_name %}class="active"{% endif %} >
{% if "event.settings.tickets" == url_name %}class="active"{% endif %}>
{% trans "Tickets" %}
</a>
</li>
<li>
<a href="{% url 'control:event.settings.mail' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if "event.settings.mail" == url_name %}class="active"{% endif %}>
{% trans "Email" %}
</a>
</li>
{% endif %}
{% if request.eventperm.can_change_permissions %}
<li>

View File

@@ -0,0 +1,19 @@
{% extends "pretixcontrol/event/settings_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inside %}
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
{% csrf_token %}
<fieldset>
<legend>{% trans "E-mail content" %}</legend>
{% bootstrap_form_errors form %}
{% bootstrap_field form.mail_text_order_placed layout="horizontal" %}
{% bootstrap_field form.mail_text_order_paid layout="horizontal" %}
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -25,6 +25,7 @@ urlpatterns = [
url(r'^settings/permissions$', event.EventPermissions.as_view(), name='event.settings.permissions'),
url(r'^settings/payment$', event.PaymentSettings.as_view(), name='event.settings.payment'),
url(r'^settings/tickets$', event.TicketSettings.as_view(), name='event.settings.tickets'),
url(r'^settings/email$', event.MailSettings.as_view(), name='event.settings.mail'),
url(r'^items/$', item.ItemList.as_view(), name='event.items'),
url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
url(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),

View File

@@ -21,7 +21,8 @@ from pretix.base.signals import (
register_payment_providers, register_ticket_outputs,
)
from pretix.control.forms.event import (
EventSettingsForm, EventUpdateForm, ProviderForm, TicketSettingsForm,
EventSettingsForm, EventUpdateForm, MailSettingsForm, ProviderForm,
TicketSettingsForm,
)
from pretix.control.permissions import EventPermissionRequiredMixin
@@ -202,6 +203,44 @@ class PaymentSettings(EventPermissionRequiredMixin, TemplateView, SingleObjectMi
})
class MailSettings(EventPermissionRequiredMixin, FormView):
model = Event
form_class = MailSettingsForm
template_name = 'pretixcontrol/event/mail.html'
permission = 'can_change_settings'
def get_context_data(self, *args, **kwargs) -> dict:
context = super().get_context_data(*args, **kwargs)
return context
def get_success_url(self) -> str:
return reverse('control:event.settings.mail', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug
})
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['obj'] = self.request.event
return kwargs
@transaction.atomic()
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
form.save()
if form.has_changed():
self.request.event.log_action(
'pretix.event.settings', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
messages.success(self.request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
else:
return self.get(request)
class TicketSettings(EventPermissionRequiredMixin, FormView):
model = Event
form_class = TicketSettingsForm

View File

@@ -1,9 +0,0 @@
{% load i18n %}{% blocktrans with event=event.name url=url|safe %}Hello,
you requested a new password. Please go to the following page to reset your password:
{{ url }}
Best regards,
Your {{ event }} team
{% endblocktrans %}

View File

@@ -1,19 +0,0 @@
{% load i18n %}{% if download %}{% blocktrans with event=event.name url=url %}Hello,
we successfully received your payment for {{ event }}. Thank you!
You will be able to download your ticket at:
{{ url }}
Best regards,
Your {{ event }} team
{% endblocktrans %}{% else %}{% blocktrans with event=event.name url=url %}Hello,
we successfully received your payment for {{ event }}. Thank you!
You can change your order details and view the status of your order at
{{ url }}
Best regards,
Your {{ event }} team
{% endblocktrans %}{% endif %}

View File

@@ -1,13 +0,0 @@
{% load i18n %}{% blocktrans with event=event.name total=order.total|floatformat:2 currency=event.currency paymentinfo=payment date=order.expires|date:"SHORT_DATE_FORMAT" url=url %}Hello,
we successfully received your order for {{ event }} with a total value
of {{ total }} {{ currency }}. Please complete your payment before {{ date }}.
{{ paymentinfo }}
You can change your order details and view the status of your order at
{{ url }}
Best regards,
Your {{ event }} team
{% endblocktrans %}