forked from CGM_Public/pretix_original
Fix #306 -- Add HTML multipart version to emails
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user