Skip to content

Commit

Permalink
Fixed #42 -- Configurable e-mail texts
Browse files Browse the repository at this point in the history
  • Loading branch information
raphaelm committed Dec 13, 2015
1 parent 1e72c58 commit d8ca0d5
Show file tree
Hide file tree
Showing 15 changed files with 1,224 additions and 900 deletions.
914 changes: 491 additions & 423 deletions src/locale/de/LC_MESSAGES/django.po

Large diffs are not rendered by default.

952 changes: 532 additions & 420 deletions src/locale/de_Informal/LC_MESSAGES/django.po

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/pretix/base/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ def __init__(self, *args, **kwargs):
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' % (
Expand Down
59 changes: 56 additions & 3 deletions src/pretix/base/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@
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:
"""
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.
"""
Expand Down Expand Up @@ -62,6 +64,32 @@ def __repr__(self) -> str:
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):
"""
Expand Down Expand Up @@ -89,7 +117,9 @@ def decompress(self, value):
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):
Expand Down Expand Up @@ -278,3 +308,26 @@ def empty_form(self):
)
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)
12 changes: 11 additions & 1 deletion src/pretix/base/services/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
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):
"""
Expand All @@ -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
Expand All @@ -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)
Expand Down
16 changes: 9 additions & 7 deletions src/pretix/base/services/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand Down
39 changes: 38 additions & 1 deletion src/pretix/base/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
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': {
Expand Down Expand Up @@ -93,6 +96,33 @@
'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"""))
}
}

Expand Down Expand Up @@ -158,6 +188,11 @@ def _unserialize(self, value: str, as_type: type) -> Any:
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
Expand All @@ -174,6 +209,8 @@ def _serialize(self, value: Any) -> str:
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

Expand Down
16 changes: 16 additions & 0 deletions src/pretix/control/forms/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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


Expand Down Expand Up @@ -198,6 +199,21 @@ def clean(self):
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"),
Expand Down
10 changes: 8 additions & 2 deletions src/pretix/control/templates/pretixcontrol/event/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
19 changes: 19 additions & 0 deletions src/pretix/control/templates/pretixcontrol/event/mail.html
Original file line number Diff line number Diff line change
@@ -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 %}
1 change: 1 addition & 0 deletions src/pretix/control/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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'),
Expand Down
41 changes: 40 additions & 1 deletion src/pretix/control/views/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
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

Expand Down Expand Up @@ -202,6 +203,44 @@ def get_success_url(self) -> str:
})


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
Expand Down

0 comments on commit d8ca0d5

Please sign in to comment.