Fix #308 -- Preview for email templates (#438)

* add ajax and dummy api response

* add preview <p> blocks

* finalise order_placed setup

- use <pre> for mail preview
- create dummy data in backend

* fix i18n text conversion

* create fragment template for mail preview

* support i18n in mail preview view

* apply mail fragment in all mail settings

fix mistake in input[lang=en] flag style
add dummy data for all placeholders
apply fragment template to all fields
add exclude option to fragment template

* remove migration file

* add translation mapping & fix field label

remove hardcoded field label
add transblock for translation file

* add test for mail setting preview

* fix code style in preview class

* bug fix in mail preview view

- fixed localised date values
- added locale index mapping
- added tests on multi-language event
- enhanced dummy data
This commit is contained in:
jlwt90
2017-04-14 18:19:58 +09:00
committed by Raphael Michel
parent 5ee79c8148
commit bc6b84f900
9 changed files with 823 additions and 345 deletions

View File

@@ -32,6 +32,7 @@
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/main.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/quota.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/question.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/mail.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asyncdownload.js" %}"></script>
{% endcompress %}

View File

@@ -2,7 +2,8 @@
{% load i18n %}
{% load bootstrap3 %}
{% block inside %}
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data"
mail-preview-url="{% url "control:event.settings.mail.preview" event=request.event.slug organizer=request.event.organizer.slug %}">
{% csrf_token %}
{% bootstrap_form_errors form %}
<fieldset>
@@ -13,106 +14,26 @@
<fieldset>
<legend>{% trans "E-mail content" %}</legend>
<div class="panel-group" id="questions_group">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#order_placed">
<strong>{% trans "Placed order" %}</strong>
</a>
</h4>
</div>
<div id="order_placed" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_order_placed layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#order_paid">
<strong>{% trans "Paid order" %}</strong>
</a>
</h4>
</div>
<div id="order_paid" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_order_paid layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#order_free">
<strong>{% trans "Free order" %}</strong>
</a>
</h4>
</div>
<div id="order_free" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_order_free layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#resend_link">
<strong>{% trans "Resend link" %}</strong>
</a>
</h4>
</div>
<div id="resend_link" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_resend_link layout="horizontal" %}
{% bootstrap_field form.mail_text_resend_all_links layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#order_changed">
<strong>{% trans "Order changed" %}</strong>
</a>
</h4>
</div>
<div id="order_changed" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_order_changed layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#order_expirew">
<strong>{% trans "Payment reminder" %}</strong>
</a>
</h4>
</div>
<div id="order_expirew" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_days_order_expire_warning layout="horizontal" %}
{% bootstrap_field form.mail_text_order_expire_warning layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#waiting_list">
<strong>{% trans "Waiting list notification" %}</strong>
</a>
</h4>
</div>
<div id="waiting_list" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_waiting_list layout="horizontal" %}
</div>
</div>
</div>
{% blocktrans asvar title_placed_order %}Placed order{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="order_placed" title=title_placed_order items="mail_text_order_placed" %}
{% blocktrans asvar title_paid_order %}Paid order{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="order_paid" title=title_paid_order items="mail_text_order_paid" %}
{% blocktrans asvar title_free_order %}Free order{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="order_free" title=title_free_order items="mail_text_order_free" %}
{% blocktrans asvar title_resend_link %}Resend link{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="resend_link" title=title_resend_link items="mail_text_resend_link,mail_text_resend_all_links" %}
{% blocktrans asvar title_order_changed %}Order changed{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="order_changed" title=title_order_changed items="mail_text_order_changed" %}
{% blocktrans asvar title_payment_reminder %}Payment reminder{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="order_expirew" title=title_payment_reminder items="mail_days_order_expire_warning,mail_text_order_expire_warning" exclude="mail_days_order_expire_warning" %}
{% blocktrans asvar title_waiting_list_notification %}Waiting list notification{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="waiting_list" title=title_waiting_list_notification items="mail_text_waiting_list" %}
</div>
</fieldset>
<fieldset>

View File

@@ -0,0 +1,53 @@
{% load i18n %}
{% load bootstrap3 %}
{% load mail_settings_preview %}
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#{{ pid }}">
<strong>{% trans title %}</strong>
</a>
</h4>
</div>
<div id="{{ pid }}" class="panel-collapse collapse">
<div class="panel-body">
{% with exclude|split as exclusion %}
{% with items|split as item_list %}
{% for item in item_list %}
{% if item in exclusion %}
{% with form|getattr:item as field %}
{% bootstrap_field field layout="horizontal" %}
{% endwith %}
{% else %}
<div id="{{ item }}_panel" class="preview-panel form-group" for="{{ item }}">
{% with form|getattr:item as field %}
<label class="col-md-3 control-label">{{ field.label }}</label>
<div class="col-md-9">
<div class="tab-content">
<div id="{{ item }}_edit" class="tab-pane fade in active">
{% bootstrap_field field show_label=False form_group_class="" %}
</div>
<div id="{{ item }}_preview" class="tab-pane">
{% for l in request.event.settings.locales %}
<pre lang="{{ l }}" for="{{ item }}"></pre>
{% endfor %}
</div>
</div>
<ul class="nav nav-pills pull-right">
<li role="presentation" class="active">
<a data-toggle="pill" type="edit" href="#{{ item }}_edit"><i class="fa fa-pencil-square-o fa-fw"></i> {% trans "Edit" %}</a>
</li>
<li role="presentation">
<a data-toggle="pill" type="preview" href="#{{ item }}_preview"><i class="fa fa-tv fa-fw"></i> {% trans "Preview" %}</a>
</li>
</ul>
</div>
{% endwith %}
</div>
{% endif %}
{% endfor %}
{% endwith %}
{% endwith %}
</div>
</div>
</div>

