forked from CGM_Public/pretix_original
Compare commits
9 Commits
v1.11.1
...
release/1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae485a77d6 | ||
|
|
29e17d2284 | ||
|
|
6b95aca3f7 | ||
|
|
f227319f29 | ||
|
|
e63b85ccd6 | ||
|
|
bf3398e5f6 | ||
|
|
765d98464e | ||
|
|
66bdf9ab79 | ||
|
|
da830ef0a4 |
14
.travis.sh
14
.travis.sh
@@ -25,27 +25,19 @@ if [ "$1" == "doctests" ]; then
|
||||
cd doc
|
||||
make doctest
|
||||
fi
|
||||
if [ "$1" == "spelling" ]; then
|
||||
XDG_CACHE_HOME=/cache pip3 install -Ur doc/requirements.txt
|
||||
cd doc
|
||||
make spelling
|
||||
if [ -s _build/spelling/output.txt ]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [ "$1" == "tests" ]; then
|
||||
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt -r src/requirements/py34.txt
|
||||
cd src
|
||||
python manage.py check
|
||||
make all compress
|
||||
py.test --reruns 5 tests
|
||||
py.test --rerun 5 tests
|
||||
fi
|
||||
if [ "$1" == "tests-cov" ]; then
|
||||
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt -r src/requirements/py34.txt
|
||||
cd src
|
||||
python manage.py check
|
||||
make all compress
|
||||
coverage run -m py.test --reruns 5 tests && codecov
|
||||
coverage run -m py.test --rerun 5 tests && codecov
|
||||
fi
|
||||
if [ "$1" == "plugins" ]; then
|
||||
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt -r src/requirements/py34.txt
|
||||
@@ -58,7 +50,7 @@ if [ "$1" == "plugins" ]; then
|
||||
cd pretix-cartshare
|
||||
python setup.py develop
|
||||
make
|
||||
py.test --reruns 5 tests
|
||||
py.test --rerun 5 tests
|
||||
popd
|
||||
|
||||
fi
|
||||
|
||||
@@ -36,10 +36,5 @@ matrix:
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||
- python: 3.6
|
||||
env: JOB=plugins
|
||||
- python: 3.6
|
||||
env: JOB=spelling
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
apt:
|
||||
packages:
|
||||
- enchant
|
||||
|
||||
@@ -175,9 +175,3 @@ pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
|
||||
spelling:
|
||||
$(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling
|
||||
@echo
|
||||
@echo "Spelling check finished, look at the results in " \
|
||||
"$(BUILDDIR)/spelling/output.txt."
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
.. _`config`:
|
||||
|
||||
.. spelling:: Galera
|
||||
|
||||
Configuration file
|
||||
==================
|
||||
|
||||
@@ -47,7 +45,7 @@ Example::
|
||||
|
||||
``datadir``
|
||||
The local path to a data directory that will be used for storing user uploads and similar
|
||||
data. Defaults to the value of the environment variable ``DATA_DIR`` or ``data``.
|
||||
data. Defaults to thea value of the environment variable ``DATA_DIR`` or ``data``.
|
||||
|
||||
``plugins_default``
|
||||
A comma-separated list of plugins that are enabled by default for all new events.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
.. highlight:: ini
|
||||
|
||||
.. spelling:: SQL
|
||||
|
||||
General remarks
|
||||
===============
|
||||
|
||||
|
||||
@@ -32,10 +32,6 @@ checkin_count integer Number of check
|
||||
|
||||
This resource has been added.
|
||||
|
||||
.. versionchanged:: 1.11
|
||||
|
||||
The ``positions`` endpoints have been added.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
@@ -169,7 +165,7 @@ Endpoints
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(id)/
|
||||
|
||||
Update a check-in list. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
|
||||
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
|
||||
the resource, other fields will be resetted to default. With ``PATCH``, you only need to provide the fields that you
|
||||
want to change.
|
||||
|
||||
You can change all fields of the resource except the ``id`` field and the ``checkin_count`` and ``position_count``
|
||||
@@ -217,7 +213,7 @@ Endpoints
|
||||
|
||||
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/checkinlist/(id)/
|
||||
|
||||
Delete a check-in list. Note that this also deletes the information on all check-ins performed via this list.
|
||||
Delete a check-in list. Note that this also deletes the information on all checkins performed via this list.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@@ -240,163 +236,3 @@ Endpoints
|
||||
:statuscode 204: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
|
||||
|
||||
|
||||
Order position endpoints
|
||||
------------------------
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/
|
||||
|
||||
Returns a list of all order positions within a given event. The result is the same as
|
||||
the :ref:`order-position-resource`, with one important difference: the ``checkins`` value will only include
|
||||
check-ins for the selected list.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/checkinlists/1/positions/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 23442,
|
||||
"order": "ABC12",
|
||||
"positionid": 1,
|
||||
"item": 1345,
|
||||
"variation": null,
|
||||
"price": "23.00",
|
||||
"attendee_name": "Peter",
|
||||
"attendee_email": null,
|
||||
"voucher": null,
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": null,
|
||||
"tax_value": "0.00",
|
||||
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
"addon_to": null,
|
||||
"subevent": null,
|
||||
"checkins": [
|
||||
{
|
||||
"list": 1,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
}
|
||||
],
|
||||
"answers": [
|
||||
{
|
||||
"question": 12,
|
||||
"answer": "Foo",
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
"downloads": [
|
||||
{
|
||||
"output": "pdf",
|
||||
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``order__code``,
|
||||
``order__datetime``, ``positionid``, ``attendee_name``, ``last_checked_in`` and ``order__email``. Default:
|
||||
``attendee_name,positionid``
|
||||
:query string order: Only return positions of the order with the given order code
|
||||
:query integer item: Only return positions with the purchased item matching the given ID.
|
||||
:query integer variation: Only return positions with the purchased item variation matching the given ID.
|
||||
:query string attendee_name: Only return positions with the given value in the attendee_name field. Also, add-on
|
||||
products positions are shown if they refer to an attendee with the given name.
|
||||
:query string secret: Only return positions with the given ticket secret.
|
||||
:query bollean has_checkin: If set to ``true`` or ``false``, only return positions that have or have not been
|
||||
checked in already on this list.
|
||||
:query integer subevent: Only return positions of the sub-event with the given ID
|
||||
:query integer addon_to: Only return positions that are add-ons to the position with the given ID.
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param list: The ID of the check-in list to look for
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
:statuscode 404: The requested check-in list does not exist.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/(id)
|
||||
|
||||
Returns information on one order position, identified by its internal ID.
|
||||
The result format is the same as the :ref:`order-position-resource`, with one important difference: the
|
||||
``checkins`` value will only include check-ins for the selected list.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/checkinlists/1/positions/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 23442,
|
||||
"order": "ABC12",
|
||||
"positionid": 1,
|
||||
"item": 1345,
|
||||
"variation": null,
|
||||
"price": "23.00",
|
||||
"attendee_name": "Peter",
|
||||
"attendee_email": null,
|
||||
"voucher": null,
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": null,
|
||||
"tax_value": "0.00",
|
||||
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
"addon_to": null,
|
||||
"subevent": null,
|
||||
"checkins": [
|
||||
{
|
||||
"list": 1,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
}
|
||||
],
|
||||
"answers": [
|
||||
{
|
||||
"question": 12,
|
||||
"answer": "Foo",
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
"downloads": [
|
||||
{
|
||||
"output": "pdf",
|
||||
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param list: The ID of the check-in list to look for
|
||||
:param id: The ``id`` field of the order position to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
:statuscode 404: The requested order position or check-in list does not exist.
|
||||
|
||||
@@ -221,5 +221,5 @@ Endpoints
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting for a few
|
||||
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting vor a few
|
||||
seconds.
|
||||
|
||||
@@ -70,7 +70,7 @@ addons list of objects Definition of a
|
||||
├ addon_category integer Internal ID of the item category the add-on can be
|
||||
chosen from.
|
||||
├ min_count integer The minimal number of add-ons that need to be chosen.
|
||||
├ max_count integer The maximal number of add-ons that can be chosen.
|
||||
├ max_count integer The maxima number of add-ons that can be chosen.
|
||||
└ position integer An integer, used for sorting
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
.. spelling:: checkins
|
||||
|
||||
Orders
|
||||
======
|
||||
|
||||
@@ -81,15 +79,13 @@ downloads list of objects List of ticket
|
||||
|
||||
The attributes ``invoice_address.vat_id_validated`` and ``invoice_address.is_business`` have been added.
|
||||
The attributes ``order.payment_fee``, ``order.payment_fee_tax_rate`` and ``order.payment_fee_tax_value`` have been
|
||||
deprecated in favor of the new ``fees`` attribute but will still be served and removed in 1.9.
|
||||
deprecated in favour of the new ``fees`` attribute but will still be served and removed in 1.9.
|
||||
|
||||
.. versionchanged:: 1.9
|
||||
|
||||
First write operations (``…/mark_paid/``, ``…/mark_pending/``, ``…/mark_canceled/``, ``…/mark_expired/``) have been added.
|
||||
The attribute ``invoice_address.internal_reference`` has been added.
|
||||
|
||||
.. _order-position-resource:
|
||||
|
||||
Order position resource
|
||||
-----------------------
|
||||
|
||||
@@ -114,7 +110,6 @@ secret string Secret code pri
|
||||
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
|
||||
subevent integer ID of the date inside an event series this position belongs to (or ``null``).
|
||||
checkins list of objects List of check-ins with this ticket
|
||||
├ list integer Internal ID of the check-in list
|
||||
└ datetime datetime Time of check-in
|
||||
downloads list of objects List of ticket download options
|
||||
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
|
||||
@@ -129,10 +124,6 @@ answers list of objects Answers to user
|
||||
|
||||
The attribute ``tax_rule`` has been added.
|
||||
|
||||
.. versionchanged:: 1.11
|
||||
|
||||
The attribute ``checkins.list`` has been added.
|
||||
|
||||
|
||||
Order endpoints
|
||||
---------------
|
||||
@@ -207,7 +198,6 @@ Order endpoints
|
||||
"subevent": null,
|
||||
"checkins": [
|
||||
{
|
||||
"list": 44,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
}
|
||||
],
|
||||
@@ -314,7 +304,6 @@ Order endpoints
|
||||
"subevent": null,
|
||||
"checkins": [
|
||||
{
|
||||
"list": 44,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
}
|
||||
],
|
||||
@@ -353,7 +342,7 @@ Order endpoints
|
||||
|
||||
Download tickets for an order, identified by its order code. Depending on the chosen output, the response might
|
||||
be a ZIP file, PDF file or something else. The order details response contains a list of output options for this
|
||||
particular order.
|
||||
partictular order.
|
||||
|
||||
Tickets can be only downloaded if the order is paid and if ticket downloads are active. Note that in some cases the
|
||||
ticket file might not yet have been created. In that case, you will receive a status code :http:statuscode:`409` and
|
||||
@@ -633,7 +622,6 @@ Order position endpoints
|
||||
"subevent": null,
|
||||
"checkins": [
|
||||
{
|
||||
"list": 44,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
}
|
||||
],
|
||||
@@ -713,7 +701,6 @@ Order position endpoints
|
||||
"subevent": null,
|
||||
"checkins": [
|
||||
{
|
||||
"list": 44,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -158,7 +158,7 @@ Endpoints
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/quotas/(id)/
|
||||
|
||||
Update a quota. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
|
||||
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
|
||||
the resource, other fields will be resetted to default. With ``PATCH``, you only need to provide the fields that you
|
||||
want to change.
|
||||
|
||||
You can change all fields of the resource except the ``id`` field.
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
from enchant.tokenize import get_tokenizer, Filter, unit_tokenize
|
||||
|
||||
class CheckinFilter(Filter):
|
||||
""" If a word looks like checkin_count, it refers to a so-called variable in
|
||||
the code, and is treated as being spelled right."""
|
||||
|
||||
def _split(self, word):
|
||||
if word[:8] == "checkin_":
|
||||
return unit_tokenize(word[8:])
|
||||
|
||||
return unit_tokenize(word)
|
||||
20
doc/conf.py
20
doc/conf.py
@@ -45,7 +45,6 @@ extensions = [
|
||||
'sphinx.ext.coverage',
|
||||
'sphinxcontrib.httpdomain',
|
||||
'sphinxcontrib.images',
|
||||
'sphinxcontrib.spelling',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
@@ -291,22 +290,3 @@ texinfo_documents = [
|
||||
images_config = {
|
||||
'default_image_width': '250px'
|
||||
}
|
||||
|
||||
# -- Options for Spelling output ------------------------------------------
|
||||
|
||||
# String specifying the language, as understood by PyEnchant and enchant.
|
||||
# Defaults to en_US for US English.
|
||||
spelling_lang = 'en_US'
|
||||
|
||||
# String specifying a file containing a list of words known to be spelled
|
||||
# correctly but that do not appear in the language dictionary selected by
|
||||
# spelling_lang. The file should contain one word per line.
|
||||
spelling_word_list_filename='spelling_wordlist.txt'
|
||||
|
||||
# Boolean controlling whether suggestions for misspelled words are printed.
|
||||
# Defaults to False.
|
||||
spelling_show_suggestions=True
|
||||
|
||||
# List of filter classes to be added to the tokenizer that produces words to be checked.
|
||||
from checkin_filter import CheckinFilter
|
||||
spelling_filters=[CheckinFilter]
|
||||
|
||||
@@ -25,7 +25,7 @@ If you want to add a custom view to the control area of an event, just register
|
||||
views.admin_view, name='backend'),
|
||||
]
|
||||
|
||||
It is required that your URL parameters are called ``organizer`` and ``event``. If you want to
|
||||
It is required that your URL paramaters are called ``organizer`` and ``event``. If you want to
|
||||
install a view on organizer level, you can leave out the ``event``.
|
||||
|
||||
You can then implement the view as you would normally do. Our middleware will automatically
|
||||
|
||||
@@ -11,7 +11,7 @@ Core
|
||||
----
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types
|
||||
:members: periodic_task, event_live_issues, event_copy_data, email_filter
|
||||
|
||||
Order events
|
||||
""""""""""""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Logging and notifications
|
||||
=========================
|
||||
Logging
|
||||
=======
|
||||
|
||||
As pretix is handling monetary transactions, we are very careful to make it possible to review all changes
|
||||
in the system that lead to the current state.
|
||||
@@ -19,7 +19,7 @@ To actually log an action, you can just call the ``log_action`` method on your o
|
||||
order.log_action('pretix.event.order.canceled', user=user, data={})
|
||||
|
||||
The positional ``action`` argument should represent the type of action and should be globally unique, we
|
||||
recommend to prefix it with your package name, e.g. ``paypal.payment.rejected``. The ``user`` argument is
|
||||
recomment do prefix it with your packagename, e.g. ``paypal.payment.rejected``. The ``user`` argument is
|
||||
optional and may contain the user who performed the action. The optional ``data`` argument can contain
|
||||
additional information about this action.
|
||||
|
||||
@@ -81,61 +81,6 @@ implementation could look like::
|
||||
if logentry.action_type in plains:
|
||||
return plains[logentry.action_type]
|
||||
|
||||
Sending notifications
|
||||
---------------------
|
||||
|
||||
If you think that the logged information might be important or urgent enough to send out a notification to interested
|
||||
organizers. In this case, you should listen for the :py:attr:`pretix.base.signals.register_notification_types` signal
|
||||
to register a notification type::
|
||||
|
||||
@receiver(register_notification_types)
|
||||
def register_my_notification_types(sender, **kwargs):
|
||||
return [MyNotificationType(sender)]
|
||||
|
||||
Note that this event is different than other events send out by pretix: ``sender`` may be an event or ``None``. The
|
||||
latter case is required to let the user define global notification preferences for all events.
|
||||
|
||||
You also need to implement a custom class that specifies how notifications should be handled for your notification type.
|
||||
You should subclass the base ``NotificationType`` class and implement all its members:
|
||||
|
||||
.. autoclass:: pretix.base.notifications.NotificationType
|
||||
:members: action_type, verbose_name, required_permission, build_notification
|
||||
|
||||
A simple implementation could look like this::
|
||||
|
||||
class MyNotificationType(NotificationType):
|
||||
required_permission = "can_view_orders"
|
||||
action_type = "pretix.event.order.paid"
|
||||
verbose_name = _("Order has been paid")
|
||||
|
||||
def build_notification(self, logentry: LogEntry):
|
||||
order = logentry.content_object
|
||||
|
||||
order_url = build_absolute_uri(
|
||||
'control:event.order',
|
||||
kwargs={
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
'event': logentry.event.slug,
|
||||
'code': order.code
|
||||
}
|
||||
)
|
||||
|
||||
n = Notification(
|
||||
event=logentry.event,
|
||||
title=_('Order {code} has been marked as paid').format(code=order.code),
|
||||
url=order_url
|
||||
)
|
||||
n.add_attribute(_('Order code'), order.code)
|
||||
n.add_action(_('View order details'), order_url)
|
||||
return n
|
||||
|
||||
As you can see, the relevant code is in the ``build_notification`` method that is supposed to create a ``Notification``
|
||||
method that has a title, description, URL, attributes, and actions. The full definition of ``Notification`` is the
|
||||
following:
|
||||
|
||||
.. autoclass:: pretix.base.notifications.Notification
|
||||
:members: add_action, add_attribute
|
||||
|
||||
|
||||
Logging technical information
|
||||
-----------------------------
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
.. highlight:: python
|
||||
:linenothreshold: 5
|
||||
|
||||
.. spelling:: answ contrib
|
||||
|
||||
Data model
|
||||
==========
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Forms
|
||||
-----
|
||||
|
||||
Hierarkey also provides a base class for forms that allow the modification of settings. pretix contains a
|
||||
subclass that also adds support for internationalized fields:
|
||||
subclass that also adds suport for internationalized fields:
|
||||
|
||||
.. autoclass:: pretix.base.forms.SettingsForm
|
||||
|
||||
@@ -65,4 +65,4 @@ Plugins can add custom hardcoded defaults in the following way::
|
||||
Make sure that you include this code in a module that is imported at app loading time.
|
||||
|
||||
.. _django-hierarkey: https://github.com/raphaelm/django-hierarkey
|
||||
.. _documentation: https://django-hierarkey.readthedocs.io/en/latest/
|
||||
.. _documentation: https://django-hierarkey.readthedocs.io/en/latest/
|
||||
@@ -67,7 +67,7 @@ available as ``plugins:sendmail:send``.
|
||||
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 behavior similar to ``reverse``:
|
||||
is a python method that emulates a behaviour similar to ``reverse``:
|
||||
|
||||
.. autofunction:: pretix.multidomain.urlreverse.eventreverse
|
||||
|
||||
@@ -82,5 +82,5 @@ Implementation details
|
||||
----------------------
|
||||
|
||||
There are some other caveats when using a design like this, e.g. you have to care about cookie domains
|
||||
and referrer 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.
|
||||
|
||||
@@ -86,7 +86,7 @@ and head to http://localhost:8000/
|
||||
|
||||
As we did not implement an overall front page yet, you need to go directly to
|
||||
http://localhost:8000/control/ for the admin view or, if you imported the test
|
||||
data as suggested above, to the event page at http://localhost:8000/bigevents/2019/
|
||||
data as suggested above, to the event page at http://localhost:8000/bigevents/2018/
|
||||
|
||||
.. note:: If you want the development server to listen on a different interface or
|
||||
port (for example because you develop on `pretixdroid`_), you can check
|
||||
@@ -106,7 +106,7 @@ Execute the following commands to check for code style errors::
|
||||
isort -c -rc .
|
||||
python manage.py check
|
||||
|
||||
Execute the following command to run pretix' test suite (might take a couple of minutes)::
|
||||
Execute the following command to run pretix' test suite (might take a coumple of minutes)::
|
||||
|
||||
py.test
|
||||
|
||||
@@ -122,7 +122,7 @@ for example::
|
||||
flake8 . || exit 1
|
||||
isort -q -rc -c . || exit 1
|
||||
|
||||
This keeps you from accidentally creating commits violating the style guide.
|
||||
This keeps you from accidentally creating commits violating the sdtyle guide.
|
||||
|
||||
Working with mails
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
.. spelling::
|
||||
Analytics
|
||||
|
||||
List of plugins
|
||||
===============
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ uses to communicate with the pretix server.
|
||||
.. http:get:: /pretixdroid/api/(organizer)/(event)/status/
|
||||
|
||||
Returns status information, such as the total number of tickets and the
|
||||
number of performed check-ins.
|
||||
number of performed checkins.
|
||||
|
||||
**Example request**:
|
||||
|
||||
|
||||
@@ -3,5 +3,3 @@ sphinx
|
||||
sphinx-rtd-theme
|
||||
sphinxcontrib-httpdomain
|
||||
sphinxcontrib-images
|
||||
sphinxcontrib-spelling
|
||||
pyenchant
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 95 KiB |
@@ -1,118 +0,0 @@
|
||||
addon
|
||||
addons
|
||||
api
|
||||
auth
|
||||
autobuild
|
||||
backend
|
||||
backends
|
||||
banktransfer
|
||||
boolean
|
||||
booleans
|
||||
cancelled
|
||||
casted
|
||||
checkbox
|
||||
checksum
|
||||
config
|
||||
contenttypes
|
||||
contextmanager
|
||||
cron
|
||||
cronjob
|
||||
debian
|
||||
deduplication
|
||||
discoverable
|
||||
django
|
||||
dockerfile
|
||||
durations
|
||||
eu
|
||||
filename
|
||||
filesystem
|
||||
fontawesome
|
||||
frontend
|
||||
frontpage
|
||||
gettext
|
||||
gunicorn
|
||||
hardcoded
|
||||
hostname
|
||||
invalidations
|
||||
iterable
|
||||
libsass
|
||||
linters
|
||||
memcached
|
||||
metadata
|
||||
middleware
|
||||
mixin
|
||||
mixins
|
||||
multi
|
||||
multidomain
|
||||
namespace
|
||||
namespaced
|
||||
namespaces
|
||||
namespacing
|
||||
natively
|
||||
nginx
|
||||
NotificationType
|
||||
ons
|
||||
optimizations
|
||||
param
|
||||
percental
|
||||
positionid
|
||||
pre
|
||||
prepend
|
||||
prepended
|
||||
prepending
|
||||
preprocessor
|
||||
presale
|
||||
pretix
|
||||
pretixdroid
|
||||
pretixpresale
|
||||
prometheus
|
||||
proxied
|
||||
proxying
|
||||
queryset
|
||||
redemptions
|
||||
redis
|
||||
refactored
|
||||
regex
|
||||
renderer
|
||||
renderers
|
||||
reportlab
|
||||
screenshot
|
||||
serializers
|
||||
serializers
|
||||
sexualized
|
||||
startup
|
||||
stdout
|
||||
stylesheet
|
||||
subdirectories
|
||||
subdirectory
|
||||
subdomain
|
||||
subdomains
|
||||
subevent
|
||||
subevents
|
||||
submodule
|
||||
subpath
|
||||
systemd
|
||||
testutils
|
||||
timestamp
|
||||
un
|
||||
unconfigured
|
||||
unix
|
||||
unprefixed
|
||||
untrusted
|
||||
username
|
||||
url
|
||||
viewset
|
||||
viewsets
|
||||
webhook
|
||||
webhooks
|
||||
webserver
|
||||
webservice
|
||||
workflow
|
||||
zipcode
|
||||
Datetime
|
||||
Embeddable
|
||||
Hierarkey
|
||||
OAuth
|
||||
SSL
|
||||
Uptime
|
||||
Yay
|
||||
@@ -30,13 +30,13 @@ available products, quotas, prices and some meta information, but most settings
|
||||
We recommend to use this feature only if you really know that you need it and if you really run a lot of events, not if
|
||||
you run e.g. a yearly conference. You can read more on this feature :ref:`here <subevents>`.
|
||||
|
||||
Once you set these values, you can proceed to the next step:
|
||||
Once you set these values, you can procede to the next step:
|
||||
|
||||
.. thumbnail:: ../../screens/event/create_step2.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
|
||||
In this step, you will be asked more detailed questions about your event. In particular, you can fill in the
|
||||
In this step, you will be asked more detailled questions about your event. In particular, you can fill in the
|
||||
following fields:
|
||||
|
||||
Name
|
||||
|
||||
@@ -14,7 +14,7 @@ Logo image
|
||||
This logo will be shown as a banner above your shop. If you set it, the event name and date will no longer be
|
||||
displayed by the shop, so we suggest to include them in the image yourself. The maximal height of the image is
|
||||
120 pixels and if you want to use the full width, make your image 1140 pixels wide. If the user's screen is
|
||||
smaller, the logo will be scaled down automatically, so it should still be legible at smaller sizes.
|
||||
smaller, the logo will be scaled down automatically, so it should still be legigible at smaller sizes.
|
||||
|
||||
Frontpage text
|
||||
This text will be shown on the front page of your ticket shop, above the list of products. You can use it to explain
|
||||
@@ -39,4 +39,4 @@ Font
|
||||
Choose one of multiple fonts to use for your web shop.
|
||||
|
||||
.. note:: Both the color and font settings can take a few seconds up to a few minutes before they become active on your
|
||||
shop.
|
||||
shop.
|
||||
@@ -1,129 +0,0 @@
|
||||
E-mail settings
|
||||
===============
|
||||
|
||||
The settings at "Settings" → "E-mail" allow you to customize the emails that pretix sends to the participants of your
|
||||
event.
|
||||
|
||||
.. thumbnail:: ../../screens/event/settings_email.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
|
||||
The page is separated into three parts: "E-mail settings", "E-mail content" and "SMTP settings". We will explain all
|
||||
of them in detail on this page.
|
||||
|
||||
E-mail settings
|
||||
---------------
|
||||
|
||||
The upper part of the page contains settings that are relevant for the generation of all e-mails alike. Those are
|
||||
currently::
|
||||
|
||||
Subject prefix
|
||||
This text will be prepended to the subject of all e-mails that are related to your event. For example, if you
|
||||
set this to "dc2018" all subjects will be formatted like "[dc2018] Your payment was successful".
|
||||
|
||||
Sender address
|
||||
All e-mails will be sent with this address in the "From" field. If you use an email address at a custom domain,
|
||||
we strongly recommend to use the SMTP settings below as well, otherwise your e-mails might be detected as spam
|
||||
due to the `Sender Policy Framework`_ and similar mechanisms.
|
||||
|
||||
Signature
|
||||
This text will be appended to all e-mails in form of a signature. This might be useful e.g. to add your contact
|
||||
details or any legal information that needs to be included with the e-mails.
|
||||
|
||||
E-mail content
|
||||
--------------
|
||||
|
||||
The middle part of the page allows you to customize the exact texts of all e-mails sent by the system automatically.
|
||||
You can click on the different boxes to expand them and see the texts.
|
||||
|
||||
Within the texts, you can use placeholders that will later by replaced by values depending on the event or order. Below
|
||||
every text box is a list of supported placeholders, but currently the following are defined (not every placeholder
|
||||
is valid in every text):
|
||||
|
||||
============================== ===============================================================================
|
||||
Placeholder Description
|
||||
============================== ===============================================================================
|
||||
event The event name
|
||||
total The order's total value
|
||||
currency The currency used for the event (three-letter code)
|
||||
payment_info Information text specific to the payment method (e.g. banking details)
|
||||
url An URL pointing to the download/status page of the order
|
||||
invoice_name The name field of the invoice address
|
||||
invoice_company The company field of the invoice address
|
||||
expire_date The order's expiration date
|
||||
date The same as ``expire_date``, but in a different e-mail (for backwards
|
||||
compatibility)
|
||||
orders A list of orders including links to their status pages, specific to the "resend
|
||||
link (requested by user)" e-mail
|
||||
code In case of the waiting list, the voucher code to redeem
|
||||
hours In case of the waiting list, the number of hours the voucher code is valid
|
||||
============================== ===============================================================================
|
||||
|
||||
The different e-mails are explained in the following:
|
||||
|
||||
Placed Order
|
||||
This e-mail is sent out to every order directly after the order has been received, except if the order total
|
||||
is zero (see below). It should specify that/how the order is to be paid.
|
||||
|
||||
Paid Order
|
||||
This e-mail is sent out as soon as the payment for an order has been received and should give the customer
|
||||
more information on how to proceed, e.g. by downloading their ticket.
|
||||
|
||||
Free Order
|
||||
This e-mail is sent out instead of "Placed Order" and "Paid Order" if the order total is zero. It therefore should
|
||||
tell the same information, except asking the customer for completing their payment.
|
||||
|
||||
Resend link
|
||||
Sent by admin
|
||||
This e-mail will be sent out if you click the "Resend link" next to the e-mail address field on the order detail
|
||||
page. It should include the link to the order and can be sent to users e.g. if they lost their original e-mails.
|
||||
|
||||
Requested by user
|
||||
Customers can also request a link to all orders they created using their e-mail address themselves by filling
|
||||
out a form on the website. In this case, they will receive an e-mail containing a list of all orders they created
|
||||
with the respective links.
|
||||
|
||||
Order changed
|
||||
This e-mail is sent out if you change the content of the order and choose to notify the user about it.
|
||||
|
||||
Payment reminder
|
||||
This e-mail is sent out a certain number of days before the order's expiry date. You can specify the number of days
|
||||
before the expiry date that this should happen and the e-mail will only ever be sent if you do specify such a
|
||||
number. The text should ask the customer to complete the payment, tell the options on how to do so and the
|
||||
consequences if no payment is received (ticket gone, depending on your other settings). You should also include
|
||||
a way to contact you in case of questions.
|
||||
|
||||
Waiting list notification
|
||||
If you enable the waiting list feature, this is the mail that will be sent out if a ticket is assigned to a person on
|
||||
the waiting list. It should include the voucher that needs to be redeemed to get the free spot and tell how long
|
||||
that voucher is valid and where to redeem it.
|
||||
|
||||
Order canceled
|
||||
This e-mail is sent to a customer if their order has been canceled.
|
||||
|
||||
|
||||
Order custom mail
|
||||
You can use pretix' admin interface to directly send an e-mail with a custom text to the customer of a specific
|
||||
order. In this case, this will be the default text and might save you time by not having to re-type all of it every
|
||||
time.
|
||||
|
||||
Reminder to download tickets
|
||||
If you want, you can configure an email that will be send out a number of days before your event to remind
|
||||
attendees to download their tickets. The e-mail should include a link to the ticket download. This e-mail will only
|
||||
ever be sent if you specify a number of days.
|
||||
|
||||
SMTP settings
|
||||
-------------
|
||||
|
||||
If you want to send your e-mails via your own e-mail address, we strongly recommend to use SMTP for this purpose.
|
||||
SMTP is a protocol that is used by e-mail clients to communicate with e-mail servers. Using SMTP, pretix can talk to
|
||||
your e-mail service provider the same way that e.g. the e-mail app on your phone can.
|
||||
|
||||
Your e-mail provider will most likely have a document that tells you the settings for the various fields to fill in
|
||||
here (hostname, port, username, password, encryption).
|
||||
|
||||
With the checkbox "Use custom SMTP server" you can turn using your SMTP server on or off completely. With the
|
||||
button "Save and test custom SMTP connection", you can test if the connection and authentication to your SMTP server
|
||||
succeeds, even before turning that checkbox on.
|
||||
|
||||
.. _Sender Policy Framework: https://en.wikipedia.org/wiki/Sender_Policy_Framework
|
||||
@@ -1,96 +0,0 @@
|
||||
Invoice settings
|
||||
================
|
||||
|
||||
.. spelling:: Inv
|
||||
|
||||
The settings at "Settings" → "Invoice" allow you to specify if and how pretix should generate invoices for your orders.
|
||||
|
||||
.. thumbnail:: ../../screens/event/settings_invoice.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
|
||||
In particular, you can configure the following things:
|
||||
|
||||
Ask for invoice address
|
||||
If this checkbox is enabled, customers will be able to enter an invoice address during checkout. If you only enable
|
||||
this box, the invoice address will be optional to fill in.
|
||||
|
||||
Require invoice address
|
||||
If this checkbox is enabled, entering an invoice address will be obligatory for all customers and it will not be
|
||||
able to create an order without entering an address.
|
||||
|
||||
Require customer name
|
||||
If this checkbox is enabled, the street, city, and country fields of the invoice address will still be optional but
|
||||
the name field will be obligatory.
|
||||
|
||||
Generate invoices
|
||||
This field controls whether pretix should generate an invoice for an order. You have the following options:
|
||||
|
||||
No
|
||||
pretix will never generate an invoice. If you want to issue invoices, you need to do it yourself based on the
|
||||
collected address data.
|
||||
|
||||
Manually in admin panel
|
||||
pretix will not create invoices automatically, but the order detail view will show a button that allows you to
|
||||
manually generate one for specific orders.
|
||||
|
||||
Automatically on user request
|
||||
pretix will not create invoices on its own, but both the panel as well as the customer view of the order will
|
||||
show a button that instantly generates an invoice for the specified order.
|
||||
|
||||
Automatically for all created orders
|
||||
pretix will automatically create an invoice every time an order is placed.
|
||||
|
||||
Automatically on payment
|
||||
pretix will automatically create an invoice for an order, as soon as the payment for the order is received.
|
||||
|
||||
pretix will never generate invoices for free orders, even though it might ask for the invoice address.
|
||||
|
||||
Attach invoices to emails
|
||||
If enabled, invoices will be attached to order confirmation e-mails if the "Generate invoices" setting is set to
|
||||
"Automatically for all created orders" or to the payment confirmation e-mails if it is set to "Automatically on
|
||||
payment".
|
||||
|
||||
Ask for VAT ID
|
||||
If enabled, the invoice address form will not only ask for a postal address, but also for a VAT ID. The VAT ID will
|
||||
always be an optional field.
|
||||
|
||||
Generate invoices with consecutive numbers
|
||||
If enabled, invoices will be created with numerical invoice numbers in the order of their creation, i.e.
|
||||
PREFIX-00001, PREFIX-00002, and so on. If disabled, invoice numbers will instead be generated from the order code,
|
||||
i.e. PREFIX-YHASD-1. When in doubt, keep this option enabled since it might be legally required in your country,
|
||||
but disabling it has the advantage that your customers can not estimate the number of tickets sold by looking at
|
||||
the invoice numbers.
|
||||
|
||||
Invoice number prefix
|
||||
This is the prefix that will be prepended to all your invoice numbers. For example, if you set this to "Inv", your
|
||||
invoices will be numbered Inv00001, Inv00002, etc. If you leave this field empty, your event slug will be used,
|
||||
followed by a dash, e.g. DEMOCON-00001.
|
||||
|
||||
Within one organizer account, events with the same number prefix will share their number range. For example, if you
|
||||
set this to "Inv" for all of your events, there will be only one invoice numbered Inv00007 across all your events
|
||||
and the numbers will have gaps within one event.
|
||||
|
||||
Show free products on invoices
|
||||
If enabled, products that do not cost anything will still show up on invoices. Note that the order needs to contain
|
||||
at least one non-free product in order to generate an invoice.
|
||||
|
||||
Show attendee names on invoices
|
||||
If enabled, the attendee name will be printed on the invoice for admission tickets.
|
||||
|
||||
Your address
|
||||
This should be set to the address of the entity issuing the invoice (read: you) and will be printed inside
|
||||
the header of the invoice.
|
||||
|
||||
Introductory text
|
||||
A free custom text that will be printed above the list of products on the invoice.
|
||||
|
||||
Additional text
|
||||
A free custom text that will be printed below the list of products and the invoice total.
|
||||
|
||||
Footer
|
||||
A text that will be printed in the foot line of the invoice. This could contain your contact details or legal
|
||||
information on the issuing entity, e.g. registration numbers, your VAT ID, etc.
|
||||
|
||||
Logo image
|
||||
A square image that will be printed in the invoice header, currently with a width of 2.5cm.
|
||||
@@ -8,7 +8,4 @@ Configuring an event
|
||||
../payments/index
|
||||
plugins
|
||||
display
|
||||
tickets
|
||||
email
|
||||
taxes
|
||||
invoicing
|
||||
|
||||
@@ -103,7 +103,7 @@ End of presale
|
||||
|
||||
Quotas
|
||||
As for all events, no tickets will be available unless there is a quota created for them that specifies the number
|
||||
of tickets available. You can create multiple quotas that are assigned to this date directly from this interface.
|
||||
of tickets available. You can create multiple quotas that are assinged to this date directly from this interface.
|
||||
|
||||
Item prices
|
||||
This is a table of all products configured for your shop. If you want, you can enter a new price for each one of them
|
||||
|
||||
@@ -18,7 +18,7 @@ your event, go to the respective section in your event's settings:
|
||||
:class: screenshot
|
||||
|
||||
On this page, you can create, edit and delete your tax rules. Clicking on the name of a tax rule will take you to its
|
||||
detailed settings:
|
||||
detailled settings:
|
||||
|
||||
.. thumbnail:: ../../screens/event/tax_detail.png
|
||||
:align: center
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
Ticket settings
|
||||
===============
|
||||
|
||||
At "Settings" → "Tickets", you can configure the ticket download options that will be presented to your customers:
|
||||
|
||||
.. thumbnail:: ../../screens/event/settings_tickets.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
|
||||
The top of this page shows a short list of options relevant for all download formats:
|
||||
|
||||
Use feature
|
||||
This can be used to completely enable or disable ticket downloads all over your ticket shop.
|
||||
|
||||
Download date
|
||||
If you set a date here, no ticket download will be offered before this date. If no date is set, tickets can be
|
||||
downloaded immediately after the payment for an order has been received.
|
||||
|
||||
Offer to download tickets separately for add-on products
|
||||
By default, tickets can not be downloaded for order positions which are only an add-on to other order positions. If
|
||||
you enable this, this behavior will be changed and add-on products will get their own tickets as well. If disabled,
|
||||
you can still print a list of chosen add-ons e.g. on the PDF tickets.
|
||||
|
||||
Generate tickets for non-admission products
|
||||
By default, tickets will only be generated for products that are marked as admission products. Enable this option to
|
||||
generate tickets for all products instead.
|
||||
|
||||
Below these settings, the detail settings for the various ticket file formats are offered. They differ from format to
|
||||
format and only share the common "Enable" setting that can be used to turn them on. By default, pretix ships with
|
||||
a PDF output plugin that you can configure through a visual design editor.
|
||||
@@ -67,7 +67,7 @@ SSL
|
||||
---
|
||||
|
||||
Since buying a ticket normally involves entering sensitive data, we strongly suggest that you use SSL/HTTPS for the page
|
||||
that includes the widget. Initiatives like `Let's Encrypt`_ allow you to obtain a SSL certificate free of charge.
|
||||
that includes the widget. Initiatives like `Let's Encrypt`_ allow you to obtain a SSL certificat free of charge.
|
||||
|
||||
All data transferred to pretix will be made over SSL, even if using the widget on a non-SSL site. However, without
|
||||
using SSL for your site, a man-in-the-middle attacker could potentially alter the widget in dangerous ways. Moreover,
|
||||
@@ -75,7 +75,7 @@ using SSL is becoming standard practice and your customers might want expect see
|
||||
granted to SSL-enabled web pages.
|
||||
|
||||
By default, the checkout process will open in a new tab in your customer's browsers if you don't use SSL for your
|
||||
website. If you confident to have a good reason for not using SSL, you can override this behavior with the
|
||||
website. If you confident to have a good reason for not using SSL, you can override this behaviour with the
|
||||
``skip-ssl-check`` attribute::
|
||||
|
||||
<pretix-widget event="https://pretix.eu/demo/democon/" skip-ssl-check></pretix-widget>
|
||||
|
||||
@@ -9,7 +9,7 @@ There are multiple ways to do this.
|
||||
First, you could just create some orders in your real shop and cancel/refund them later. If you don't want to process
|
||||
real payments for the tests, you can either use a "manual" payment method like bank transfer and just mark the orders
|
||||
as paid with the button in the backend, or if you want to use e.g. Stripe, you can configure pretix to use your keys
|
||||
for the Stripe test system and use their test credit cars. Read our :ref:`Stripe documentation <stripe>` for more
|
||||
for the Stripe test sytem and use their test credit cars. Read our :ref:`Stripe documentation <stripe>` for more
|
||||
information.
|
||||
|
||||
Second, you could create a separate event, just for testing. In the last step of the :ref:`event creation process <event_create>`,
|
||||
@@ -48,4 +48,4 @@ If you created a product and it doesn't show up, please follow the following ste
|
||||
variation is contained in a quota. If your event is an event series, make sure that the product is contained in a
|
||||
quota that is assigned to the series date that you access the shop for.
|
||||
6. If the sale period has not started yet or is already over, check the "Show items outside presale period" setting of
|
||||
your event.
|
||||
your event.
|
||||
@@ -163,4 +163,4 @@ All other elements and attributes will be stripped during parsing.
|
||||
|
||||
|
||||
.. _Markdown: https://en.wikipedia.org/wiki/Markdown
|
||||
.. _Wikipedia: https://en.wikipedia.org
|
||||
.. _Wikipedia: https://en.wikipedia.org
|
||||
@@ -47,11 +47,11 @@ Permissions separate into two areas:
|
||||
* Can change product settings – This permission allows to create and modify products and objects that are closely
|
||||
related to products, such as product categories, quotas, and questions.
|
||||
|
||||
* Can view orders – This permission allows viewing the list of orders and all individual order details, but not
|
||||
* Can view orders – This permission allows viewing the list of orders and allindividual order details, but not
|
||||
changing anything about it. This also includes the various exports offered.
|
||||
|
||||
* Can change orders – This permission allows all actions that involve changing an order, such as changing the products
|
||||
in an order, marking an order as paid or refunded, importing banking data, etc. This only works properly if the
|
||||
in an order, marking an order as paid or refunden, importing banking data, etc. This only works properly if the
|
||||
same users also have the "Can view orders" permission.
|
||||
|
||||
* Can view vouchers – This permission allows viewing the list of vouchers including the voucher codes themselves and
|
||||
|
||||
@@ -60,4 +60,4 @@ same 5 %, such that for a ticket with a list price of 100 € you will get your
|
||||
===================================================== =============
|
||||
|
||||
Due to the various rounding steps performed by pretix and by the payment provider, the end total on
|
||||
your bank account might still vary by one cent.
|
||||
your bank account might stil vary by one cent.
|
||||
|
||||
@@ -2,7 +2,7 @@ Payment method overview
|
||||
=======================
|
||||
|
||||
pretix allows you to accept payments using a variety of payment methods to fit the needs of very different events.
|
||||
This page gives you a short overview over them and links to more detailed descriptions in some cases.
|
||||
This page gives you a short overview over them and links to more detailled descriptions in some cases.
|
||||
|
||||
Payment methods are built as pretix plugins. For this reason, you might first need to enable a certain plugin at
|
||||
"Settings" → "Plugins" in your event settings. Then, you can configure them in detail at "Settings" -> "Payment".
|
||||
@@ -13,4 +13,5 @@ on this page as well as for additional ones.
|
||||
To get an overview of the officially supported payment methods and their pros and cons, head to the `pretix website`_.
|
||||
On these pages, you get more information on how to configure :ref:`stripe`, :ref:`paypal`, and :ref:`banktransfer`.
|
||||
|
||||
.. _pretix website: https://pretix.eu/about/en/payments
|
||||
|
||||
.. _pretix website: https://pretix.eu/about/en/payments
|
||||
@@ -11,7 +11,7 @@ of the page shows a number of general settings that affect all payment methods:
|
||||
In particular, these are:
|
||||
|
||||
Payment term in days
|
||||
If a order has been created, it is supposed to be paid within this number of days. Of course, some payment methods
|
||||
If a order has been created, it is supposed to be paid within this number of days. Of course, some payment mehtods
|
||||
(like credit card) succeed immediately in most cases, but others don't (like bank transfer) and even credit card
|
||||
payments might fail and you might want to give the customer a chance to try another credit card before losing their
|
||||
ticket. Therefore, we recommend setting a few days here. If you are accepting bank transfers, we wouldn't recommend
|
||||
@@ -19,7 +19,7 @@ Payment term in days
|
||||
|
||||
Last date of payments
|
||||
There is probably no use for payments received after your event, so you can set a date that the payment deadline of
|
||||
a new order will never exceed. This has precedence over the number of days configured above, so if I create an order
|
||||
a new order will never exceed. This has precendence over the number of days configured above, so if I create an order
|
||||
two days before the configured last date of payments, my payment term will only be two days, not ten. If you have
|
||||
payment methods that always require some time (like bank transfer), you will later be able to selectively disable them
|
||||
once the event comes closer.
|
||||
|
||||
34
res/logo.svg
34
res/logo.svg
@@ -9,16 +9,15 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="600"
|
||||
height="400"
|
||||
width="294.15625"
|
||||
height="149.59375"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.92.1 r"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:export-filename="/tmp/LOGO.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
viewBox="0 0 562.50001 375.00002">
|
||||
inkscape:export-filename="/home/raphael/proj/pretix/pretix/logo_draft.png"
|
||||
inkscape:export-xdpi="88.529999"
|
||||
inkscape:export-ydpi="88.529999">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
@@ -29,14 +28,14 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.9899495"
|
||||
inkscape:cx="133.36756"
|
||||
inkscape:cy="276.10571"
|
||||
inkscape:cx="134.70089"
|
||||
inkscape:cy="277.43904"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1916"
|
||||
inkscape:window-width="636"
|
||||
inkscape:window-height="1041"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-x="3200"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="20"
|
||||
@@ -59,14 +58,11 @@
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-259.03125,-322.09374)">
|
||||
transform="translate(-257.78125,-548.75)">
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#3b1c4a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.91138947;marker:none;enable-background:accumulate"
|
||||
d="m 297.38548,404.85558 v 65.16643 c 21.86016,0 39.6016,17.74144 39.6016,39.60159 0,21.86015 -17.74144,39.54187 -39.6016,39.54187 v 65.16644 h 280.37693 v -18.1582 h 5.73417 v 18.1582 h 199.68046 v -65.16644 c -0.02,4e-5 -0.0397,0 -0.0596,0 -21.86015,0 -39.6016,-17.68172 -39.6016,-39.54187 0,-21.86015 17.74145,-39.60159 39.6016,-39.60159 0.02,0 0.0397,-4e-5 0.0596,0 V 404.85558 H 583.49658 v 17.38169 h -5.73417 V 404.85558 Z M 577.76241,435.617 h 5.73417 v 26.75945 h -5.73417 z m 79.21068,24.53074 c 5.33405,0 9.60172,3.68466 9.60172,8.24287 0,4.5582 -4.26767,8.33993 -9.60172,8.33993 -5.2371,0 -9.50469,-3.78173 -9.50469,-8.33993 0,-4.55821 4.26759,-8.24287 9.50469,-8.24287 z m -25.28486,8.04874 v 15.32472 h 7.56717 v 12.02458 h -7.56717 v 23.47051 c 0,3.87934 1.35737,5.33473 4.0729,5.33473 1.35775,0 2.03951,-0.19461 3.49427,-0.77651 v 11.34514 c -1.35777,0.77585 -4.56174,1.84419 -8.829,1.84419 -11.73495,0 -17.16515,-7.17511 -17.16515,-16.19455 v -25.02351 h -5.43179 V 483.5212 h 5.43179 v -10.66944 z m -53.92582,7.5597 h 5.73417 v 26.75945 h -5.73417 z m -142.52917,6.79439 c 16.19618,0 27.15517,8.63124 27.15517,26.96104 0,16.48713 -9.6016,26.96105 -23.08227,26.96105 -3.29741,0 -6.01528,-0.38857 -7.37304,-0.77651 v 20.95062 H 413.50612 V 486.5264 c 4.94614,-2.32759 11.64087,-3.97583 21.72712,-3.97583 z m 95.30815,0 c 16.39014,0 24.14917,11.34479 23.17933,29.09269 l -30.45158,4.27076 c 1.1638,5.62501 4.16799,8.437 10.85985,8.437 6.20689,0 11.05633,-1.36007 13.96583,-2.81482 l 4.0729,11.2518 c -4.5582,2.23062 -10.6662,3.97584 -20.36451,3.97584 -17.06903,0 -26.08749,-11.54096 -26.08749,-27.25223 0,-15.71126 8.43552,-26.96104 24.82567,-26.96104 z m -34.25568,0.0113 c 1.70649,0.026 3.49392,0.0859 5.36084,0.18292 l -3.20307,12.80108 c -3.39442,-1.6487 -6.69185,-1.74642 -9.11643,-0.87356 v 41.121 h -18.42698 v -49.2668 c 5.3462,-2.63068 13.44024,-4.1463 25.38564,-3.96465 z m 151.47387,0.95943 h 18.42699 v 52.27202 h -18.42699 z m 25.29605,0 h 19.20348 l 6.30535,13.09227 h 0.19412 l 6.884,-13.09227 h 17.16517 l -15.22393,24.14622 16.67986,28.1258 h -20.3645 l -6.69361,-14.45115 h -0.19412 l -6.98104,14.45115 h -18.62112 l 16.38867,-26.96104 z m -143.29075,9.60175 c -5.43106,0 -8.43651,4.94275 -7.85461,14.05916 l 14.8394,-2.22871 c 0,-7.9526 -2.3296,-11.83045 -6.98479,-11.83045 z m -94.6287,0.19038 c -1.35776,0 -2.33024,0.19437 -3.20308,0.48532 v 31.4222 c 0.67888,0.29093 1.65112,0.48531 2.91189,0.48531 6.59487,0 9.31055,-5.42933 9.31055,-16.09748 0,-10.95908 -2.32753,-16.29535 -9.01936,-16.29535 z m 142.62623,22.58194 h 5.73417 v 26.75946 h -5.73417 z m 0,40.13918 h 5.73417 v 26.75946 h -5.73417 z"
|
||||
id="rect3888"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:export-filename="/tmp/LOGO.png"
|
||||
inkscape:export-xdpi="88"
|
||||
inkscape:export-ydpi="88" />
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#3b1c4a;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;enable-background:accumulate"
|
||||
d="M 20 20 L 20 54.09375 C 31.43679 54.09375 40.71875 63.37571 40.71875 74.8125 C 40.71875 86.24928 31.43679 95.5 20 95.5 L 20 129.59375 L 166.6875 129.59375 L 166.6875 120.09375 L 169.6875 120.09375 L 169.6875 129.59375 L 274.15625 129.59375 L 274.15625 95.5 C 274.14575 95.50002 274.1354 95.5 274.125 95.5 C 262.68822 95.5 253.40625 86.24928 253.40625 74.8125 C 253.40625 63.37571 262.68822 54.09375 274.125 54.09375 C 274.1355 54.09375 274.14585 54.09373 274.15625 54.09375 L 274.15625 20 L 169.6875 20 L 169.6875 29.09375 L 166.6875 29.09375 L 166.6875 20 L 20 20 z M 166.6875 36.09375 L 169.6875 36.09375 L 169.6875 50.09375 L 166.6875 50.09375 L 166.6875 36.09375 z M 208.12891 48.927734 C 210.91958 48.927734 213.15234 50.855474 213.15234 53.240234 C 213.15234 55.624994 210.91958 57.603516 208.12891 57.603516 C 205.38897 57.603516 203.15625 55.624994 203.15625 53.240234 C 203.15625 50.855474 205.38897 48.927734 208.12891 48.927734 z M 194.90039 53.138672 L 194.90039 61.15625 L 198.85938 61.15625 L 198.85938 67.447266 L 194.90039 67.447266 L 194.90039 79.726562 C 194.90039 81.756152 195.61054 82.517578 197.03125 82.517578 C 197.7416 82.517578 198.09828 82.415768 198.85938 82.111328 L 198.85938 88.046875 C 198.14902 88.452785 196.47277 89.011719 194.24023 89.011719 C 188.10074 89.011719 185.25977 85.257843 185.25977 80.539062 L 185.25977 67.447266 L 182.41797 67.447266 L 182.41797 61.15625 L 185.25977 61.15625 L 185.25977 55.574219 L 194.90039 53.138672 z M 166.6875 57.09375 L 169.6875 57.09375 L 169.6875 71.09375 L 166.6875 71.09375 L 166.6875 57.09375 z M 92.119141 60.648438 C 100.59265 60.648438 106.32617 65.164126 106.32617 74.753906 C 106.32617 83.379636 101.30281 88.859375 94.25 88.859375 C 92.52486 88.859375 91.102928 88.656085 90.392578 88.453125 L 90.392578 99.414062 L 80.751953 99.414062 L 80.751953 62.728516 C 83.339673 61.510766 86.842221 60.648435 92.119141 60.648438 z M 141.98242 60.648438 C 150.55741 60.648438 154.61678 66.583801 154.10938 75.869141 L 138.17773 78.103516 C 138.78661 81.046406 140.35834 82.517578 143.85938 82.517578 C 147.1067 82.517578 149.64383 81.806022 151.16602 81.044922 L 153.29688 86.931641 C 150.91212 88.098651 147.71654 89.011719 142.64258 89.011719 C 133.71241 89.011719 128.99414 82.973726 128.99414 74.753906 C 128.99414 66.534096 133.40743 60.648438 141.98242 60.648438 z M 124.06055 60.654297 C 124.95335 60.667874 125.8885 60.69926 126.86523 60.75 L 125.18945 67.447266 C 123.41356 66.584696 121.68841 66.533574 120.41992 66.990234 L 120.41992 88.503906 L 110.7793 88.503906 L 110.7793 62.728516 C 113.57632 61.352202 117.81096 60.559256 124.06055 60.654297 z M 203.30859 61.15625 L 212.94922 61.15625 L 212.94922 88.503906 L 203.30859 88.503906 L 203.30859 61.15625 z M 216.54297 61.15625 L 226.58984 61.15625 L 229.88867 68.005859 L 229.99023 68.005859 L 233.5918 61.15625 L 242.57227 61.15625 L 234.60742 73.789062 L 243.33398 88.503906 L 232.67969 88.503906 L 229.17773 80.943359 L 229.07617 80.943359 L 225.42383 88.503906 L 215.68164 88.503906 L 224.25586 74.398438 L 216.54297 61.15625 z M 141.57617 66.179688 C 138.73475 66.179688 137.16236 68.765636 137.4668 73.535156 L 145.23047 72.369141 C 145.23047 68.208501 144.01167 66.179687 141.57617 66.179688 z M 92.068359 66.279297 C 91.358009 66.279297 90.849228 66.380983 90.392578 66.533203 L 90.392578 82.972656 C 90.747748 83.124866 91.256406 83.226562 91.916016 83.226562 C 95.366316 83.226562 96.787109 80.386048 96.787109 74.804688 C 96.787109 69.071117 95.569389 66.279297 92.068359 66.279297 z M 166.6875 78.09375 L 169.6875 78.09375 L 169.6875 92.09375 L 166.6875 92.09375 L 166.6875 78.09375 z M 166.6875 99.09375 L 169.6875 99.09375 L 169.6875 113.09375 L 166.6875 113.09375 L 166.6875 99.09375 z "
|
||||
transform="translate(257.78125,548.75)"
|
||||
id="rect3888" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 5.7 KiB |
@@ -1 +1 @@
|
||||
__version__ = "1.11.1"
|
||||
__version__ = "1.10.1"
|
||||
|
||||
@@ -38,7 +38,7 @@ class AnswerSerializer(I18nAwareModelSerializer):
|
||||
class CheckinSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Checkin
|
||||
fields = ('datetime', 'list')
|
||||
fields = ('datetime',)
|
||||
|
||||
|
||||
class OrderDownloadsField(serializers.Field):
|
||||
|
||||
@@ -26,9 +26,6 @@ event_router.register(r'taxrules', event.TaxRuleViewSet)
|
||||
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
|
||||
event_router.register(r'checkinlists', checkin.CheckinListViewSet)
|
||||
|
||||
checkinlist_router = routers.DefaultRouter()
|
||||
checkinlist_router.register(r'positions', checkin.CheckinListPositionViewSet)
|
||||
|
||||
# Force import of all plugins to give them a chance to register URLs with the router
|
||||
for app in apps.get_app_configs():
|
||||
if hasattr(app, 'PretixPluginMeta'):
|
||||
@@ -39,6 +36,4 @@ urlpatterns = [
|
||||
url(r'^', include(router.urls)),
|
||||
url(r'^organizers/(?P<organizer>[^/]+)/', include(orga_router.urls)),
|
||||
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/', include(event_router.urls)),
|
||||
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/checkinlists/(?P<list>[^/]+)/',
|
||||
include(checkinlist_router.urls)),
|
||||
]
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
from rest_framework.filters import OrderingFilter
|
||||
|
||||
|
||||
class RichOrderingFilter(OrderingFilter):
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
ordering = self.get_ordering(request, queryset, view)
|
||||
|
||||
if ordering:
|
||||
if hasattr(view, 'ordering_custom'):
|
||||
newo = []
|
||||
for ordering_part in ordering:
|
||||
ob = view.ordering_custom.get(ordering_part)
|
||||
if ob:
|
||||
ob = dict(ob)
|
||||
newo.append(ob.pop('_order'))
|
||||
queryset = queryset.annotate(**ob)
|
||||
else:
|
||||
newo.append(ordering_part)
|
||||
ordering = newo
|
||||
return queryset.order_by(*ordering)
|
||||
|
||||
return queryset
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import django_filters
|
||||
from django.db.models import F, Max, OuterRef, Prefetch, Q, Subquery
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.functional import cached_property
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from rest_framework import viewsets
|
||||
|
||||
from pretix.api.serializers.checkin import CheckinListSerializer
|
||||
from pretix.api.serializers.order import OrderPositionSerializer
|
||||
from pretix.api.views import RichOrderingFilter
|
||||
from pretix.base.models import Checkin, CheckinList, Order, OrderPosition
|
||||
from pretix.base.models import CheckinList
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
from pretix.helpers.database import FixedOrderBy
|
||||
|
||||
|
||||
class CheckinListFilter(FilterSet):
|
||||
@@ -65,79 +57,3 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
)
|
||||
super().perform_destroy(instance)
|
||||
|
||||
|
||||
class OrderPositionFilter(FilterSet):
|
||||
order = django_filters.CharFilter(name='order', lookup_expr='code')
|
||||
has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs')
|
||||
attendee_name = django_filters.CharFilter(method='attendee_name_qs')
|
||||
|
||||
def has_checkin_qs(self, queryset, name, value):
|
||||
return queryset.filter(last_checked_in__isnull=not value)
|
||||
|
||||
def attendee_name_qs(self, queryset, name, value):
|
||||
return queryset.filter(Q(attendee_name=value) | Q(addon_to__attendee_name=value))
|
||||
|
||||
class Meta:
|
||||
model = OrderPosition
|
||||
fields = ['item', 'variation', 'attendee_name', 'secret', 'order', 'has_checkin', 'addon_to', 'subevent']
|
||||
|
||||
|
||||
class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = OrderPositionSerializer
|
||||
queryset = OrderPosition.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
|
||||
ordering = ('attendee_name', 'positionid')
|
||||
ordering_fields = (
|
||||
'order__code', 'order__datetime', 'positionid', 'attendee_name',
|
||||
'last_checked_in', 'order__email',
|
||||
)
|
||||
ordering_custom = {
|
||||
'attendee_name': {
|
||||
'_order': F('display_name').asc(nulls_first=True),
|
||||
'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')
|
||||
},
|
||||
'-attendee_name': {
|
||||
'_order': F('display_name').desc(nulls_last=True),
|
||||
'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')
|
||||
},
|
||||
'last_checked_in': {
|
||||
'_order': FixedOrderBy(F('last_checked_in'), nulls_first=True),
|
||||
},
|
||||
'-last_checked_in': {
|
||||
'_order': FixedOrderBy(F('last_checked_in'), nulls_last=True, descending=True),
|
||||
},
|
||||
}
|
||||
|
||||
filter_class = OrderPositionFilter
|
||||
permission = 'can_view_orders'
|
||||
|
||||
@cached_property
|
||||
def checkinlist(self):
|
||||
return get_object_or_404(CheckinList, event=self.request.event, pk=self.kwargs.get("list"))
|
||||
|
||||
def get_queryset(self):
|
||||
cqs = Checkin.objects.filter(
|
||||
position_id=OuterRef('pk'),
|
||||
list_id=self.checkinlist.pk
|
||||
).order_by().values('position_id').annotate(
|
||||
m=Max('datetime')
|
||||
).values('m')
|
||||
|
||||
qs = OrderPosition.objects.filter(
|
||||
order__event=self.request.event,
|
||||
order__status=Order.STATUS_PAID,
|
||||
subevent=self.checkinlist.subevent
|
||||
).annotate(
|
||||
last_checked_in=Subquery(cqs)
|
||||
).prefetch_related(
|
||||
Prefetch(
|
||||
lookup='checkins',
|
||||
queryset=Checkin.objects.filter(list_id=self.checkinlist.pk)
|
||||
)
|
||||
).select_related('item', 'variation', 'order', 'addon_to')
|
||||
|
||||
if not self.checkinlist.all_products:
|
||||
qs = qs.filter(item__in=self.checkinlist.limit_products.values_list('id', flat=True))
|
||||
|
||||
return qs
|
||||
|
||||
@@ -11,8 +11,7 @@ class PretixBaseConfig(AppConfig):
|
||||
from . import payment # NOQA
|
||||
from . import exporters # NOQA
|
||||
from . import invoice # NOQA
|
||||
from . import notifications # NOQA
|
||||
from .services import export, mail, tickets, cart, orders, invoices, cleanup, update_check, quotas, notifications # NOQA
|
||||
from .services import export, mail, tickets, cart, orders, invoices, cleanup, update_check, quotas # NOQA
|
||||
|
||||
try:
|
||||
from .celery_app import app as celery_app # NOQA
|
||||
|
||||
@@ -25,7 +25,7 @@ class BaseExporter:
|
||||
"""
|
||||
A short and unique identifier for this exporter.
|
||||
This should only contain lowercase letters and in most
|
||||
cases will be the same as your package name.
|
||||
cases will be the same as your packagename.
|
||||
"""
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import os
|
||||
import tempfile
|
||||
from collections import OrderedDict
|
||||
from zipfile import ZipFile
|
||||
|
||||
import dateutil.parser
|
||||
from django import forms
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@@ -18,26 +15,9 @@ class InvoiceExporter(BaseExporter):
|
||||
verbose_name = _('All invoices')
|
||||
|
||||
def render(self, form_data: dict):
|
||||
qs = self.event.invoices.all()
|
||||
|
||||
if form_data.get('payment_provider'):
|
||||
qs = qs.filter(order__payment_provider=form_data.get('payment_provider'))
|
||||
|
||||
if form_data.get('date_from'):
|
||||
date_value = form_data.get('date_from')
|
||||
if isinstance(date_value, str):
|
||||
date_value = dateutil.parser.parse(date_value).date()
|
||||
qs = qs.filter(date__gte=date_value)
|
||||
|
||||
if form_data.get('date_to'):
|
||||
date_value = form_data.get('date_to')
|
||||
if isinstance(date_value, str):
|
||||
date_value = dateutil.parser.parse(date_value).date()
|
||||
qs = qs.filter(date__lte=date_value)
|
||||
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
with ZipFile(os.path.join(d, 'tmp.zip'), 'w') as zipf:
|
||||
for i in qs:
|
||||
for i in self.event.invoices.all():
|
||||
if not i.file:
|
||||
invoice_pdf_task.apply(args=(i.pk,))
|
||||
i.refresh_from_db()
|
||||
@@ -46,44 +26,7 @@ class InvoiceExporter(BaseExporter):
|
||||
i.file.close()
|
||||
|
||||
with open(os.path.join(d, 'tmp.zip'), 'rb') as zipf:
|
||||
return '{}_invoices.zip'.format(self.event.slug), 'application/zip', zipf.read()
|
||||
|
||||
@property
|
||||
def export_form_fields(self):
|
||||
return OrderedDict(
|
||||
[
|
||||
('date_from',
|
||||
forms.DateField(
|
||||
label=_('Start date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
required=False,
|
||||
help_text=_('Only include invoices issued on or after this date. Note that the invoice date does '
|
||||
'not always correspond to the order or payment date.')
|
||||
)),
|
||||
('date_to',
|
||||
forms.DateField(
|
||||
label=_('End date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
required=False,
|
||||
help_text=_('Only include invoices issued on or before this date. Note that the invoice date '
|
||||
'does not always correspond to the order or payment date.')
|
||||
)),
|
||||
('payment_provider',
|
||||
forms.ChoiceField(
|
||||
label=_('Payment provider'),
|
||||
choices=[
|
||||
('', _('All payment providers')),
|
||||
] + [
|
||||
(k, v.verbose_name) for k, v in self.event.get_payment_providers().items()
|
||||
],
|
||||
required=False,
|
||||
help_text=_('Only include invoices for orders that are currently set to this payment provider. '
|
||||
'Note that this might include some invoices of other payment providers or misses '
|
||||
'some invoices if the payment provider of an order has been changed and a new invoice '
|
||||
'has been generated.')
|
||||
)),
|
||||
]
|
||||
)
|
||||
return 'invoices.zip', 'application/zip', zipf.read()
|
||||
|
||||
|
||||
@receiver(register_data_exporters, dispatch_uid="exporter_invoices")
|
||||
|
||||
@@ -101,7 +101,7 @@ class JSONExporter(BaseExporter):
|
||||
}
|
||||
}
|
||||
|
||||
return '{}_pretixdata.json'.format(self.event.slug), 'application/json', json.dumps(jo, cls=DjangoJSONEncoder)
|
||||
return 'pretixdata.json', 'application/json', json.dumps(jo, cls=DjangoJSONEncoder)
|
||||
|
||||
|
||||
@receiver(register_data_exporters, dispatch_uid="exporter_json")
|
||||
|
||||
@@ -23,7 +23,7 @@ class MailExporter(BaseExporter):
|
||||
).values('attendee_email')
|
||||
data = "\r\n".join(set(a['email'] for a in addrs)
|
||||
| set(a['attendee_email'] for a in pos if a['attendee_email']))
|
||||
return '{}_pretixemails.txt'.format(self.event.slug), 'text/plain', data.encode("utf-8")
|
||||
return 'pretixemails.txt', 'text/plain', data.encode("utf-8")
|
||||
|
||||
@property
|
||||
def export_form_fields(self):
|
||||
|
||||
@@ -171,7 +171,7 @@ class QuotaListExporter(BaseExporter):
|
||||
]
|
||||
writer.writerow(row)
|
||||
|
||||
return '{}_quotas.csv'.format(self.event.slug), 'text/csv', output.getvalue().encode("utf-8")
|
||||
return 'quotas.csv', 'text/csv', output.getvalue().encode("utf-8")
|
||||
|
||||
|
||||
@receiver(register_data_exporters, dispatch_uid="exporter_orderlist")
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.2 on 2017-12-06 16:03
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.models.auth
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0077_auto_20171124_1629'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='NotificationSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('action_type', models.CharField(max_length=255)),
|
||||
('method', models.CharField(choices=[('mail', 'E-mail')], max_length=255)),
|
||||
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='notificationsetting',
|
||||
unique_together=set([('user', 'action_type', 'event', 'method')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='logentry',
|
||||
name='visible',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notificationsetting',
|
||||
name='event',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notification_settings', to='pretixbase.Event'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notificationsetting',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_settings', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='notifications_send',
|
||||
field=models.BooleanField(default=True, help_text='If turned off, you will not get any notifications.', verbose_name='Receive notifications according to my settings below'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='notifications_token',
|
||||
field=models.CharField(default=pretix.base.models.auth.generate_notifications_token, max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -12,7 +12,6 @@ from .items import (
|
||||
Quota, SubEventItem, SubEventItemVariation, itempicture_upload_to,
|
||||
)
|
||||
from .log import LogEntry
|
||||
from .notifications import NotificationSetting
|
||||
from .orders import (
|
||||
AbstractPosition, CachedCombinedTicket, CachedTicket, CartPosition,
|
||||
InvoiceAddress, Order, OrderPosition, QuestionAnswer,
|
||||
|
||||
@@ -7,7 +7,6 @@ from django.contrib.auth.models import (
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_otp.models import Device
|
||||
|
||||
@@ -41,10 +40,6 @@ class UserManager(BaseUserManager):
|
||||
return user
|
||||
|
||||
|
||||
def generate_notifications_token():
|
||||
return get_random_string(length=32)
|
||||
|
||||
|
||||
class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
"""
|
||||
This is the user model used by pretix for authentication.
|
||||
@@ -86,12 +81,6 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
default=settings.TIME_ZONE,
|
||||
verbose_name=_('Timezone'))
|
||||
require_2fa = models.BooleanField(default=False)
|
||||
notifications_send = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_('Receive notifications according to my settings below'),
|
||||
help_text=_('If turned off, you will not get any notifications.')
|
||||
)
|
||||
notifications_token = models.CharField(max_length=255, default=generate_notifications_token)
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
|
||||
@@ -47,8 +47,6 @@ class LoggingMixin:
|
||||
"""
|
||||
from .log import LogEntry
|
||||
from .event import Event
|
||||
from ..notifications import get_all_notification_types
|
||||
from ..services.notifications import notify
|
||||
|
||||
event = None
|
||||
if isinstance(self, Event):
|
||||
@@ -62,9 +60,6 @@ class LoggingMixin:
|
||||
logentry.data = json.dumps(data, cls=CustomJSONEncoder)
|
||||
logentry.save()
|
||||
|
||||
if action in get_all_notification_types():
|
||||
notify.apply_async(args=(logentry.pk,))
|
||||
|
||||
|
||||
class LoggedModel(models.Model, LoggingMixin):
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class CheckinList(LoggedModel):
|
||||
|
||||
class Checkin(models.Model):
|
||||
"""
|
||||
A check-in object is created when a person enters the event.
|
||||
A checkin object is created when a person enters the event.
|
||||
"""
|
||||
position = models.ForeignKey('pretixbase.OrderPosition', related_name='checkins')
|
||||
datetime = models.DateTimeField(default=now)
|
||||
|
||||
@@ -10,7 +10,7 @@ from django.core.files.storage import default_storage
|
||||
from django.core.mail import get_connection
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.db.models import Q
|
||||
from django.template.defaultfilters import date as _date
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
@@ -26,7 +26,7 @@ from pretix.helpers.daterange import daterange
|
||||
from pretix.helpers.json import safe_string
|
||||
|
||||
from ..settings import settings_hierarkey
|
||||
from .organizer import Organizer, Team
|
||||
from .organizer import Organizer
|
||||
|
||||
|
||||
class EventMixin:
|
||||
@@ -511,39 +511,6 @@ class Event(EventMixin, LoggedModel):
|
||||
data.update({v.property.name: v.value for v in self.meta_values.select_related('property').all()})
|
||||
return data
|
||||
|
||||
def get_users_with_any_permission(self):
|
||||
"""
|
||||
Returns a queryset of users who have any permission to this event.
|
||||
|
||||
:return: Iterable of User
|
||||
"""
|
||||
return self.get_users_with_permission(None)
|
||||
|
||||
def get_users_with_permission(self, permission):
|
||||
"""
|
||||
Returns a queryset of users who have a specific permission to this event.
|
||||
|
||||
:return: Iterable of User
|
||||
"""
|
||||
from .auth import User
|
||||
|
||||
if permission:
|
||||
kwargs = {permission: True}
|
||||
else:
|
||||
kwargs = {}
|
||||
|
||||
team_with_perm = Team.objects.filter(
|
||||
members__pk=OuterRef('pk'),
|
||||
organizer=self.organizer,
|
||||
**kwargs
|
||||
).filter(
|
||||
Q(all_events=True) | Q(limit_events__pk=self.pk)
|
||||
)
|
||||
|
||||
return User.objects.annotate(twp=Exists(team_with_perm)).filter(
|
||||
Q(is_superuser=True) | Q(twp=True)
|
||||
)
|
||||
|
||||
|
||||
class SubEvent(EventMixin, LoggedModel):
|
||||
"""
|
||||
@@ -698,21 +665,6 @@ class RequiredAction(models.Model):
|
||||
return response
|
||||
return self.action_type
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
created = not self.pk
|
||||
super().save(*args, **kwargs)
|
||||
if created:
|
||||
from .log import LogEntry
|
||||
from ..services.notifications import notify
|
||||
|
||||
logentry = LogEntry.objects.create(
|
||||
content_object=self,
|
||||
action_type='pretix.event.action_required',
|
||||
event=self.event,
|
||||
visible=False
|
||||
)
|
||||
notify.apply_async(args=(logentry.pk,))
|
||||
|
||||
|
||||
class EventMetaProperty(LoggedModel):
|
||||
"""
|
||||
|
||||
@@ -55,9 +55,9 @@ class Invoice(models.Model):
|
||||
:type footer_text: str
|
||||
:param foreign_currency_display: A different currency that taxes should also be displayed in.
|
||||
:type foreign_currency_display: str
|
||||
:param foreign_currency_rate: The rate of a foreign currency that the taxes should be displayed in.
|
||||
:param foreign_currency_rate: The rate of a forein currency that the taxes should be displayed in.
|
||||
:type foreign_currency_rate: Decimal
|
||||
:param foreign_currency_rate_date: The date of the foreign currency exchange rates.
|
||||
:param foreign_currency_rate_date: The date of the forein currency exchange rates.
|
||||
:type foreign_currency_rate_date: date
|
||||
:param file: The filename of the rendered invoice
|
||||
:type file: File
|
||||
|
||||
@@ -179,7 +179,7 @@ class Item(LoggedModel):
|
||||
:type max_per_order: int
|
||||
:param min_per_order: Minimum number of times this item needs to be in an order if bought at all. None for unlimited.
|
||||
:type min_per_order: int
|
||||
:param checkin_attention: Requires special attention at check-in
|
||||
:param checkin_attention: Requires special attention at checkin
|
||||
:type checkin_attention: bool
|
||||
"""
|
||||
|
||||
@@ -550,7 +550,7 @@ class Question(LoggedModel):
|
||||
:param question: The question text. This will be displayed next to the input field.
|
||||
:type question: str
|
||||
:param type: One of the above types
|
||||
:param required: Whether answering this question is required for submitting an order including
|
||||
:param required: Whether answering this question is required for submiting an order including
|
||||
items associated with this question.
|
||||
:type required: bool
|
||||
:param items: A set of ``Items`` objects that this question should be applied to
|
||||
@@ -667,7 +667,7 @@ class Quota(LoggedModel):
|
||||
again if those people do not proceed to the checkout.
|
||||
|
||||
AVAILABILITY_ORDERED
|
||||
This item is currently not available 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.
|
||||
|
||||
|
||||
@@ -11,11 +11,6 @@ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from pretix.base.signals import logentry_object_link
|
||||
|
||||
|
||||
class VisibleOnlyManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(visible=True)
|
||||
|
||||
|
||||
class LogEntry(models.Model):
|
||||
"""
|
||||
Represents a change or action that has been performed on another object
|
||||
@@ -44,10 +39,6 @@ class LogEntry(models.Model):
|
||||
event = models.ForeignKey('Event', null=True, blank=True, on_delete=models.CASCADE)
|
||||
action_type = models.CharField(max_length=255)
|
||||
data = models.TextField(default='{}')
|
||||
visible = models.BooleanField(default=True)
|
||||
|
||||
objects = VisibleOnlyManager()
|
||||
all = models.Manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('-datetime',)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class NotificationSetting(models.Model):
|
||||
"""
|
||||
Stores that a user wants to get notifications of a certain type via a certain
|
||||
method for a certain event. If event is None, the notification shall be sent
|
||||
for all events the user has access to.
|
||||
|
||||
:param user: The user to nofify.
|
||||
:type user: User
|
||||
:param action_type: The type of action to notify for.
|
||||
:type action_type: str
|
||||
:param event: The event to notify for.
|
||||
:type event: Event
|
||||
:param method: The method to notify with.
|
||||
:type method: str
|
||||
:param enabled: Indicates whether the specified notification is enabled. If no
|
||||
event is set, this must always be true. If no event is set, setting
|
||||
this to false is equivalent to deleting the object.
|
||||
:type enabled: bool
|
||||
"""
|
||||
CHANNELS = (
|
||||
('mail', _('E-mail')),
|
||||
)
|
||||
user = models.ForeignKey('User', on_delete=models.CASCADE,
|
||||
related_name='notification_settings')
|
||||
action_type = models.CharField(max_length=255)
|
||||
event = models.ForeignKey('Event', null=True, blank=True, on_delete=models.CASCADE,
|
||||
related_name='notification_settings')
|
||||
method = models.CharField(max_length=255, choices=CHANNELS)
|
||||
enabled = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'action_type', 'event', 'method')
|
||||
@@ -188,7 +188,7 @@ class Order(LoggedModel):
|
||||
def full_code(self):
|
||||
"""
|
||||
An order code which is unique among all events of a single organizer,
|
||||
built by concatenating the event slug and the order code.
|
||||
built by contatenating the event slug and the order code.
|
||||
"""
|
||||
return '{event}-{code}'.format(event=self.event.slug.upper(), code=self.code)
|
||||
|
||||
@@ -519,7 +519,7 @@ class AbstractPosition(models.Model):
|
||||
:type variation: ItemVariation
|
||||
:param datetime: The datetime this item was put into the cart
|
||||
:type datetime: datetime
|
||||
:param expires: The date until this item is guaranteed to be reserved
|
||||
:param expires: The date until this item is guarenteed to be reserved
|
||||
:type expires: datetime
|
||||
:param price: The price of this item
|
||||
:type price: decimal.Decimal
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
import logging
|
||||
from collections import OrderedDict, namedtuple
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.utils.formats import date_format, localize
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.models import Event, LogEntry
|
||||
from pretix.base.signals import register_notification_types
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_ALL_TYPES = None
|
||||
|
||||
|
||||
NotificationAttribute = namedtuple('NotificationAttribute', ('title', 'value'))
|
||||
NotificationAction = namedtuple('NotificationAction', ('label', 'url'))
|
||||
|
||||
|
||||
class Notification:
|
||||
"""
|
||||
Represents a notification that is sent/shown to a user. A notification consists of:
|
||||
|
||||
* one ``event`` reference
|
||||
* one ``title`` text that is shown e.g. in the email subject or in a headline
|
||||
* optionally one ``detail`` text that may or may not be shown depending on the notification method
|
||||
* optionally one ``url`` that should be absolute and point to the context of an notification (e.g. an order)
|
||||
* optionally a number of attributes consisting of a title and a value that can be used to add additional details
|
||||
to the notification (e.g. "Customer: ABC")
|
||||
* optionally a number of actions that may or may not be shown as buttons depending on the notification method,
|
||||
each consisting of a button label and an absolute URL to point to.
|
||||
"""
|
||||
|
||||
def __init__(self, event: Event, title: str, detail: str=None, url: str=None):
|
||||
self.title = title
|
||||
self.event = event
|
||||
self.detail = detail
|
||||
self.url = url
|
||||
self.attributes = []
|
||||
self.actions = []
|
||||
|
||||
def add_action(self, label, url):
|
||||
"""
|
||||
Add an action to the notification, defined by a label and an url. An example could be a label of "View order"
|
||||
and an url linking to the order detail page.
|
||||
"""
|
||||
self.actions.append(NotificationAction(label, url))
|
||||
|
||||
def add_attribute(self, title, value):
|
||||
"""
|
||||
Add an attribute to the notification, defined by a title and a value. An example could be a title of
|
||||
"Date" and a value of "2017-12-14".
|
||||
"""
|
||||
self.attributes.append(NotificationAttribute(title, value))
|
||||
|
||||
|
||||
class NotificationType:
|
||||
def __init__(self, event: Event = None):
|
||||
self.event = event
|
||||
|
||||
def __repr__(self):
|
||||
return '<NotificationType: {}>'.format(self.action_type)
|
||||
|
||||
@property
|
||||
def action_type(self) -> str:
|
||||
"""
|
||||
The action_type string that this notification handles, for example
|
||||
``"pretix.event.order.paid"``. Only one notification type should be registered
|
||||
per action type.
|
||||
"""
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
@property
|
||||
def verbose_name(self) -> str:
|
||||
"""
|
||||
A human-readable name of this notification type.
|
||||
"""
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
@property
|
||||
def required_permission(self) -> str:
|
||||
"""
|
||||
The permission a user needs to hold for the related event to receive this
|
||||
notification.
|
||||
"""
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
def build_notification(self, logentry: LogEntry) -> Notification:
|
||||
"""
|
||||
This is the main function that you should override. It is supposed to turn a log entry
|
||||
object into a notification object that can then be rendered e.g. into an email.
|
||||
"""
|
||||
return Notification(
|
||||
logentry.event,
|
||||
logentry.display()
|
||||
)
|
||||
|
||||
|
||||
def get_all_notification_types(event=None):
|
||||
global _ALL_TYPES
|
||||
|
||||
if event is None and _ALL_TYPES:
|
||||
return _ALL_TYPES
|
||||
|
||||
types = OrderedDict()
|
||||
for recv, ret in register_notification_types.send(event):
|
||||
if isinstance(ret, (list, tuple)):
|
||||
for r in ret:
|
||||
types[r.action_type] = r
|
||||
else:
|
||||
types[ret.action_type] = ret
|
||||
if event is None:
|
||||
_ALL_TYPES = types
|
||||
return types
|
||||
|
||||
|
||||
class ActionRequiredNotificationType(NotificationType):
|
||||
required_permission = "can_change_orders"
|
||||
action_type = "pretix.event.action_required"
|
||||
verbose_name = _("Administrative action required")
|
||||
|
||||
def build_notification(self, logentry: LogEntry):
|
||||
control_url = build_absolute_uri(
|
||||
'control:event.requiredactions',
|
||||
kwargs={
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
'event': logentry.event.slug,
|
||||
}
|
||||
)
|
||||
|
||||
n = Notification(
|
||||
event=logentry.event,
|
||||
title=_('Administrative action required'),
|
||||
detail=_('Something happened in your event that our system cannot handle automatically, e.g. an external '
|
||||
'refund. You need to resolve it manually or choose to ignore it, depending on the issue at hand.'),
|
||||
url=control_url
|
||||
)
|
||||
n.add_action(_('View all unresolved problems'), control_url)
|
||||
return n
|
||||
|
||||
|
||||
class ParametrizedOrderNotificationType(NotificationType):
|
||||
required_permission = "can_view_orders"
|
||||
|
||||
def __init__(self, event, action_type, verbose_name, title):
|
||||
self._action_type = action_type
|
||||
self._verbose_name = verbose_name
|
||||
self._title = title
|
||||
super().__init__(event)
|
||||
|
||||
@property
|
||||
def action_type(self):
|
||||
return self._action_type
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return self._verbose_name
|
||||
|
||||
def build_notification(self, logentry: LogEntry):
|
||||
order = logentry.content_object
|
||||
|
||||
order_url = build_absolute_uri(
|
||||
'control:event.order',
|
||||
kwargs={
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
'event': logentry.event.slug,
|
||||
'code': order.code
|
||||
}
|
||||
)
|
||||
|
||||
n = Notification(
|
||||
event=logentry.event,
|
||||
title=self._title.format(order=order, event=logentry.event),
|
||||
url=order_url
|
||||
)
|
||||
n.add_attribute(_('Order code'), order.code)
|
||||
n.add_attribute(_('Order total'), '{} {}'.format(localize(order.total), logentry.event.currency))
|
||||
n.add_attribute(_('Order date'), date_format(order.datetime, 'SHORT_DATETIME_FORMAT'))
|
||||
n.add_attribute(_('Order status'), order.get_status_display())
|
||||
n.add_attribute(_('Order positions'), str(order.positions.count()))
|
||||
n.add_action(_('View order details'), order_url)
|
||||
return n
|
||||
|
||||
|
||||
@receiver(register_notification_types, dispatch_uid="base_register_default_notification_types")
|
||||
def register_default_notification_types(sender, **kwargs):
|
||||
return (
|
||||
ParametrizedOrderNotificationType(
|
||||
sender,
|
||||
'pretix.event.order.placed',
|
||||
_('New order placed'),
|
||||
_('A new order has been placed: {order.code}'),
|
||||
),
|
||||
ParametrizedOrderNotificationType(
|
||||
sender,
|
||||
'pretix.event.order.paid',
|
||||
_('Order marked as paid'),
|
||||
_('Order {order.code} has been marked as paid.')
|
||||
),
|
||||
ParametrizedOrderNotificationType(
|
||||
sender,
|
||||
'pretix.event.order.canceled',
|
||||
_('Order canceled'),
|
||||
_('Order {order.code} has been canceled.')
|
||||
),
|
||||
ParametrizedOrderNotificationType(
|
||||
sender,
|
||||
'pretix.event.order.modified',
|
||||
_('Order information changed'),
|
||||
_('The ticket information of order {order.code} has been changed.')
|
||||
),
|
||||
ParametrizedOrderNotificationType(
|
||||
sender,
|
||||
'pretix.event.order.contact.changed',
|
||||
_('Order contact address changed'),
|
||||
_('The contact address of order {order.code} has been changed.')
|
||||
),
|
||||
ParametrizedOrderNotificationType(
|
||||
sender,
|
||||
'pretix.event.order.changed',
|
||||
_('Order changed'),
|
||||
_('Order {order.code} has been changed.')
|
||||
),
|
||||
ParametrizedOrderNotificationType(
|
||||
sender,
|
||||
'pretix.event.order.refunded',
|
||||
_('Order refunded'),
|
||||
_('Order {order.code} has been refunded.')
|
||||
),
|
||||
ActionRequiredNotificationType(
|
||||
sender,
|
||||
)
|
||||
)
|
||||
@@ -11,7 +11,7 @@ from django.forms import Form
|
||||
from django.http import HttpRequest
|
||||
from django.template.loader import get_template
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
@@ -184,9 +184,7 @@ class BasePaymentProvider:
|
||||
('_invoice_text',
|
||||
I18nFormField(
|
||||
label=_('Text on invoices'),
|
||||
help_text=_('Will be printed just below the payment figures and above the closing text on invoices. '
|
||||
'This will only be used if the invoice is generated before the order is paid. If the '
|
||||
'invoice is generated later, it will show a text stating that it has already been paid.'),
|
||||
help_text=_('Will be printed just below the payment figures and above the closing text on invoices.'),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
widget_kwargs={'attrs': {'rows': '2'}}
|
||||
@@ -207,8 +205,6 @@ class BasePaymentProvider:
|
||||
The default implementation returns the content of the _invoice_text configuration
|
||||
variable (an I18nString), or an empty string if unconfigured.
|
||||
"""
|
||||
if order.status == Order.STATUS_PAID:
|
||||
return pgettext_lazy('invoice', 'The payment for this invoice has already been received.')
|
||||
return self.settings.get('_invoice_text', as_type=LazyI18nString, default='')
|
||||
|
||||
@property
|
||||
@@ -328,7 +324,7 @@ class BasePaymentProvider:
|
||||
at least store the user's input into his session.
|
||||
|
||||
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 behavior
|
||||
if the input was valid and the frontend should continue with default behaviour
|
||||
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
|
||||
@@ -379,7 +375,7 @@ class BasePaymentProvider:
|
||||
"""
|
||||
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 behavior, you can return a string
|
||||
If you need any special behaviour, you can return a string
|
||||
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.
|
||||
|
||||
@@ -527,8 +523,8 @@ class BasePaymentProvider:
|
||||
Will be called if the event administrator confirms the refund.
|
||||
|
||||
This should transfer the money back (if possible). You can return the URL the
|
||||
user should be redirected to if you need special behavior or None to continue
|
||||
with default behavior.
|
||||
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.
|
||||
@@ -592,8 +588,8 @@ class FreeOrderProvider(BasePaymentProvider):
|
||||
Will be called if the event administrator confirms the refund.
|
||||
|
||||
This should transfer the money back (if possible). You can return the URL the
|
||||
user should be redirected to if you need special behavior or None to continue
|
||||
with default behavior.
|
||||
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.
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
from django.conf import settings
|
||||
from django.template.loader import get_template
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import LogEntry, NotificationSetting, User
|
||||
from pretix.base.notifications import Notification, get_all_notification_types
|
||||
from pretix.base.services.async import ProfiledTask, TransactionAwareTask
|
||||
from pretix.base.services.mail import mail_send_task
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
|
||||
|
||||
@app.task(base=TransactionAwareTask)
|
||||
def notify(logentry_id: int):
|
||||
logentry = LogEntry.all.get(id=logentry_id)
|
||||
if not logentry.event:
|
||||
return # Ignore, we only have event-related notifications right now
|
||||
types = get_all_notification_types(logentry.event)
|
||||
notification_type = types.get(logentry.action_type)
|
||||
if not notification_type:
|
||||
return # Ignore, e.g. plugin not active for this event
|
||||
|
||||
# All users that have the permission to get the notification
|
||||
users = logentry.event.get_users_with_permission(
|
||||
notification_type.required_permission
|
||||
).filter(notifications_send=True)
|
||||
if logentry.user:
|
||||
users = users.exclude(pk=logentry.user.pk)
|
||||
|
||||
# Get all notification settings, both specific to this event as well as global
|
||||
notify_specific = {
|
||||
(ns.user, ns.method): ns.enabled
|
||||
for ns in NotificationSetting.objects.filter(
|
||||
event=logentry.event,
|
||||
action_type=logentry.action_type,
|
||||
user__pk__in=users.values_list('pk', flat=True)
|
||||
)
|
||||
}
|
||||
notify_global = {
|
||||
(ns.user, ns.method): ns.enabled
|
||||
for ns in NotificationSetting.objects.filter(
|
||||
action_type=logentry.action_type,
|
||||
user__pk__in=users.values_list('pk', flat=True)
|
||||
)
|
||||
}
|
||||
|
||||
for um, enabled in notify_specific.items():
|
||||
user, method = um
|
||||
if enabled:
|
||||
send_notification.apply_async(args=(logentry_id, user.pk, method))
|
||||
|
||||
for um, enabled in notify_global.items():
|
||||
user, method = um
|
||||
if enabled and um not in notify_specific:
|
||||
send_notification.apply_async(args=(logentry_id, user.pk, method))
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask)
|
||||
def send_notification(logentry_id: int, user_id: int, method: str):
|
||||
logentry = LogEntry.all.get(id=logentry_id)
|
||||
user = User.objects.get(id=user_id)
|
||||
types = get_all_notification_types(logentry.event)
|
||||
notification_type = types.get(logentry.action_type)
|
||||
if not notification_type:
|
||||
return # Ignore, e.g. plugin not active for this event
|
||||
|
||||
with language(user.locale):
|
||||
notification = notification_type.build_notification(logentry)
|
||||
|
||||
if method == "mail":
|
||||
send_notification_mail(notification, user)
|
||||
|
||||
|
||||
def send_notification_mail(notification: Notification, user: User):
|
||||
ctx = {
|
||||
'site': settings.PRETIX_INSTANCE_NAME,
|
||||
'site_url': settings.SITE_URL,
|
||||
'color': '#8E44B3',
|
||||
'notification': notification,
|
||||
'settings_url': build_absolute_uri(
|
||||
'control:user.settings.notifications',
|
||||
),
|
||||
'disable_url': build_absolute_uri(
|
||||
'control:user.settings.notifications.off',
|
||||
kwargs={
|
||||
'token': user.notifications_token,
|
||||
'id': user.pk
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
tpl_html = get_template('pretixbase/email/notification.html')
|
||||
body_html = tpl_html.render(ctx)
|
||||
tpl_plain = get_template('pretixbase/email/notification.txt')
|
||||
body_plain = tpl_plain.render(ctx)
|
||||
|
||||
mail_send_task.apply_async(kwargs={
|
||||
'to': [user.email],
|
||||
'subject': '[{}] {}'.format(settings.PRETIX_INSTANCE_NAME, notification.title),
|
||||
'body': body_plain,
|
||||
'html': body_html,
|
||||
'sender': settings.MAIL_FROM,
|
||||
'headers': {},
|
||||
})
|
||||
@@ -25,8 +25,8 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.orders import (
|
||||
CachedCombinedTicket, CachedTicket, InvoiceAddress, OrderFee,
|
||||
generate_position_secret, generate_secret,
|
||||
CachedTicket, InvoiceAddress, OrderFee, generate_position_secret,
|
||||
generate_secret,
|
||||
)
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
from pretix.base.models.tax import TaxedPrice
|
||||
@@ -1085,10 +1085,6 @@ class OrderChangeManager:
|
||||
|
||||
def _clear_tickets_cache(self):
|
||||
CachedTicket.objects.filter(order_position__order=self.order).delete()
|
||||
CachedCombinedTicket.objects.filter(order=self.order).delete()
|
||||
if self.split_order:
|
||||
CachedTicket.objects.filter(order_position__order=self.split_order).delete()
|
||||
CachedCombinedTicket.objects.filter(order=self.split_order).delete()
|
||||
|
||||
def _get_payment_provider(self):
|
||||
pprov = self.order.event.get_payment_providers().get(self.order.payment_provider)
|
||||
@@ -1107,7 +1103,7 @@ def perform_order(self, event: str, payment_provider: str, positions: List[str],
|
||||
except LockTimeoutException:
|
||||
self.retry()
|
||||
except (MaxRetriesExceededError, LockTimeoutException):
|
||||
raise OrderError(str(error_messages['busy']))
|
||||
return OrderError(error_messages['busy'])
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
||||
@@ -1116,6 +1112,6 @@ def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_tok
|
||||
try:
|
||||
return _cancel_order(order, user, send_mail, api_token)
|
||||
except LockTimeoutException:
|
||||
self.retry()
|
||||
self.retry(exc=OrderError(error_messages['busy']))
|
||||
except (MaxRetriesExceededError, LockTimeoutException):
|
||||
raise OrderError(error_messages['busy'])
|
||||
return OrderError(error_messages['busy'])
|
||||
|
||||
@@ -26,10 +26,6 @@ class EventPluginSignal(django.dispatch.Signal):
|
||||
"""
|
||||
|
||||
def _is_active(self, sender, receiver):
|
||||
if sender is None:
|
||||
# Send to all events!
|
||||
return True
|
||||
|
||||
# Find the Django application this belongs to
|
||||
searchpath = receiver.__module__
|
||||
core_module = any([searchpath.startswith(cm) for cm in settings.CORE_MODULES])
|
||||
@@ -144,19 +140,6 @@ subclass of pretix.base.ticketoutput.BaseTicketOutput
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
register_notification_types = EventPluginSignal(
|
||||
providing_args=[]
|
||||
)
|
||||
"""
|
||||
This signal is sent out to get all known notification types. Receivers should return an
|
||||
instance of a subclass of pretix.base.notifications.NotificationType or a list of such
|
||||
instances.
|
||||
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event,
|
||||
however for this signal, the ``sender`` **may also be None** to allow creating the general
|
||||
notification settings!
|
||||
"""
|
||||
|
||||
register_data_exporters = EventPluginSignal(
|
||||
providing_args=[]
|
||||
)
|
||||
@@ -332,7 +315,7 @@ This signal allows you to implement a middleware-style filter on all outgoing em
|
||||
return a (possibly modified) copy of the message object passed to you.
|
||||
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
The ``message`` argument will contain an ``EmailMultiAlternatives`` object.
|
||||
The ``message`` argument will contian an ``EmailMultiAlternatives`` object.
|
||||
If the email is associated with a specific order, the ``order`` argument will be passed as well, otherwise
|
||||
it will be ``None``.
|
||||
"""
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
{% load eventurl %}
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=false">
|
||||
</head>
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #e8e8e8;
|
||||
background-position: top;
|
||||
background-repeat: repeat-x;
|
||||
font-family: "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 a, .content h2 a, .content h3 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.content h2, .content h3 {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: {{ color }};
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
color: {{ color }};
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover, a:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
|
||||
/* These are technically the same, but use both */
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
|
||||
-ms-word-break: break-all;
|
||||
/* This is the dangerous one in WebKit, as it breaks things wherever */
|
||||
word-break: break-all;
|
||||
/* Instead use this non-standard one: */
|
||||
word-break: break-word;
|
||||
|
||||
/* Adds a hyphen where the word breaks, if supported (No Blink) */
|
||||
-ms-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 8px 18px 8px;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: {{ color }};
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
table.layout {
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
border-spacing: 0px;
|
||||
border-collapse: separate;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.content table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content table td {
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
padding: 5px 0;
|
||||
}
|
||||
a.button {
|
||||
display: inline-block;
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
line-height: 1.33333;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 6px;
|
||||
-webkit-border-radius: 6px;
|
||||
-moz-border-radius: 6px;
|
||||
margin: 5px;
|
||||
text-decoration: none;
|
||||
color: {{ color }};
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: 19px;
|
||||
line-height: 24px;
|
||||
margin: 0 9px 3px 0;
|
||||
border-radius: 5px 5px;
|
||||
-webkit-border-radius: 5px 5px;
|
||||
-moz-border-radius: 5px 5px;
|
||||
}
|
||||
|
||||
.header h1 a {
|
||||
padding: 3px 9px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 0;
|
||||
padding: 12px 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
td.containertd {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
{% block addcss %}{% endblock %}
|
||||
</style>
|
||||
<body>
|
||||
<table class="layout">
|
||||
<tr>
|
||||
<td class="header" background="">
|
||||
{% if event %}
|
||||
<h1><a href="{% abseventurl event "presale:event.index" %}" target="_blank">{{ event.name }}</a></h1>
|
||||
{% else %}
|
||||
<h1><a href="{{ site_url }}" target="_blank">{{ site }}</a></h1>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
<tr>
|
||||
<td class="footer">
|
||||
<div>
|
||||
{% include "pretixbase/email/email_footer.html" %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<br/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,53 +0,0 @@
|
||||
{% extends "pretixbase/email/base.html" %}
|
||||
{% load eventurl %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<tr>
|
||||
<td class="containertd">
|
||||
<div class="content">
|
||||
<h3>
|
||||
{% if notification.url %}<a href="{{ notification.url }}">{% endif %}
|
||||
{{ notification.title }}
|
||||
{% if notification.url %}</a>{% endif %}
|
||||
</h3>
|
||||
{% if notification.detail %}
|
||||
<p>{{ notification.detail }}</p>
|
||||
{% endif %}
|
||||
{% if notification.attributes %}
|
||||
<table>
|
||||
{% for attr in notification.attributes %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ attr.title }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
{{ attr.value }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if notification.actions %}
|
||||
<p class="actions" style="text-align: center">
|
||||
{% for action in notification.actions %}
|
||||
<a href="{{ action.url }}" class="button">{{ action.label }}</a>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="containertd">
|
||||
<div class="content">
|
||||
{% trans "You receive these emails based on your notification settings." %}<br>
|
||||
<a href="{{ settings_url }}">
|
||||
{% trans "Click here to view and change your notification settings" %}
|
||||
</a><br>
|
||||
<a href="{{ disable_url }}">
|
||||
{% trans "Click here disable all notifications immediately." %}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endblock %}
|
||||
@@ -1,18 +0,0 @@
|
||||
{% load i18n %}
|
||||
{{ notification.title }}{% if notification.detail %}
|
||||
|
||||
{{ notification.detail }}
|
||||
{% endif %}{% if notification.url %}
|
||||
|
||||
{{ notification.url }}{% endif %}{% for attr in notification.attributes %}
|
||||
|
||||
{{ attr.title }}: {{ attr.value }}{% endfor %}{% for action in notification.actions %}
|
||||
|
||||
{{ action.label }}
|
||||
{{ action.url }}{% endfor %}
|
||||
|
||||
{% trans "You receive these emails based on your notification settings." %}
|
||||
{% trans "Click here to view and change your notification settings:" %}
|
||||
{{ settings_url }}
|
||||
{% trans "Click here disable all notifications immediately:" %}
|
||||
{{ disable_url }}
|
||||
@@ -1,42 +1,174 @@
|
||||
{% extends "pretixbase/email/base.html" %}
|
||||
{% load eventurl %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<tr>
|
||||
<td class="containertd">
|
||||
<div class="content">
|
||||
{{ body|safe }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% if order %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=false">
|
||||
</head>
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #e8e8e8;
|
||||
background-position: top;
|
||||
background-repeat: repeat-x;
|
||||
font-family: "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: {{ color }};
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
color: {{ color }};
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover, a:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
|
||||
/* These are technically the same, but use both */
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
|
||||
-ms-word-break: break-all;
|
||||
/* This is the dangerous one in WebKit, as it breaks things wherever */
|
||||
word-break: break-all;
|
||||
/* Instead use this non-standard one: */
|
||||
word-break: break-word;
|
||||
|
||||
/* Adds a hyphen where the word breaks, if supported (No Blink) */
|
||||
-ms-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 8px 18px 8px;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: {{ color }};
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
border-spacing: 0px;
|
||||
border-collapse: separate;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: 19px;
|
||||
line-height: 24px;
|
||||
margin: 0 9px 3px 0;
|
||||
border-radius: 5px 5px;
|
||||
-webkit-border-radius: 5px 5px;
|
||||
-moz-border-radius: 5px 5px;
|
||||
}
|
||||
|
||||
.header h1 a {
|
||||
padding: 3px 9px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 0;
|
||||
padding: 12px 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
td.containertd {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
{% block addcss %}{% endblock %}
|
||||
</style>
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="gap"></td>
|
||||
<td class="header" background="">
|
||||
{% if event %}
|
||||
<h1><a href="{% abseventurl event "presale:event.index" %}" target="_blank">{{ event.name }}</a></h1>
|
||||
{% else %}
|
||||
<h1><a href="{{ site_url }}" target="_blank">{{ site }}</a></h1>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="order containertd">
|
||||
<td class="containertd">
|
||||
<div class="content">
|
||||
{% trans "You are receiving this email because you placed an order for the following event:" %}<br>
|
||||
<strong>{% trans "Event:" %}</strong> {{ event.name }}<br>
|
||||
<strong>{% trans "Order code:" %}</strong> {{ order.code }}<br>
|
||||
<strong>{% trans "Order date:" %}</strong> {{ order.datetime|date:"SHORT_DATE_FORMAT" }}<br>
|
||||
<a href="{% abseventurl event "presale:event.order" order=order.code secret=order.secret %}">
|
||||
{% trans "View order details" %}
|
||||
</a>
|
||||
{{ body|safe }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if order %}
|
||||
<tr>
|
||||
<td class="gap"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="order containertd">
|
||||
<div class="content">
|
||||
{% trans "You are receiving this email because you placed an order for the following event:" %}<br>
|
||||
<strong>{% trans "Event:" %}</strong> {{ event.name }}<br>
|
||||
<strong>{% trans "Order code:" %}</strong> {{ order.code }}<br>
|
||||
<strong>{% trans "Order date:" %}</strong> {{ order.datetime|date:"SHORT_DATE_FORMAT" }}<br>
|
||||
<a href="{% abseventurl event "presale:event.order" order=order.code secret=order.secret %}">
|
||||
{% trans "View order details" %}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if signature %}
|
||||
<tr>
|
||||
<td class="gap"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="order containertd">
|
||||
<div class="content">
|
||||
{{ signature | safe }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="gap"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="order containertd">
|
||||
<div class="content">
|
||||
{{ signature | safe }}
|
||||
<td class="footer">
|
||||
<div>
|
||||
{% include "pretixbase/email/email_footer.html" %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</table>
|
||||
<br/>
|
||||
<br/>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -459,8 +459,7 @@ class CheckInFilterForm(FilterForm):
|
||||
if fdata.get('user'):
|
||||
u = fdata.get('user')
|
||||
qs = qs.filter(
|
||||
Q(order__code__istartswith=u)
|
||||
| Q(order__email__icontains=u)
|
||||
Q(order__email__icontains=u)
|
||||
| Q(attendee_name__icontains=u)
|
||||
| Q(attendee_email__icontains=u)
|
||||
| Q(order__invoice_address__name__icontains=u)
|
||||
|
||||
@@ -43,7 +43,7 @@ class UpdateSettingsForm(SettingsForm):
|
||||
"the current version of pretix and your installed plugins and the number of active and "
|
||||
"inactive events in your installation to servers operated by the pretix developers. We "
|
||||
"will only store anonymous data, never any IP addresses and we will not know who you are "
|
||||
"or where to find your instance. You can disable this behavior here at any time.")
|
||||
"or where to find your instance. You can disable this behaviour here at any time.")
|
||||
)
|
||||
update_check_email = forms.EmailField(
|
||||
required=False,
|
||||
|
||||
@@ -165,7 +165,7 @@ class VoucherBulkForm(VoucherForm):
|
||||
data = super().clean()
|
||||
|
||||
if Voucher.objects.filter(code__in=data['codes'], event=self.instance.event).exists():
|
||||
raise ValidationError(_('A voucher with one of these codes already exists.'))
|
||||
raise ValidationError(_('A voucher with one of this codes already exists.'))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -119,7 +119,6 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
'pretix.event.order.placed': _('The order has been created.'),
|
||||
'pretix.event.order.contact.changed': _('The email address has been changed from "{old_email}" '
|
||||
'to "{new_email}".'),
|
||||
'pretix.event.order.locale.changed': _('The order locale has been changed.'),
|
||||
'pretix.event.order.invoice.generated': _('The invoice has been generated.'),
|
||||
'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'),
|
||||
'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'),
|
||||
@@ -142,9 +141,6 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
'your account.'),
|
||||
'pretix.user.settings.2fa.device.deleted': _('The two-factor authentication device "{name}" has been removed '
|
||||
'from your account.'),
|
||||
'pretix.user.settings.notifications.enabled': _('Notifications have been enabled.'),
|
||||
'pretix.user.settings.notifications.disabled': _('Notifications have been disabled.'),
|
||||
'pretix.user.settings.notifications.changed': _('Your notification settings have been changed.'),
|
||||
'pretix.control.auth.user.forgot_password.mail_sent': _('Password reset mail sent.'),
|
||||
'pretix.control.auth.user.forgot_password.recovered': _('The password has been reset.'),
|
||||
'pretix.voucher.added': _('The voucher has been created.'),
|
||||
|
||||
@@ -27,7 +27,6 @@ class PermissionMiddleware(MiddlewareMixin):
|
||||
"auth.forgot",
|
||||
"auth.forgot.recover",
|
||||
"auth.invite",
|
||||
"user.settings.notifications.off",
|
||||
)
|
||||
|
||||
def _login_redirect(self, request):
|
||||
|
||||
@@ -176,7 +176,7 @@ as active.
|
||||
|
||||
If your linked view should stay in the tab-like context of this page, we recommend
|
||||
that you use ``pretix.control.views.organizer.OrganizerDetailViewMixin`` for your view
|
||||
and your template inherits from ``pretixcontrol/organizers/base.html``.
|
||||
and your tempalte inherits from ``pretixcontrol/organizers/base.html``.
|
||||
|
||||
This is a regular django signal (no pretix event signal). Receivers will be passed
|
||||
the keyword arguments ``organizer`` and ``request``.
|
||||
@@ -205,7 +205,7 @@ as active.
|
||||
|
||||
If your linked view should stay in the tab-like context of this page, we recommend
|
||||
that you use ``pretix.control.views.event.EventSettingsViewMixin`` for your view
|
||||
and your template inherits from ``pretixcontrol/event/settings_base.html``.
|
||||
and your tempalte inherits from ``pretixcontrol/event/settings_base.html``.
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
A second keyword argument ``request`` will contain the request object.
|
||||
|
||||
@@ -7,13 +7,6 @@
|
||||
{% block content %}
|
||||
<h1>
|
||||
{{ request.event.name }}
|
||||
<small>
|
||||
{% if request.event.has_subevents %}
|
||||
{% trans "Event series" %}
|
||||
{% else %}
|
||||
{{ request.event.get_date_range_display }}
|
||||
{% endif %}
|
||||
</small>
|
||||
</h1>
|
||||
{% if actions|length > 0 %}
|
||||
<div class="panel panel-danger">
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</li>
|
||||
<li {% if "event.settings.mail" == url_name %}class="active"{% endif %}>
|
||||
<a href="{% url 'control:event.settings.mail' organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||
{% trans "E-mail" %}
|
||||
{% trans "Email" %}
|
||||
</a>
|
||||
</li>
|
||||
<li {% if "event.settings.tax" in url_name %}class="active"{% endif %}>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% load urlreplace %}
|
||||
<nav class="text-center pagination-container">
|
||||
<nav class="text-center">
|
||||
<ul class="pagination">
|
||||
{% if is_paginated %}
|
||||
{% if page_obj.has_previous %}
|
||||
@@ -32,17 +32,4 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% if page_size %}
|
||||
<div class="clearfix">
|
||||
<small>
|
||||
{% trans "Show per page:" %}
|
||||
</small>
|
||||
<a href="?{% url_replace request "page_size" "25" "page" "1" %}">
|
||||
{% if page_size == 25 %}<strong>{% endif %}25{% if page_size == 25 %}</strong>{% endif %}</a> |
|
||||
<a href="?{% url_replace request "page_size" "50" "page" "1" %}">
|
||||
{% if page_size == 50 %}<strong>{% endif %}50{% if page_size == 50 %}</strong>{% endif %}</a> |
|
||||
<a href="?{% url_replace request "page_size" "100" "page" "1" %}">
|
||||
{% if page_size == 100 %}<strong>{% endif %}100{% if page_size == 100 %}</strong>{% endif %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Notification settings" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Notification settings" %}</h1>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
{% if request.user.notifications_send %}
|
||||
<div class="alert alert-info">
|
||||
<button name="notifications_send" value="off" type="submit" class="pull-right btn btn-default">
|
||||
<span class="fa fa-bell-slash"></span>
|
||||
{% trans "Disable" %}
|
||||
</button>
|
||||
{% trans "Notifications are turned on according to the settings below." %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning">
|
||||
<button name="notifications_send" value="on" type="submit" class="pull-right btn btn-default">
|
||||
<span class="fa fa-bell"></span>
|
||||
{% trans "Enable" %}
|
||||
</button>
|
||||
{% trans "All notifications are turned off globally." %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
</form>
|
||||
<form class="form-inline" method="get">
|
||||
<fieldset>
|
||||
<legend>{% trans "Choose event" %}</legend>
|
||||
<p>
|
||||
<select name="event" class="form-control">
|
||||
<option value="">{% trans "All my events" %}</option>
|
||||
{% for e in events %}
|
||||
<option value="{{ e.pk }}"
|
||||
{% if e.pk|floatformat:0 == request.GET.event %}selected="selected"{% endif %}>
|
||||
{{ e.name }} – {{ e.get_date_range_display }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-primary" type="submit">{% trans "Choose" %}</button>
|
||||
<span class="help-block">{% trans "Save your modifications before switching events." %}</span>
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Choose notifications to get" %}</legend>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Notification type" %}</th>
|
||||
<th class="text-center">{% trans "E-Mail notification" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for type, enabled, global in types %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ type.verbose_name }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if not event or type.required_permission in permset %}
|
||||
<select name="mail:{{ type.action_type }}" class="form-control">
|
||||
{% if event %}
|
||||
<option value="global">{% trans "Global" %} ({% if global.mail %}{% trans "On" %}{% else %}{% trans "Off" %}{% endif %})</option>{% endif %}
|
||||
<option value="off" {% if "mail" in enabled and enabled.mail == False %}selected{% endif %}>{% trans "Off" %}</option>
|
||||
<option value="on" {% if enabled.mail %}selected{% endif %}>{% trans "On" %}</option>
|
||||
</select>
|
||||
{% else %}
|
||||
<span class="fa fa-lock" data-toggle="tooltip" title="{% trans "You have no permission to receive this notification" %}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -12,24 +12,6 @@
|
||||
{% bootstrap_field form.fullname layout='horizontal' %}
|
||||
{% bootstrap_field form.locale layout='horizontal' %}
|
||||
{% bootstrap_field form.timezone layout='horizontal' %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="id_new_pw_repeat">{% trans "Notifications" %}</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
{% if request.user.notifications_send and request.user.notification_settings.exists %}
|
||||
<span class="label label-success">
|
||||
<span class="fa fa-bell-o"></span> {% trans "On" %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="label label-warning">
|
||||
<span class="fa fa-bell-slash-o"></span> {% trans "Off" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url "control:user.settings.notifications" %}">
|
||||
{% trans "Change notification settings" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Login settings" %}</legend>
|
||||
@@ -41,12 +23,12 @@
|
||||
<label class="col-md-3 control-label" for="id_new_pw_repeat">{% trans "Two-factor authentication" %}</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
{% if user.require_2fa %}
|
||||
<span class="label label-success">{% trans "Enabled" %}</span>
|
||||
<span class="label label-success">{% trans "Enabled" %}</span>
|
||||
<a href="{% url "control:user.settings.2fa" %}">
|
||||
{% trans "Change two-factor settings" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="label label-default">{% trans "Disabled" %}</span>
|
||||
<span class="label label-default">{% trans "Disabled" %}</span>
|
||||
<a href="{% url "control:user.settings.2fa" %}">
|
||||
{% trans "Enable" %}
|
||||
</a>
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
<option value="v" {% if request.GET.status == "v" %}selected="selected"{% endif %}>{% trans "Valid" %}</option>
|
||||
<option value="r" {% if request.GET.status == "r" %}selected="selected"{% endif %}>{% trans "Redeemed" %}</option>
|
||||
<option value="e" {% if request.GET.status == "e" %}selected="selected"{% endif %}>{% trans "Expired" %}</option>
|
||||
<option value="c" {% if request.GET.status == "c" %}selected="selected"{% endif %}>{% trans "Redeemed and checked in with ticket" %}</option>
|
||||
</select>
|
||||
{% if request.event.has_subevents %}
|
||||
<select name="subevent" class="form-control">
|
||||
|
||||
@@ -18,11 +18,8 @@ urlpatterns = [
|
||||
url(r'^global/update/$', global_settings.UpdateCheckView.as_view(), name='global.update'),
|
||||
url(r'^reauth/$', user.ReauthView.as_view(), name='user.reauth'),
|
||||
url(r'^settings/?$', user.UserSettings.as_view(), name='user.settings'),
|
||||
url(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'),
|
||||
url(r'^settings/notifications/$', user.UserNotificationsEditView.as_view(), name='user.settings.notifications'),
|
||||
url(r'^settings/notifications/off/(?P<id>\d+)/(?P<token>[^/]+)/$', user.UserNotificationsDisableView.as_view(),
|
||||
name='user.settings.notifications.off'),
|
||||
url(r'^settings/2fa/$', user.User2FAMainView.as_view(), name='user.settings.2fa'),
|
||||
url(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'),
|
||||
url(r'^settings/2fa/add$', user.User2FADeviceAddView.as_view(), name='user.settings.2fa.add'),
|
||||
url(r'^settings/2fa/enable', user.User2FAEnableView.as_view(), name='user.settings.2fa.enable'),
|
||||
url(r'^settings/2fa/disable', user.User2FADisableView.as_view(), name='user.settings.2fa.disable'),
|
||||
|
||||
@@ -35,24 +35,3 @@ class ChartContainingView:
|
||||
# required by raphael.js
|
||||
resp['Content-Security-Policy'] = "script-src 'unsafe-eval'; style-src 'unsafe-inline'"
|
||||
return resp
|
||||
|
||||
|
||||
class PaginationMixin:
|
||||
DEFAULT_PAGINATION = 25
|
||||
|
||||
def get_paginate_by(self, queryset):
|
||||
skey = 'stored_page_size_' + self.request.resolver_match.url_name
|
||||
default = self.request.session.get(skey) or self.paginate_by or self.DEFAULT_PAGINATION
|
||||
if self.request.GET.get('page_size'):
|
||||
try:
|
||||
size = min(250, int(self.request.GET.get("page_size")))
|
||||
self.request.session[skey] = size
|
||||
return min(250, int(self.request.GET.get("page_size")))
|
||||
except ValueError:
|
||||
return default
|
||||
return default
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['page_size'] = self.get_paginate_by(None)
|
||||
return ctx
|
||||
|
||||
@@ -16,12 +16,13 @@ from pretix.base.models.checkin import CheckinList
|
||||
from pretix.control.forms.checkin import CheckinListForm
|
||||
from pretix.control.forms.filter import CheckInFilterForm
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import CreateView, PaginationMixin, UpdateView
|
||||
from pretix.control.views import CreateView, UpdateView
|
||||
|
||||
|
||||
class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
class CheckInListShow(EventPermissionRequiredMixin, ListView):
|
||||
model = Checkin
|
||||
context_object_name = 'entries'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/checkin/index.html'
|
||||
permission = 'can_view_orders'
|
||||
|
||||
@@ -108,9 +109,10 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
}) + '?' + request.GET.urlencode())
|
||||
|
||||
|
||||
class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
class CheckinListList(EventPermissionRequiredMixin, ListView):
|
||||
model = CheckinList
|
||||
context_object_name = 'checkinlists'
|
||||
paginate_by = 30
|
||||
permission = 'can_view_orders'
|
||||
template_name = 'pretixcontrol/checkin/lists.html'
|
||||
|
||||
@@ -207,7 +209,6 @@ class CheckinListDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
success_url = self.get_success_url()
|
||||
self.object.checkins.all().delete()
|
||||
self.object.log_action(action='pretix.event.orders.deleted', user=request.user)
|
||||
self.object.delete()
|
||||
messages.success(self.request, _('The selected list has been deleted.'))
|
||||
|
||||
@@ -45,7 +45,7 @@ from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import get_domain
|
||||
from pretix.presale.style import regenerate_css
|
||||
|
||||
from . import CreateView, PaginationMixin, UpdateView
|
||||
from . import CreateView, UpdateView
|
||||
from ..logdisplay import OVERVIEW_BLACKLIST
|
||||
|
||||
|
||||
@@ -877,9 +877,10 @@ class EventComment(EventPermissionRequiredMixin, View):
|
||||
})
|
||||
|
||||
|
||||
class TaxList(EventSettingsViewMixin, EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
class TaxList(EventSettingsViewMixin, EventPermissionRequiredMixin, ListView):
|
||||
model = TaxRule
|
||||
context_object_name = 'taxrules'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/event/tax_index.html'
|
||||
permission = 'can_change_event_settings'
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ from pretix.control.permissions import (
|
||||
EventPermissionRequiredMixin, event_permission_required,
|
||||
)
|
||||
|
||||
from . import ChartContainingView, CreateView, PaginationMixin, UpdateView
|
||||
from . import ChartContainingView, CreateView, UpdateView
|
||||
|
||||
|
||||
class ItemList(ListView):
|
||||
@@ -192,9 +192,10 @@ class CategoryCreate(EventPermissionRequiredMixin, CreateView):
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class CategoryList(PaginationMixin, ListView):
|
||||
class CategoryList(ListView):
|
||||
model = ItemCategory
|
||||
context_object_name = 'categories'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/items/categories.html'
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -244,9 +245,10 @@ def category_move_down(request, organizer, event, category):
|
||||
event=request.event.slug)
|
||||
|
||||
|
||||
class QuestionList(PaginationMixin, ListView):
|
||||
class QuestionList(ListView):
|
||||
model = Question
|
||||
context_object_name = 'questions'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/items/questions.html'
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -543,9 +545,10 @@ class QuestionCreate(EventPermissionRequiredMixin, QuestionMixin, CreateView):
|
||||
return ret
|
||||
|
||||
|
||||
class QuotaList(PaginationMixin, ListView):
|
||||
class QuotaList(ListView):
|
||||
model = Quota
|
||||
context_object_name = 'quotas'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/items/quotas.html'
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
@@ -22,12 +22,12 @@ from pretix.control.forms.event import (
|
||||
)
|
||||
from pretix.control.forms.filter import EventFilterForm
|
||||
from pretix.control.permissions import OrganizerPermissionRequiredMixin
|
||||
from pretix.control.views import PaginationMixin
|
||||
|
||||
|
||||
class EventList(PaginationMixin, ListView):
|
||||
class EventList(ListView):
|
||||
model = Event
|
||||
context_object_name = 'events'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/events/index.html'
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
@@ -50,7 +50,6 @@ from pretix.control.forms.orders import (
|
||||
OtherOperationsForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.signals import question_form_fields
|
||||
@@ -58,9 +57,10 @@ from pretix.presale.signals import question_form_fields
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OrderList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
class OrderList(EventPermissionRequiredMixin, ListView):
|
||||
model = Order
|
||||
context_object_name = 'orders'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/orders/index.html'
|
||||
permission = 'can_view_orders'
|
||||
|
||||
@@ -815,7 +815,7 @@ class OrderEmailHistory(EventPermissionRequiredMixin, OrderViewMixin, ListView):
|
||||
permission = 'can_view_orders'
|
||||
model = LogEntry
|
||||
context_object_name = 'logs'
|
||||
paginate_by = 10
|
||||
paginate_by = 5
|
||||
|
||||
def get_queryset(self):
|
||||
order = Order.objects.filter(
|
||||
|
||||
@@ -24,15 +24,15 @@ from pretix.control.forms.organizer import (
|
||||
)
|
||||
from pretix.control.permissions import OrganizerPermissionRequiredMixin
|
||||
from pretix.control.signals import nav_organizer
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.presale.style import regenerate_organizer_css
|
||||
|
||||
|
||||
class OrganizerList(PaginationMixin, ListView):
|
||||
class OrganizerList(ListView):
|
||||
model = Organizer
|
||||
context_object_name = 'organizers'
|
||||
template_name = 'pretixcontrol/organizers/index.html'
|
||||
paginate_by = 30
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Organizer.objects.all()
|
||||
|
||||
@@ -4,12 +4,12 @@ from django.views.generic import ListView
|
||||
|
||||
from pretix.base.models import Order
|
||||
from pretix.control.forms.filter import OrderSearchFilterForm
|
||||
from pretix.control.views import PaginationMixin
|
||||
|
||||
|
||||
class OrderSearch(PaginationMixin, ListView):
|
||||
class OrderSearch(ListView):
|
||||
model = Order
|
||||
context_object_name = 'orders'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/search/orders.html'
|
||||
|
||||
@cached_property
|
||||
|
||||
@@ -22,13 +22,13 @@ from pretix.control.forms.subevents import (
|
||||
SubEventItemVariationForm, SubEventMetaValueForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.control.views.event import MetaDataEditorMixin
|
||||
|
||||
|
||||
class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
class SubEventList(EventPermissionRequiredMixin, ListView):
|
||||
model = SubEvent
|
||||
context_object_name = 'subevents'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/subevents/index.html'
|
||||
permission = 'can_change_settings'
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import base64
|
||||
import logging
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.conf import settings
|
||||
@@ -20,8 +19,7 @@ from u2flib_server import u2f
|
||||
from u2flib_server.jsapi import DeviceRegistration
|
||||
|
||||
from pretix.base.forms.user import User2FADeviceAddForm, UserSettingsForm
|
||||
from pretix.base.models import Event, NotificationSetting, U2FDevice, User
|
||||
from pretix.base.notifications import get_all_notification_types
|
||||
from pretix.base.models import U2FDevice, User
|
||||
from pretix.control.views.auth import get_u2f_appid
|
||||
|
||||
REAL_DEVICE_TYPES = (TOTPDevice, U2FDevice)
|
||||
@@ -354,121 +352,3 @@ class User2FARegenerateEmergencyView(RecentAuthenticationRequiredMixin, Template
|
||||
messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe '
|
||||
'place in case you lose access to your devices.'))
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
|
||||
|
||||
class UserNotificationsDisableView(TemplateView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
user = get_object_or_404(User, notifications_token=kwargs.get('token'), pk=kwargs.get('id'))
|
||||
user.notifications_send = False
|
||||
user.save()
|
||||
messages.success(request, _('Your notifications have been disabled.'))
|
||||
|
||||
if request.user.is_authenticated:
|
||||
return redirect(
|
||||
reverse('control:user.settings.notifications')
|
||||
)
|
||||
else:
|
||||
return redirect(
|
||||
reverse('control:auth.login')
|
||||
)
|
||||
|
||||
|
||||
class UserNotificationsEditView(TemplateView):
|
||||
template_name = 'pretixcontrol/user/notifications.html'
|
||||
|
||||
@cached_property
|
||||
def event(self):
|
||||
if self.request.GET.get('event'):
|
||||
try:
|
||||
return self.request.user.get_events_with_any_permission().select_related(
|
||||
'organizer'
|
||||
).get(pk=self.request.GET.get('event'))
|
||||
except Event.DoesNotExist:
|
||||
return None
|
||||
return None
|
||||
|
||||
@cached_property
|
||||
def types(self):
|
||||
return get_all_notification_types(self.event)
|
||||
|
||||
@cached_property
|
||||
def currently_set(self):
|
||||
set_per_method = defaultdict(dict)
|
||||
for n in self.request.user.notification_settings.filter(event=self.event):
|
||||
set_per_method[n.method][n.action_type] = n.enabled
|
||||
return set_per_method
|
||||
|
||||
@cached_property
|
||||
def global_set(self):
|
||||
set_per_method = defaultdict(dict)
|
||||
for n in self.request.user.notification_settings.filter(event__isnull=True):
|
||||
set_per_method[n.method][n.action_type] = n.enabled
|
||||
return set_per_method
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if "notifications_send" in request.POST:
|
||||
request.user.notifications_send = request.POST.get("notifications_send", "") == "on"
|
||||
request.user.save()
|
||||
|
||||
messages.success(request, _('Your notification settings have been saved.'))
|
||||
if request.user.notifications_send:
|
||||
self.request.user.log_action('pretix.user.settings.notifications.disabled', user=self.request.user)
|
||||
else:
|
||||
self.request.user.log_action('pretix.user.settings.notifications.enabled', user=self.request.user)
|
||||
return redirect(
|
||||
reverse('control:user.settings.notifications') +
|
||||
('?event={}'.format(self.event.pk) if self.event else '')
|
||||
)
|
||||
else:
|
||||
for method, __ in NotificationSetting.CHANNELS:
|
||||
old_enabled = self.currently_set[method]
|
||||
|
||||
for at in self.types.keys():
|
||||
val = request.POST.get('{}:{}'.format(method, at))
|
||||
|
||||
# True → False
|
||||
if old_enabled.get(at) is True and val == 'off':
|
||||
self.request.user.notification_settings.filter(
|
||||
event=self.event, action_type=at, method=method
|
||||
).update(enabled=False)
|
||||
|
||||
# True/False → None
|
||||
if old_enabled.get(at) is not None and val == 'global':
|
||||
self.request.user.notification_settings.filter(
|
||||
event=self.event, action_type=at, method=method
|
||||
).delete()
|
||||
|
||||
# None → True/False
|
||||
if old_enabled.get(at) is None and val in ('on', 'off'):
|
||||
self.request.user.notification_settings.create(
|
||||
event=self.event, action_type=at, method=method, enabled=(val == 'on'),
|
||||
)
|
||||
|
||||
# False → True
|
||||
if old_enabled.get(at) is False and val == 'on':
|
||||
self.request.user.notification_settings.filter(
|
||||
event=self.event, action_type=at, method=method
|
||||
).update(enabled=True)
|
||||
|
||||
messages.success(request, _('Your notification settings have been saved.'))
|
||||
self.request.user.log_action('pretix.user.settings.notifications.changed', user=self.request.user)
|
||||
return redirect(
|
||||
reverse('control:user.settings.notifications') +
|
||||
('?event={}'.format(self.event.pk) if self.event else '')
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['events'] = self.request.user.get_events_with_any_permission().order_by('-date_from')
|
||||
ctx['types'] = [
|
||||
(
|
||||
tv,
|
||||
{k: a.get(t) for k, a in self.currently_set.items()},
|
||||
{k: a.get(t) for k, a in self.global_set.items()},
|
||||
)
|
||||
for t, tv in self.types.items()
|
||||
]
|
||||
ctx['event'] = self.event
|
||||
if self.event:
|
||||
ctx['permset'] = self.request.user.get_event_permission_set(self.event.organizer, self.event)
|
||||
return ctx
|
||||
|
||||
@@ -5,7 +5,7 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import resolve, reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import Exists, OuterRef, Q, Sum
|
||||
from django.db.models import Q, Sum
|
||||
from django.http import (
|
||||
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
|
||||
JsonResponse,
|
||||
@@ -16,17 +16,17 @@ from django.views.generic import (
|
||||
CreateView, DeleteView, ListView, TemplateView, UpdateView, View,
|
||||
)
|
||||
|
||||
from pretix.base.models import Checkin, Voucher
|
||||
from pretix.base.models import Voucher
|
||||
from pretix.base.models.vouchers import _generate_random_code
|
||||
from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import voucher_form_class
|
||||
from pretix.control.views import PaginationMixin
|
||||
|
||||
|
||||
class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||
class VoucherList(EventPermissionRequiredMixin, ListView):
|
||||
model = Voucher
|
||||
context_object_name = 'vouchers'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/vouchers/index.html'
|
||||
permission = 'can_view_vouchers'
|
||||
|
||||
@@ -46,13 +46,6 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||
qs = qs.filter(redeemed__gt=0)
|
||||
elif s == 'e':
|
||||
qs = qs.filter(Q(valid_until__isnull=False) & Q(valid_until__lt=now())).filter(redeemed=0)
|
||||
elif s == 'c':
|
||||
checkins = Checkin.objects.filter(
|
||||
position__voucher=OuterRef('pk')
|
||||
)
|
||||
qs = qs.annotate(has_checkin=Exists(checkins)).filter(
|
||||
redeemed__gt=0, has_checkin=True
|
||||
)
|
||||
if self.request.GET.get("subevent", "") != "":
|
||||
s = self.request.GET.get("subevent", "")
|
||||
qs = qs.filter(subevent_id=s)
|
||||
|
||||
@@ -15,7 +15,6 @@ from pretix.base.models.waitinglist import WaitingListException
|
||||
from pretix.base.services.waitinglist import assign_automatically
|
||||
from pretix.base.views.async import AsyncAction
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import PaginationMixin
|
||||
|
||||
|
||||
class AutoAssign(EventPermissionRequiredMixin, AsyncAction, View):
|
||||
@@ -40,9 +39,10 @@ class AutoAssign(EventPermissionRequiredMixin, AsyncAction, View):
|
||||
self.request.POST.get('subevent'))
|
||||
|
||||
|
||||
class WaitingListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
class WaitingListView(EventPermissionRequiredMixin, ListView):
|
||||
model = WaitingListEntry
|
||||
context_object_name = 'entries'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/waitinglist/index.html'
|
||||
permission = 'can_view_orders'
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-06 21:48+0000\n"
|
||||
"POT-Creation-Date: 2017-12-04 17:14+0000\n"
|
||||
"PO-Revision-Date: 2017-10-28 22:59+0200\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: \n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user