forked from CGM_Public/pretix_original
Fixed #42 -- Configurable e-mail texts
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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' % (
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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>
|
||||
|
||||
19
src/pretix/control/templates/pretixcontrol/event/mail.html
Normal file
19
src/pretix/control/templates/pretixcontrol/event/mail.html
Normal 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 %}
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user