mirror of
https://github.com/pretix/pretix.git
synced 2026-08-19 12:16:26 +00:00
Improved and documented i18n and background tasks
This commit is contained in:
@@ -29,6 +29,9 @@ on the type of navigation. You should also return an ``active`` key with a boole
|
||||
set to ``True``, when this item should be marked as active. The ``request`` object
|
||||
will have an attribute ``event``.
|
||||
|
||||
If you use this, you should read the documentation on :ref:`how to deal with URLs <urlconf>`
|
||||
in pretix.
|
||||
|
||||
``pretix.control.signals.nav_event``
|
||||
The sidebar navigation when the admin has selected an event.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Plugin API
|
||||
==========
|
||||
Plugin hooks
|
||||
============
|
||||
|
||||
Contents:
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
Background tasks
|
||||
================
|
||||
|
||||
pretix provides the ability to run all longer-running tasks like generating ticket files or sending emails
|
||||
in a background thread instead of the webserver process. We use the well-established `Celery`_ project to
|
||||
implement this. However, as celery requires running a task queue like RabbitMQ and a result storage such as
|
||||
Redis to work efficiently, we don't like to *depend* on celery being available to make small-scale installations
|
||||
of pretix more straightforward. For this reason, the "background" in "background task" is always optional.
|
||||
|
||||
The Django settings variable ``settings.HAS_CELERY`` provides information on whether celery is configured
|
||||
in the current installation.
|
||||
|
||||
Implementing a task
|
||||
-------------------
|
||||
|
||||
A common pattern for implementing "optionally-asynchronous" tasks that can be seen a lot in ``pretix.base.services``
|
||||
looks like this::
|
||||
|
||||
def my_task(argument1, argument2):
|
||||
# Important: All arguments and return values need to be serializable into JSON.
|
||||
# Do not use model instances, use their primary keys instead!
|
||||
pass # do your work here
|
||||
|
||||
|
||||
if settings.HAS_CELERY:
|
||||
# Transform this into a background task
|
||||
from pretix.celery import app # Important: Do not import this unconditionally!
|
||||
|
||||
my_task_async = app.task(export)
|
||||
|
||||
def my_task(*args, **kwargs):
|
||||
my_task_async.apply_async(args=args, kwargs=kwargs)
|
||||
|
||||
This explicit declaration method also allows you to place some custom retry logic etc. in the asynchronous version.
|
||||
|
||||
Tasks in the request-response flow
|
||||
----------------------------------
|
||||
|
||||
If your user needs to wait for the response of the asynchronous task, there are helpers available in ``pretix.presale``
|
||||
that will probably move to ``pretix.base`` at some point. They consist of the view mixin ``AsyncAction`` that allows
|
||||
you to easily write a view that kicks off and waits for an asynchronous task. ``AsyncAction`` will determine whether
|
||||
to run the task asynchronously or not and will do some magic to look nice for users with and without JavaScript support.
|
||||
A usage example taken directly from the code is::
|
||||
|
||||
class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View):
|
||||
"""
|
||||
A view that executes a task asynchronously. A POST request will kick of the
|
||||
task into the background or run it in the foreground, if celery is not installed.
|
||||
In the former case, subsequent GET calls can be used to determinine the current
|
||||
status of the task.
|
||||
"""
|
||||
|
||||
task = cancel_order # The task to be used, defined like above
|
||||
|
||||
def get_success_url(self, value):
|
||||
"""
|
||||
Returns the URL the user will be redirected to if the task succeeded.
|
||||
"""
|
||||
return self.get_order_url()
|
||||
|
||||
def get_error_url(self):
|
||||
"""
|
||||
Returns the URL the user will be redirected to if the task failed.
|
||||
"""
|
||||
return self.get_order_url()
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Will be called while handling a POST request. This should process the
|
||||
request arguments in some way and call ``self.do`` with the task arguments
|
||||
to kick of the task.
|
||||
"""
|
||||
if not self.order:
|
||||
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
||||
return self.do(self.order.pk)
|
||||
|
||||
def get_error_message(self, exception):
|
||||
"""
|
||||
Returns the message that will be shown to the user if the task has failed.
|
||||
"""
|
||||
if isinstance(exception, dict) and exception['exc_type'] == 'OrderError':
|
||||
return gettext(exception['exc_message'])
|
||||
elif isinstance(exception, OrderError):
|
||||
return str(exception)
|
||||
return super().get_error_message(exception)
|
||||
|
||||
On the client side, this can be used by simply adding a ``data-asynctask`` attribute to an HTML form. This will enable
|
||||
AJAX sending of the form and display a loading indicator::
|
||||
|
||||
<form method="post" data-asynctask
|
||||
action="{% eventurl request.event "presale:event.order.cancel.do" … %}">
|
||||
{% csrf_token %}
|
||||
...
|
||||
</form>
|
||||
|
||||
.. _Celery: http://www.celeryproject.org/
|
||||
@@ -0,0 +1,72 @@
|
||||
Internationalization
|
||||
====================
|
||||
|
||||
One of pretix' major selling points is it's multi-language capability. We make heavy use of Django's
|
||||
`translation features`_ that are built upon `GNU gettext`_. However, Django does not provide a standard
|
||||
way to translate *user-generated content*. In our case, we need to translate strings like product names
|
||||
or event descriptions, so we need event organizers to be able to fill in all fields in multiple languages
|
||||
at the same time.
|
||||
|
||||
.. note:: Implementing object-level translation in a relational database is a task that requires some difficult
|
||||
trade-off. We decided for a design that is not elegant on the database level (as it violates the `1NF`_) and
|
||||
makes searching in the respective database fields very hard, but allows for a simple design on the ORM level
|
||||
and adds only minimal performance overhead.
|
||||
|
||||
All classes and functions introduced in this document are located in ``pretix.base.i18n`` if not stated otherwise.
|
||||
|
||||
Database storage
|
||||
----------------
|
||||
|
||||
pretix provides two custom model field types that allow you to work with localized strings: ``I18nCharField`` and
|
||||
``I18nTextField``. Both of them are stored in the database as a ``TextField`` internally, they only differ in the
|
||||
default form widget that is used by ``ModelForm``.
|
||||
|
||||
Yes, we know that this has negative impact on performance when indexing or searching them, but as mentioned above,
|
||||
within pretix this is not used in places that need to be searched. Lookups are currently not even implemented on these
|
||||
fields. In the database, the strings will be stored as a JSON-encoded mapping of language codes to strings.
|
||||
|
||||
Whenever you interact with those fields, you will either provide or receive an instance of the following class:
|
||||
|
||||
.. autoclass:: pretix.base.i18n.LazyI18nString
|
||||
:members: __init__, localize, __str__
|
||||
|
||||
|
||||
Forms
|
||||
-----
|
||||
|
||||
We provide i18n-aware versions of the respective form fields and widgets: ``I18nFormField`` with the ``I18nTextInput``
|
||||
and ``I18nTextarea`` widgets. They transparently allow you to use ``LazyI18nString`` values in forms and render text
|
||||
inputs for multiple languages.
|
||||
|
||||
.. autoclass:: pretix.base.i18n.I18nFormField
|
||||
|
||||
To easily limit the displayed languages to the languages relevant to an event, there is a custom ``ModelForm`` subclass
|
||||
that deals with this for you:
|
||||
|
||||
.. autoclass:: pretix.base.forms.I18nModelForm
|
||||
|
||||
There are equivalents for ``BaseModelFormSet`` and ``BaseInlineFormSet``:
|
||||
|
||||
.. autoclass:: pretix.base.forms.I18nFormSet
|
||||
|
||||
.. autoclass:: pretix.base.forms.I18nInlineFormSet
|
||||
|
||||
Useful utilities
|
||||
----------------
|
||||
|
||||
The ``i18n`` module contains a few more useful utilities, starting with simple lazy-evaluation wrappers for formatted
|
||||
numbers and dates, ``LazyDate`` and ``LazyNumber``. There also is a ``LazyLocaleException`` base class that provides
|
||||
exceptions with gettext-localized exception messages.
|
||||
|
||||
Last, but definitely not least, we have the ``language`` context manager that allows you to execute a piece of code with
|
||||
a different locale::
|
||||
|
||||
with language('de'):
|
||||
render_mail_template()
|
||||
|
||||
This is very useful e.g. when sending an email to a user that has a different language than the user performing the
|
||||
action that causes the mail to be sent.
|
||||
|
||||
.. _translation features: https://docs.djangoproject.com/en/1.9/topics/i18n/translation/
|
||||
.. _GNU gettext: https://www.gnu.org/software/gettext/
|
||||
.. _1NF: https://en.wikipedia.org/wiki/First_normal_form
|
||||
@@ -0,0 +1,16 @@
|
||||
Implementation and Utilities
|
||||
============================
|
||||
|
||||
This chapter describes the various inner workings that power pretix, most of them living in ``pretix.base``.
|
||||
If you want to develop around pretix' core or advanced plugins, this aims to describe everything you absolutely
|
||||
need to know.
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
models
|
||||
background
|
||||
urlconfig
|
||||
i18n
|
||||
@@ -1,10 +1,10 @@
|
||||
.. highlight:: python
|
||||
:linenothreshold: 5
|
||||
|
||||
Data models
|
||||
===========
|
||||
Data model
|
||||
==========
|
||||
|
||||
Pretix provides the following data(base) models. Every model and every model method or field that is not
|
||||
pretix provides the following data(base) models. Every model and every model method or field that is not
|
||||
documented here is considered private and should not be used by third-party plugins, as it may change
|
||||
without advance notice.
|
||||
|
||||
@@ -10,9 +10,8 @@ Contents:
|
||||
setup
|
||||
structure
|
||||
contribution/index
|
||||
models
|
||||
implementation/index
|
||||
api/index
|
||||
urlconfig
|
||||
|
||||
.. TODO::
|
||||
Document settings objects, ItemVariation objects, form fields.
|
||||
Reference in New Issue
Block a user