View File

@@ -0,0 +1,13 @@
from django import template
register = template.Library()
@register.filter(name='split', delimiter=",")
def split(value, delimiter=","):
return value.split(delimiter)
@register.filter(name="getattr")
def get_attribute(value, key):
return value[key]

View File

@@ -52,6 +52,7 @@ urlpatterns = [
url(r'^settings/tickets/preview/(?P<output>[^/]+)$', event.TicketSettingsPreview.as_view(),
name='event.settings.tickets.preview'),
url(r'^settings/email$', event.MailSettings.as_view(), name='event.settings.mail'),
url(r'^settings/email/preview$', event.MailSettingsPreview.as_view(), name='event.settings.mail.preview'),
url(r'^settings/invoice$', event.InvoiceSettings.as_view(), name='event.settings.invoice'),
url(r'^settings/invoice/preview$', event.InvoicePreview.as_view(), name='event.settings.invoice.preview'),
url(r'^settings/display', event.DisplaySettings.as_view(), name='event.settings.display'),

View File

@@ -1,15 +1,21 @@
import re
from collections import OrderedDict
from datetime import timedelta
from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.files import File
from django.core.urlresolvers import reverse
from django.db import transaction
from django.forms import modelformset_factory
from django.http import HttpResponse
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.utils import translation
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView, ListView
from django.views.generic.base import TemplateView, View
@@ -400,6 +406,112 @@ class MailSettings(EventSettingsFormView):
return self.get(request)
class MailSettingsPreview(EventPermissionRequiredMixin, View):
permission = 'can_change_settings'
# return the origin text if key is missing in dict
class SafeDict(dict):
def __missing__(self, key):
return '{' + key + '}'
@staticmethod
def generate_order_fullname(slug, code):
return '{event}-{code}'.format(event=slug.upper(), code=code)
# create data which depend on locale
def localized_data(self):
return {
'date': date_format(now() + timedelta(days=7), 'SHORT_DATE_FORMAT'),
'expire_date': date_format(now() + timedelta(days=15), 'SHORT_DATE_FORMAT'),
'payment_info': _('{} {} has been transferred to account<9999-9999-9999-9999> at {}').format(
42.23, self.request.event.currency, date_format(now(), 'SHORT_DATETIME_FORMAT'))
}
# create index-language mapping
@cached_property
def supported_locale(self):
locales = {}
for idx, val in enumerate(settings.LANGUAGES):
if val[0] in self.request.event.settings.locales:
locales[str(idx)] = val[0]
return locales
@cached_property
def items(self):
return {
'mail_text_order_placed': ['total', 'currency', 'date', 'invoice_company',
'event', 'paymentinfo', 'url', 'invoice_name'],
'mail_text_order_paid': ['event', 'url', 'invoice_name', 'invoice_company', 'payment_info'],
'mail_text_order_free': ['event', 'url', 'invoice_name', 'invoice_company'],
'mail_text_resend_link': ['event', 'url', 'invoice_name', 'invoice_company'],
'mail_text_resend_all_links': ['event', 'orders'],
'mail_text_order_changed': ['event', 'url', 'invoice_name', 'invoice_company'],
'mail_text_order_expire_warning': ['event', 'url', 'expire_date', 'invoice_name', 'invoice_company'],
'mail_text_waiting_list': ['event', 'url', 'product', 'hours', 'code']
}
@cached_property
def base_data(self):
user_orders = [
{'code': 'F8VVL', 'secret': '6zzjnumtsx136ddy'},
{'code': 'HIDHK', 'secret': '98kusd8ofsj8dnkd'},
{'code': 'OPKSB', 'secret': '09pjdksflosk3njd'}
]
orders = [' - {} - {}'.format(self.generate_order_fullname(self.request.event.slug, order['code']),
self.generate_order_url(order['code'], order['secret']))
for order in user_orders]
return {
'event': self.request.event.name,
'total': 42.23,
'currency': self.request.event.currency,
'url': self.generate_order_url(user_orders[0]['code'], user_orders[0]['secret']),
'orders': '\n'.join(orders),
'hours': self.request.event.settings.waiting_list_hours,
'product': _('Sample Admission Ticket'),
'code': '68CYU2H6ZTP3WLK5',
'invoice_name': _('John Doe'),
'invoice_company': _('Sample Corporation'),
'paymentinfo': _('Please transfer money to this bank account: 9999-9999-9999-9999')
}
def generate_order_url(self, code, secret):
return build_absolute_uri('presale:event.order', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
'order': code,
'secret': secret
})
# get all supported placeholders with dummy values
def placeholders(self, item):
supported = {}
local_data = self.localized_data()
for key in self.items.get(item):
supported[key] = self.base_data.get(key) if key in self.base_data else local_data.get(key)
return self.SafeDict(supported)
def post(self, request, *args, **kwargs):
preview_item = request.POST.get('item', '')
if preview_item not in self.items:
return HttpResponseBadRequest(_('invalid item'))
regex = r"^" + re.escape(preview_item) + r"_(?P<idx>[\d+])$"
msgs = {}
for k, v in request.POST.items():
# only accept allowed fields
matched = re.search(regex, k)
if matched is not None:
idx = matched.group('idx')
if idx in self.supported_locale:
with translation.override(self.supported_locale[idx]):
msgs[self.supported_locale[idx]] = v.format_map(self.placeholders(preview_item))
return JsonResponse({
'item': preview_item,
'msgs': msgs
})
class TicketSettingsPreview(EventPermissionRequiredMixin, View):
permission = 'can_change_settings'