Compare commits

..

9 Commits

Author SHA1 Message Date
Raphael Michel
ae485a77d6 Bump version number 2017-12-16 18:17:08 +01:00
Raphael Michel
29e17d2284 Force upgrade to celery 4.1 2017-12-16 17:37:37 +01:00
Raphael Michel
6b95aca3f7 Fix line in docker service file 2017-12-16 17:37:30 +01:00
Raphael Michel
f227319f29 Fix annotated check-in list query 2017-12-16 17:37:25 +01:00
Raphael Michel
e63b85ccd6 Refs #705 -- Further try to workaround reportlab issue 2017-12-16 17:37:01 +01:00
Raphael Michel
bf3398e5f6 Fix a bug leading to loss of answers for addon-related questions 2017-12-16 17:36:47 +01:00
Raphael Michel
765d98464e Fix reverse ordering of lists 2017-12-16 17:36:34 +01:00
Raphael Michel
66bdf9ab79 Fix #702 -- Fix U2F support in Firefox 57 2017-12-16 17:36:27 +01:00
Raphael Michel
da830ef0a4 Fix markup in emails: Consistently allow <pre> in markdown content 2017-12-16 17:35:03 +01:00
148 changed files with 1604 additions and 4779 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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."

View File

@@ -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.

View File

@@ -1,7 +1,5 @@
.. highlight:: ini
.. spelling:: SQL
General remarks
===============

View File

@@ -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.

View File

@@ -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.

View File

@@ -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
===================================== ========================== =======================================================

View File

@@ -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"
}
],

View File

@@ -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.

View File

@@ -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)

View File

@@ -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]

View File

@@ -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

View File

@@ -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
""""""""""""

View File

@@ -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
-----------------------------

View File

@@ -1,8 +1,6 @@
.. highlight:: python
:linenothreshold: 5
.. spelling:: answ contrib
Data model
==========

View File

@@ -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/

View File

@@ -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.

View File

@@ -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
^^^^^^^^^^^^^^^^^^

View File

@@ -1,6 +1,3 @@
.. spelling::
Analytics
List of plugins
===============

View File

@@ -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**:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -8,7 +8,4 @@ Configuring an event
../payments/index
plugins
display
tickets
email
taxes
invoicing

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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>

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -1 +1 @@
__version__ = "1.11.1"
__version__ = "1.10.1"

View File

@@ -38,7 +38,7 @@ class AnswerSerializer(I18nAwareModelSerializer):
class CheckinSerializer(I18nAwareModelSerializer):
class Meta:
model = Checkin
fields = ('datetime', 'list')
fields = ('datetime',)
class OrderDownloadsField(serializers.Field):

View File

@@ -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)),
]

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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")

View File

@@ -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):

View File

@@ -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")

View File

@@ -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),
),
]

View File

@@ -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,

View File

@@ -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()

View File

@@ -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):

View File

@@ -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)

View File

@@ -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):
"""

View File

@@ -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

View 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.

View File

@@ -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',)

View File

@@ -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')

View File

@@ -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

View File

@@ -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,
)
)

View File

@@ -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.

View File

@@ -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': {},
})

View File

@@ -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'])

View File

@@ -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``.
"""

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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 }}

View File

@@ -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>

View File

@@ -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)

View File

@@ -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,

View File

@@ -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

View File

@@ -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.'),

View File

@@ -27,7 +27,6 @@ class PermissionMiddleware(MiddlewareMixin):
"auth.forgot",
"auth.forgot.recover",
"auth.invite",
"user.settings.notifications.off",
)
def _login_redirect(self, request):

View File

@@ -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.

View File

@@ -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">

View File

@@ -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 %}>

View File

@@ -68,6 +68,7 @@
</div>
</form>
</div>
{% include "pretixcontrol/pagination.html" %}
<div class="table-responsive">
<table class="table table-condensed table-hover">
<thead>

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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 %}
&nbsp;
<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> &nbsp;
<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> &nbsp;
<span class="label label-default">{% trans "Disabled" %}</span>
<a href="{% url "control:user.settings.2fa" %}">
{% trans "Enable" %}
</a>

View File

@@ -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">

View File

@@ -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'),

View File

@@ -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

View File

@@ -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.'))

View File

@@ -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'

View File

@@ -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):

View File

@@ -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):

View File

@@ -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(

View File

@@ -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()

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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