Fix #306 -- Add HTML multipart version to emails

This commit is contained in:
Raphael Michel
2017-01-04 13:31:40 +01:00
parent 954af1de3d
commit 9d08e23a48
4 changed files with 206 additions and 9 deletions
+1
View File
@@ -1,4 +1,5 @@
import json import json
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
+36 -9
View File
@@ -1,10 +1,14 @@
import logging import logging
from typing import Any, Dict from typing import Any, Dict
import bleach
import cssutils
import markdown
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMessage, get_connection from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import get_template from django.template.loader import get_template
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from inlinestyler.utils import inline_css
from pretix.base.i18n import LazyI18nString, language from pretix.base.i18n import LazyI18nString, language
from pretix.base.models import Event, Order from pretix.base.models import Event, Order
@@ -13,6 +17,7 @@ from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger('pretix.base.mail') logger = logging.getLogger('pretix.base.mail')
INVALID_ADDRESS = 'invalid-pretix-mail-address' INVALID_ADDRESS = 'invalid-pretix-mail-address'
cssutils.log.setLevel(logging.CRITICAL)
class TolerantDict(dict): class TolerantDict(dict):
@@ -63,25 +68,42 @@ def mail(email: str, subject: str, template: str,
body = str(template) body = str(template)
if context: if context:
body = body.format_map(TolerantDict(context)) body = body.format_map(TolerantDict(context))
body_md = bleach.linkify(bleach.clean(markdown.markdown(body), tags=bleach.ALLOWED_TAGS + [
'p',
]))
else: else:
tpl = get_template(template) tpl = get_template(template)
body = tpl.render(context) body = tpl.render(context)
body_md = bleach.linkify(markdown.markdown(body))
sender = event.settings.get('mail_from') if event else settings.MAIL_FROM sender = event.settings.get('mail_from') if event else settings.MAIL_FROM
subject = str(subject) subject = str(subject)
body_plain = body
htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL,
'body': body_md,
'color': '#8E44B3'
}
if event: if event:
htmlctx['event'] = event
htmlctx['color'] = event.settings.primary_color
prefix = event.settings.get('mail_prefix') prefix = event.settings.get('mail_prefix')
if prefix: if prefix:
subject = "[%s] %s" % (prefix, subject) subject = "[%s] %s" % (prefix, subject)
body += "\r\n\r\n-- \r\n" body_plain += "\r\n\r\n-- \r\n"
body += _( body_plain += _(
"You are receiving this email because you placed an order for {event}." "You are receiving this email because you placed an order for {event}."
).format(event=event.name) ).format(event=event.name)
if order: if order:
body += "\r\n" htmlctx['order'] = order
body += _( body_plain += "\r\n"
body_plain += _(
"You can view your order details at the following URL:\n{orderurl}." "You can view your order details at the following URL:\n{orderurl}."
).replace("\n", "\r\n").format( ).replace("\n", "\r\n").format(
event=event.name, orderurl=build_absolute_uri( event=event.name, orderurl=build_absolute_uri(
@@ -91,13 +113,18 @@ def mail(email: str, subject: str, template: str,
} }
) )
) )
body += "\r\n" body_plain += "\r\n"
return mail_send([email], subject, body, sender, event.id if event else None, headers)
tpl = get_template('pretixbase/email/plainwrapper.html')
body_html = tpl.render(htmlctx)
return mail_send([email], subject, body_plain, body_html, sender, event.id if event else None, headers)
@app.task @app.task
def mail_send_task(to: str, subject: str, body: str, sender: str, event: int=None, headers: dict=None) -> bool: def mail_send_task(to: str, subject: str, body: str, html: str, sender: str,
email = EmailMessage(subject, body, sender, to=to, headers=headers) event: int=None, headers: dict=None) -> bool:
email = EmailMultiAlternatives(subject, body, sender, to=to, headers=headers)
email.attach_alternative(inline_css(html), "text/html")
if event: if event:
event = Event.objects.get(id=event) event = Event.objects.get(id=event)
backend = event.get_mail_backend() backend = event.get_mail_backend()
@@ -0,0 +1,166 @@
{% load eventurl %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=false">
</head>
<style type="text/css">
body {
background-color: #e8e8e8;
background-position: top;
background-repeat: repeat-x;
font-family: "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
color: #333;
margin: 0;
}
.header h1 {
margin-top: 20px;
margin-bottom: 5px;
}
.header h1 a {
text-decoration: none;
}
a {
color: {{ color }};
font-weight: bold;
}
a:hover, a:focus {
color: {{ color }};
text-decoration: underline;
}
a:hover, a:active {
outline: 0;
}
p {
margin: 0 0 10px;
/* These are technically the same, but use both */
overflow-wrap: break-word;
word-wrap: break-word;
-ms-word-break: break-all;
/* This is the dangerous one in WebKit, as it breaks things wherever */
word-break: break-all;
/* Instead use this non-standard one: */
word-break: break-word;
/* Adds a hyphen where the word breaks, if supported (No Blink) */
-ms-hyphens: auto;
-moz-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto;
}
.footer {
padding: 10px;
text-align: center;
font-size: 12px;
}
.content {
padding: 8px 18px 8px;
}
::selection {
background: {{ color }};
color: #FFF;
}
table {
width: 90%;
max-width: 900px;
border-spacing: 0px;
border-collapse: separate;
margin: auto;
}
@media (max-width: 480px) {
.header h1 {
font-size: 19px;
line-height: 24px;
margin: 0 9px 3px 0;
border-radius: 5px 5px;
-webkit-border-radius: 5px 5px;
-moz-border-radius: 5px 5px;
}
.header h1 a {
padding: 3px 9px;
display: block;
}
.header {
margin: 0;
padding: 12px 0 8px;
}
}
td.containertd {
background-color: #FFFFFF;
border: 1px solid #cccccc;
}
{% block addcss %}{% endblock %}
</style>
<body>
<table>
<tr>
<td class="header" background="">
{% if event %}
<h1><a href="{% eventurl event "presale:event.index" %}" target="_blank">{{ event.name }}</a></h1>
{% else %}
<h1><a href="{{ site_url }}" target="_blank">{{ site }}</a></h1>
{% endif %}
</td>
</tr>
<tr>
<td class="containertd">
<div class="content">
{{ body|safe }}
</div>
</td>
</tr>
{% if order %}
<tr>
<td class="gap"></td>
</tr>
<tr>
<td class="order containertd">
<div class="content">
{% trans "You are receiving this email because you placed an order for the following event:" %}<br>
<strong>{% trans "Event:" %}</strong> {{ event.name }}<br>
<strong>{% trans "Order code:" %}</strong> {{ order.code }}<br>
<strong>{% trans "Order date:" %}</strong> {{ order.datetime|date:"SHORT_DATE_FORMAT" }}<br>
<a href="{% eventurl event "presale:event.order" order=order.code secret=order.secret %}">
{% trans "View order details" %}
</a>
</div>
</td>
</tr>
{% endif %}
<tr>
<td class="footer">
<div>
{% with 'target="blank" href="https://pretix.eu"'|safe as a_attr %}
{% blocktrans trimmed %}
powered by <a {{ a_attr }}>pretix</a>
{% endblocktrans %}
{% endwith %}
</div>
</td>
</tr>
</table>
<br/>
<br/>
</body>
</html>
+3
View File
@@ -18,6 +18,7 @@ django-formtools==1.0
# https://github.com/celery/celery/pull/3199 # https://github.com/celery/celery/pull/3199
git+https://github.com/pretix/celery.git@pretix#egg=celery git+https://github.com/pretix/celery.git@pretix#egg=celery
django-statici18n==1.2.* django-statici18n==1.2.*
inlinestyler==0.2.*
# Deployment / static file compilation requirements # Deployment / static file compilation requirements
BeautifulSoup4 BeautifulSoup4
@@ -29,3 +30,5 @@ dj-static
csscompressor csscompressor
django-markup django-markup
markdown markdown
bleach