mirror of
https://github.com/pretix/pretix.git
synced 2026-01-26 00:52:26 +00:00
Fixed minor documentation errors and mistakes (#151)
This commit is contained in:
committed by
Raphael Michel
parent
f779b70deb
commit
bfc721978d
@@ -147,7 +147,7 @@ Example::
|
||||
Use STARTTLS or SSL for the SMTP connection. Off by default.
|
||||
|
||||
``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
|
||||
---------------
|
||||
@@ -160,13 +160,13 @@ Example::
|
||||
debug=off
|
||||
|
||||
``hosts``
|
||||
Comma-seperated list of allowed host names for this installation.
|
||||
Comma-separated list of allowed host names for this installation.
|
||||
Default: ``localhost``
|
||||
|
||||
``secret``
|
||||
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
|
||||
and store it in the filesystem for later usage.
|
||||
and will store it in the filesystem for later usage.
|
||||
|
||||
``debug``
|
||||
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``
|
||||
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
|
||||
@@ -199,22 +199,22 @@ to speed up various operations::
|
||||
sessions=false
|
||||
|
||||
``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``
|
||||
|
||||
``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.
|
||||
|
||||
Celery task queue
|
||||
-----------------
|
||||
|
||||
For processing long-running tasks asynchronously, pretix needs help of the celery task queue.
|
||||
For communicating between the web server and the task workers in both direction, a messaging
|
||||
For processing long-running tasks asynchronously, pretix requires the celery task queue.
|
||||
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
|
||||
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]
|
||||
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.
|
||||
|
||||
.. _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
|
||||
|
||||
@@ -18,8 +18,8 @@ modern distributions, especially on all systemd-based ones.
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Please set up the following systems beforehand, we'll not explain them here (but link to installation guides
|
||||
somewhere else):
|
||||
Please set up the following systems beforehand, we'll not explain them here (but see these links for external
|
||||
installation guides):
|
||||
|
||||
* `Docker`_
|
||||
* 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
|
||||
----------
|
||||
|
||||
First of all, you need a directory on your server that pretix can use to store data files and make that directory
|
||||
writable to the user that runs pretix inside the docker container::
|
||||
First of all, you need to create a directory on your server that pretix can use to store data files and make that
|
||||
directory writable to the user that runs pretix inside the docker container::
|
||||
|
||||
# mkdir /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
|
||||
--------
|
||||
|
||||
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::
|
||||
|
||||
$ mysql -u root -p
|
||||
@@ -61,15 +61,15 @@ our database's shell, e.g. for MySQL::
|
||||
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
|
||||
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.
|
||||
|
||||
Redis
|
||||
-----
|
||||
|
||||
For caching and messaging in small-scale setups, pretix recomments 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
|
||||
connecting to redis via an unix socket. To enable redis on unix sockets, add the following to your
|
||||
For caching and messaging in small-scale setups, pretix recommends using redis. In this small-scale setup we assume a
|
||||
redis instance to be running on the same host. To avoid the hassle with network configurations and firewalls, we
|
||||
recommend connecting to redis via a unix socket. To enable redis on unix sockets, add the following to your
|
||||
``/etc/redis/redis.conf``::
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
*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!
|
||||
|
||||
Updates
|
||||
@@ -220,7 +220,7 @@ Updates
|
||||
|
||||
.. 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
|
||||
# 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
|
||||
.. _redis website: http://redis.io/topics/security
|
||||
.. _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/
|
||||
|
||||
@@ -29,7 +29,7 @@ Database
|
||||
|
||||
Reverse proxy
|
||||
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.
|
||||
|
||||
.. 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
|
||||
|
||||
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/
|
||||
|
||||
@@ -54,7 +54,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'pretix'
|
||||
copyright = '2014, Raphael Michel'
|
||||
copyright = '2014-2016, Raphael Michel'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
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.
|
||||
|
||||
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
|
||||
all available exporters. Your plugin should listen for this signal and return the subclass of
|
||||
``pretix.base.exporter.BaseExporter``
|
||||
that we'll soon create::
|
||||
that we'll provide in this plugin::
|
||||
|
||||
from django.dispatch import receiver
|
||||
|
||||
@@ -36,8 +36,7 @@ The exporter class
|
||||
|
||||
.. class:: pretix.base.exporter.BaseExporter
|
||||
|
||||
The central object of each exporter is the subclass of ``BaseExporter`` we already mentioned above.
|
||||
In this section, we will discuss it's interface in detail.
|
||||
The central object of each exporter is the subclass of ``BaseExporter``.
|
||||
|
||||
.. py:attribute:: BaseExporter.event
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ Dashboards
|
||||
:members: event_dashboard_widgets, user_dashboard_widgets
|
||||
|
||||
|
||||
Displaying of log entries
|
||||
-------------------------
|
||||
Display of log entries
|
||||
----------------------
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:members: logentry_display
|
||||
|
||||
@@ -14,7 +14,7 @@ Provider registration
|
||||
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
|
||||
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
|
||||
|
||||
@@ -32,8 +32,7 @@ The provider class
|
||||
|
||||
.. class:: pretix.base.payment.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.
|
||||
The central object of each payment provider is the subclass of ``BasePaymentProvider``.
|
||||
|
||||
.. py:attribute:: BasePaymentProvider.event
|
||||
|
||||
@@ -105,9 +104,9 @@ Additional views
|
||||
For most simple payment providers it is more than sufficient to implement
|
||||
some of the :py:class:`BasePaymentProvider` methods. However, in some cases
|
||||
it is necessary to introduce additional views. One example is the PayPal
|
||||
provider. It redirects the user to a paypal website in the
|
||||
: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
|
||||
provider. It redirects the user to a PayPal website in the
|
||||
:py:meth:`BasePaymentProvider.checkout_prepare` step of the checkout process
|
||||
and provides PayPal with a URL to redirect back to. This URL points to a
|
||||
view which looks roughly like this::
|
||||
|
||||
@login_required
|
||||
@@ -123,7 +122,7 @@ view which looks roughly like this::
|
||||
try:
|
||||
# Redirect back to the confirm page. We chose to save the
|
||||
# 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'])
|
||||
return redirect(reverse('presale:event.checkout.confirm', kwargs={
|
||||
'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
|
||||
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*
|
||||
redirect URL, it might be necessary to define custom views.
|
||||
|
||||
@@ -6,7 +6,7 @@ Plugin basics
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
The communication between pretix and the plugins happens mostly using Django's
|
||||
@@ -16,13 +16,13 @@ on the next pages.
|
||||
|
||||
.. _`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.
|
||||
|
||||
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,
|
||||
they do assume that you have prior knowledge about Django (e.g. it's view layer,
|
||||
how it's ORM works, etc.).
|
||||
they do assume that you have prior knowledge about Django (e.g. its view layer,
|
||||
how its ORM works, etc.).
|
||||
|
||||
Plugin metadata
|
||||
---------------
|
||||
@@ -83,7 +83,7 @@ Plugin registration
|
||||
|
||||
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
|
||||
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(
|
||||
…
|
||||
@@ -129,4 +129,4 @@ your Django app label.
|
||||
.. _Django app: https://docs.djangoproject.com/en/1.7/ref/applications/
|
||||
.. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/
|
||||
.. _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
|
||||
|
||||
@@ -17,7 +17,7 @@ Output registration
|
||||
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
|
||||
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
|
||||
|
||||
@@ -35,8 +35,7 @@ The output class
|
||||
|
||||
.. class:: pretix.base.ticketoutput.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.
|
||||
The central object of each ticket output is the subclass of ``BaseTicketOutput``.
|
||||
|
||||
.. py:attribute:: BaseTicketOutput.event
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
**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.
|
||||
|
||||
Users and events
|
||||
@@ -28,25 +28,24 @@ Users and events
|
||||
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.
|
||||
|
||||
Pretix has a concept of **users** that is used for all the people who have to log
|
||||
in to the control panel to manage one or more events. No user is required to place an
|
||||
order.
|
||||
Pretix has a concept of **users** that is used for all people who have to log in to the
|
||||
control panel to manage one or more events. No user is required to place an order.
|
||||
|
||||
|
||||
Items and variations
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
The purpose of pretix is to sell **items** (which belong to **events**) to **users**.
|
||||
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,
|
||||
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
|
||||
^^^^^^^^^
|
||||
|
||||
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
|
||||
include 'Name' or 'age'.
|
||||
include 'name' or 'age'.
|
||||
|
||||
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
|
||||
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).
|
||||
* Every time a user places a binding order, the position object is replaced by an **order position** which behaves
|
||||
much the same as the cart position. It reduces the number of available item and is valid for a fixed time, this
|
||||
time for the configured payment term (e.g. 14 days).
|
||||
* Every time a user places a binding order, the **cart position** object is replaced by an **order position**
|
||||
object which behaves much the same as the cart position. It reduces the number of available items and is valid
|
||||
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.
|
||||
* Once there are no available tickets left and user A wants to buy a ticket, he 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
|
||||
are more cart position than available tickets and therefore 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
|
||||
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 specified timeframe. This policy allows the organizer to
|
||||
sell as much items as possible. Moreover, it guarantees the users to get their items if they check out within the validity
|
||||
* 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. Now there are
|
||||
more cart positions than available tickets and therefore we have to remove one of the expired cart positions.
|
||||
We will choose to delete the cart position of 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
|
||||
sell as many 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
|
||||
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.
|
||||
|
||||
@@ -20,11 +20,11 @@ on our `GitHub repository`_.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
We automatically run the tests and the code style check on every pull request on Travis CI and we won’t
|
||||
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.
|
||||
|
||||
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/
|
||||
.. _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
|
||||
|
||||
@@ -6,7 +6,7 @@ Python code
|
||||
|
||||
* 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
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@ Background tasks
|
||||
================
|
||||
|
||||
pretix provides the ability to run all longer-running tasks like generating ticket files or sending emails
|
||||
in a background thread instead of the webserver process. We use the well-established `Celery`_ project to
|
||||
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
|
||||
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.
|
||||
@@ -13,8 +13,8 @@ in the current installation.
|
||||
Implementing a task
|
||||
-------------------
|
||||
|
||||
A common pattern for implementing "optionally-asynchronous" tasks that can be seen a lot in ``pretix.base.services``
|
||||
looks like this::
|
||||
A common pattern for implementing "optionally-asynchronous" tasks can be seen a lot in ``pretix.base.services``
|
||||
and looks like this::
|
||||
|
||||
def my_task(argument1, argument2):
|
||||
# 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):
|
||||
"""
|
||||
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
|
||||
status of the task.
|
||||
"""
|
||||
@@ -93,4 +93,4 @@ AJAX sending of the form and display a loading indicator::
|
||||
...
|
||||
</form>
|
||||
|
||||
.. _Celery: http://www.celeryproject.org/
|
||||
.. _Celery: http://www.celeryproject.org/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Sending Email
|
||||
=============
|
||||
|
||||
As pretix allows event organizers to configure how they want to sent emails to their users in multiple ways.
|
||||
Therefore, all emails should be sent throught the following function:
|
||||
pretix allows event organizers to configure how they want to send emails to their users in multiple ways.
|
||||
Therefore, all emails should be sent through the following function:
|
||||
|
||||
.. autofunction:: pretix.base.services.mail.mail
|
||||
.. autofunction:: pretix.base.services.mail.mail
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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
|
||||
way to translate *user-generated content*. In our case, we need to translate strings like product names
|
||||
or event descriptions, so we need event organizers to be able to fill in all fields in multiple languages
|
||||
at the same time.
|
||||
or event descriptions, so we need event organizers to be able to fill in all fields in multiple languages.
|
||||
|
||||
.. 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
|
||||
@@ -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
|
||||
default form widget that is used by ``ModelForm``.
|
||||
|
||||
Yes, we know that this has negative impact on performance when indexing or searching them, but as mentioned above,
|
||||
within pretix this is not used in places that need to be searched. Lookups are currently not even implemented on these
|
||||
As pretix does not use these fields in places that need to be searched, the negative performance impact when searching
|
||||
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.
|
||||
|
||||
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
|
||||
exceptions with gettext-localized exception messages.
|
||||
|
||||
Last, but definitely not least, we have the ``language`` context manager that allows you to execute a piece of code with
|
||||
a different locale::
|
||||
Last, but definitely not least, we have the ``language`` context manager (``pretix.base.i18n.language``) that allows
|
||||
you to execute a piece of code with a different locale::
|
||||
|
||||
with language('de'):
|
||||
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/
|
||||
.. _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
|
||||
|
||||
@@ -2,7 +2,7 @@ Implementation and Utilities
|
||||
============================
|
||||
|
||||
This chapter describes the various inner workings that power pretix, most of them living in ``pretix.base``.
|
||||
If you want to develop around pretix' core or advanced plugins, this aims to describe everything you absolutely
|
||||
If you want to develop around pretix's core or advanced plugins, this aims to describe everything you absolutely
|
||||
need to know.
|
||||
|
||||
Contents:
|
||||
|
||||
@@ -3,7 +3,8 @@ Settings storage
|
||||
|
||||
pretix is highly configurable and therefore needs to store a lot of per-event and per-organizer settings.
|
||||
Those settings are stored in the database and accessed through a ``SettingsProxy`` instance. You can obtain
|
||||
such an instance from any event or organizer model instance by just accessing ``event.settings``.
|
||||
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
|
||||
includes serializers for serializing the following types:
|
||||
|
||||
@@ -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
|
||||
contribute to pretix itself), you need to understand how URLs work in pretix as it slightly
|
||||
differs from the standard Django system.
|
||||
contribute to pretix itself), you need to understand how URLs work in pretix as it differs
|
||||
slightly from the standard Django system.
|
||||
|
||||
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``
|
||||
@@ -24,10 +24,10 @@ URL routing
|
||||
The hard part about implementing this URL routing in Django is that
|
||||
``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
|
||||
this is by having *seperate* URL configuration for those two cases. In pretix, we call the
|
||||
former our ``maindomain`` config and the latter our ``subdomain`` config. For pretix' core
|
||||
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's core
|
||||
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
|
||||
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
|
||||
------------
|
||||
|
||||
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
|
||||
be available in the ``plugins:<applabel>`` namespace, e.g. the form of the email sending plugin is
|
||||
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
|
||||
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``:
|
||||
@@ -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
|
||||
and referer verification yourself. If you want to see how we built this, look into the ``pretix/multidomain/``
|
||||
sub-tree.
|
||||
sub-tree.
|
||||
|
||||
@@ -22,8 +22,8 @@ Your local python environment
|
||||
-----------------------------
|
||||
|
||||
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
|
||||
``pip3 -V`` to check. Then use Python 3.4's internal tools to create a virtual
|
||||
(or newer) installed. Also make sure you have pip for Python 3 installed, you can
|
||||
execute ``pip3 -V`` to check. Then use Python's internal tools to create a virtual
|
||||
environment and activate it for your current session::
|
||||
|
||||
pyvenv env
|
||||
|
||||
@@ -10,14 +10,14 @@ pretix/
|
||||
This directory contains nearly all source code.
|
||||
|
||||
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.
|
||||
|
||||
control/
|
||||
This is the django app containing the front end for organizers.
|
||||
This is the Django app containing the front end for organizers.
|
||||
|
||||
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 contain a very few modules providing workarounds for low-level flaws in
|
||||
|
||||
@@ -42,7 +42,7 @@ class BaseExporter:
|
||||
@property
|
||||
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.
|
||||
|
||||
It should therefore return a dictionary where the keys should be field names and
|
||||
|
||||
@@ -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
|
||||
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
|
||||
will be displayed.
|
||||
"""
|
||||
@@ -83,7 +83,7 @@ class I18nInlineFormSet(BaseInlineFormSet):
|
||||
|
||||
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
|
||||
settings storage. It also deals with setting the available languages for internationalized
|
||||
fields.
|
||||
|
||||
@@ -17,7 +17,7 @@ class LoginForm(forms.Form):
|
||||
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class LoginForm(forms.Form):
|
||||
|
||||
class RegistrationForm(forms.Form):
|
||||
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"),
|
||||
}
|
||||
email = forms.EmailField(
|
||||
|
||||
@@ -24,7 +24,7 @@ class LazyI18nString:
|
||||
|
||||
: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 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
|
||||
if isinstance(self.data, str) and self.data is not None:
|
||||
|
||||
@@ -36,14 +36,12 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
"""
|
||||
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
|
||||
:param givenname: The user's given name. May be empty or null.
|
||||
:type givenname: str
|
||||
:param familyname: The user's given name. May be empty or null.
|
||||
: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.
|
||||
:type is_active: bool
|
||||
:param is_staff: ``True`` for system operators.
|
||||
@@ -98,7 +96,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
|
||||
* Given name
|
||||
* Family name
|
||||
* E-mail address
|
||||
* Email address
|
||||
"""
|
||||
if self.givenname:
|
||||
return self.givenname
|
||||
|
||||
@@ -27,7 +27,7 @@ class Event(LoggedModel):
|
||||
|
||||
:param organizer: The organizer this event belongs to
|
||||
:type organizer: Organizer
|
||||
:param name: This events full title
|
||||
:param name: This event's full title
|
||||
:type name: str
|
||||
: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.
|
||||
@@ -42,7 +42,7 @@ class Event(LoggedModel):
|
||||
:type date_to: datetime
|
||||
:param presale_start: No tickets will be sold before this date.
|
||||
: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
|
||||
:param plugins: A comma-separated list of plugin names that are active for this
|
||||
event.
|
||||
@@ -110,14 +110,14 @@ class Event(LoggedModel):
|
||||
|
||||
def clean(self):
|
||||
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:
|
||||
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()
|
||||
|
||||
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:
|
||||
return []
|
||||
@@ -160,7 +160,7 @@ class Event(LoggedModel):
|
||||
@cached_property
|
||||
def settings(self) -> SettingsProxy:
|
||||
"""
|
||||
Returns an object representing this event's settings
|
||||
Returns an object representing this event's settings.
|
||||
"""
|
||||
try:
|
||||
return SettingsProxy(self, type=EventSetting, parent=self.organizer)
|
||||
@@ -184,7 +184,7 @@ class Event(LoggedModel):
|
||||
|
||||
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
|
||||
|
||||
@@ -205,12 +205,12 @@ class Event(LoggedModel):
|
||||
|
||||
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.
|
||||
|
||||
:param event: The event this refers to
|
||||
:param event: The event this permission refers to
|
||||
:type event: Event
|
||||
:param user: The user these permission set applies to
|
||||
:param user: The user this permission set applies to
|
||||
:type user: User
|
||||
:param can_change_settings: If ``True``, the user can change all basic settings for this event.
|
||||
:type can_change_settings: bool
|
||||
|
||||
@@ -19,19 +19,30 @@ def invoice_filename(instance, filename: str) -> str:
|
||||
class Invoice(models.Model):
|
||||
"""
|
||||
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
|
||||
:type order: Order
|
||||
:param event: The event this belongs to (for convenience)
|
||||
:type event: Event
|
||||
: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 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
|
||||
:type invoice_from: str
|
||||
:param invoice_to: The receiver address
|
||||
:type invoice_to: str
|
||||
:param date: The invoice date
|
||||
:type date: date
|
||||
:param locale: The locale in which the invoice should be printed
|
||||
:type locale: str
|
||||
:param additional_text: Additional text for the invoice
|
||||
:type additional_text: str
|
||||
:param file: The filename of the rendered invoice
|
||||
:type file: File
|
||||
"""
|
||||
order = models.ForeignKey('Order', 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):
|
||||
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:
|
||||
self.event = self.order.event
|
||||
if not self.invoice_no:
|
||||
@@ -65,7 +76,7 @@ class Invoice(models.Model):
|
||||
@property
|
||||
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)
|
||||
|
||||
@@ -75,13 +86,18 @@ class Invoice(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
|
||||
:type invoice: Invoice
|
||||
:param description: The item description
|
||||
:type description: str
|
||||
:param gross_value: The gross value
|
||||
:type gross_value: decimal.Decimal
|
||||
:param tax_value: The included tax (as an absolute value)
|
||||
:type tax_value: decimal.Decimal
|
||||
:param tax_rate: The applied tax rate in percent
|
||||
:type tax_rate: decimal.Decimal
|
||||
"""
|
||||
invoice = models.ForeignKey('Invoice', related_name='lines')
|
||||
description = models.TextField()
|
||||
|
||||
@@ -20,7 +20,7 @@ class ItemCategory(LoggedModel):
|
||||
"""
|
||||
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
|
||||
:param name: The name of this category
|
||||
: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.
|
||||
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
|
||||
:param category: The category this belongs to. May be null.
|
||||
:type category: ItemCategory
|
||||
:param name: The name of this item:
|
||||
:param name: The name of this item
|
||||
:type name: str
|
||||
:param active: Whether this item is being sold
|
||||
:param active: Whether this item is being sold.
|
||||
:type active: bool
|
||||
:param description: A short description
|
||||
:type description: str
|
||||
@@ -97,7 +97,7 @@ class Item(LoggedModel):
|
||||
:type tax_rate: decimal.Decimal
|
||||
:param admission: ``True``, if this item allows persons to enter the event (as opposed to e.g. merchandise)
|
||||
: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
|
||||
:param available_from: The date this product goes on sale
|
||||
:type available_from: datetime
|
||||
@@ -235,7 +235,8 @@ class ItemVariation(models.Model):
|
||||
:param item: The item this variation belongs to
|
||||
:type item: Item
|
||||
: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
|
||||
:param default_price: This variation's default price
|
||||
:type default_price: decimal.Decimal
|
||||
@@ -299,7 +300,7 @@ class ItemVariation(models.Model):
|
||||
class Question(LoggedModel):
|
||||
"""
|
||||
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:
|
||||
|
||||
* 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
|
||||
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
|
||||
building), and also a quota of 100 applied to the VIP tickets for
|
||||
exclusivity. In this case, no more than 500 tickets will be sold in total
|
||||
and no more than 100 of them will be VIP tickets (but 450 normal and 50
|
||||
VIP tickets will be fine).
|
||||
applied to all of your items (because you only have that much space in your
|
||||
venue), and also a quota of 100 applied to the VIP tickets for exclusivity.
|
||||
In this case, no more than 500 tickets will be sold in total and no more
|
||||
than 100 of them will be VIP tickets (but 450 normal and 50 VIP tickets
|
||||
will be fine).
|
||||
|
||||
As always, a quota can not only be tied to an item, but also to specific
|
||||
variations.
|
||||
@@ -400,19 +401,19 @@ class Quota(LoggedModel):
|
||||
anything with quotas. This might confuse you otherwise.
|
||||
http://docs.pretix.eu/en/latest/development/concepts.html#restriction-by-number
|
||||
|
||||
The AVAILABILITY_* constants represent various states of an quota allowing
|
||||
its items/variations being for sale.
|
||||
The AVAILABILITY_* constants represent various states of a quota allowing
|
||||
its items/variations to be up for sale.
|
||||
|
||||
AVAILABILITY_OK
|
||||
This item is available for sale.
|
||||
|
||||
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
|
||||
again if those people do not proceed with checkout.
|
||||
again if those people do not proceed to the checkout.
|
||||
|
||||
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
|
||||
do not pay.
|
||||
|
||||
@@ -422,7 +423,7 @@ class Quota(LoggedModel):
|
||||
:param event: The event this belongs to
|
||||
:type event: Event
|
||||
:param name: This quota's name
|
||||
:type str:
|
||||
:type name: str
|
||||
:param size: The number of items in this quota
|
||||
:type size: int
|
||||
:param items: The set of :py:class:`Item` objects this quota applies to
|
||||
|
||||
@@ -10,12 +10,13 @@ class LogEntry(models.Model):
|
||||
relation to an arbitrary database object.
|
||||
|
||||
:param datatime: The timestamp of the logged action
|
||||
:type datetime: datetime
|
||||
:param user: The user that performed the action
|
||||
:type user: User
|
||||
: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-
|
||||
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"``.
|
||||
:type action_type: str
|
||||
:param data: Arbitrary data that can be used by the log action renderer
|
||||
|
||||
@@ -27,17 +27,18 @@ def generate_position_secret():
|
||||
class Order(LoggedModel):
|
||||
"""
|
||||
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
|
||||
their expiration date might be cancelled.
|
||||
|
||||
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
|
||||
unique among a single conference.
|
||||
unique within a single conference.
|
||||
|
||||
:param code: In addition to the ID, which is globally unique, every
|
||||
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:
|
||||
|
||||
* ``STATUS_PENDING``
|
||||
@@ -46,7 +47,7 @@ class Order(LoggedModel):
|
||||
* ``STATUS_CANCELLED``
|
||||
* ``STATUS_REFUNDED``
|
||||
|
||||
:param event: The event this belongs to
|
||||
:param event: The event this order belongs to
|
||||
:type event: Event
|
||||
:param email: The email of the person who ordered this
|
||||
:type email: str
|
||||
@@ -56,15 +57,15 @@ class Order(LoggedModel):
|
||||
:type secret: str
|
||||
:param datetime: The datetime of the order placement
|
||||
: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
|
||||
: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
|
||||
:param payment_provider: The payment provider selected by the user
|
||||
:type payment_provider: str
|
||||
:param payment_fee: The payment fee calculated at checkout time
|
||||
: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
|
||||
:param payment_fee_tax_rate: The tax rate applied to the payment fee (in percent)
|
||||
:type payment_fee_tax_rate: decimal.Decimal
|
||||
@@ -161,7 +162,7 @@ class Order(LoggedModel):
|
||||
@property
|
||||
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.
|
||||
"""
|
||||
return self.event.slug.upper() + self.code
|
||||
@@ -198,9 +199,9 @@ class Order(LoggedModel):
|
||||
@property
|
||||
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
|
||||
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):
|
||||
return False
|
||||
@@ -323,7 +324,7 @@ class AbstractPosition(models.Model):
|
||||
|
||||
:param item: The selected 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
|
||||
:param datetime: The datetime this item was put into the cart
|
||||
:type datetime: datetime
|
||||
@@ -404,11 +405,11 @@ class AbstractPosition(models.Model):
|
||||
|
||||
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
|
||||
AbstractPosition.
|
||||
|
||||
:param order: The order this is a part of
|
||||
:param order: The order this position is a part of
|
||||
:type order: Order
|
||||
"""
|
||||
order = models.ForeignKey(
|
||||
@@ -470,7 +471,7 @@ class OrderPosition(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
|
||||
her cart. It therefore normally has a much shorter expiration time
|
||||
than an ordered position, but still blocks an item in the quota pool
|
||||
|
||||
@@ -73,7 +73,7 @@ class Organizer(LoggedModel):
|
||||
|
||||
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.
|
||||
|
||||
:param organizer: The organizer this relation refers to
|
||||
|
||||
@@ -21,23 +21,33 @@ def generate_code():
|
||||
|
||||
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
|
||||
:type event: Event
|
||||
:param code: The secret voucher code
|
||||
:type code: str
|
||||
:param redeemed: Whether or not this voucher has already been redeemed
|
||||
:type redeemed: bool
|
||||
: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
|
||||
:type block_quota: bool
|
||||
: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
|
||||
:type price: decimal.Decimal
|
||||
:param item: If set, the item to sell
|
||||
:type item: Item
|
||||
:param variation: If set, the variation to sell
|
||||
:type variation: ItemVariation
|
||||
:param quota: If set, the quota to choose an item from
|
||||
:type quota: Quota
|
||||
|
||||
Various constraints apply:
|
||||
|
||||
* You can either select a quota or an item and you need to select one of those
|
||||
* If you select an item that as variations but not select a variation, you cannot set block_quota
|
||||
* You need to either select a quota or an item
|
||||
* If you select an item that has variations but do not select a variation, you cannot set block_quota
|
||||
"""
|
||||
event = models.ForeignKey(
|
||||
Event,
|
||||
|
||||
@@ -35,7 +35,7 @@ class BasePaymentProvider:
|
||||
@property
|
||||
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.
|
||||
"""
|
||||
return self.settings.get('_enabled', as_type=bool)
|
||||
@@ -78,7 +78,7 @@ class BasePaymentProvider:
|
||||
@property
|
||||
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.
|
||||
|
||||
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:
|
||||
"""
|
||||
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
|
||||
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:
|
||||
"""
|
||||
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`
|
||||
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
|
||||
of explainatory text.
|
||||
of explanatory text.
|
||||
"""
|
||||
form = self.payment_form(request)
|
||||
template = get_template('pretixpresale/event/checkout_payment_form_default.html')
|
||||
@@ -202,25 +202,25 @@ class BasePaymentProvider:
|
||||
|
||||
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.
|
||||
This method should return the HTML which should be displayed inside the
|
||||
'Payment' box on this page.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
def payment_perform(self, request: HttpRequest, order: Order) -> str:
|
||||
"""
|
||||
After the user confirmed his purchase, this method will be called to complete
|
||||
the payment process. This is the place to actually move the money, if applicable.
|
||||
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.
|
||||
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.
|
||||
|
||||
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:
|
||||
"""
|
||||
After the user submitted his order, he or she will receive a confirmation
|
||||
e-mail. You can return a string from this method if you want to add additional
|
||||
information to this e-mail.
|
||||
After the user has submitted their order, they will receive a confirmation
|
||||
email. You can return a string from this method if you want to add additional
|
||||
information to this email.
|
||||
|
||||
:param order: The order object
|
||||
"""
|
||||
@@ -342,7 +342,7 @@ class BasePaymentProvider:
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
with default behaviour.
|
||||
|
||||
On failure, you should use Django's message framework to display an error message
|
||||
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.
|
||||
|
||||
:param request: The HTTP request
|
||||
@@ -446,14 +446,14 @@ class FreeOrderProvider(BasePaymentProvider):
|
||||
"""
|
||||
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
|
||||
with default behaviour.
|
||||
|
||||
On failure, you should use Django's message framework to display an error message
|
||||
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.
|
||||
|
||||
:param request: The HTTP request
|
||||
|
||||
@@ -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.
|
||||
|
||||
: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.
|
||||
|
||||
:param template: The filename of a template to be used. It will be rendered with the locale given in the locale
|
||||
argument and the context given in the next argument. Alternatively, you can pass a LazyI18nString and
|
||||
``context`` will be used as the argument to a Python ``.format()`` call on the template.
|
||||
|
||||
:param context: The context for rendering the template (see ``template`` parameter).
|
||||
:param context: The context for rendering the template (see ``template`` parameter)
|
||||
|
||||
:param event: The event this email is related to (optional). If set, this will be used to determine the sender,
|
||||
a possible prefix for the subject and the SMTP server that should be used to send this email.
|
||||
|
||||
:param locale: The locale to be used while evaluating the subject and the template.
|
||||
:param locale: The locale to be used while evaluating the subject and the template
|
||||
|
||||
:return: ``False`` on obvious, immediate failures, ``True`` otherwise. ``True`` does not necessarily mean that
|
||||
the email has been sent, just that it has been queued by the e-mail backend.
|
||||
the email has been sent, just that it has been queued by the email backend.
|
||||
"""
|
||||
with language(locale):
|
||||
if isinstance(template, LazyI18nString):
|
||||
@@ -61,7 +61,7 @@ def mail(email: str, subject: str, template: str,
|
||||
|
||||
body += "\r\n\r\n----\r\n"
|
||||
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)
|
||||
body += "\r\n"
|
||||
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])
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception('Error sending e-mail')
|
||||
logger.exception('Error sending email')
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -115,6 +115,6 @@ periodic_task = django.dispatch.Signal()
|
||||
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
|
||||
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.
|
||||
"""
|
||||
|
||||
@@ -24,7 +24,7 @@ class BaseTicketOutput:
|
||||
@property
|
||||
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.
|
||||
"""
|
||||
return self.settings.get('_enabled', as_type=bool)
|
||||
@@ -57,7 +57,7 @@ class BaseTicketOutput:
|
||||
@property
|
||||
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.
|
||||
|
||||
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:
|
||||
"""
|
||||
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
|
||||
that is displayed below the form fields configured in ``settings_form_fields``.
|
||||
"""
|
||||
|
||||
@@ -276,7 +276,7 @@ class MailSettingsForm(SettingsForm):
|
||||
)
|
||||
mail_from = forms.EmailField(
|
||||
label=_("Sender address"),
|
||||
help_text=_("Sender address for outgoing e-mails")
|
||||
help_text=_("Sender address for outgoing emails")
|
||||
)
|
||||
mail_text_order_placed = I18nFormField(
|
||||
label=_("Placed order"),
|
||||
|
||||
@@ -17,8 +17,8 @@ html_head = EventPluginSignal(
|
||||
)
|
||||
"""
|
||||
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
|
||||
``request`` and can return plain HTML.
|
||||
of every page in the backend. You will get the request as the keyword argument
|
||||
``request`` and are expected to return plain HTML.
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
@@ -46,7 +46,7 @@ event_dashboard_widgets = EventPluginSignal(
|
||||
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:
|
||||
|
||||
* content (str, containing HTML)
|
||||
@@ -62,7 +62,7 @@ user_dashboard_widgets = Signal(
|
||||
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:
|
||||
|
||||
* 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.
|
||||
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.
|
||||
"""
|
||||
|
||||
@@ -28,6 +28,7 @@ def eventreverse(obj, name, kwargs=None):
|
||||
|
||||
:param obj: An ``Event`` or ``Organizer`` object
|
||||
: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
|
||||
need to provide the organizer or event slug here, it will be added automatically as
|
||||
needed.
|
||||
|
||||
@@ -178,12 +178,12 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
emailval = EmailValidator()
|
||||
if 'email' not in request.session:
|
||||
if warn:
|
||||
messages.warning(request, _('Please enter a valid e-mail address.'))
|
||||
messages.warning(request, _('Please enter a valid email address.'))
|
||||
return False
|
||||
emailval(request.session.get('email'))
|
||||
except ValidationError:
|
||||
if warn:
|
||||
messages.warning(request, _('Please enter a valid e-mail address.'))
|
||||
messages.warning(request, _('Please enter a valid email address.'))
|
||||
return False
|
||||
|
||||
for cp in self.positions:
|
||||
|
||||
@@ -5,8 +5,8 @@ html_head = EventPluginSignal(
|
||||
)
|
||||
"""
|
||||
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
|
||||
``request`` and can return plain HTML.
|
||||
of every page in the frontend. You will get the request as the keyword argument
|
||||
``request`` and are expected to return plain HTML.
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user