Files
pretix_original/src/pretix/base/services/mail.py
T
Ian WilliamsandGitHub 847d409a00 Syncing fork to upstream (#1)
Sync master with master of pretix/pretix@300f8f6

* Automatically sort new products to the end

* Drop "squash your commits" from the dev guide

* Add variation descriptions and allow to order addons

* Link to Django's runserver options in dev docs

* Allow <br> tags in rich text

* Copy from event: deal with deleted items

* Make validate_cart useful together with addons

* Fix collapsing panels in the addon choice step

* Button text change if addons are present

* Update translations

* Squash migrations and bump version

* Ticket PDFs: Do not hide attendee name if code is hidden

* Add a user guide on payments

* Link PayPal and Stripe documentation in the respective forms

* Hide payment fees if they are all equal to 0.00

* Refs #39 -- New concept of "teams" (#478)

* New models

* CRUD UI

* UI for adding/removing team members

* Log display for teams

* Fix invitations, move frontend

* Drop old models (incomplete)

* Drop more old stuff

* Drop even more old stuff

* Fix tests

* Fix permission test

* flake8 fix

* Add tests fore the new code

* Rebase migrations

* Fix typo in method name

* Update translations

* Force ordering of events on dashboard

* Fix typos in events

* Prepare the pretixdroid API for an async mode in the app

* Pretixdroid tests: Ignore microseconds (chopped by mysql)

* pretixdroid API: Add related lookups

* Add idempotenty nonces to pretixdroid API

* pretixdroid: force-accepting unpaids and time display

* Marked webfonts as binary files (#487)

Webfonts now listed as binary in `.gitattributes`.
Works on pretix/pretix#486

* Fix #456 -- Allow products to be excluded from ticket-generation (#483)

* Added non-admission setting to event

`ticket_download_nonadm` now setting in storage. Still need logic for
order page/PDF generation.
Works on pretix/pretix#456.

* Download button considers `ticket_download_nonadm`

Modified Django tags to look at item admission attribute and
`ticket_download_nonadm` setting.
Works on pretix/pretix#456.

* Ticket output for non-admission disabled

PDFs/etc. will only be permitted/generated for items with the
`admission` attribute, or if the `ticket_download_nonadm` event setting
is true. Applies to single and whole-order ticket downloads.
Works on pretix/pretix#456.

* Fixed product exclusion in PDF output

Forgot PDF output was a plugin, now includes same check as base
`BaseTicketOutput.generate_order`.
Works on pretix/pretix#456

* Mail signature (#485)

* added signature field -- no function yet

* added mail signature feature

* fixed style issue

* fixed problem with signature default

* added unit test for mail signatures

* added unit test for mail signatures

* [WIP] Fix #447 -- Sendmail plugin: Create new mail based on an old one (#476)

* send old email content to the new one

* error key event

* test commit

* query bad ID

* query bad ID

* query bad ID

* query bad ID

* Update pretixdroid API version

* Refs #447 -- Extend copying old mails to subject and receipients

* Fixed bugs and added test for date range rendering (#488)

* fixed bug for same dates, added unit check for daterange

* fixed local language override in unit test

* Fix #297 -- pretixdroid: Show metrics in the control panel (#481)

* add checkin status page

add dashboard widget
add checkin page under orders

* modify checkin logic

added new fields in checkin page
added filter items

* add tests for checkins & minor improvement

* support addin_product & noadm setting logic

* remove name ordering check test case

* Fix #379 -- Add logo to event organizers (#431)

* [WIP] Add logo to event organizers.

* Fix indentation issues.

* Refactor code

Refactor code

Refactor code

* Add new migration

* Take files into account for organizer sform (settings form)

* Fix grammer

* Make bootstrap form errors specific to each fieldset

* Display logo on organizer's page

* Fix PR issues

Fix PR issues

Fix PR issues

* Reorder imports

* Remove conflicting migration

* Fix rebase conflict

* Fix #41 -- Drag-and-drop ticket editor

Undo/redo

Useful toolbox

Font selection

Add text content

Use hex for colors

JS-side dump and load

Save

Load layout, proper undo/redo

First steps to Python rendering

More PDF rendering

Copy and paste

Buttons for keyboard actions

Splash Screen

Block unbeforeunload in dirty state

Remove debugging output

Preview

Upload new PDFs via the editor

Fix bugs during PDF reload, link in settings form

New default ticket

Add OpenSans BI

Custom fonts, fix tests

* Added bootstrap-colorpicker

* Allow inline PDF display in CSP header

* Add fontpack to list of plugins

* Update German translation

* Add ticketoutputpdf's assets to MANIFEST.i

* Fix migration of old ticket styles

* Fix iCal download URL

* Multi-line location field, new field for admission time

* Admission date and time in editor

* Remove icon from "add to calendar"

* Try to fix PDF display problems in Safari

* Proxy cachedfiles that are used as editor previews

* Check Event.presale_is_running in more places

* Fix CSS generation with an empty color field

* Fix missing placeholders and reformat the sendmail view

* Fix bug that lead to wrong payment amount when switching payment method to PayPal later

* Update translation
2017-05-11 09:22:03 -07:00

170 lines
6.4 KiB
Python

import logging
from typing import Any, Dict, List, Union
import bleach
import cssutils
import markdown
from django.conf import settings
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import get_template
from django.utils.translation import ugettext as _
from i18nfield.strings import LazyI18nString
from inlinestyler.utils import inline_css
from pretix.base.i18n import language
from pretix.base.models import Event, InvoiceAddress, Order
from pretix.celery_app import app
from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger('pretix.base.mail')
INVALID_ADDRESS = 'invalid-pretix-mail-address'
cssutils.log.setLevel(logging.CRITICAL)
class TolerantDict(dict):
def __missing__(self, key):
return key
class SendMailException(Exception):
pass
def mail(email: str, subject: str, template: Union[str, LazyI18nString],
context: Dict[str, Any]=None, event: Event=None, locale: str=None,
order: Order=None, headers: dict=None):
"""
Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation.
:param email: The email address of the recipient
:param subject: The email subject. Should be localized to the recipients's locale or a lazy object that will be
localized by being casted to a string.
:param template: The filename of a template to be used. It will be rendered with the locale given in the locale
argument and the context given in the next argument. Alternatively, you can pass a LazyI18nString and
``context`` will be used as the argument to a Python ``.format_map()`` call on the template.
:param context: The context for rendering the template (see ``template`` parameter)
:param event: The event this email is related to (optional). If set, this will be used to determine the sender,
a possible prefix for the subject and the SMTP server that should be used to send this email.
:param order: The order this email is related to (optional). If set, this will be used to include a link to the
order below the email.
:param headers: A dict of custom mail headers to add to the mail
:param locale: The locale to be used while evaluating the subject and the template
:raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean
that the email has been sent, just that it has been queued by the email backend.
"""
if email == INVALID_ADDRESS:
return
headers = headers or {}
with language(locale):
if isinstance(context, dict) and order:
try:
context.update({
'invoice_name': order.invoice_address.name,
'invoice_company': order.invoice_address.company
})
except InvoiceAddress.DoesNotExist:
context.update({
'invoice_name': '',
'invoice_company': ''
})
if isinstance(template, LazyI18nString):
body = str(template)
if context:
body = body.format_map(TolerantDict(context))
body_md = bleach.linkify(bleach.clean(markdown.markdown(body), tags=bleach.ALLOWED_TAGS + [
'p',
]))
else:
tpl = get_template(template)
body = tpl.render(context)
body_md = bleach.linkify(markdown.markdown(body))
sender = event.settings.get('mail_from') if event else settings.MAIL_FROM
subject = str(subject)
body_plain = body
htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL,
'body': body_md,
'color': '#8E44B3'
}
if event:
htmlctx['event'] = event
htmlctx['color'] = event.settings.primary_color
if event.settings.mail_from == settings.DEFAULT_FROM_EMAIL and event.settings.contact_mail:
headers['Reply-To'] = event.settings.contact_mail
prefix = event.settings.get('mail_prefix')
if prefix:
subject = "[%s] %s" % (prefix, subject)
body_plain += "\r\n\r\n-- \r\n"
signature = str(event.settings.get('mail_text_signature'))
if signature:
signature = signature.format(event=event.name)
signature_md = signature.replace('\n', '<br>\n')
signature_md = bleach.linkify(bleach.clean(markdown.markdown(signature_md), tags=bleach.ALLOWED_TAGS + ['p', 'br']))
htmlctx['signature'] = signature_md
body_plain += signature
body_plain += "\r\n\r\n-- \r\n"
body_plain += _(
"You are receiving this email because you placed an order for {event}."
).format(event=event.name)
if order:
htmlctx['order'] = order
body_plain += "\r\n"
body_plain += _(
"You can view your order details at the following URL:\n{orderurl}."
).replace("\n", "\r\n").format(
event=event.name, orderurl=build_absolute_uri(
order.event, 'presale:event.order', kwargs={
'order': order.code,
'secret': order.secret
}
)
)
body_plain += "\r\n"
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
def mail_send_task(to: List[str], subject: str, body: str, html: str, sender: str,
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:
event = Event.objects.get(id=event)
backend = event.get_mail_backend()
else:
backend = get_connection(fail_silently=False)
try:
backend.send_messages([email])
except Exception:
logger.exception('Error sending email')
raise SendMailException('Failed to send an email to {}.'.format(to))
def mail_send(*args, **kwargs):
mail_send_task.apply_async(args=args, kwargs=kwargs)