Fixed minor documentation errors and mistakes (#151)

This commit is contained in:
Tobias Kunze
2016-07-14 20:01:38 +02:00
committed by Raphael Michel
parent f779b70deb
commit bfc721978d
41 changed files with 231 additions and 208 deletions
+11 -11
View File
@@ -147,7 +147,7 @@ Example::
Use STARTTLS or SSL for the SMTP connection. Off by default. Use STARTTLS or SSL for the SMTP connection. Off by default.
``admins`` ``admins``
Comma-separated list of e-mail addresses that should receive a report about every error 500 thrown by pretix. Comma-separated list of email addresses that should receive a report about every error code 500 thrown by pretix.
Django settings Django settings
--------------- ---------------
@@ -160,13 +160,13 @@ Example::
debug=off debug=off
``hosts`` ``hosts``
Comma-seperated list of allowed host names for this installation. Comma-separated list of allowed host names for this installation.
Default: ``localhost`` Default: ``localhost``
``secret`` ``secret``
The secret to be used by Django for signing and verification purposes. If this The secret to be used by Django for signing and verification purposes. If this
setting is not provided, pretix will generate a random secret on the first start setting is not provided, pretix will generate a random secret on the first start
and store it in the filesystem for later usage. and will store it in the filesystem for later usage.
``debug`` ``debug``
Whether or not to run in debug mode. Default is ``False``. Whether or not to run in debug mode. Default is ``False``.
@@ -185,7 +185,7 @@ You can use an existing memcached server as pretix's caching backend::
``location`` ``location``
The location of memcached, either a host:port combination or a socket file. The location of memcached, either a host:port combination or a socket file.
If no memcached is configures, pretix will use Django's built-in local-memory caching method. If no memcached is configured, pretix will use Django's built-in local-memory caching method.
Redis Redis
@@ -199,22 +199,22 @@ to speed up various operations::
sessions=false sessions=false
``location`` ``location``
The location of memcached, as an URL of the form ``redis://[:password]@localhost:6379/0`` The location of redis, as a URL of the form ``redis://[:password]@localhost:6379/0``
or ``unix://[:password]@/path/to/socket.sock?db=0`` or ``unix://[:password]@/path/to/socket.sock?db=0``
``session`` ``session``
When this is set to true, redis will be used as the session storage. When this is set to ``True``, redis will be used as the session storage.
If no redis is configured, pretix will store sessions and locks in the database. If memcached If redis is not configured, pretix will store sessions and locks in the database. If memcached
is configured, memcached will be used for caching instead of redis. is configured, memcached will be used for caching instead of redis.
Celery task queue Celery task queue
----------------- -----------------
For processing long-running tasks asynchronously, pretix needs help of the celery task queue. For processing long-running tasks asynchronously, pretix requires the celery task queue.
For communicating between the web server and the task workers in both direction, a messaging For communication between the web server and the task workers in both direction, a messaging
queue and a result backend is needed. You can use a redis database for both directions, or queue and a result backend is needed. You can use a redis database for both directions, or
an AMQP server (e.h. RabbitMQ) as a broker and redis or your database as a result backend:: an AMQP server (e.g. RabbitMQ) as a broker and redis or your database as a result backend::
[celery] [celery]
broker=amqp://guest:guest@localhost:5672// broker=amqp://guest:guest@localhost:5672//
@@ -225,4 +225,4 @@ but as you already should have a redis instance ready for session and lock stora
redis for convenience. See the `Celery documentation`_ for more details. redis for convenience. See the `Celery documentation`_ for more details.
.. _Python documentation: https://docs.python.org/3/library/configparser.html?highlight=configparser#supported-ini-file-structure .. _Python documentation: https://docs.python.org/3/library/configparser.html?highlight=configparser#supported-ini-file-structure
.. _Celery documentation: http://docs.celeryproject.org/en/latest/configuration.html .. _Celery documentation: http://docs.celeryproject.org/en/latest/configuration.html
+13 -13
View File
@@ -18,8 +18,8 @@ modern distributions, especially on all systemd-based ones.
Requirements Requirements
------------ ------------
Please set up the following systems beforehand, we'll not explain them here (but link to installation guides Please set up the following systems beforehand, we'll not explain them here (but see these links for external
somewhere else): installation guides):
* `Docker`_ * `Docker`_
* A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for * A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for
@@ -43,8 +43,8 @@ all lines prepended with a ``$`` symbol can also be run by an unprivileged user.
Data files Data files
---------- ----------
First of all, you need a directory on your server that pretix can use to store data files and make that directory First of all, you need to create a directory on your server that pretix can use to store data files and make that
writable to the user that runs pretix inside the docker container:: directory writable to the user that runs pretix inside the docker container::
# mkdir /var/pretix-data # mkdir /var/pretix-data
# chown -R 15371:15371 /var/pretix-data # chown -R 15371:15371 /var/pretix-data
@@ -52,7 +52,7 @@ writable to the user that runs pretix inside the docker container::
Database Database
-------- --------
Next, we need a database and a database user. We create this with any kind of database managing tool or directly on Next, we need a database and a database user. We can create these with any kind of database managing tool or directly on
our database's shell, e.g. for MySQL:: our database's shell, e.g. for MySQL::
$ mysql -u root -p $ mysql -u root -p
@@ -61,15 +61,15 @@ our database's shell, e.g. for MySQL::
mysql> FLUSH PRIVILEGES; mysql> FLUSH PRIVILEGES;
Replace the asterisks with a password of your own. For MySQL, we will use a unix domain socket to connect to the Replace the asterisks with a password of your own. For MySQL, we will use a unix domain socket to connect to the
database. For PostgreSQL, be sure to configure the interface binding and your firewall such that the docker container database. For PostgreSQL, be sure to configure the interface binding and your firewall so that the docker container
can reach PostgreSQL. can reach PostgreSQL.
Redis Redis
----- -----
For caching and messaging in small-scale setups, pretix recomments using redis. In this small-scale setup we assume a For caching and messaging in small-scale setups, pretix recommends using redis. In this small-scale setup we assume a
redis instance running on the same host. To avoid the hassle about network configurations and firewalls, we recommend redis instance to be running on the same host. To avoid the hassle with network configurations and firewalls, we
connecting to redis via an unix socket. To enable redis on unix sockets, add the following to your recommend connecting to redis via a unix socket. To enable redis on unix sockets, add the following to your
``/etc/redis/redis.conf``:: ``/etc/redis/redis.conf``::
unixsocket /tmp/redis.sock unixsocket /tmp/redis.sock
@@ -85,7 +85,7 @@ Now restart redis-server::
can easily become an even worse security hole when configured slightly wrong. Read more about security can easily become an even worse security hole when configured slightly wrong. Read more about security
on the `redis website`_. on the `redis website`_.
Another possible solution is to run `redis in docker`_ and link the containers using dockers networking Another possible solution is to run `redis in docker`_ and link the containers using docker's networking
features. features.
Config file Config file
@@ -212,7 +212,7 @@ Next steps
---------- ----------
Yay, you are done! You should now be able to reach pretix at https://pretix.yourdomain.com/control/ and log in as Yay, you are done! You should now be able to reach pretix at https://pretix.yourdomain.com/control/ and log in as
*admin@localhost* with a passwort of *admin*. Don't forget to change that password! Create an organizer first, then *admin@localhost* with a password of *admin*. Don't forget to change that password! Create an organizer first, then
create an event and start selling tickets! create an event and start selling tickets!
Updates Updates
@@ -220,7 +220,7 @@ Updates
.. warning:: While we try hard not to break things, **please perform a backup before every upgrade**. .. warning:: While we try hard not to break things, **please perform a backup before every upgrade**.
Updates are fairly simple, but require at least a small downtime:: Updates are fairly simple, but require at least a short downtime::
# docker pull pretix/standalone # docker pull pretix/standalone
# systemctl restart pretix.service # systemctl restart pretix.service
@@ -238,4 +238,4 @@ Restarting the service can take a few seconds, especially if the update requires
.. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall .. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall
.. _redis website: http://redis.io/topics/security .. _redis website: http://redis.io/topics/security
.. _redis in docker: https://hub.docker.com/r/_/redis/ .. _redis in docker: https://hub.docker.com/r/_/redis/
.. _strong encryption settings: https://mozilla.github.io/server-side-tls/ssl-config-generator/ .. _strong encryption settings: https://mozilla.github.io/server-side-tls/ssl-config-generator/
+2 -2
View File
@@ -29,7 +29,7 @@ Database
Reverse proxy Reverse proxy
pretix needs to deliver some static content to your users (e.g. CSS, images, ...). While pretix is capable of pretix needs to deliver some static content to your users (e.g. CSS, images, ...). While pretix is capable of
doing this, having this handled by a proper webserver like **nginx** or **Apache** will be much faster. Also, you doing this, having this handled by a proper web server like **nginx** or **Apache** will be much faster. Also, you
need a proxying web server in front to provide SSL encryption. need a proxying web server in front to provide SSL encryption.
.. warning:: Do not ever run without SSL in production. Your users deserve encrypted connections and thanks to .. warning:: Do not ever run without SSL in production. Your users deserve encrypted connections and thanks to
@@ -50,6 +50,6 @@ Redis
* Queuing and result storage for the task worker queue * Queuing and result storage for the task worker queue
RabbitMQ RabbitMQ
RabbitMQ can be used as a more advanced queue manager for the task workers, if necessary. RabbitMQ can be used as a more advanced queue manager for the task workers if necessary.
.. _Let's Encrypt: https://letsencrypt.org/ .. _Let's Encrypt: https://letsencrypt.org/
+1 -1
View File
@@ -54,7 +54,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = 'pretix' project = 'pretix'
copyright = '2014, Raphael Michel' copyright = '2014-2016, Raphael Michel'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
+3 -4
View File
@@ -4,7 +4,7 @@
Writing an exporter plugin Writing an exporter plugin
========================== ==========================
An is a method to export the product and order data in pretix for later use in another An Exporter is a method to export the product and order data in pretix for later use in another
context. context.
In this document, we will walk through the creation of an exporter output plugin. This In this document, we will walk through the creation of an exporter output plugin. This
@@ -18,7 +18,7 @@ Exporter registration
The exporter API does not make a lot of usage from signals, however, it does use a signal to get a list of The exporter API does not make a lot of usage from signals, however, it does use a signal to get a list of
all available exporters. Your plugin should listen for this signal and return the subclass of all available exporters. Your plugin should listen for this signal and return the subclass of
``pretix.base.exporter.BaseExporter`` ``pretix.base.exporter.BaseExporter``
that we'll soon create:: that we'll provide in this plugin::
from django.dispatch import receiver from django.dispatch import receiver
@@ -36,8 +36,7 @@ The exporter class
.. class:: pretix.base.exporter.BaseExporter .. class:: pretix.base.exporter.BaseExporter
The central object of each exporter is the subclass of ``BaseExporter`` we already mentioned above. The central object of each exporter is the subclass of ``BaseExporter``.
In this section, we will discuss it's interface in detail.
.. py:attribute:: BaseExporter.event .. py:attribute:: BaseExporter.event
+2 -2
View File
@@ -60,8 +60,8 @@ Dashboards
:members: event_dashboard_widgets, user_dashboard_widgets :members: event_dashboard_widgets, user_dashboard_widgets
Displaying of log entries Display of log entries
------------------------- ----------------------
.. automodule:: pretix.base.signals .. automodule:: pretix.base.signals
:members: logentry_display :members: logentry_display
+7 -8
View File
@@ -14,7 +14,7 @@ Provider registration
The payment provider API does not make a lot of usage from signals, however, it The payment provider API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available payment providers. Your plugin does use a signal to get a list of all available payment providers. Your plugin
should listen for this signal and return the subclass of ``pretix.base.payment.BasePaymentProvider`` should listen for this signal and return the subclass of ``pretix.base.payment.BasePaymentProvider``
that we'll soon create:: that the plugin will provide::
from django.dispatch import receiver from django.dispatch import receiver
@@ -32,8 +32,7 @@ The provider class
.. class:: pretix.base.payment.BasePaymentProvider .. class:: pretix.base.payment.BasePaymentProvider
The central object of each payment provider is the subclass of ``BasePaymentProvider`` The central object of each payment provider is the subclass of ``BasePaymentProvider``.
we already mentioned above. In this section, we will discuss it's interface in detail.
.. py:attribute:: BasePaymentProvider.event .. py:attribute:: BasePaymentProvider.event
@@ -105,9 +104,9 @@ Additional views
For most simple payment providers it is more than sufficient to implement For most simple payment providers it is more than sufficient to implement
some of the :py:class:`BasePaymentProvider` methods. However, in some cases some of the :py:class:`BasePaymentProvider` methods. However, in some cases
it is necessary to introduce additional views. One example is the PayPal it is necessary to introduce additional views. One example is the PayPal
provider. It redirects the user to a paypal website in the provider. It redirects the user to a PayPal website in the
:py:meth:`BasePaymentProvider.checkout_prepare`` step of the checkout process :py:meth:`BasePaymentProvider.checkout_prepare` step of the checkout process
and provides PayPal with an URL to redirect back to. This URL points to a and provides PayPal with a URL to redirect back to. This URL points to a
view which looks roughly like this:: view which looks roughly like this::
@login_required @login_required
@@ -123,7 +122,7 @@ view which looks roughly like this::
try: try:
# Redirect back to the confirm page. We chose to save the # Redirect back to the confirm page. We chose to save the
# event ID in the user's session. We could also put this # event ID in the user's session. We could also put this
# information into an URL parameter. # information into a URL parameter.
event = Event.objects.current.get(identity=request.session['payment_paypal_event']) event = Event.objects.current.get(identity=request.session['payment_paypal_event'])
return redirect(reverse('presale:event.checkout.confirm', kwargs={ return redirect(reverse('presale:event.checkout.confirm', kwargs={
'event': event.slug, 'event': event.slug,
@@ -136,6 +135,6 @@ view which looks roughly like this::
If you do not want to provide a view of your own, you could even let PayPal If you do not want to provide a view of your own, you could even let PayPal
redirect directly back to the confirm page and handle the query parameters redirect directly back to the confirm page and handle the query parameters
inside :py:meth:`BasePaymentProvider.checkout_is_valid_session``. However, inside :py:meth:`BasePaymentProvider.checkout_is_valid_session`. However,
because some external providers (not PayPal) force you to have a *constant* because some external providers (not PayPal) force you to have a *constant*
redirect URL, it might be necessary to define custom views. redirect URL, it might be necessary to define custom views.
+6 -6
View File
@@ -6,7 +6,7 @@ Plugin basics
It is possible to extend pretix with custom Python code using the official plugin It is possible to extend pretix with custom Python code using the official plugin
API. Every plugin has to be implemented as an independent Django 'app' living API. Every plugin has to be implemented as an independent Django 'app' living
in an own python package nstalled like any other python module. There are also some in its own python package installed like any other python module. There are also some
official plugins inside the ``pretix/plugins/`` directory of your pretix installation. official plugins inside the ``pretix/plugins/`` directory of your pretix installation.
The communication between pretix and the plugins happens mostly using Django's The communication between pretix and the plugins happens mostly using Django's
@@ -16,13 +16,13 @@ on the next pages.
.. _`pluginsetup`: .. _`pluginsetup`:
To create a new plugin, create a new python package which must be a vaild `Django app`_ To create a new plugin, create a new python package which must be a valid `Django app`_
and must contain plugin metadata, as described below. and must contain plugin metadata, as described below.
The following pages go into detail about the several types of plugins currently The following pages go into detail about the several types of plugins currently
supported. While these instructions don't assume that you know a lot about pretix, supported. While these instructions don't assume that you know a lot about pretix,
they do assume that you have prior knowledge about Django (e.g. it's view layer, they do assume that you have prior knowledge about Django (e.g. its view layer,
how it's ORM works, etc.). how its ORM works, etc.).
Plugin metadata Plugin metadata
--------------- ---------------
@@ -83,7 +83,7 @@ Plugin registration
Somehow, pretix needs to know that your plugin exists at all. For this purpose, we Somehow, pretix needs to know that your plugin exists at all. For this purpose, we
make use of the `entry point`_ feature of setuptools. To register a plugin that lives make use of the `entry point`_ feature of setuptools. To register a plugin that lives
in a seperate python package, your ``setup.py`` sould contain something like this:: in a separate python package, your ``setup.py`` should contain something like this::
setup( setup(
@@ -129,4 +129,4 @@ your Django app label.
.. _Django app: https://docs.djangoproject.com/en/1.7/ref/applications/ .. _Django app: https://docs.djangoproject.com/en/1.7/ref/applications/
.. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/ .. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/
.. _namespace packages: http://legacy.python.org/dev/peps/pep-0420/ .. _namespace packages: http://legacy.python.org/dev/peps/pep-0420/
.. _entry point: https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins .. _entry point: https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins
+2 -3
View File
@@ -17,7 +17,7 @@ Output registration
The ticket output API does not make a lot of usage from signals, however, it The ticket output API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available ticket outputs. Your plugin does use a signal to get a list of all available ticket outputs. Your plugin
should listen for this signal and return the subclass of ``pretix.base.ticketoutput.BaseTicketOutput`` should listen for this signal and return the subclass of ``pretix.base.ticketoutput.BaseTicketOutput``
that we'll soon create:: that we'll provide in this plugin::
from django.dispatch import receiver from django.dispatch import receiver
@@ -35,8 +35,7 @@ The output class
.. class:: pretix.base.ticketoutput.BaseTicketOutput .. class:: pretix.base.ticketoutput.BaseTicketOutput
The central object of each ticket output is the subclass of ``BaseTicketOutput`` The central object of each ticket output is the subclass of ``BaseTicketOutput``.
we already mentioned above. In this section, we will discuss it's interface in detail.
.. py:attribute:: BaseTicketOutput.event .. py:attribute:: BaseTicketOutput.event
+16 -18
View File
@@ -19,7 +19,7 @@ The project pretix is split into several components. The main three of them are:
create and manage their events, items, orders and tickets. create and manage their events, items, orders and tickets.
**pretix.presale** **pretix.presale**
Pretixpresale is the ticket-shop itself, containing all the parts visible to the Pretixpresale is the ticket-shop itself, containing all of the parts visible to the
end user. end user.
Users and events Users and events
@@ -28,25 +28,24 @@ Users and events
Pretix is all about **events**, which are defined as something happening somewhere. Pretix is all about **events**, which are defined as something happening somewhere.
Every event is managed by the **organizer**, an abstract entity running the event. Every event is managed by the **organizer**, an abstract entity running the event.
Pretix has a concept of **users** that is used for all the people who have to log Pretix has a concept of **users** that is used for all people who have to log in to the
in to the control panel to manage one or more events. No user is required to place an control panel to manage one or more events. No user is required to place an order.
order.
Items and variations Items and variations
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
The purpose of pretix is to sell **items** (which belong to **events**) to **users**. The purpose of pretix is to sell **items** (which belong to **events**) to **users**.
An **item** is a abstract thing, popular examples being event tickets or a piece of An **item** is a abstract thing, popular examples being an event ticket or a piece of
merchandise, like 'T-shirt'. An **item** can have multiple **variations**. For example, merchandise, like 'T-shirt'. An **item** can have multiple **variations**. For example,
the **item** 'T-Shirt' could have the **variations** S', 'M' and 'L'. the **item** 'T-Shirt' could have the **variations** 'S', 'M' and 'L'.
Questions Questions
^^^^^^^^^ ^^^^^^^^^
An item can be extended using **questions**. Questions enable items to be extended by An item can be extended using **questions**. Questions enable items to be extended by
additional information which can be entered by the user. Examples of possible questions additional information which can be entered by the user. Examples of possible questions
include 'Name' or 'age'. include 'name' or 'age'.
Restriction by number Restriction by number
""""""""""""""""""""" """""""""""""""""""""
@@ -59,17 +58,16 @@ special care in the implementation to never sell more tickets than allowed, even
* Every time a user places a item in the cart, a **cart position** is created, reducing the number of * Every time a user places a item in the cart, a **cart position** is created, reducing the number of
available items in the pool by one. The position is valid for a fixed time (e.g. 30 minutes), but not available items in the pool by one. The position is valid for a fixed time (e.g. 30 minutes), but not
instantly deleted after those 30 minutes (we'll get to that). instantly deleted after those 30 minutes (we'll get to that).
* Every time a user places a binding order, the position object is replaced by an **order position** which behaves * Every time a user places a binding order, the **cart position** object is replaced by an **order position**
much the same as the cart position. It reduces the number of available item and is valid for a fixed time, this object which behaves much the same as the cart position. It reduces the number of available items and is valid
time for the configured payment term (e.g. 14 days). for a fixed time, this time for the configured payment term (e.g. 14 days).
* If the order is being paid, the **order** becomes permanent. * If the order is being paid, the **order** becomes permanent.
* Once there are no available tickets left and user A wants to buy a ticket, he can do so, as long as * Once there are no available tickets left and user A wants to buy a ticket, they can do so, as long as
there are *expired* cart position in the system. In this case, user A gets a new cart position, so that there there are *expired* cart position in the system. In this case, user A gets a new cart position. Now there are
are more cart position than available tickets and therefore have to remove one of the expired cart positions. more cart positions than available tickets and therefore we have to remove one of the expired cart positions.
However, we do not choose one by random, but keep the surplus in a way that leads to the deletion We will choose to delete the cart position of the user who tries *last* to use his cart position.
of the cart position f the user who tries *last* to use his cart position. * The same goes for orders which are not paid within the configured time frame. This policy allows the organizer to
* The same goes for orders which are not paid within the specified timeframe. This policy allows the organizer to sell as many items as possible. Moreover, it guarantees the users to get their items if they check out within the validity
sell as much items as possible. Moreover, it guarantees the users to get their items if they check out within the validity
period of their positions and pay within the validity period of their orders. It does not guarantee them anything period of their positions and pay within the validity period of their orders. It does not guarantee them anything
any longer, but it tries to be *as tolerant as possible* to users who are paying after their payment any longer, but it tries to be *as tolerant as possible* to users who are paying after their payment
period or click checkout after the expiry of their position. period or click checkout after the expiry of their position.
+3 -3
View File
@@ -20,11 +20,11 @@ on our `GitHub repository`_.
Before you do so, please `squash all your changes`_ into one single commit. Please Before you do so, please `squash all your changes`_ into one single commit. Please
use the test suite to check whether your changes break any existing features and run use the test suite to check whether your changes break any existing features and run
the code style checks to confirm you are consistent with pretix' coding style. You'll the code style checks to confirm you are consistent with pretix's coding style. You'll
find instructions on this in the :ref:`checksandtests` section of the development setup guide. find instructions on this in the :ref:`checksandtests` section of the development setup guide.
We automatically run the tests and the code style check on every pull request on Travis CI and we wont We automatically run the tests and the code style check on every pull request on Travis CI and we wont
accept any pull requets without all tests passing. However, if you don't find out *why* they are not passing, accept any pull requests without all tests passing. However, if you don't find out *why* they are not passing,
just send the pull request and tell us we'll be glad to help. just send the pull request and tell us we'll be glad to help.
If you add a new feature, please include appropriate documentation into your patch. If you fix a bug, If you add a new feature, please include appropriate documentation into your patch. If you fix a bug,
@@ -34,4 +34,4 @@ Again: If you get stuck, do not hesitate to contact any of us, or Raphael person
.. _create a pull request: https://help.github.com/articles/creating-a-pull-request/ .. _create a pull request: https://help.github.com/articles/creating-a-pull-request/
.. _GitHub repository: https://github.com/pretix/pretix .. _GitHub repository: https://github.com/pretix/pretix
.. _squash all your changes: https://davidwalsh.name/squash-commits-git .. _squash all your changes: https://davidwalsh.name/squash-commits-git
+1 -1
View File
@@ -6,7 +6,7 @@ Python code
* Basically: Follow `PEP 8`_. * Basically: Follow `PEP 8`_.
Use `flake8`_ to check for conformance problems. The project includes a setup.cfg Use `flake8`_ to check for conformance problems. The project includes a setup.cfg file
with a default configuration for flake8 that excludes migrations and other non-relevant with a default configuration for flake8 that excludes migrations and other non-relevant
code parts. It also silences a few checks, e.g. ``N802`` (function names should be lowercase) code parts. It also silences a few checks, e.g. ``N802`` (function names should be lowercase)
and increases the maximum line length to more than 79 characters. **However** you should and increases the maximum line length to more than 79 characters. **However** you should
@@ -2,7 +2,7 @@ Background tasks
================ ================
pretix provides the ability to run all longer-running tasks like generating ticket files or sending emails 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 in a background thread instead of the web server 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 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 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. of pretix more straightforward. For this reason, the "background" in "background task" is always optional.
@@ -13,8 +13,8 @@ in the current installation.
Implementing a task Implementing a task
------------------- -------------------
A common pattern for implementing "optionally-asynchronous" tasks that can be seen a lot in ``pretix.base.services`` A common pattern for implementing "optionally-asynchronous" tasks can be seen a lot in ``pretix.base.services``
looks like this:: and looks like this::
def my_task(argument1, argument2): def my_task(argument1, argument2):
# Important: All arguments and return values need to be serializable into JSON. # Important: All arguments and return values need to be serializable into JSON.
@@ -45,7 +45,7 @@ A usage example taken directly from the code is::
class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View): class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View):
""" """
A view that executes a task asynchronously. A POST request will kick of the 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. 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 In the former case, subsequent GET calls can be used to determinine the current
status of the task. status of the task.
""" """
@@ -93,4 +93,4 @@ AJAX sending of the form and display a loading indicator::
... ...
</form> </form>
.. _Celery: http://www.celeryproject.org/ .. _Celery: http://www.celeryproject.org/
+3 -3
View File
@@ -1,7 +1,7 @@
Sending Email Sending Email
============= =============
As pretix allows event organizers to configure how they want to sent emails to their users in multiple ways. pretix allows event organizers to configure how they want to send emails to their users in multiple ways.
Therefore, all emails should be sent throught the following function: Therefore, all emails should be sent through the following function:
.. autofunction:: pretix.base.services.mail.mail .. autofunction:: pretix.base.services.mail.mail
+7 -8
View File
@@ -1,11 +1,10 @@
Internationalization Internationalization
==================== ====================
One of pretix' major selling points is it's multi-language capability. We make heavy use of Django's One of pretix's major selling points is its 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 `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 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 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 .. 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 trade-off. We decided for a design that is not elegant on the database level (as it violates the `1NF`_) and
@@ -21,8 +20,8 @@ pretix provides two custom model field types that allow you to work with localiz
``I18nTextField``. Both of them are stored in the database as a ``TextField`` internally, they only differ in the ``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``. 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, As pretix does not use these fields in places that need to be searched, the negative performance impact when searching
within pretix this is not used in places that need to be searched. Lookups are currently not even implemented on these and indexing these fields in negligible, as mentioned above. 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. 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: Whenever you interact with those fields, you will either provide or receive an instance of the following class:
@@ -58,8 +57,8 @@ The ``i18n`` module contains a few more useful utilities, starting with simple l
numbers and dates, ``LazyDate`` and ``LazyNumber``. There also is a ``LazyLocaleException`` base class that provides numbers and dates, ``LazyDate`` and ``LazyNumber``. There also is a ``LazyLocaleException`` base class that provides
exceptions with gettext-localized exception messages. 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 Last, but definitely not least, we have the ``language`` context manager (``pretix.base.i18n.language``) that allows
a different locale:: you to execute a piece of code with a different locale::
with language('de'): with language('de'):
render_mail_template() render_mail_template()
@@ -69,4 +68,4 @@ action that causes the mail to be sent.
.. _translation features: https://docs.djangoproject.com/en/1.9/topics/i18n/translation/ .. _translation features: https://docs.djangoproject.com/en/1.9/topics/i18n/translation/
.. _GNU gettext: https://www.gnu.org/software/gettext/ .. _GNU gettext: https://www.gnu.org/software/gettext/
.. _1NF: https://en.wikipedia.org/wiki/First_normal_form .. _1NF: https://en.wikipedia.org/wiki/First_normal_form
+1 -1
View File
@@ -2,7 +2,7 @@ Implementation and Utilities
============================ ============================
This chapter describes the various inner workings that power pretix, most of them living in ``pretix.base``. 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 If you want to develop around pretix's core or advanced plugins, this aims to describe everything you absolutely
need to know. need to know.
Contents: Contents:
+2 -1
View File
@@ -3,7 +3,8 @@ Settings storage
pretix is highly configurable and therefore needs to store a lot of per-event and per-organizer settings. 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 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``. such an instance from any event or organizer model instance by just accessing ``event.settings`` or
``organizer.settings``, respectively.
Any setting consists of a key and a value. By default, all settings are strings, but the settings system 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: includes serializers for serializing the following types:
+8 -8
View File
@@ -4,8 +4,8 @@ Working with URLs
================= =================
As soon as you write a plugin that provides a new view to the user (or if you want to As soon as you write a plugin that provides a new view to the user (or if you want to
contribute to pretix itself), you need to understand how URLs work in pretix as it slightly contribute to pretix itself), you need to understand how URLs work in pretix as it differs
differs from the standard Django system. slightly from the standard Django system.
The reason for the complicated URL handling is that pretix supports custom subdomains for The reason for the complicated URL handling is that pretix supports custom subdomains for
single organizers. In this example we will use an event organizer with the slug ``bigorg`` single organizers. In this example we will use an event organizer with the slug ``bigorg``
@@ -24,10 +24,10 @@ URL routing
The hard part about implementing this URL routing in Django is that The hard part about implementing this URL routing in Django is that
``https://pretix.eu/bigorg/awesomecon/`` contains two parameters of nearly arbitrary content ``https://pretix.eu/bigorg/awesomecon/`` contains two parameters of nearly arbitrary content
and ``https://tickets.bigorg.com/awesomecon/`` contains only one. The only robust way to do and ``https://tickets.bigorg.com/awesomecon/`` contains only one. The only robust way to do
this is by having *seperate* URL configuration for those two cases. In pretix, we call the this is by having *separate* URL configuration for those two cases. In pretix, we call the
former our ``maindomain`` config and the latter our ``subdomain`` config. For pretix' core former our ``maindomain`` config and the latter our ``subdomain`` config. For pretix's core
modules we do some magic to avoid duplicate configuration, but for a fairly simple plugin with modules we do some magic to avoid duplicate configuration, but for a fairly simple plugin with
only a handful of routes, we recommend just configuring the two URL sets seperately. only a handful of routes, we recommend just configuring the two URL sets separately.
The file ``maindomain_urls.py`` inside your plugin package will be loaded and scanned for The file ``maindomain_urls.py`` inside your plugin package will be loaded and scanned for
URL configuration automatically and should be provided by any plugin that provides any view. URL configuration automatically and should be provided by any plugin that provides any view.
@@ -63,12 +63,12 @@ If you only provide URLs in the admin area, you do not need to provide a ``subdo
URL reversal URL reversal
------------ ------------
pretix uses Django's URL namespacing feature. The URLs of pretix' core are available in the ``control`` pretix uses Django's URL namespacing feature. The URLs of pretix's core are available in the ``control``
and ``presale`` namespaces, there are only very few URLs in the root namespace. Your plugin's URLs will and ``presale`` namespaces, there are only very few URLs in the root namespace. Your plugin's URLs will
be available in the ``plugins:<applabel>`` namespace, e.g. the form of the email sending plugin is be available in the ``plugins:<applabel>`` namespace, e.g. the form of the email sending plugin is
available as ``plugins:sendmail:send``. available as ``plugins:sendmail:send``.
Generating an URL for the frontend is a complicated task, because you need to know whether the event's Generating a URL for the frontend is a complicated task, because you need to know whether the event's
organizer uses a custom URL or not and then generate the URL with a different domain and different organizer uses a custom URL or not and then generate the URL with a different domain and different
arguments based on this information. pretix provides some helpers to make this easier. The first helper arguments based on this information. pretix provides some helpers to make this easier. The first helper
is a python method that emulates a behaviour similar to ``reverse``: is a python method that emulates a behaviour similar to ``reverse``:
@@ -87,4 +87,4 @@ Implementation details
There are some other caveats when using a design like this, e.g. you have to care about cookie domains There are some other caveats when using a design like this, e.g. you have to care about cookie domains
and referer verification yourself. If you want to see how we built this, look into the ``pretix/multidomain/`` and referer verification yourself. If you want to see how we built this, look into the ``pretix/multidomain/``
sub-tree. sub-tree.
+2 -2
View File
@@ -22,8 +22,8 @@ Your local python environment
----------------------------- -----------------------------
Please execute ``python -V`` or ``python3 -V`` to make sure you have Python 3.4 Please execute ``python -V`` or ``python3 -V`` to make sure you have Python 3.4
installed. Also make sure you have pip for Python 3 installed, you can execute (or newer) installed. Also make sure you have pip for Python 3 installed, you can
``pip3 -V`` to check. Then use Python 3.4's internal tools to create a virtual execute ``pip3 -V`` to check. Then use Python's internal tools to create a virtual
environment and activate it for your current session:: environment and activate it for your current session::
pyvenv env pyvenv env
+3 -3
View File
@@ -10,14 +10,14 @@ pretix/
This directory contains nearly all source code. This directory contains nearly all source code.
base/ base/
This is the django app containing all the models and methods which are This is the Django app containing all the models and methods which are
essential to all of pretix's features. essential to all of pretix's features.
control/ control/
This is the django app containing the front end for organizers. This is the Django app containing the front end for organizers.
presale/ presale/
This is the django app containing the front end for users buying tickets. This is the Django app containing the front end for users buying tickets.
helpers/ helpers/
Helpers contain a very few modules providing workarounds for low-level flaws in Helpers contain a very few modules providing workarounds for low-level flaws in
+1 -1
View File
@@ -42,7 +42,7 @@ class BaseExporter:
@property @property
def export_form_fields(self) -> dict: def export_form_fields(self) -> dict:
""" """
When the event's administrator administrator visits the export page, this method When the event's administrator visits the export page, this method
is called to return the configuration fields available. is called to return the configuration fields available.
It should therefore return a dictionary where the keys should be field names and It should therefore return a dictionary where the keys should be field names and
+2 -2
View File
@@ -33,7 +33,7 @@ class I18nModelForm(six.with_metaclass(ModelFormMetaclass, BaseI18nModelForm)):
""" """
This is a modified version of Django's ModelForm which differs from ModelForm in This is a modified version of Django's ModelForm which differs from ModelForm in
only one way: The constructor takes one additional optional argument ``event`` only one way: The constructor takes one additional optional argument ``event``
which may be given an `Event` instance. If given, this instance is used to select expecting an `Event` instance. If given, this instance is used to select
the visible languages in all I18nFormFields of the form. If not given, all languages the visible languages in all I18nFormFields of the form. If not given, all languages
will be displayed. will be displayed.
""" """
@@ -83,7 +83,7 @@ class I18nInlineFormSet(BaseInlineFormSet):
class SettingsForm(forms.Form): class SettingsForm(forms.Form):
""" """
This form is meant to be used for modifying Event- or OrganizerSettings. It takes This form is meant to be used for modifying EventSettings or OrganizerSettings. It takes
care of loading the current values of the fields and saving the field inputs to the 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 settings storage. It also deals with setting the available languages for internationalized
fields. fields.
+2 -2
View File
@@ -17,7 +17,7 @@ class LoginForm(forms.Form):
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput) password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
error_messages = { error_messages = {
'invalid_login': _("Please enter a correct e-mail address and password."), 'invalid_login': _("Please enter a correct email address and password."),
'inactive': _("This account is inactive.") 'inactive': _("This account is inactive.")
} }
@@ -69,7 +69,7 @@ class LoginForm(forms.Form):
class RegistrationForm(forms.Form): class RegistrationForm(forms.Form):
error_messages = { error_messages = {
'duplicate_email': _("You already registered with that e-mail address, please use the login form."), 'duplicate_email': _("You already registered with that email address, please use the login form."),
'pw_mismatch': _("Please enter the same password twice"), 'pw_mismatch': _("Please enter the same password twice"),
} }
email = forms.EmailField( email = forms.EmailField(
+1 -1
View File
@@ -24,7 +24,7 @@ class LazyI18nString:
:param data: If this is a dictionary, it is expected to map language codes to translations. :param data: If this is a dictionary, it is expected to map language codes to translations.
If this is a string that can be parsed as JSON, it will be parsed and used as such a dictionary. If this is a string that can be parsed as JSON, it will be parsed and used as such a dictionary.
If this is anything else, it will be casted to a string and used for all languages. If this is anything else, it will be cast to a string and used for all languages.
""" """
self.data = data self.data = data
if isinstance(self.data, str) and self.data is not None: if isinstance(self.data, str) and self.data is not None:
+2 -4
View File
@@ -36,14 +36,12 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
""" """
This is the user model used by pretix for authentication. This is the user model used by pretix for authentication.
:param email: The user's e-mail address, used for identification. :param email: The user's email address, used for identification.
:type email: str :type email: str
:param givenname: The user's given name. May be empty or null. :param givenname: The user's given name. May be empty or null.
:type givenname: str :type givenname: str
:param familyname: The user's given name. May be empty or null. :param familyname: The user's given name. May be empty or null.
:type familyname: str :type familyname: str
:param givenname: The user's given name. May be empty or null.
:type givenname: str
:param is_active: Whether this user account is activated. :param is_active: Whether this user account is activated.
:type is_active: bool :type is_active: bool
:param is_staff: ``True`` for system operators. :param is_staff: ``True`` for system operators.
@@ -98,7 +96,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
* Given name * Given name
* Family name * Family name
* E-mail address * Email address
""" """
if self.givenname: if self.givenname:
return self.givenname return self.givenname
+10 -10
View File
@@ -27,7 +27,7 @@ class Event(LoggedModel):
:param organizer: The organizer this event belongs to :param organizer: The organizer this event belongs to
:type organizer: Organizer :type organizer: Organizer
:param name: This events full title :param name: This event's full title
:type name: str :type name: str
:param slug: A short, alphanumeric, all-lowercase name for use in URLs. The slug has to :param slug: A short, alphanumeric, all-lowercase name for use in URLs. The slug has to
be unique among the events of the same organizer. be unique among the events of the same organizer.
@@ -42,7 +42,7 @@ class Event(LoggedModel):
:type date_to: datetime :type date_to: datetime
:param presale_start: No tickets will be sold before this date. :param presale_start: No tickets will be sold before this date.
:type presale_start: datetime :type presale_start: datetime
:param presale_end: No tickets will be sold before this date. :param presale_end: No tickets will be sold after this date.
:type presale_end: datetime :type presale_end: datetime
:param plugins: A comma-separated list of plugin names that are active for this :param plugins: A comma-separated list of plugin names that are active for this
event. event.
@@ -110,14 +110,14 @@ class Event(LoggedModel):
def clean(self): def clean(self):
if self.presale_start and self.presale_end and self.presale_start > self.presale_end: if self.presale_start and self.presale_end and self.presale_start > self.presale_end:
raise ValidationError({'presale_end': _('The end of the presale period has to be later than it\'s start.')}) raise ValidationError({'presale_end': _('The end of the presale period has to be later than its start.')})
if self.date_from and self.date_to and self.date_from > self.date_to: if self.date_from and self.date_to and self.date_from > self.date_to:
raise ValidationError({'date_to': _('The end of the event has to be later than it\'s start.')}) raise ValidationError({'date_to': _('The end of the event has to be later than its start.')})
super().clean() super().clean()
def get_plugins(self) -> "list[str]": def get_plugins(self) -> "list[str]":
""" """
Get the names of the plugins activated for this event as a list. Returns the names of the plugins activated for this event as a list.
""" """
if self.plugins is None: if self.plugins is None:
return [] return []
@@ -160,7 +160,7 @@ class Event(LoggedModel):
@cached_property @cached_property
def settings(self) -> SettingsProxy: def settings(self) -> SettingsProxy:
""" """
Returns an object representing this event's settings Returns an object representing this event's settings.
""" """
try: try:
return SettingsProxy(self, type=EventSetting, parent=self.organizer) return SettingsProxy(self, type=EventSetting, parent=self.organizer)
@@ -184,7 +184,7 @@ class Event(LoggedModel):
def lock(self): def lock(self):
""" """
Returns a contextmanager that can be used to lock an event for bookings Returns a contextmanager that can be used to lock an event for bookings.
""" """
from pretix.base.services import locking from pretix.base.services import locking
@@ -205,12 +205,12 @@ class Event(LoggedModel):
class EventPermission(models.Model): class EventPermission(models.Model):
""" """
The relation between an Event and an User who has permissions to The relation between an Event and a User who has permissions to
access an event. access an event.
:param event: The event this refers to :param event: The event this permission refers to
:type event: Event :type event: Event
:param user: The user these permission set applies to :param user: The user this permission set applies to
:type user: User :type user: User
:param can_change_settings: If ``True``, the user can change all basic settings for this event. :param can_change_settings: If ``True``, the user can change all basic settings for this event.
:type can_change_settings: bool :type can_change_settings: bool
+21 -5
View File
@@ -19,19 +19,30 @@ def invoice_filename(instance, filename: str) -> str:
class Invoice(models.Model): class Invoice(models.Model):
""" """
Represents an invoice that is issued because of an order. Because invoices are legally required Represents an invoice that is issued because of an order. Because invoices are legally required
not to change, this object duplicates a log of data (e.g. the invoice address). not to change, this object duplicates a lot of data (e.g. the invoice address).
:param order: The associated order :param order: The associated order
:type order: Order
:param event: The event this belongs to (for convenience) :param event: The event this belongs to (for convenience)
:type event: Event
:param invoice_no: The human-readable, event-unique invoice number :param invoice_no: The human-readable, event-unique invoice number
:type invoice_no: int
:param is_cancellation: Whether or not this is a cancellation instead of an invoice :param is_cancellation: Whether or not this is a cancellation instead of an invoice
:param refers: A link to another invoice this invoice referse to, e.g. the cancelled invoice in an cancellation :type is_cancellation: bool
:param refers: A link to another invoice this invoice refers to, e.g. the cancelled invoice in a cancellation
:type refers: Invoice
:param invoice_from: The sender address :param invoice_from: The sender address
:type invoice_from: str
:param invoice_to: The receiver address :param invoice_to: The receiver address
:type invoice_to: str
:param date: The invoice date :param date: The invoice date
:type date: date
:param locale: The locale in which the invoice should be printed :param locale: The locale in which the invoice should be printed
:type locale: str
:param additional_text: Additional text for the invoice :param additional_text: Additional text for the invoice
:type additional_text: str
:param file: The filename of the rendered invoice :param file: The filename of the rendered invoice
:type file: File
""" """
order = models.ForeignKey('Order', related_name='invoices', db_index=True) order = models.ForeignKey('Order', related_name='invoices', db_index=True)
event = models.ForeignKey('Event', related_name='invoices', db_index=True) event = models.ForeignKey('Event', related_name='invoices', db_index=True)
@@ -47,7 +58,7 @@ class Invoice(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.order: if not self.order:
raise ValueError('Any invoice needs to be connected to an order') raise ValueError('Every invoice needs to be connected to an order')
if not self.event: if not self.event:
self.event = self.order.event self.event = self.order.event
if not self.invoice_no: if not self.invoice_no:
@@ -65,7 +76,7 @@ class Invoice(models.Model):
@property @property
def number(self): def number(self):
""" """
Returns the invoice number in a human-readable way with the event slug prepended. Returns the invoice number in a human-readable string with the event slug prepended.
""" """
return '%s-%05d' % (self.event.slug.upper(), self.invoice_no) return '%s-%05d' % (self.event.slug.upper(), self.invoice_no)
@@ -75,13 +86,18 @@ class Invoice(models.Model):
class InvoiceLine(models.Model): class InvoiceLine(models.Model):
""" """
One position listed on an invoice. One position listed on an Invoice.
:param invoice: The invoice this belongs to :param invoice: The invoice this belongs to
:type invoice: Invoice
:param description: The item description :param description: The item description
:type description: str
:param gross_value: The gross value :param gross_value: The gross value
:type gross_value: decimal.Decimal
:param tax_value: The included tax (as an absolute value) :param tax_value: The included tax (as an absolute value)
:type tax_value: decimal.Decimal
:param tax_rate: The applied tax rate in percent :param tax_rate: The applied tax rate in percent
:type tax_rate: decimal.Decimal
""" """
invoice = models.ForeignKey('Invoice', related_name='lines') invoice = models.ForeignKey('Invoice', related_name='lines')
description = models.TextField() description = models.TextField()
+19 -18
View File
@@ -20,7 +20,7 @@ class ItemCategory(LoggedModel):
""" """
Items can be sorted into these categories. Items can be sorted into these categories.
:param event: The event this belongs to :param event: The event this category belongs to
:type event: Event :type event: Event
:param name: The name of this category :param name: The name of this category
:type name: str :type name: str
@@ -81,13 +81,13 @@ class Item(LoggedModel):
An item is a thing which can be sold. It belongs to an event and may or may not belong to a category. An item is a thing which can be sold. It belongs to an event and may or may not belong to a category.
Items are often also called 'products' but are named 'items' internally due to historic reasons. Items are often also called 'products' but are named 'items' internally due to historic reasons.
:param event: The event this belongs to. :param event: The event this item belongs to
:type event: Event :type event: Event
:param category: The category this belongs to. May be null. :param category: The category this belongs to. May be null.
:type category: ItemCategory :type category: ItemCategory
:param name: The name of this item: :param name: The name of this item
:type name: str :type name: str
:param active: Whether this item is being sold :param active: Whether this item is being sold.
:type active: bool :type active: bool
:param description: A short description :param description: A short description
:type description: str :type description: str
@@ -97,7 +97,7 @@ class Item(LoggedModel):
:type tax_rate: decimal.Decimal :type tax_rate: decimal.Decimal
:param admission: ``True``, if this item allows persons to enter the event (as opposed to e.g. merchandise) :param admission: ``True``, if this item allows persons to enter the event (as opposed to e.g. merchandise)
:type admission: bool :type admission: bool
:param picture: A product picture to be shown next to the product description. :param picture: A product picture to be shown next to the product description
:type picture: File :type picture: File
:param available_from: The date this product goes on sale :param available_from: The date this product goes on sale
:type available_from: datetime :type available_from: datetime
@@ -235,7 +235,8 @@ class ItemVariation(models.Model):
:param item: The item this variation belongs to :param item: The item this variation belongs to
:type item: Item :type item: Item
:param value: A string defining this variation :param value: A string defining this variation
:param active: Whether this value is to be sold. :type value: str
:param active: Whether this variation is being sold.
:type active: bool :type active: bool
:param default_price: This variation's default price :param default_price: This variation's default price
:type default_price: decimal.Decimal :type default_price: decimal.Decimal
@@ -299,7 +300,7 @@ class ItemVariation(models.Model):
class Question(LoggedModel): class Question(LoggedModel):
""" """
A question is an input field that can be used to extend a ticket A question is an input field that can be used to extend a ticket
by custom information, e.g. "Attendee age". A question can allow one o several by custom information, e.g. "Attendee age". A question can allow one of several
input types, currently: input types, currently:
* a number (``TYPE_NUMBER``) * a number (``TYPE_NUMBER``)
@@ -387,11 +388,11 @@ class Quota(LoggedModel):
""" """
A quota is a "pool of tickets". It is there to limit the number of items A quota is a "pool of tickets". It is there to limit the number of items
of a certain type to be sold. For example, you could have a quota of 500 of a certain type to be sold. For example, you could have a quota of 500
applied to all your items (because you only have that much space in your applied to all of your items (because you only have that much space in your
building), and also a quota of 100 applied to the VIP tickets for venue), and also a quota of 100 applied to the VIP tickets for exclusivity.
exclusivity. In this case, no more than 500 tickets will be sold in total In this case, no more than 500 tickets will be sold in total and no more
and no more than 100 of them will be VIP tickets (but 450 normal and 50 than 100 of them will be VIP tickets (but 450 normal and 50 VIP tickets
VIP tickets will be fine). will be fine).
As always, a quota can not only be tied to an item, but also to specific As always, a quota can not only be tied to an item, but also to specific
variations. variations.
@@ -400,19 +401,19 @@ class Quota(LoggedModel):
anything with quotas. This might confuse you otherwise. anything with quotas. This might confuse you otherwise.
http://docs.pretix.eu/en/latest/development/concepts.html#restriction-by-number http://docs.pretix.eu/en/latest/development/concepts.html#restriction-by-number
The AVAILABILITY_* constants represent various states of an quota allowing The AVAILABILITY_* constants represent various states of a quota allowing
its items/variations being for sale. its items/variations to be up for sale.
AVAILABILITY_OK AVAILABILITY_OK
This item is available for sale. This item is available for sale.
AVAILABILITY_RESERVED AVAILABILITY_RESERVED
This item is currently not available for sale, because all available This item is currently not available for sale because all available
items are in people's shopping carts. It might become available items are in people's shopping carts. It might become available
again if those people do not proceed with checkout. again if those people do not proceed to the checkout.
AVAILABILITY_ORDERED AVAILABILITY_ORDERED
This item is currently not availalbe for sale, because all available This item is currently not availalbe for sale because all available
items are ordered. It might become available again if those people items are ordered. It might become available again if those people
do not pay. do not pay.
@@ -422,7 +423,7 @@ class Quota(LoggedModel):
:param event: The event this belongs to :param event: The event this belongs to
:type event: Event :type event: Event
:param name: This quota's name :param name: This quota's name
:type str: :type name: str
:param size: The number of items in this quota :param size: The number of items in this quota
:type size: int :type size: int
:param items: The set of :py:class:`Item` objects this quota applies to :param items: The set of :py:class:`Item` objects this quota applies to
+2 -1
View File
@@ -10,12 +10,13 @@ class LogEntry(models.Model):
relation to an arbitrary database object. relation to an arbitrary database object.
:param datatime: The timestamp of the logged action :param datatime: The timestamp of the logged action
:type datetime: datetime
:param user: The user that performed the action :param user: The user that performed the action
:type user: User :type user: User
:param action_type: The type of action that has been performed. This is :param action_type: The type of action that has been performed. This is
used to look up the renderer used to describe the action in a human- used to look up the renderer used to describe the action in a human-
readable way. This should be some namespaced value using dotted readable way. This should be some namespaced value using dotted
notationto avaoid duplicates, e.g. notation to avoid duplicates, e.g.
``"pretix.plugins.banktransfer.incoming_transfer"``. ``"pretix.plugins.banktransfer.incoming_transfer"``.
:type action_type: str :type action_type: str
:param data: Arbitrary data that can be used by the log action renderer :param data: Arbitrary data that can be used by the log action renderer
+15 -14
View File
@@ -27,17 +27,18 @@ def generate_position_secret():
class Order(LoggedModel): class Order(LoggedModel):
""" """
An order is created when a user clicks 'buy' on his cart. It holds An order is created when a user clicks 'buy' on his cart. It holds
several OrderPositions and is connected to an user. It has an several OrderPositions and is connected to a user. It has an
expiration date: If items run out of capacity, orders which are over expiration date: If items run out of capacity, orders which are over
their expiration date might be cancelled. their expiration date might be cancelled.
An order -- like all objects -- has an ID, which is globally unique, An order -- like all objects -- has an ID, which is globally unique,
but also a code, which is shorter and easier to memorize, but only but also a code, which is shorter and easier to memorize, but only
unique among a single conference. unique within a single conference.
:param code: In addition to the ID, which is globally unique, every :param code: In addition to the ID, which is globally unique, every
order has an order code, which is shorter and easier to order has an order code, which is shorter and easier to
memorize, but is only unique among a single conference. memorize, but is only unique within a single conference.
:type code: str
:param status: The status of this order. One of: :param status: The status of this order. One of:
* ``STATUS_PENDING`` * ``STATUS_PENDING``
@@ -46,7 +47,7 @@ class Order(LoggedModel):
* ``STATUS_CANCELLED`` * ``STATUS_CANCELLED``
* ``STATUS_REFUNDED`` * ``STATUS_REFUNDED``
:param event: The event this belongs to :param event: The event this order belongs to
:type event: Event :type event: Event
:param email: The email of the person who ordered this :param email: The email of the person who ordered this
:type email: str :type email: str
@@ -56,15 +57,15 @@ class Order(LoggedModel):
:type secret: str :type secret: str
:param datetime: The datetime of the order placement :param datetime: The datetime of the order placement
:type datetime: datetime :type datetime: datetime
:param expires: The date until this order has to be paid to guarantee the :param expires: The date until this order has to be paid to guarantee the fulfillment
:type expires: datetime :type expires: datetime
:param payment_date: The date of the payment completion (null, if not yet paid). :param payment_date: The date of the payment completion (null if not yet paid)
:type payment_date: datetime :type payment_date: datetime
:param payment_provider: The payment provider selected by the user :param payment_provider: The payment provider selected by the user
:type payment_provider: str :type payment_provider: str
:param payment_fee: The payment fee calculated at checkout time :param payment_fee: The payment fee calculated at checkout time
:type payment_fee: decimal.Decimal :type payment_fee: decimal.Decimal
:param payment_fee_tax_value: The absolute amound of tax included in the payment fee :param payment_fee_tax_value: The absolute amount of tax included in the payment fee
:type payment_fee_tax_value: decimal.Decimal :type payment_fee_tax_value: decimal.Decimal
:param payment_fee_tax_rate: The tax rate applied to the payment fee (in percent) :param payment_fee_tax_rate: The tax rate applied to the payment fee (in percent)
:type payment_fee_tax_rate: decimal.Decimal :type payment_fee_tax_rate: decimal.Decimal
@@ -161,7 +162,7 @@ class Order(LoggedModel):
@property @property
def full_code(self): def full_code(self):
""" """
A order code which is unique among all events of a single organizer, An order code which is unique among all events of a single organizer,
built by contatenating the event slug and the order code. built by contatenating the event slug and the order code.
""" """
return self.event.slug.upper() + self.code return self.event.slug.upper() + self.code
@@ -198,9 +199,9 @@ class Order(LoggedModel):
@property @property
def can_modify_answers(self) -> bool: def can_modify_answers(self) -> bool:
""" """
Is ``True`` if the user can change the question answers / attendee names that are ``True`` if the user can change the question answers / attendee names that are
related to the order. This checks order status and modification deadlines. It also related to the order. This checks order status and modification deadlines. It also
returns ``False``, if there are no questions that can be answered. returns ``False`` if there are no questions that can be answered.
""" """
if self.status not in (Order.STATUS_PENDING, Order.STATUS_PAID, Order.STATUS_EXPIRED): if self.status not in (Order.STATUS_PENDING, Order.STATUS_PAID, Order.STATUS_EXPIRED):
return False return False
@@ -323,7 +324,7 @@ class AbstractPosition(models.Model):
:param item: The selected item :param item: The selected item
:type item: Item :type item: Item
:param variation: The selected ItemVariation or null, if the item has no properties :param variation: The selected ItemVariation or null, if the item has no variations
:type variation: ItemVariation :type variation: ItemVariation
:param datetime: The datetime this item was put into the cart :param datetime: The datetime this item was put into the cart
:type datetime: datetime :type datetime: datetime
@@ -404,11 +405,11 @@ class AbstractPosition(models.Model):
class OrderPosition(AbstractPosition): class OrderPosition(AbstractPosition):
""" """
An OrderPosition is one line of an order, representing one ordered items An OrderPosition is one line of an order, representing one ordered item
of a specified type (or variation). This has all properties of of a specified type (or variation). This has all properties of
AbstractPosition. AbstractPosition.
:param order: The order this is a part of :param order: The order this position is a part of
:type order: Order :type order: Order
""" """
order = models.ForeignKey( order = models.ForeignKey(
@@ -470,7 +471,7 @@ class OrderPosition(AbstractPosition):
class CartPosition(AbstractPosition): class CartPosition(AbstractPosition):
""" """
A cart position is similar to a order line, except that it is not A cart position is similar to an order line, except that it is not
yet part of a binding order but just placed by some user in his or yet part of a binding order but just placed by some user in his or
her cart. It therefore normally has a much shorter expiration time her cart. It therefore normally has a much shorter expiration time
than an ordered position, but still blocks an item in the quota pool than an ordered position, but still blocks an item in the quota pool
+1 -1
View File
@@ -73,7 +73,7 @@ class Organizer(LoggedModel):
class OrganizerPermission(models.Model): class OrganizerPermission(models.Model):
""" """
The relation between an Organizer and an User who has permissions to The relation between an Organizer and a User who has permissions to
access an organizer profile. access an organizer profile.
:param organizer: The organizer this relation refers to :param organizer: The organizer this relation refers to
+13 -3
View File
@@ -21,23 +21,33 @@ def generate_code():
class Voucher(LoggedModel): class Voucher(LoggedModel):
""" """
Represents a voucher. A voucher can reserve ticket quota or allow special prices. A Voucher can reserve ticket quota or allow special prices.
:param event: The event this voucher is valid for :param event: The event this voucher is valid for
:type event: Event
:param code: The secret voucher code :param code: The secret voucher code
:type code: str
:param redeemed: Whether or not this voucher has already been redeemed :param redeemed: Whether or not this voucher has already been redeemed
:type redeemed: bool
:param valid_until: The expiration date of this voucher (optional) :param valid_until: The expiration date of this voucher (optional)
:type valid_until: datetime
:param block_quota: If set to true, this voucher will reserve quota for its holder :param block_quota: If set to true, this voucher will reserve quota for its holder
:type block_quota: bool
:param allow_ignore_quota: If set to true, this voucher can be redeemed even if the event is sold out :param allow_ignore_quota: If set to true, this voucher can be redeemed even if the event is sold out
:type allow_ignore_quota: bool
:param price: If set, the voucher will allow the sale of associated items for this price :param price: If set, the voucher will allow the sale of associated items for this price
:type price: decimal.Decimal
:param item: If set, the item to sell :param item: If set, the item to sell
:type item: Item
:param variation: If set, the variation to sell :param variation: If set, the variation to sell
:type variation: ItemVariation
:param quota: If set, the quota to choose an item from :param quota: If set, the quota to choose an item from
:type quota: Quota
Various constraints apply: Various constraints apply:
* You can either select a quota or an item and you need to select one of those * You need to either select a quota or an item
* If you select an item that as variations but not select a variation, you cannot set block_quota * If you select an item that has variations but do not select a variation, you cannot set block_quota
""" """
event = models.ForeignKey( event = models.ForeignKey(
Event, Event,
+22 -22
View File
@@ -35,7 +35,7 @@ class BasePaymentProvider:
@property @property
def is_enabled(self) -> bool: def is_enabled(self) -> bool:
""" """
Returns, whether or whether not this payment provider is enabled. Returns whether or whether not this payment provider is enabled.
By default, this is determined by the value of the ``_enabled`` setting. By default, this is determined by the value of the ``_enabled`` setting.
""" """
return self.settings.get('_enabled', as_type=bool) return self.settings.get('_enabled', as_type=bool)
@@ -78,7 +78,7 @@ class BasePaymentProvider:
@property @property
def settings_form_fields(self) -> dict: def settings_form_fields(self) -> dict:
""" """
When the event's administrator administrator visits the event configuration When the event's administrator visits the event configuration
page, this method is called to return the configuration fields available. page, this method is called to return the configuration fields available.
It should therefore return a dictionary where the keys should be (unprefixed) It should therefore return a dictionary where the keys should be (unprefixed)
@@ -138,7 +138,7 @@ class BasePaymentProvider:
def settings_content_render(self, request: HttpRequest) -> str: def settings_content_render(self, request: HttpRequest) -> str:
""" """
When the event's administrator administrator visits the event configuration When the event's administrator visits the event configuration
page, this method is called. It may return HTML containing additional information page, this method is called. It may return HTML containing additional information
that is displayed below the form fields configured in ``settings_form_fields``. that is displayed below the form fields configured in ``settings_form_fields``.
""" """
@@ -188,12 +188,12 @@ class BasePaymentProvider:
def payment_form_render(self, request: HttpRequest) -> str: def payment_form_render(self, request: HttpRequest) -> str:
""" """
When the user selects this provider as his prefered payment method, When the user selects this provider as his prefered payment method,
he will be shown the HTML you return from this method. they will be shown the HTML you return from this method.
The default implementation will call :py:meth:`checkout_form` The default implementation will call :py:meth:`checkout_form`
and render the returned form. If your payment method doesn't require and render the returned form. If your payment method doesn't require
the user to fill out form fields, you should just return a paragraph the user to fill out form fields, you should just return a paragraph
of explainatory text. of explanatory text.
""" """
form = self.payment_form(request) form = self.payment_form(request)
template = get_template('pretixpresale/event/checkout_payment_form_default.html') template = get_template('pretixpresale/event/checkout_payment_form_default.html')
@@ -202,25 +202,25 @@ class BasePaymentProvider:
def checkout_confirm_render(self, request) -> str: def checkout_confirm_render(self, request) -> str:
""" """
If the user successfully filled in his payment data, he will be redirected If the user has successfully filled in his payment data, they will be redirected
to a confirmation page which lists all details of his order for a final review. to a confirmation page which lists all details of his order for a final review.
This method should return the HTML which should be displayed inside the This method should return the HTML which should be displayed inside the
'Payment' box on this page. 'Payment' box on this page.
In most cases, this should include a short summary of the user's input and In most cases, this should include a short summary of the user's input and
a short explaination on how the payment process will continue. a short explanation on how the payment process will continue.
""" """
raise NotImplementedError() # NOQA raise NotImplementedError() # NOQA
def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> "bool|str": def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> "bool|str":
""" """
Will be called after the user selected this provider as his payment method. Will be called after the user selects this provider as his payment method.
If you provided a form to the user to enter payment data, this method should If you provided a form to the user to enter payment data, this method should
at least store the user's input into his session. at least store the user's input into his session.
This method should return ``False``, if the user's input was invalid, ``True`` This method should return ``False`` if the user's input was invalid, ``True``
if the input was valid and the frontend should continue with default behaviour if the input was valid and the frontend should continue with default behaviour
or a string containing an URL, if the user should be redirected somewhere else. or a string containing a URL if the user should be redirected somewhere else.
On errors, you should use Django's message framework to display an error message On errors, you should use Django's message framework to display an error message
to the user (or the normal form validation error messages). to the user (or the normal form validation error messages).
@@ -261,17 +261,17 @@ class BasePaymentProvider:
def payment_is_valid_session(self, request: HttpRequest) -> bool: def payment_is_valid_session(self, request: HttpRequest) -> bool:
""" """
This is called at the time the user tries to place the order. It should return This is called at the time the user tries to place the order. It should return
``True``, if the user's session is valid and all data your payment provider requires ``True`` if the user's session is valid and all data your payment provider requires
in future steps is present. in future steps is present.
""" """
raise NotImplementedError() # NOQA raise NotImplementedError() # NOQA
def payment_perform(self, request: HttpRequest, order: Order) -> str: def payment_perform(self, request: HttpRequest, order: Order) -> str:
""" """
After the user confirmed his purchase, this method will be called to complete After the user has confirmed their purchase, this method will be called to complete
the payment process. This is the place to actually move the money, if applicable. the payment process. This is the place to actually move the money if applicable.
If you need any special behaviour, you can return a string If you need any special behaviour, you can return a string
containing an URL the user will be redirected to. If you are done with your process containing the URL the user will be redirected to. If you are done with your process
you should return the user to the order's detail page. you should return the user to the order's detail page.
If the payment is completed, you should call ``pretix.base.services.orders.mark_order_paid(order, provider, info)`` If the payment is completed, you should call ``pretix.base.services.orders.mark_order_paid(order, provider, info)``
@@ -293,9 +293,9 @@ class BasePaymentProvider:
def order_pending_mail_render(self, order: Order) -> str: def order_pending_mail_render(self, order: Order) -> str:
""" """
After the user submitted his order, he or she will receive a confirmation After the user has submitted their order, they will receive a confirmation
e-mail. You can return a string from this method if you want to add additional email. You can return a string from this method if you want to add additional
information to this e-mail. information to this email.
:param order: The order object :param order: The order object
""" """
@@ -342,7 +342,7 @@ class BasePaymentProvider:
def order_paid_render(self, request: HttpRequest, order: Order) -> str: def order_paid_render(self, request: HttpRequest, order: Order) -> str:
""" """
Will be called if the user views the detail page of an paid order which is Will be called if the user views the detail page of a paid order which is
associated with this payment provider. associated with this payment provider.
It should return HTML code which should be displayed to the user or None, It should return HTML code which should be displayed to the user or None,
@@ -385,14 +385,14 @@ class BasePaymentProvider:
""" """
Will be called if the event administrator confirms the refund. Will be called if the event administrator confirms the refund.
This should transfer the money back (if possible). You can return an URL the This should transfer the money back (if possible). You can return the URL the
user should be redirected to if you need special behaviour or None to continue user should be redirected to if you need special behaviour or None to continue
with default behaviour. with default behaviour.
On failure, you should use Django's message framework to display an error message On failure, you should use Django's message framework to display an error message
to the user. to the user.
The default implementation sets the Orders state to refunded and shows a success The default implementation sets the Order's state to refunded and shows a success
message. message.
:param request: The HTTP request :param request: The HTTP request
@@ -446,14 +446,14 @@ class FreeOrderProvider(BasePaymentProvider):
""" """
Will be called if the event administrator confirms the refund. Will be called if the event administrator confirms the refund.
This should transfer the money back (if possible). You can return an URL the This should transfer the money back (if possible). You can return the URL the
user should be redirected to if you need special behaviour or None to continue user should be redirected to if you need special behaviour or None to continue
with default behaviour. with default behaviour.
On failure, you should use Django's message framework to display an error message On failure, you should use Django's message framework to display an error message
to the user. to the user.
The default implementation sets the Orders state to refunded and shows a success The default implementation sets the Order's state to refunded and shows a success
message. message.
:param request: The HTTP request :param request: The HTTP request
+7 -7
View File
@@ -23,24 +23,24 @@ def mail(email: str, subject: str, template: str,
""" """
Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation. Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation.
:param email: The e-mail address of the recipient. :param email: The email address of the recipient
:param subject: The e-mail subject. Should be localized to the recipients's locale or a lazy object that will be :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. 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 :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 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. ``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 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, :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. 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. :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 :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. the email has been sent, just that it has been queued by the email backend.
""" """
with language(locale): with language(locale):
if isinstance(template, LazyI18nString): if isinstance(template, LazyI18nString):
@@ -61,7 +61,7 @@ def mail(email: str, subject: str, template: str,
body += "\r\n\r\n----\r\n" body += "\r\n\r\n----\r\n"
body += _( body += _(
"You are receiving this e-mail 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)
body += "\r\n" body += "\r\n"
return mail_send([email], subject, body, sender, event.id if event else None) return mail_send([email], subject, body, sender, event.id if event else None)
@@ -79,7 +79,7 @@ def mail_send(to: str, subject: str, body: str, sender: str, event: int=None) ->
backend.send_messages([email]) backend.send_messages([email])
return True return True
except Exception: except Exception:
logger.exception('Error sending e-mail') logger.exception('Error sending email')
return False return False
+1 -1
View File
@@ -115,6 +115,6 @@ periodic_task = django.dispatch.Signal()
This is a regular django signal (no pretix event signal) that we send out every This is a regular django signal (no pretix event signal) that we send out every
time the periodic task cronjob runs. This interval is not sharply defined, it can time the periodic task cronjob runs. This interval is not sharply defined, it can
be everything between a minute and a day. The actions you perform should be be everything between a minute and a day. The actions you perform should be
idempotent, i.e. it should not make a difference if this is send out more often idempotent, i.e. it should not make a difference if this is sent out more often
than expected. than expected.
""" """
+3 -3
View File
@@ -24,7 +24,7 @@ class BaseTicketOutput:
@property @property
def is_enabled(self) -> bool: def is_enabled(self) -> bool:
""" """
Returns, whether or whether not this output is enabled. Returns whether or whether not this output is enabled.
By default, this is determined by the value of the ``_enabled`` setting. By default, this is determined by the value of the ``_enabled`` setting.
""" """
return self.settings.get('_enabled', as_type=bool) return self.settings.get('_enabled', as_type=bool)
@@ -57,7 +57,7 @@ class BaseTicketOutput:
@property @property
def settings_form_fields(self) -> dict: def settings_form_fields(self) -> dict:
""" """
When the event's administrator administrator visits the event configuration When the event's administrator visits the event configuration
page, this method is called to return the configuration fields available. page, this method is called to return the configuration fields available.
It should therefore return a dictionary where the keys should be (unprefixed) It should therefore return a dictionary where the keys should be (unprefixed)
@@ -95,7 +95,7 @@ class BaseTicketOutput:
def settings_content_render(self, request: HttpRequest) -> str: def settings_content_render(self, request: HttpRequest) -> str:
""" """
When the event's administrator administrator visits the event configuration When the event's administrator visits the event configuration
page, this method is called. It may return HTML containing additional information page, this method is called. It may return HTML containing additional information
that is displayed below the form fields configured in ``settings_form_fields``. that is displayed below the form fields configured in ``settings_form_fields``.
""" """
+1 -1
View File
@@ -276,7 +276,7 @@ class MailSettingsForm(SettingsForm):
) )
mail_from = forms.EmailField( mail_from = forms.EmailField(
label=_("Sender address"), label=_("Sender address"),
help_text=_("Sender address for outgoing e-mails") help_text=_("Sender address for outgoing emails")
) )
mail_text_order_placed = I18nFormField( mail_text_order_placed = I18nFormField(
label=_("Placed order"), label=_("Placed order"),
+5 -5
View File
@@ -17,8 +17,8 @@ html_head = EventPluginSignal(
) )
""" """
This signal allows you to put code inside the HTML ``<head>`` tag This signal allows you to put code inside the HTML ``<head>`` tag
of every page in the backend. You will get the request as a keyword argument of every page in the backend. You will get the request as the keyword argument
``request`` and can return plain HTML. ``request`` and are expected to return plain HTML.
As with all plugin signals, the ``sender`` keyword argument will contain the event. As with all plugin signals, the ``sender`` keyword argument will contain the event.
""" """
@@ -46,7 +46,7 @@ event_dashboard_widgets = EventPluginSignal(
providing_args=[] providing_args=[]
) )
""" """
This signal is sent out to include widgets to the event dashboard. Receivers This signal is sent out to include widgets in the event dashboard. Receivers
should return a list of dictionaries, where each dictionary can have the keys: should return a list of dictionaries, where each dictionary can have the keys:
* content (str, containing HTML) * content (str, containing HTML)
@@ -62,7 +62,7 @@ user_dashboard_widgets = Signal(
providing_args=['user'] providing_args=['user']
) )
""" """
This signal is sent out to include widgets to the personal user dashboard. Receivers This signal is sent out to include widgets in the personal user dashboard. Receivers
should return a list of dictionaries, where each dictionary can have the keys: should return a list of dictionaries, where each dictionary can have the keys:
* content (str, containing HTML) * content (str, containing HTML)
@@ -90,7 +90,7 @@ voucher_form_class = EventPluginSignal(
""" """
This signal allows you to replace the form class that is used for modifying vouchers. This signal allows you to replace the form class that is used for modifying vouchers.
You will receive the default form class (or the class set by a previous plugin) in the You will receive the default form class (or the class set by a previous plugin) in the
``cls`` argument such that you can inherit from it. ``cls`` argument so that you can inherit from it.
As with all plugin signals, the ``sender`` keyword argument will contain the event. As with all plugin signals, the ``sender`` keyword argument will contain the event.
""" """
+1
View File
@@ -28,6 +28,7 @@ def eventreverse(obj, name, kwargs=None):
:param obj: An ``Event`` or ``Organizer`` object :param obj: An ``Event`` or ``Organizer`` object
:param name: The name of the URL route :param name: The name of the URL route
:type name: str
:param kwargs: A dictionary of additional keyword arguments that should be used. You do not :param kwargs: A dictionary of additional keyword arguments that should be used. You do not
need to provide the organizer or event slug here, it will be added automatically as need to provide the organizer or event slug here, it will be added automatically as
needed. needed.
+2 -2
View File
@@ -178,12 +178,12 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
emailval = EmailValidator() emailval = EmailValidator()
if 'email' not in request.session: if 'email' not in request.session:
if warn: if warn:
messages.warning(request, _('Please enter a valid e-mail address.')) messages.warning(request, _('Please enter a valid email address.'))
return False return False
emailval(request.session.get('email')) emailval(request.session.get('email'))
except ValidationError: except ValidationError:
if warn: if warn:
messages.warning(request, _('Please enter a valid e-mail address.')) messages.warning(request, _('Please enter a valid email address.'))
return False return False
for cp in self.positions: for cp in self.positions:
+2 -2
View File
@@ -5,8 +5,8 @@ html_head = EventPluginSignal(
) )
""" """
This signal allows you to put code inside the HTML ``<head>`` tag This signal allows you to put code inside the HTML ``<head>`` tag
of every page in the frontend. You will get the request as a keyword argument of every page in the frontend. You will get the request as the keyword argument
``request`` and can return plain HTML. ``request`` and are expected to return plain HTML.
As with all plugin signals, the ``sender`` keyword argument will contain the event. As with all plugin signals, the ``sender`` keyword argument will contain the event.
""" """