Document setting storage and mail sending

This commit is contained in:
Raphael Michel
2016-05-29 20:34:20 +02:00
parent ead7d8ed78
commit e4b1cf8d6f
6 changed files with 122 additions and 28 deletions
+7
View File
@@ -0,0 +1,7 @@
Sending Email
=============
As pretix allows event organizers to configure how they want to sent emails to their users in multiple ways.
Therefore, all emails should be sent throught the following function:
.. autofunction:: pretix.base.services.mail.mail
+3 -1
View File
@@ -11,6 +11,8 @@ Contents:
:maxdepth: 2 :maxdepth: 2
models models
background
urlconfig urlconfig
i18n i18n
settings
background
email
@@ -0,0 +1,52 @@
Settings storage
================
pretix is highly configurable and therefore needs to store a lot of per-event and per-organizer settings.
Those settings are stored in the database and accessed through a ``SettingsProxy`` instance. You can obtain
such an instance from any event or organizer model instance by just accessing ``event.settings``.
Any setting consists of a key and a value. By default, all settings are strings, but the settings system
includes serializers for serializing the following types:
* Built-in types: ``int``, ``float``, ``decimal.Decimal``, ``dict``, ``list``, ``bool``
* ``datetime.date``, ``datetime.datetime``, ``datetime.time``
* ``LazyI18nString``
* References to Django ``File`` objects that are already stored in a storage backend
* References to model instances
In code, we recommend to always use the ``.get()`` method on the settings object to access a value, but for
convenience in templates you can also access settings values at ``settings[name]`` and ``settings.name``.
.. autoclass:: pretix.base.settings.SettingsProxy
:members: get, set, delete, freeze
To avoid naming conflicts, plugins are requested to prefix all settings they use with the name of the plugin
or something unique, e.g. ``payment.paypal.api_key``. To reduce redundant typing of this prefix, we provide
another helper class:
.. autoclass:: pretix.base.settings.SettingsSandbox
When implementing e.g. a payment or export provider, you do not event need to create this sandbox yourself,
you will just be passed a sandbox object with a prefix generated from your provider name.
Forms
-----
We also provide a base class for forms that allow the modification of settings:
.. autoclass:: pretix.base.forms.SettingsForm
:members: save
You can simply use it like this::
class EventSettingsForm(SettingsForm):
show_date_to = forms.BooleanField(
label=_("Show event end date"),
help_text=_("If disabled, only event's start date will be displayed to the public."),
required=False
)
payment_term_days = forms.IntegerField(
label=_('Payment term in days'),
help_text=_("The number of days after placing an order the user has to pay to "
"preserve his reservation."),
)
+9 -1
View File
@@ -83,7 +83,12 @@ class I18nInlineFormSet(BaseInlineFormSet):
class SettingsForm(forms.Form): class SettingsForm(forms.Form):
""" """
This form is meant to be used for modifying Event- or OrganizerSettings This form is meant to be used for modifying Event- or OrganizerSettings. It takes
care of loading the current values of the fields and saving the field inputs to the
settings storage. It also deals with setting the available languages for internationalized
fields.
:param obj: The event or organizer object which should be used for the settings storage
""" """
BOOL_CHOICES = ( BOOL_CHOICES = (
('False', _('disabled')), ('False', _('disabled')),
@@ -99,6 +104,9 @@ class SettingsForm(forms.Form):
field.widget.enabled_langcodes = self.obj.settings.get('locales') field.widget.enabled_langcodes = self.obj.settings.get('locales')
def save(self): def save(self):
"""
Performs the save operation
"""
for name, field in self.fields.items(): for name, field in self.fields.items():
value = self.cleaned_data[name] value = self.cleaned_data[name]
if isinstance(value, UploadedFile): if isinstance(value, UploadedFile):
+18 -15
View File
@@ -3,7 +3,6 @@ import logging
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMessage, get_connection from django.core.mail import EmailMessage, get_connection
from django.template.loader import get_template from django.template.loader import get_template
from django.utils import translation
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from typing import Any, Dict from typing import Any, Dict
@@ -22,22 +21,26 @@ class TolerantDict(dict):
def mail(email: str, subject: str, template: str, def mail(email: str, subject: str, template: str,
context: Dict[str, Any]=None, event: Event=None, locale: str=None): context: Dict[str, Any]=None, event: Event=None, locale: str=None):
""" """
Sends out an email to a user. Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation.
:param email: The e-mail this should be sent to. :param email: The e-mail address of the recipient.
: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 ``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 :param subject: The e-mail subject. Should be localized to the recipients's locale or a lazy object that will be
address, ``True`` otherwise. ``True`` does not necessarily mean that localized by being casted to a string.
the email has been sent, just that it has been queued by the e-mail
backend. :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()`` 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 locale: The locale to be used while evaluating the subject and the template.
:return: ``False`` on obvious, immediate failures, ``True`` otherwise. ``True`` does not necessarily mean that
the email has been sent, just that it has been queued by the e-mail backend.
""" """
with language(locale): with language(locale):
if isinstance(template, LazyI18nString): if isinstance(template, LazyI18nString):
+33 -11
View File
@@ -213,7 +213,7 @@ class SettingsProxy:
This objects allows convenient access to settings stored in the This objects allows convenient access to settings stored in the
EventSettings/OrganizerSettings database model. It exposes all settings as EventSettings/OrganizerSettings database model. It exposes all settings as
properties and it will do all the nasty inheritance and defaults stuff for properties and it will do all the nasty inheritance and defaults stuff for
you. It will return None for non-existing properties. you.
""" """
def __init__(self, obj: Model, parent: Optional[Model]=None, type=None): def __init__(self, obj: Model, parent: Optional[Model]=None, type=None):
@@ -232,7 +232,11 @@ class SettingsProxy:
def _flush(self) -> None: def _flush(self) -> None:
self._cached_obj = None self._cached_obj = None
def freeze(self): def freeze(self) -> dict:
"""
Returns a dictionary of all settings set for this object, including
any default values of its parents or hardcoded in pretix.
"""
settings = {} settings = {}
for key, v in DEFAULTS.items(): for key, v in DEFAULTS.items():
settings[key] = self._unserialize(v['default'], v['type']) settings[key] = self._unserialize(v['default'], v['type'])
@@ -297,11 +301,17 @@ class SettingsProxy:
raise TypeError('Unable to serialize %s into a setting.' % str(type(value))) raise TypeError('Unable to serialize %s into a setting.' % str(type(value)))
def get(self, key: str, default: Any=None, as_type: type=None): def get(self, key: str, default=None, as_type: type=None):
""" """
Get a setting specified by key 'key'. Normally, settings are strings, but Get a setting specified by key ``key``. Normally, settings are strings, but
if you put non-strings into the settings object, you can request unserialization if you put non-strings into the settings object, you can request unserialization
by specifying 'as_type' by specifying ``as_type``. If the key does not have a harcdoded type in the pretix source,
omitting ``as_type`` always will get you a string.
If the setting with the specified name does not exist on this object, any parent object
will be queried (e.g. the organizer of an event). If still no value is found, a default
value hardcoded will be returned if one exists. If not, the value of the ``default`` argument
will be returned instead.
""" """
if as_type is None and key in DEFAULTS: if as_type is None and key in DEFAULTS:
as_type = DEFAULTS[key]['type'] as_type = DEFAULTS[key]['type']
@@ -336,6 +346,9 @@ class SettingsProxy:
self.set(key, value) self.set(key, value)
def set(self, key: str, value: Any) -> None: def set(self, key: str, value: Any) -> None:
"""
Stores a setting to the database of this object.
"""
if key in self._cache(): if key in self._cache():
s = self._cache()[key] s = self._cache()[key]
else: else:
@@ -347,9 +360,15 @@ class SettingsProxy:
def __delattr__(self, key: str) -> None: def __delattr__(self, key: str) -> None:
if key.startswith('_'): if key.startswith('_'):
return super().__delattr__(key) return super().__delattr__(key)
return self.__delitem__(key) self.delete(key)
def __delitem__(self, key: str) -> None: def __delitem__(self, key: str) -> None:
self.delete(key)
def delete(self, key: str) -> None:
"""
Deletes a setting from this object's storage.
"""
if key in self._cache(): if key in self._cache():
self._cache()[key].delete() self._cache()[key].delete()
del self._cache()[key] del self._cache()[key]
@@ -357,13 +376,16 @@ class SettingsProxy:
class SettingsSandbox: class SettingsSandbox:
""" """
Transparently proxied access to event settings, handling your domain- Transparently proxied access to event settings, handling your prefixes for you.
prefixes for you.
:param typestr: The first part of the pretix, e.g. ``plugin``
:param key: The prefix, e.g. the name of your plugin
:param obj: The event or organizer that should be queried
""" """
def __init__(self, type: str, key: str, event: Model): def __init__(self, typestr: str, key: str, obj: Model):
self._event = event self._event = obj
self._type = type self._type = typestr
self._key = key self._key = key
def _convert_key(self, key: str) -> str: def _convert_key(self, key: str) -> str: