mirror of
https://github.com/pretix/pretix.git
synced 2025-12-06 21:42:49 +00:00
Compare commits
423 Commits
clean-docs
...
import-.wr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79ecaaa04b | ||
|
|
33462a7a6e | ||
|
|
2e674916c2 | ||
|
|
ac09b56a2c | ||
|
|
b415f8066f | ||
|
|
9af31cd29b | ||
|
|
c051623b3a | ||
|
|
8bb8c92e03 | ||
|
|
5c4451cfa3 | ||
|
|
d0116ab7dd | ||
|
|
02b5bdb321 | ||
|
|
ebfcb6f7c8 | ||
|
|
6190b93f89 | ||
|
|
40357681df | ||
|
|
a1d078b48e | ||
|
|
adca856cdb | ||
|
|
a811a31dcc | ||
|
|
177a7d07fc | ||
|
|
05e71d8e57 | ||
|
|
cd4759fb44 | ||
|
|
64c040c186 | ||
|
|
bc2cb7212d | ||
|
|
06dfba1a1e | ||
|
|
da718f3d24 | ||
|
|
2dc772cfcc | ||
|
|
d250fdf327 | ||
|
|
76fccb66bf | ||
|
|
3ecff6b8ed | ||
|
|
4bd1aba677 | ||
|
|
8853f2b189 | ||
|
|
bff0f54bf8 | ||
|
|
50c1c9c724 | ||
|
|
802268df46 | ||
|
|
a823f261f3 | ||
|
|
59a754f913 | ||
|
|
82eca01e5c | ||
|
|
943f594b6b | ||
|
|
15cbb3a416 | ||
|
|
f447e7b9c4 | ||
|
|
dcf473c543 | ||
|
|
85a9a3caa6 | ||
|
|
42b1010c36 | ||
|
|
5b851e270b | ||
|
|
2b796aa45e | ||
|
|
11460d878b | ||
|
|
1ce4c11572 | ||
|
|
11269c277b | ||
|
|
2650bf6f4f | ||
|
|
301191e4bd | ||
|
|
867cd8c59e | ||
|
|
7e8da3cef6 | ||
|
|
25f57f89b0 | ||
|
|
22f351cb89 | ||
|
|
2611ff74a5 | ||
|
|
cc1c7e1c23 | ||
|
|
e2eedac93b | ||
|
|
432064c3ae | ||
|
|
457115f4ca | ||
|
|
9d5563018e | ||
|
|
425f4da1f1 | ||
|
|
aa0ea27d6c | ||
|
|
5a2219124a | ||
|
|
f79813ea32 | ||
|
|
ba62db7a19 | ||
|
|
aa8b699b89 | ||
|
|
6adabd54dc | ||
|
|
e61f8035d3 | ||
|
|
fc4a9406e1 | ||
|
|
9d2ef94389 | ||
|
|
c060322f2f | ||
|
|
7629bbbb6a | ||
|
|
21d084c3fa | ||
|
|
b8d09a15e2 | ||
|
|
af2d35cc5a | ||
|
|
cbe18608e4 | ||
|
|
37d0a0de22 | ||
|
|
3bebdb3e28 | ||
|
|
4ad6a92f1d | ||
|
|
a34b6a04ea | ||
|
|
39e5711e95 | ||
|
|
6d422f9ae4 | ||
|
|
ccf4cbfd63 | ||
|
|
132a9aa9f2 | ||
|
|
257bd17b4a | ||
|
|
10bfd51d99 | ||
|
|
50225fd2f4 | ||
|
|
cd4fc1d6d8 | ||
|
|
4931059da3 | ||
|
|
15d15f978f | ||
|
|
34dbbdd82f | ||
|
|
9a54823515 | ||
|
|
4c76bb85a8 | ||
|
|
ed01a149b7 | ||
|
|
89adcc11c6 | ||
|
|
7037f348bf | ||
|
|
e694d3ca14 | ||
|
|
f3e1fd9135 | ||
|
|
850552c235 | ||
|
|
fb8a8142d9 | ||
|
|
5416c0cdfd | ||
|
|
a2421f9c66 | ||
|
|
8d06c79dd9 | ||
|
|
08961091f6 | ||
|
|
a7cbcb29b5 | ||
|
|
11fede5432 | ||
|
|
b8b89f3040 | ||
|
|
3b30553880 | ||
|
|
dd441c09f7 | ||
|
|
31b2841c4f | ||
|
|
baab35b81f | ||
|
|
c488901dc5 | ||
|
|
2679f79c3b | ||
|
|
ca3570df11 | ||
|
|
5bd08061a1 | ||
|
|
724c7d572f | ||
|
|
75dd98519f | ||
|
|
9bf4466732 | ||
|
|
ed9250c522 | ||
|
|
b3974067a5 | ||
|
|
cd6fbd886c | ||
|
|
0bb390f0a9 | ||
|
|
0183f3d40f | ||
|
|
82fcc4fe42 | ||
|
|
d42f8ece53 | ||
|
|
a8bffbd402 | ||
|
|
991b116026 | ||
|
|
2374d9b78c | ||
|
|
80785bee54 | ||
|
|
ea530ac6bf | ||
|
|
2dd8cc82f2 | ||
|
|
38fae12c37 | ||
|
|
e34a3ab2ce | ||
|
|
9401fbb1bc | ||
|
|
5d002d8b28 | ||
|
|
9b2c919026 | ||
|
|
e5ec1fd89a | ||
|
|
0f5c4b5cf5 | ||
|
|
c501066cff | ||
|
|
7ccb6682cf | ||
|
|
e5301dcdc5 | ||
|
|
4148cc4664 | ||
|
|
49057590f1 | ||
|
|
fc18659196 | ||
|
|
0c721c17e5 | ||
|
|
422567a6b7 | ||
|
|
0fcaeda0e9 | ||
|
|
ad8ed599dc | ||
|
|
4c2efa0a97 | ||
|
|
6efcd4b983 | ||
|
|
c29b7f28f1 | ||
|
|
871a8a2620 | ||
|
|
b7803565d6 | ||
|
|
f3b6627e63 | ||
|
|
574513550d | ||
|
|
f145d447a2 | ||
|
|
72b9b49b9d | ||
|
|
6d20d0e840 | ||
|
|
4a662a1aa1 | ||
|
|
8213b09847 | ||
|
|
c54f776b39 | ||
|
|
fdd03536f2 | ||
|
|
44303a0030 | ||
|
|
5ba10416ce | ||
|
|
efa117c836 | ||
|
|
70cd2265db | ||
|
|
b5afbfa1bf | ||
|
|
2dffe0e2c8 | ||
|
|
df0e0f9115 | ||
|
|
2fc47c5d71 | ||
|
|
c23d2e5504 | ||
|
|
58c7e3d316 | ||
|
|
2d5c3fbea6 | ||
|
|
222851620e | ||
|
|
9ac772b2f3 | ||
|
|
1408f31ec5 | ||
|
|
04f32284a8 | ||
|
|
318b80c3a5 | ||
|
|
102d172942 | ||
|
|
c084698821 | ||
|
|
edffe5c9dd | ||
|
|
09e9273a57 | ||
|
|
24ac588119 | ||
|
|
d23735b1a6 | ||
|
|
d8156186d8 | ||
|
|
abab7e5bc6 | ||
|
|
f89a33862a | ||
|
|
deb7cfa899 | ||
|
|
3f00fa58a0 | ||
|
|
49c0f6b967 | ||
|
|
fe9a7eaa24 | ||
|
|
ebac7d563c | ||
|
|
7ecc64ec73 | ||
|
|
c9a806a7d0 | ||
|
|
ab812a7d9c | ||
|
|
500bca1323 | ||
|
|
32be6a159e | ||
|
|
0152d0c639 | ||
|
|
e591c74862 | ||
|
|
29de29fe96 | ||
|
|
7bea17c70f | ||
|
|
f2b295e2a2 | ||
|
|
64c7bc67bd | ||
|
|
c41a754ce6 | ||
|
|
0bcb6b33bb | ||
|
|
1556226ff5 | ||
|
|
4c022cb964 | ||
|
|
8fb87fc489 | ||
|
|
c8775fb21a | ||
|
|
df0b322707 | ||
|
|
c200072471 | ||
|
|
076233cba8 | ||
|
|
b6efa9da7d | ||
|
|
489636c335 | ||
|
|
cbee131378 | ||
|
|
05c74b7ad6 | ||
|
|
37910f6037 | ||
|
|
0cc8e59bb0 | ||
|
|
7cdccc7d8e | ||
|
|
7e3f6df945 | ||
|
|
727ed67ff4 | ||
|
|
a51a6123f5 | ||
|
|
56964b6764 | ||
|
|
527bc83e5f | ||
|
|
626d7ecc90 | ||
|
|
69e50d35a7 | ||
|
|
32b704de70 | ||
|
|
1da00f575a | ||
|
|
b7d01e3b28 | ||
|
|
650b4b461f | ||
|
|
d14f7fb108 | ||
|
|
160f1c2e62 | ||
|
|
b9e627a86c | ||
|
|
328867c089 | ||
|
|
3e45274343 | ||
|
|
538ca9f0c2 | ||
|
|
99e10adad4 | ||
|
|
10b5f76356 | ||
|
|
39a0093c6b | ||
|
|
d8bf3d0b07 | ||
|
|
4e56ce8927 | ||
|
|
807df01f5d | ||
|
|
067e11c265 | ||
|
|
b4264c0ae7 | ||
|
|
61eff28978 | ||
|
|
4e89772c2d | ||
|
|
3212dd9b40 | ||
|
|
97c1fb9101 | ||
|
|
d5bccf8726 | ||
|
|
d768c46fa1 | ||
|
|
5a506bfbd6 | ||
|
|
3508d22591 | ||
|
|
4a6dd12884 | ||
|
|
60b906d8b7 | ||
|
|
4285612162 | ||
|
|
a3b1e4d208 | ||
|
|
3a6d7b8e92 | ||
|
|
a5d01aa2d1 | ||
|
|
89d8ca0fc2 | ||
|
|
34b656989f | ||
|
|
154f10af8f | ||
|
|
782d659c59 | ||
|
|
1b4308e101 | ||
|
|
9a119c35a8 | ||
|
|
a8ac1b1a94 | ||
|
|
6338dceb9e | ||
|
|
e4a171c11f | ||
|
|
e9edcfdfdc | ||
|
|
ef3ff52be3 | ||
|
|
a8f74d87ec | ||
|
|
6f920e6bcd | ||
|
|
a6201c841f | ||
|
|
b5ac28e36c | ||
|
|
bf5e1aeaff | ||
|
|
3f6d230c01 | ||
|
|
a4aa3cbd3b | ||
|
|
8ee90cd1c4 | ||
|
|
8d1e679a84 | ||
|
|
87f829f4d2 | ||
|
|
75dcb920a7 | ||
|
|
e68f0a7402 | ||
|
|
4255dbfb83 | ||
|
|
9def5cc7b2 | ||
|
|
17a467887c | ||
|
|
0736babf3c | ||
|
|
a5b773924c | ||
|
|
391918afe7 | ||
|
|
d8f9f9478d | ||
|
|
4d9f1a8efc | ||
|
|
23b07e29cd | ||
|
|
e1756a1ebb | ||
|
|
f5b0454e9f | ||
|
|
724a109c52 | ||
|
|
96df3d6831 | ||
|
|
dc164f7817 | ||
|
|
61ff0a767a | ||
|
|
423f0cbb90 | ||
|
|
200d520535 | ||
|
|
e2ae553c69 | ||
|
|
3ddf759a1b | ||
|
|
614a086227 | ||
|
|
35583f30bb | ||
|
|
38be6d13da | ||
|
|
6a8ec1ec7f | ||
|
|
0b799b132d | ||
|
|
0dd66f9468 | ||
|
|
149f1ee871 | ||
|
|
ec60ea9603 | ||
|
|
04e92e9f2f | ||
|
|
14d6013292 | ||
|
|
415bff5c72 | ||
|
|
582c6c1771 | ||
|
|
13833b05b1 | ||
|
|
a381adac33 | ||
|
|
177b9cdcbb | ||
|
|
a5f7f2bd0c | ||
|
|
6bc88b3c0d | ||
|
|
d7759f7eab | ||
|
|
1aeaa39882 | ||
|
|
1e62d06f2d | ||
|
|
a90b40035c | ||
|
|
1c79e06af8 | ||
|
|
fda8c8bc37 | ||
|
|
3f11f351b8 | ||
|
|
43cc4333a6 | ||
|
|
e1821f1bb7 | ||
|
|
4514701d1b | ||
|
|
08baf0ee32 | ||
|
|
08bbdbbd97 | ||
|
|
25cd84c459 | ||
|
|
7177ac18f7 | ||
|
|
2788ba10fe | ||
|
|
19a7042c16 | ||
|
|
14ed6982a5 | ||
|
|
090358833d | ||
|
|
f0212d910d | ||
|
|
a4c74f6310 | ||
|
|
f66a41f6a7 | ||
|
|
1a990dfecc | ||
|
|
74ac6ab102 | ||
|
|
eb912f1e22 | ||
|
|
fc7d0025ab | ||
|
|
e58e1187d0 | ||
|
|
436960ff76 | ||
|
|
e796dc3a65 | ||
|
|
545625b732 | ||
|
|
9bf302e5ae | ||
|
|
0c7c50cffc | ||
|
|
2c094f4c30 | ||
|
|
e820424bdf | ||
|
|
cb3d88a923 | ||
|
|
530ce06155 | ||
|
|
9017128513 | ||
|
|
5d3fc62ba4 | ||
|
|
243db008e1 | ||
|
|
5ea9f819e6 | ||
|
|
a5eb009e55 | ||
|
|
5129ed3846 | ||
|
|
f51906338f | ||
|
|
d67e1116f4 | ||
|
|
f6df03c427 | ||
|
|
308eac20b2 | ||
|
|
ab3c03b278 | ||
|
|
161404f152 | ||
|
|
8b119b329c | ||
|
|
512ca1966d | ||
|
|
90ec82ea1a | ||
|
|
d55f411989 | ||
|
|
40855e14d9 | ||
|
|
7bb2e4c170 | ||
|
|
dec07b2df1 | ||
|
|
9fc9aaa661 | ||
|
|
70f71c8077 | ||
|
|
dc198d4ab6 | ||
|
|
fdbb03d038 | ||
|
|
8418d03add | ||
|
|
b5f8438c18 | ||
|
|
5420f57aa2 | ||
|
|
b5e20df508 | ||
|
|
eba5c1b36d | ||
|
|
7d30ecf527 | ||
|
|
2359307462 | ||
|
|
325f7c565d | ||
|
|
df48adef1b | ||
|
|
74cea09f6c | ||
|
|
e8abe5cad8 | ||
|
|
6c9f66487d | ||
|
|
5f828127bf | ||
|
|
c5b3093f20 | ||
|
|
ae4073b3e4 | ||
|
|
362ac8de6f | ||
|
|
cced9cd768 | ||
|
|
dfb45e13ca | ||
|
|
23489f50f8 | ||
|
|
80148a8435 | ||
|
|
9f49b7747c | ||
|
|
b75f8bf893 | ||
|
|
d53af424cf | ||
|
|
24c02751cc | ||
|
|
2f7a00e660 | ||
|
|
767b01be9a | ||
|
|
f9acefc0f9 | ||
|
|
234a3d0db1 | ||
|
|
b7228ff5b8 | ||
|
|
053c713a2a | ||
|
|
6959dca7c1 | ||
|
|
87312c9d8a | ||
|
|
4b697b9244 | ||
|
|
cc55aba2e6 | ||
|
|
fbbc6502f3 | ||
|
|
62b3af2197 | ||
|
|
177717d594 | ||
|
|
2f2991105a | ||
|
|
d03af3ce06 | ||
|
|
6b95bfbc96 | ||
|
|
0f4d5b639d | ||
|
|
53ebee37fe | ||
|
|
572973b5c0 | ||
|
|
ab72abea0a | ||
|
|
c53fc8df4e | ||
|
|
87fb3d2df8 | ||
|
|
6aa3747403 | ||
|
|
d255c40a0b | ||
|
|
f600200ec6 |
@@ -1,7 +1,7 @@
|
||||
This file is part of pretix (Community Edition).
|
||||
|
||||
Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
Copyright (C) 2020-today pretix GmbH and contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -8,6 +8,7 @@ pretix
|
||||
:target: https://docs.pretix.eu/
|
||||
|
||||
.. image:: https://github.com/pretix/pretix/workflows/Tests/badge.svg
|
||||
:target: https://github.com/pretix/pretix/actions/workflows/tests.yml
|
||||
|
||||
.. image:: https://codecov.io/gh/pretix/pretix/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/pretix/pretix
|
||||
|
||||
@@ -48,11 +48,6 @@ seat objects The assigned se
|
||||
└ seat_guid string Identifier of the seat within the seating plan
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.14
|
||||
|
||||
This ``is_bundled`` attribute has been added and the cart creation endpoints have been updated.
|
||||
|
||||
|
||||
Cart position endpoints
|
||||
-----------------------
|
||||
|
||||
|
||||
@@ -9,14 +9,6 @@ This page describes special APIs built for ticket scanning apps. For managing ch
|
||||
please also see :ref:`rest-checkinlists`. The check-in list API also contains endpoints to obtain statistics or log
|
||||
failed scans.
|
||||
|
||||
.. versionchanged:: 4.12
|
||||
|
||||
The endpoints listed on this page have been added.
|
||||
|
||||
.. versionchanged:: 4.18
|
||||
|
||||
The ``source_type`` parameter has been added.
|
||||
|
||||
.. _`rest-checkin-redeem`:
|
||||
|
||||
Checking a ticket in
|
||||
@@ -367,3 +359,65 @@ Performing a ticket search
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or check-in list does not exist **or** you have no permission to view this resource.
|
||||
:statuscode 404: The requested check-in list does not exist.
|
||||
|
||||
.. _`rest-checkin-annul`:
|
||||
|
||||
Annulment of a check-in
|
||||
-----------------------
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/checkinrpc/annul/
|
||||
|
||||
If a check-in was made in error and the person was not let in, it can be annulled. We do not recommend this to be used
|
||||
in case of manual check-ins or user interfaces because it is too prone for human errors. It is mostly intended for
|
||||
automated entry systems like a turnstile or automated door, where the check-in is first created, then the door is
|
||||
opened, and then the check-in may be annulled if the system knows that the turnstile did not turn or was out of
|
||||
order.
|
||||
|
||||
This endpoint supports passing multiple check-in lists for the context of a multi-event scan. However, each
|
||||
check-in list passed needs to be from a distinct event.
|
||||
|
||||
Check-ins created by a device can only be annulled by the same device. The datetime of annulment may not be more than
|
||||
15 minutes after the datetime of check-in (value subject to change).
|
||||
|
||||
A status code of 404 is returned if no check-in was found for the given nonce. A status code of 400 is returned when
|
||||
multiple check-ins match the nonce, the input is invalid in another way, the annulment is made from the wrong device,
|
||||
the check-in is already in an annulled or failed state, or the datetime constraint is not valid.
|
||||
|
||||
:<json string nonce: ``nonce`` value of the original check-in.
|
||||
:<json array lists: List of check-in list IDs to search on. No two check-in lists may be from the same event.
|
||||
:<json datetime datetime: Specifies the client-side datetime of the annulment. If not supplied, the current time will be used.
|
||||
:<json string error_explanation: A human-readable description of why the check-in was annulled (optional).
|
||||
:>json string status: ``"ok"``
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/checkinrpc/annul/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
{
|
||||
"lists": [1],
|
||||
"nonce": "Pvrk50vUzQd0DhdpNRL4I4OcXsvg70uA",
|
||||
"error_explanation": "Turnstile did not turn"
|
||||
}
|
||||
|
||||
**Example successful response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "ok",
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: Invalid or incomplete request, see above
|
||||
: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 nonce does not exist.
|
||||
|
||||
@@ -40,10 +40,6 @@ ignore_in_statistics boolean If ``true``, ch
|
||||
consider_tickets_used boolean If ``true`` (default), tickets checked in on this list will be considered "used" by other functionality, i.e. when checking if they can still be canceled.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.12
|
||||
|
||||
The ``addon_match`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 2023.9
|
||||
|
||||
The ``ignore_in_statistics`` and ``consider_tickets_used`` attributes have been added.
|
||||
|
||||
@@ -34,12 +34,6 @@ password string Can only be set
|
||||
not be included in any responses.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionadded:: 4.0
|
||||
|
||||
.. versionchanged:: 4.3
|
||||
|
||||
Passwords can now be set through the API during customer creation.
|
||||
|
||||
.. versionchanged:: 2024.3
|
||||
|
||||
The attribute ``phone`` has been added.
|
||||
|
||||
@@ -61,25 +61,6 @@ public_url string The public, cus
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
|
||||
The ``clone_from`` parameter has been added to the event creation endpoint.
|
||||
|
||||
.. versionchanged:: 4.1
|
||||
|
||||
The ``with_availability_for`` parameter has been added.
|
||||
|
||||
The ``search`` query parameter has been added to filter events by their slug, name, or location in any language.
|
||||
|
||||
.. versionchanged:: 4.17
|
||||
|
||||
The ``public_url`` field has been added.
|
||||
|
||||
.. versionchanged:: 5.0
|
||||
|
||||
The ``date_from_before``, ``date_from_after``, ``date_to_before``, and ``date_to_after`` query parameters have been
|
||||
added.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/
|
||||
|
||||
Returns a list of all events within a given organizer the authenticated user/token has access to.
|
||||
@@ -443,9 +424,9 @@ Endpoints
|
||||
:param organizer: The ``slug`` field of the organizer of the event to create.
|
||||
:param event: The ``slug`` field of the event to copy settings and items from.
|
||||
:statuscode 201: no error
|
||||
:statuscode 400: The event could not be created due to invalid submitted data.
|
||||
:statuscode 400: The event could not be updated due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to update this resource.
|
||||
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/
|
||||
@@ -630,10 +611,6 @@ organizer level.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
|
||||
.. versionchanged:: 4.18
|
||||
|
||||
The ``readonly`` flag has been added.
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/settings/
|
||||
|
||||
Updates event settings. Note that ``PUT`` is not allowed here, only ``PATCH``.
|
||||
|
||||
@@ -349,6 +349,45 @@ Endpoints
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event or exhibitor does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/exhibitors/(id)/vouchers/bulk_attach/
|
||||
|
||||
Attaches many **existing** vouchers to an exhibitor. You need to send either the ``id`` **or** the ``code`` field of
|
||||
the voucher, but you need to send the same field for all entries.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/exhibitors/1/vouchers/bulk_attach/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
[
|
||||
{
|
||||
"id": 15,
|
||||
"exhibitor_comment": "Free ticket"
|
||||
},
|
||||
..
|
||||
]
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{}
|
||||
|
||||
:param organizer: The ``slug`` field of a valid organizer
|
||||
:param event: The ``slug`` field of the event to use
|
||||
:param id: The ``id`` field of the exhibitor to use
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: Invalid data sent, e.g. voucher does not exist
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event or exhibitor does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/exhibitors/
|
||||
|
||||
Create a new exhibitor.
|
||||
|
||||
@@ -47,11 +47,6 @@ acceptor string Organizer slug
|
||||
this field was added.)
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.20
|
||||
|
||||
The ``owner_ticket`` and ``issuer`` attributes of the gift card and the ``info`` and ``acceptor`` attributes of the
|
||||
gift card transaction resource have been added.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ at :ref:`plugin-docs`.
|
||||
seats
|
||||
orders
|
||||
invoices
|
||||
transactions
|
||||
vouchers
|
||||
discounts
|
||||
checkin
|
||||
@@ -54,6 +55,7 @@ at :ref:`plugin-docs`.
|
||||
digital
|
||||
exhibitors
|
||||
imported_secrets
|
||||
offlinesales
|
||||
shipping
|
||||
billing_invoices
|
||||
billing_var
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
.. _rest-invoices:
|
||||
|
||||
Invoices
|
||||
========
|
||||
|
||||
@@ -24,6 +26,8 @@ invoice_from_country string Sender address:
|
||||
invoice_from_tax_id string Sender address: Local Tax ID
|
||||
invoice_from_vat_id string Sender address: EU VAT ID
|
||||
invoice_to string Full recipient address
|
||||
invoice_to_is_business boolean Recipient address: Business vs individual (``null`` for
|
||||
invoices created before pretix 2025.6).
|
||||
invoice_to_company string Recipient address: Company name
|
||||
invoice_to_name string Recipient address: Person name
|
||||
invoice_to_street string Recipient address: Address lines
|
||||
@@ -33,6 +37,7 @@ invoice_to_state string Recipient addre
|
||||
invoice_to_country string Recipient address: Country code
|
||||
invoice_to_vat_id string Recipient address: EU VAT ID
|
||||
invoice_to_beneficiary string Invoice beneficiary
|
||||
invoice_to_transmission_info object Additional transmission info (see :ref:`rest-transmission-types`)
|
||||
custom_field string Custom invoice address field
|
||||
date date Invoice date
|
||||
refers string Invoice number of an invoice this invoice refers to
|
||||
@@ -75,17 +80,12 @@ lines list of objects The actual invo
|
||||
for all invoice lines
|
||||
created before this field was introduced as well as for
|
||||
all lines not created by a fee (e.g. a product).
|
||||
├ event_date_from datetime Start date of the (sub)event this line was created for as it
|
||||
was set during invoice creation. Can be ``null`` for all invoice
|
||||
lines created before this was introduced as well as for lines in
|
||||
an event series not created by a product (e.g. shipping or
|
||||
cancellation fees).
|
||||
├ event_date_to datetime End date of the (sub)event this line was created for as it
|
||||
was set during invoice creation. Can be ``null`` for all invoice
|
||||
lines created before this was introduced as well as for lines in
|
||||
an event series not created by a product (e.g. shipping or
|
||||
cancellation fees) as well as whenever the respective (sub)event
|
||||
has no end date set.
|
||||
├ period_start datetime Start date of the service or delivery period of the invoice line.
|
||||
Can be ``null`` if not known.
|
||||
├ period_end datetime End date of the service or delivery period of the invoice line.
|
||||
Can be ``null`` if not known.
|
||||
├ event_date_from datetime Deprecated alias of ``period_start``.
|
||||
├ event_date_to datetime Deprecated alias of ``period_end``.
|
||||
├ event_location string Location of the (sub)event this line was created for as it
|
||||
was set during invoice creation. Can be ``null`` for all invoice
|
||||
lines created before this was introduced as well as for lines in
|
||||
@@ -108,21 +108,15 @@ foreign_currency_rate decimal (string) If ``foreign_cu
|
||||
foreign_currency_rate_date date If ``foreign_currency_rate`` is set, this signifies the
|
||||
date at which the currency rate was obtained.
|
||||
internal_reference string Customer's reference to be printed on the invoice.
|
||||
transmission_type string Requested transmission channel (see :ref:`rest-transmission-types`)
|
||||
transmission_provider string Selected transmission provider (depends on installed
|
||||
plugins). ``null`` if not yet chosen.
|
||||
transmission_status string Transmission status, one of ``unknown`` (pre-2025.6),
|
||||
``pending``, ``inflight``, ``failed``, and ``completed``.
|
||||
transmission_date datetime Time of last change in transmission status (may be ``null``).
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
.. versionchanged:: 4.1
|
||||
|
||||
The attributes ``fee_type`` and ``fee_internal_type`` have been added.
|
||||
|
||||
.. versionchanged:: 4.1
|
||||
|
||||
The attribute ``lines.event_location`` has been added.
|
||||
|
||||
.. versionchanged:: 4.6
|
||||
|
||||
The attribute ``lines.subevent`` has been added.
|
||||
|
||||
.. versionchanged:: 2023.8
|
||||
|
||||
The ``event`` attribute has been added. The organizer-level endpoint has been added.
|
||||
@@ -131,6 +125,76 @@ internal_reference string Customer's refe
|
||||
|
||||
The ``tax_code`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 2025.6
|
||||
|
||||
The attributes ``invoice_to_is_business``, ``invoice_to_transmission_info``, ``transmission_type``,
|
||||
``transmission_provider``, ``transmission_status``, and ``transmission_date`` have been added.
|
||||
|
||||
|
||||
.. _`rest-transmission-types`:
|
||||
|
||||
Transmission types
|
||||
------------------
|
||||
|
||||
pretix supports multiple ways to transmit an invoice from the organizer to the invoice recipient.
|
||||
For each transmission type, different fields are supported in the ``transmission_info`` object of the
|
||||
invoice address. Currently, pretix supports the following transmission types:
|
||||
|
||||
Email
|
||||
"""""
|
||||
|
||||
The identifier ``"email"`` represents the transmission of PDF invoices through email.
|
||||
This is the default transmission type in pretix and has some special behavior for backwards compatibility.
|
||||
Transmission is always executed through the provider ``"email_pdf"``.
|
||||
The ``transmission_info`` object may contain the following properties:
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
transmission_email_address string Optional. An email address other than the order address
|
||||
that the invoice should be sent to.
|
||||
Business customers only.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
Peppol
|
||||
""""""
|
||||
|
||||
The identifier ``"peppol"`` represents the transmission of XML invoices through the `Peppol`_ network.
|
||||
This is only available for business addresses.
|
||||
This is not supported by pretix out of the box and requires the use of a suitable plugin.
|
||||
The ``transmission_info`` object may contain the following properties:
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
transmission_peppol_participant_id string Required. The Peppol participant ID of the recipient.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
Italian Exchange System
|
||||
"""""""""""""""""""""""
|
||||
|
||||
The identifier ``"it_sdi"`` represents the transmission of XML invoices through the `Sistema di Interscambio`_ network used in Italy.
|
||||
This is only available for addresses with country ``"IT"``.
|
||||
This is not supported by pretix out of the box and requires the use of a suitable plugin.
|
||||
The ``transmission_info`` object may contain the following properties:
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
transmission_it_sdi_codice_fiscale string Required for non-business address. Fiscal code of the
|
||||
recipient.
|
||||
transmission_it_sdi_pec string Required for business addresses. Address for certified
|
||||
electronic mail.
|
||||
transmission_it_sdi_recipient_code string Required for businesses. SdI recipient code.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
If this type is selected, ``vat_id`` is required for business addresses.
|
||||
|
||||
List of all invoices
|
||||
--------------------
|
||||
@@ -174,6 +238,7 @@ List of all invoices
|
||||
"invoice_from_vat_id":"",
|
||||
"invoice_to": "Sample company\nJohn Doe\nTest street 12\n12345 Testington\nTestikistan\nVAT-ID: EU123456789",
|
||||
"invoice_to_company": "Sample company",
|
||||
"invoice_to_is_business": true,
|
||||
"invoice_to_name": "John Doe",
|
||||
"invoice_to_street": "Test street 12",
|
||||
"invoice_to_zipcode": "12345",
|
||||
@@ -182,6 +247,7 @@ List of all invoices
|
||||
"invoice_to_country": "TE",
|
||||
"invoice_to_vat_id": "EU123456789",
|
||||
"invoice_to_beneficiary": "",
|
||||
"invoice_to_transmission_info": {},
|
||||
"custom_field": null,
|
||||
"date": "2017-12-01",
|
||||
"refers": null,
|
||||
@@ -203,6 +269,8 @@ List of all invoices
|
||||
"fee_internal_type": null,
|
||||
"event_date_from": "2017-12-27T10:00:00Z",
|
||||
"event_date_to": null,
|
||||
"period_start": "2017-12-27T10:00:00Z",
|
||||
"period_end": "2017-12-27T10:00:00Z",
|
||||
"event_location": "Heidelberg",
|
||||
"attendee_name": null,
|
||||
"gross_value": "23.00",
|
||||
@@ -214,7 +282,11 @@ List of all invoices
|
||||
],
|
||||
"foreign_currency_display": "PLN",
|
||||
"foreign_currency_rate": "4.2408",
|
||||
"foreign_currency_rate_date": "2017-07-24"
|
||||
"foreign_currency_rate_date": "2017-07-24",
|
||||
"transmission_type": "email",
|
||||
"transmission_provider": "email_pdf",
|
||||
"transmission_status": "completed",
|
||||
"transmission_date": "2017-07-24T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -314,6 +386,7 @@ Fetching individual invoices
|
||||
"invoice_from_vat_id":"",
|
||||
"invoice_to": "Sample company\nJohn Doe\nTest street 12\n12345 Testington\nTestikistan\nVAT-ID: EU123456789",
|
||||
"invoice_to_company": "Sample company",
|
||||
"invoice_to_is_business": true,
|
||||
"invoice_to_name": "John Doe",
|
||||
"invoice_to_street": "Test street 12",
|
||||
"invoice_to_zipcode": "12345",
|
||||
@@ -322,6 +395,7 @@ Fetching individual invoices
|
||||
"invoice_to_country": "TE",
|
||||
"invoice_to_vat_id": "EU123456789",
|
||||
"invoice_to_beneficiary": "",
|
||||
"invoice_to_transmission_info": {},
|
||||
"custom_field": null,
|
||||
"date": "2017-12-01",
|
||||
"refers": null,
|
||||
@@ -343,6 +417,8 @@ Fetching individual invoices
|
||||
"fee_internal_type": null,
|
||||
"event_date_from": "2017-12-27T10:00:00Z",
|
||||
"event_date_to": null,
|
||||
"period_start": "2017-12-27T10:00:00Z",
|
||||
"period_end": "2017-12-27T10:00:00Z",
|
||||
"event_location": "Heidelberg",
|
||||
"attendee_name": null,
|
||||
"gross_value": "23.00",
|
||||
@@ -354,7 +430,11 @@ Fetching individual invoices
|
||||
],
|
||||
"foreign_currency_display": "PLN",
|
||||
"foreign_currency_rate": "4.2408",
|
||||
"foreign_currency_rate_date": "2017-07-24"
|
||||
"foreign_currency_rate_date": "2017-07-24",
|
||||
"transmission_type": "email",
|
||||
"transmission_provider": "email_pdf",
|
||||
"transmission_status": "completed",
|
||||
"transmission_date": "2017-07-24T10:00:00Z"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -459,3 +539,70 @@ Invoices cannot be edited directly, but the following actions can be triggered:
|
||||
:statuscode 400: The invoice has already been canceled
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
|
||||
|
||||
|
||||
Transmitting invoices
|
||||
---------------------
|
||||
|
||||
Invoices are transmitted automatically when created during order creation or payment receipt,
|
||||
but in other cases transmission may need to be triggered manually.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/transmit/
|
||||
|
||||
Transmits the invoice to the recipient, but only if it is in ``pending`` state.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/00001/transmit/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Vary: Accept
|
||||
Content-Type: application/pdf
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param number: The ``number`` field of the invoice to transmit
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to transmit this invoice **or** the invoice may not be transmitted
|
||||
:statuscode 409: The invoice is currently in transmission
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/retransmit/
|
||||
|
||||
Transmits the invoice to the recipient even if transmission was already attempted previously.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/00001/retransmit/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Vary: Accept
|
||||
Content-Type: application/pdf
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param number: The ``number`` field of the invoice to transmit
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to transmit this invoice **or** the invoice may not be transmitted
|
||||
:statuscode 409: The invoice is currently in transmission
|
||||
|
||||
|
||||
.. _Peppol: https://en.wikipedia.org/wiki/PEPPOL
|
||||
.. _Sistema di Interscambio: https://it.wikipedia.org/wiki/Fattura_elettronica_in_Italia
|
||||
@@ -64,10 +64,6 @@ hide_without_voucher boolean If ``true``, th
|
||||
meta_data object Values set for event-specific meta data parameters.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The ``meta_data`` and ``checkin_attention`` attributes have been added.
|
||||
|
||||
.. versionchanged:: 2023.10
|
||||
|
||||
The ``free_price_suggestion`` attribute has been added.
|
||||
|
||||
@@ -211,28 +211,6 @@ bundles list of objects Definition of
|
||||
meta_data object Values set for event-specific meta data parameters.
|
||||
======================================= ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
|
||||
The attributes ``require_membership``, ``require_membership_types``, ``grant_membership_type``, ``grant_membership_duration_like_event``,
|
||||
``grant_membership_duration_days`` and ``grant_membership_duration_months`` have been added.
|
||||
|
||||
.. versionchanged:: 4.4
|
||||
|
||||
The attributes ``require_membership_hidden`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The ``variations[x].meta_data`` and ``variations[x].checkin_attention`` attributes have been added.
|
||||
The ``personalized`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 4.17
|
||||
|
||||
The ``validity_*`` attributes have been added.
|
||||
|
||||
.. versionchanged:: 4.18
|
||||
|
||||
The ``media_policy`` and ``media_type`` attributes have been added.
|
||||
|
||||
.. versionchanged:: 2023.10
|
||||
|
||||
The ``checkin_text`` and ``variations[x].checkin_text`` attributes have been added.
|
||||
|
||||
219
doc/api/resources/offlinesales.rst
Normal file
219
doc/api/resources/offlinesales.rst
Normal file
@@ -0,0 +1,219 @@
|
||||
Offline sales
|
||||
=============
|
||||
|
||||
.. note:: This API is only available when the plugin **pretix-offlinesales** is installed (pretix Hosted and Enterprise only).
|
||||
|
||||
The offline sales module allows you to create batches of tickets intended for the sale outside the system.
|
||||
|
||||
Resource description
|
||||
--------------------
|
||||
|
||||
The offline sales batch resource contains the following public fields:
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal batch ID
|
||||
creation datetime Time of creation
|
||||
testmode boolean ``true`` if orders are created in test mode
|
||||
sales_channel string Sales channel of the orders
|
||||
layout integer Internal ID of the chosen ticket layout
|
||||
subevent integer Internal ID of the chosen subevent (or ``null``)
|
||||
item integer Internal ID of the chosen product
|
||||
variation integer Internal ID of the chosen variation (or ``null``)
|
||||
amount integer Number of tickets in the batch
|
||||
comment string Internal comment
|
||||
orders list of strings List of order codes (omitted in list view for performance reasons)
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/offlinesalesbatches/
|
||||
|
||||
Returns a list of all offline sales batches
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/democon/offlinesalesbatches/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"creation": "2025-07-08T18:27:32.134368+02:00",
|
||||
"testmode": False,
|
||||
"sales_channel": "web",
|
||||
"comment": "Batch for sale at the event",
|
||||
"layout": 3,
|
||||
"subevent": null,
|
||||
"item": 23,
|
||||
"variation": null,
|
||||
"amount": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:query page: The page number in case of a multi-page result set, default is 1
|
||||
:param organizer: The ``slug`` field of a valid organizer
|
||||
:param event: The ``slug`` field of a valid event
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/offlinesalesbatches/(id)/
|
||||
|
||||
Returns information on a given batch.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/democon/offlinesalesbatches/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"creation": "2025-07-08T18:27:32.134368+02:00",
|
||||
"testmode": False,
|
||||
"sales_channel": "web",
|
||||
"comment": "Batch for sale at the event",
|
||||
"layout": 3,
|
||||
"subevent": null,
|
||||
"item": 23,
|
||||
"variation": null,
|
||||
"amount": 7,
|
||||
"orders": ["TSRNN", "3FBSL", "WMDNJ", "BHW9H", "MXSUG", "DSDAP", "URLLE"]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param id: The ``id`` field of the batch 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 it.
|
||||
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/offlinesalesbatches/
|
||||
|
||||
With this API call, you can instruct the system to create a new batch.
|
||||
|
||||
Since batches can contain up to 10,000 tickets, they are created asynchronously on the server.
|
||||
If your input parameters validate correctly, a ``202 Accepted`` status code is returned.
|
||||
The body points you to the check URL of the result. Running a ``GET`` request on that result URL will
|
||||
yield one of the following status codes:
|
||||
|
||||
* ``200 OK`` – The creation of the batch has succeeded. The body will be your resulting batch with the same information as in the detail endpoint above.
|
||||
* ``409 Conflict`` – Your creation job is still running. The body will be JSON with the structure ``{"status": "running"}``. ``status`` can be ``waiting`` before the task is actually being processed. Please retry, but wait at least one second before you do.
|
||||
* ``410 Gone`` – Creating the batch has failed permanently (e.g. quota no longer available). The body will be JSON with the structure ``{"status": "failed", "message": "Error message"}``
|
||||
* ``404 Not Found`` – The job does not exist / is expired.
|
||||
|
||||
.. note:: To avoid performance issues, a maximum amount of 10000 is currently allowed.
|
||||
|
||||
.. note:: Do not wait multiple hours or more to retrieve your result. After a longer wait time, ``409`` might be returned permanently due to technical constraints, even though nothing will happen any more.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/offlinesalesbatches/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"testmode": True,
|
||||
"layout": 123,
|
||||
"item": 14,
|
||||
"sales_channel": "web",
|
||||
"amount": 10,
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"check": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/offlinesalesbatches/check/29891ede-196f-4942-9e26-d055a36e98b8/"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:statuscode 202: no error
|
||||
:statuscode 400: Invalid input options
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/offlinesalesbatches/(id)/render/
|
||||
|
||||
With this API call, you can render the PDF representation of a batch.
|
||||
|
||||
Since batches can contain up to 10,000 tickets, they are rendered asynchronously on the server.
|
||||
If your input parameters validate correctly, a ``202 Accepted`` status code is returned.
|
||||
The body points you to the download URL of the result. Running a ``GET`` request on that result URL will
|
||||
yield one of the following status codes:
|
||||
|
||||
* ``200 OK`` – The creation of the batch has succeeded. The body will be your resulting batch with the same information as in the detail endpoint above.
|
||||
* ``409 Conflict`` – Your rendering process is still running. The body will be JSON with the structure ``{"status": "running"}``. ``status`` can be ``waiting`` before the task is actually being processed. Please retry, but wait at least one second before you do.
|
||||
* ``410 Gone`` – Rendering the batch has failed permanently. The body will be JSON with the structure ``{"status": "failed", "message": "Error message"}``
|
||||
* ``404 Not Found`` – The rendering job does not exist / is expired.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/offlinesalesbatches/1/render 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
|
||||
|
||||
{
|
||||
"download": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/offlinesalesbatches/1/download/29891ede-196f-4942-9e26-d055a36e98b8/3f279f13-c198-4137-b49b-9b360ce9fcce/"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param id: The ``id`` field of the batch to fetch
|
||||
:statuscode 202: no error
|
||||
:statuscode 400: Invalid input options
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
|
||||
@@ -65,11 +65,16 @@ invoice_address object Invoice address
|
||||
├ state string Customer state (ISO 3166-2 code). Only supported in
|
||||
AU, BR, CA, CN, MY, MX, and US.
|
||||
├ internal_reference string Customer's internal reference to be printed on the invoice
|
||||
|
||||
├ custom_field string Custom invoice address field
|
||||
├ vat_id string Customer VAT ID
|
||||
└ vat_id_validated string ``true``, if the VAT ID has been validated against the
|
||||
├ vat_id_validated string ``true``, if the VAT ID has been validated against the
|
||||
EU VAT service and validation was successful. This only
|
||||
happens in rare cases.
|
||||
├ transmission_type string Transmission channel for invoice (see also :ref:`rest-transmission-types`).
|
||||
Defaults to ``email``.
|
||||
└ transmission_info object Transmission-channel specific information (or ``null``).
|
||||
See also :ref:`rest-transmission-types`.
|
||||
positions list of objects List of order positions (see below). By default, only
|
||||
non-canceled positions are included.
|
||||
fees list of objects List of fees included in the order total. By default, only
|
||||
@@ -114,34 +119,6 @@ plugin_data object Additional data
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
|
||||
The ``customer`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 4.1
|
||||
|
||||
The ``custom_followup_at`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 4.4
|
||||
|
||||
The ``item`` and ``variation`` query parameters have been added.
|
||||
|
||||
.. versionchanged:: 4.6
|
||||
|
||||
The ``subevent`` query parameters has been added.
|
||||
|
||||
.. versionchanged:: 4.8
|
||||
|
||||
The ``order.fees.id`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 4.15
|
||||
|
||||
The ``include`` query parameter has been added.
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The ``valid_if_pending`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 2023.8
|
||||
|
||||
The ``event`` attribute has been added. The organizer-level endpoint has been added.
|
||||
@@ -170,6 +147,10 @@ plugin_data object Additional data
|
||||
|
||||
The ``plugin_data`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 2025.6
|
||||
|
||||
The ``invoice_address.transmission_type`` and ``invoice_address.transmission_info`` attributes have been added.
|
||||
|
||||
.. _order-position-resource:
|
||||
|
||||
Order position resource
|
||||
@@ -260,10 +241,6 @@ pdf_data object Data object req
|
||||
plugin_data object Additional data added by plugins.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The attributes ``blocked``, ``valid_from`` and ``valid_until`` have been added.
|
||||
|
||||
.. versionchanged:: 2024.9
|
||||
|
||||
The attribute ``print_logs`` has been added.
|
||||
@@ -400,7 +377,9 @@ List of all orders
|
||||
"state": "",
|
||||
"internal_reference": "",
|
||||
"vat_id": "EU123456789",
|
||||
"vat_id_validated": false
|
||||
"vat_id_validated": false,
|
||||
"transmission_type": "email",
|
||||
"transmission_info": {}
|
||||
},
|
||||
"positions": [
|
||||
{
|
||||
@@ -439,6 +418,7 @@ List of all orders
|
||||
"seat": null,
|
||||
"checkins": [
|
||||
{
|
||||
"id": 1337,
|
||||
"list": 44,
|
||||
"type": "entry",
|
||||
"gate": null,
|
||||
@@ -642,7 +622,9 @@ Fetching individual orders
|
||||
"state": "",
|
||||
"internal_reference": "",
|
||||
"vat_id": "EU123456789",
|
||||
"vat_id_validated": false
|
||||
"vat_id_validated": false,
|
||||
"transmission_type": "email",
|
||||
"transmission_info": {}
|
||||
},
|
||||
"positions": [
|
||||
{
|
||||
@@ -681,6 +663,7 @@ Fetching individual orders
|
||||
"seat": null,
|
||||
"checkins": [
|
||||
{
|
||||
"id": 1337,
|
||||
"list": 44,
|
||||
"type": "entry",
|
||||
"gate": null,
|
||||
@@ -756,10 +739,6 @@ Fetching individual orders
|
||||
Order ticket download
|
||||
---------------------
|
||||
|
||||
.. versionchanged:: 4.10
|
||||
|
||||
The API now supports ticket downloads for pending orders if allowed by the event settings.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/download/(output)/
|
||||
|
||||
Download tickets for an order, identified by its order code. Depending on the chosen output, the response might
|
||||
@@ -1053,6 +1032,8 @@ Creating orders
|
||||
* ``vat_id_validated`` (optional) – If you need support for reverse charge (rarely the case), you need to check
|
||||
yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will
|
||||
trigger reverse charge taxation. Don't forget to set ``is_business`` as well!
|
||||
* ``transmission_type`` (optional, defaults to ``email``)
|
||||
* ``transmission_info`` (optional, see also :ref:`rest-transmission-types`)
|
||||
|
||||
* ``positions``
|
||||
|
||||
@@ -1095,9 +1076,10 @@ Creating orders
|
||||
prices. Note that this will not include other fees and is calculated once during order generation and will not
|
||||
be respected automatically when the order changes later.)
|
||||
* ``_split_taxes_like_products`` (Optional convenience flag. If set to ``true``, your ``tax_rule`` will be ignored
|
||||
and the fee will be taxed like the products in the order. If the products have multiple tax rates, multiple fees
|
||||
will be generated with weights adjusted to the net price of the products. Note that this will be calculated once
|
||||
during order generation and is not respected automatically when the order changes later.)
|
||||
and the fee will be taxed like the products in the order *unless* the total amount of the positions is zero.
|
||||
If the products have multiple tax rates, multiple fees will be generated with weights adjusted to the net price
|
||||
of the products. Note that this will be calculated once during order generation and is not respected automatically
|
||||
when the order changes later.)
|
||||
|
||||
* ``force`` (optional). If set to ``true``, quotas will be ignored.
|
||||
* ``send_email`` (optional). If set to ``true``, the same emails will be sent as for a regular order, regardless of
|
||||
@@ -1652,6 +1634,7 @@ List of all order positions
|
||||
"blocked": null,
|
||||
"checkins": [
|
||||
{
|
||||
"id": 1337,
|
||||
"list": 44,
|
||||
"type": "entry",
|
||||
"gate": null,
|
||||
@@ -1780,6 +1763,7 @@ Fetching individual positions
|
||||
"seat": null,
|
||||
"checkins": [
|
||||
{
|
||||
"id": 1337,
|
||||
"list": 44,
|
||||
"type": "entry",
|
||||
"gate": null,
|
||||
@@ -1832,10 +1816,6 @@ Fetching individual positions
|
||||
Order position ticket download
|
||||
------------------------------
|
||||
|
||||
.. versionchanged:: 4.10
|
||||
|
||||
The API now supports ticket downloads for pending orders if allowed by the event settings.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/download/(output)/
|
||||
|
||||
Download tickets for one order position, identified by its internal ID.
|
||||
@@ -1888,15 +1868,6 @@ Order position ticket download
|
||||
Manipulating individual positions
|
||||
---------------------------------
|
||||
|
||||
.. versionchanged:: 4.8
|
||||
|
||||
The ``PATCH`` method now supports changing items, variations, subevents, seats, prices, and tax rules.
|
||||
The ``POST`` endpoint to add individual positions has been added.
|
||||
|
||||
.. versionadded:: 4.16
|
||||
|
||||
The endpoints to manage blocks have been added.
|
||||
|
||||
.. versionchanged:: 2024.9
|
||||
|
||||
The API now supports logging ticket and badge prints.
|
||||
@@ -1974,6 +1945,7 @@ Manipulating individual positions
|
||||
|
||||
(Full order position resource, see above.)
|
||||
|
||||
:query boolean check_quotas: Whether to check quotas before committing item changes, default is ``true``
|
||||
:param organizer: The ``slug`` field of the organizer of the event
|
||||
:param event: The ``slug`` field of the event
|
||||
:param id: The ``id`` field of the order position to update
|
||||
@@ -2053,6 +2025,7 @@ Manipulating individual positions
|
||||
|
||||
(Full order position resource, see above.)
|
||||
|
||||
:query boolean check_quotas: Whether to check quotas before creating the new position, default is ``true``
|
||||
:param organizer: The ``slug`` field of the organizer of the event
|
||||
:param event: The ``slug`` field of the event
|
||||
|
||||
@@ -2226,10 +2199,6 @@ multiple changes to an order at once within one transaction. This makes it possi
|
||||
attendees in an order without running into conflicts. This interface also offers some possibilities not available
|
||||
otherwise, such as splitting an order or changing fees.
|
||||
|
||||
.. versionchanged:: 4.8
|
||||
|
||||
This endpoint has been added to the system.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/change/
|
||||
|
||||
Performs a change operation on an order. You can supply the following fields:
|
||||
@@ -2343,6 +2312,7 @@ otherwise, such as splitting an order or changing fees.
|
||||
|
||||
(Full order position resource, see above.)
|
||||
|
||||
:query boolean check_quotas: Whether to check quotas before patching or creating positions, default is ``true``
|
||||
:param organizer: The ``slug`` field of the organizer of the event
|
||||
:param event: The ``slug`` field of the event
|
||||
:param code: The ``code`` field of the order to update
|
||||
@@ -2538,6 +2508,7 @@ Order payment endpoints
|
||||
|
||||
{
|
||||
"amount": "23.00",
|
||||
"comment": "Overpayment",
|
||||
"mark_canceled": false
|
||||
}
|
||||
|
||||
|
||||
@@ -19,16 +19,17 @@ name string The organizer's
|
||||
slug string A short form of the name, used e.g. in URLs.
|
||||
public_url string The public, customer-facing URL of the organizer, where
|
||||
the list of all events can be found (read-only).
|
||||
plugins list A list of package names of the enabled plugins for this
|
||||
organizer. Note that most plugins are enabled on the
|
||||
event level (or both levels). If you remove a plugin
|
||||
that is also enabled on some events, it will
|
||||
automatically be removed from all events as well.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. versionchanged:: 4.17
|
||||
|
||||
The ``public_url`` field has been added.
|
||||
|
||||
.. http:get:: /api/v1/organizers/
|
||||
|
||||
Returns a list of all organizers the authenticated user/token has access to.
|
||||
@@ -57,7 +58,10 @@ Endpoints
|
||||
{
|
||||
"name": "Big Events LLC",
|
||||
"slug": "Big Events",
|
||||
"public_url": "https://pretix.eu/bigevents/"
|
||||
"public_url": "https://pretix.eu/bigevents/",
|
||||
"plugins": [
|
||||
"pretix_datev"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -91,7 +95,10 @@ Endpoints
|
||||
{
|
||||
"name": "Big Events LLC",
|
||||
"slug": "Big Events",
|
||||
"public_url": "https://pretix.eu/bigevents/"
|
||||
"public_url": "https://pretix.eu/bigevents/",
|
||||
"plugins": [
|
||||
"pretix_datev"
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -99,6 +106,50 @@ Endpoints
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/
|
||||
|
||||
Updates an organizer. Currently only the ``plugins`` field may be updated.
|
||||
|
||||
Permission required: "Can change organizer settings"
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"plugins": [
|
||||
"pretix_seating"
|
||||
]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Big Events LLC",
|
||||
"slug": "Big Events",
|
||||
"public_url": "https://pretix.eu/bigevents/",
|
||||
"plugins": [
|
||||
"pretix_seating"
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to update
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The organizer could not be updated due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to update this resource.
|
||||
|
||||
Organizer settings
|
||||
------------------
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ closed boolean Whether the quo
|
||||
field).
|
||||
release_after_exit boolean Whether the quota regains capacity as soon as some tickets
|
||||
have been scanned at an exit.
|
||||
ignore_for_event_availability boolean Whether the quota is ignored when calculating the event's
|
||||
availability of tickets.
|
||||
available boolean Whether this quota is available. Only returned if ``with_availability=true``
|
||||
is set on the request. Do not rely on this value for critical operations, it may be
|
||||
slightly out of date.
|
||||
@@ -36,10 +38,9 @@ available_number integer Number of avail
|
||||
slightly out of date. ``null`` means unlimited.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.1
|
||||
|
||||
The ``with_availability`` query parameter has been added.
|
||||
.. versionchanged:: 2025.7
|
||||
|
||||
The attribute ``ignore_for_event_availability`` has been added.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
@@ -77,7 +78,8 @@ Endpoints
|
||||
"variations": [1, 4, 5, 7],
|
||||
"subevent": null,
|
||||
"close_when_sold_out": false,
|
||||
"closed": false
|
||||
"closed": false,
|
||||
"ignore_for_event_availability": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -86,7 +88,8 @@ Endpoints
|
||||
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
|
||||
Default: ``position``
|
||||
:query integer subevent: Only return quotas of the sub-event with the given ID.
|
||||
:query integer subevent__in: Only return quotas of sub-events with one the given IDs (comma-separated).
|
||||
:query integer subevent__in: Only return quotas of sub-events with one of the given IDs (comma-separated).
|
||||
:query integer items__in: Only return quotas that include a product with one of the given IDs (comma-separated).
|
||||
:query string with_availability: Set to ``true`` to get availability information. Can lead to increased answer times.
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
@@ -122,7 +125,8 @@ Endpoints
|
||||
"variations": [1, 4, 5, 7],
|
||||
"subevent": null,
|
||||
"close_when_sold_out": false,
|
||||
"closed": false
|
||||
"closed": false,
|
||||
"ignore_for_event_availability": false
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -153,7 +157,8 @@ Endpoints
|
||||
"variations": [1, 4, 5, 7],
|
||||
"subevent": null,
|
||||
"close_when_sold_out": false,
|
||||
"closed": false
|
||||
"closed": false,
|
||||
"ignore_for_event_availability": false
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -172,7 +177,8 @@ Endpoints
|
||||
"variations": [1, 4, 5, 7],
|
||||
"subevent": null,
|
||||
"close_when_sold_out": false,
|
||||
"closed": false
|
||||
"closed": false,
|
||||
"ignore_for_event_availability": false
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event/item to create a quota for
|
||||
@@ -227,7 +233,8 @@ Endpoints
|
||||
],
|
||||
"subevent": null,
|
||||
"close_when_sold_out": false,
|
||||
"closed": false
|
||||
"closed": false,
|
||||
"ignore_for_event_availability": false
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
|
||||
@@ -6,10 +6,6 @@ Data shredders
|
||||
pretix and it's plugins include a number of data shredders that allow you to clear personal information from the system.
|
||||
This page shows you how to use these shredders through the API.
|
||||
|
||||
.. versionchanged:: 4.12
|
||||
|
||||
This feature has been added to the API.
|
||||
|
||||
.. warning::
|
||||
|
||||
Unlike the user interface, the API will not force you to download tax-relevant data before you delete it.
|
||||
|
||||
@@ -59,15 +59,6 @@ seat_category_mapping object An object mappi
|
||||
last_modified datetime Last modification of this object
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.15
|
||||
|
||||
The ``search`` query parameter has been added to filter sub-events by their name or location in any language.
|
||||
|
||||
.. versionchanged:: 5.0
|
||||
|
||||
The ``date_from_before``, ``date_from_after``, ``date_to_before``, and ``date_to_after`` query parameters have been
|
||||
added.
|
||||
|
||||
.. versionchanged:: 2023.8.0
|
||||
|
||||
For the organizer-wide endpoint, the ``search`` query parameter has been modified to filter sub-events by their parent events slug too.
|
||||
@@ -75,10 +66,6 @@ last_modified datetime Last modificati
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. versionchanged:: 4.1
|
||||
|
||||
The ``with_availability_for`` parameter has been added.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/subevents/
|
||||
|
||||
Returns a list of all sub-events of an event.
|
||||
|
||||
@@ -26,6 +26,8 @@ rate decimal (string) Tax rate in per
|
||||
code string Codified reason for tax rate (or ``null``), see :ref:`rest-taxcodes`.
|
||||
price_includes_tax boolean If ``true`` (default), tax is assumed to be included in
|
||||
the specified product price
|
||||
default boolean If ``true`` (default), this is the default tax rate for this event
|
||||
(there can only be one per event).
|
||||
eu_reverse_charge boolean **DEPRECATED**. If ``true``, EU reverse charge rules
|
||||
are applied. Will be ignored if custom rules are set.
|
||||
Use custom rules instead.
|
||||
@@ -40,10 +42,6 @@ custom_rules object Dynamic rules s
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
.. versionchanged:: 4.6
|
||||
|
||||
The ``internal_name`` and ``keep_gross_if_rate_changes`` attributes have been added.
|
||||
|
||||
.. versionchanged:: 2023.6
|
||||
|
||||
The ``custom_rules`` attribute has been added.
|
||||
@@ -52,6 +50,10 @@ custom_rules object Dynamic rules s
|
||||
|
||||
The ``code`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 2025.4
|
||||
|
||||
The ``default`` attribute has been added.
|
||||
|
||||
.. _rest-taxcodes:
|
||||
|
||||
Tax codes
|
||||
@@ -115,6 +117,7 @@ Endpoints
|
||||
{
|
||||
"id": 1,
|
||||
"name": {"en": "VAT"},
|
||||
"default": true,
|
||||
"internal_name": "VAT",
|
||||
"code": "S/standard",
|
||||
"rate": "19.00",
|
||||
@@ -157,6 +160,7 @@ Endpoints
|
||||
{
|
||||
"id": 1,
|
||||
"name": {"en": "VAT"},
|
||||
"default": true,
|
||||
"internal_name": "VAT",
|
||||
"code": "S/standard",
|
||||
"rate": "19.00",
|
||||
@@ -207,6 +211,7 @@ Endpoints
|
||||
{
|
||||
"id": 1,
|
||||
"name": {"en": "VAT"},
|
||||
"default": false,
|
||||
"internal_name": "VAT",
|
||||
"code": "S/standard",
|
||||
"rate": "19.00",
|
||||
|
||||
@@ -39,10 +39,6 @@ can_change_vouchers boolean
|
||||
can_checkin_orders boolean
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.18
|
||||
|
||||
The ``can_manage_reusable_media`` permission has been added.
|
||||
|
||||
Team member resource
|
||||
--------------------
|
||||
|
||||
|
||||
232
doc/api/resources/transactions.rst
Normal file
232
doc/api/resources/transactions.rst
Normal file
@@ -0,0 +1,232 @@
|
||||
.. _rest-transactions:
|
||||
|
||||
Transactions
|
||||
============
|
||||
|
||||
Transactions are an additional way to think about orders. They are are an immutable, filterable view into an order's
|
||||
history and are a good basis for financial reporting.
|
||||
|
||||
Our financial model
|
||||
-------------------
|
||||
|
||||
You can think of a pretix order similar to a debtor account in double-entry bookkeeping. For example, the flow of an
|
||||
order could look like this:
|
||||
|
||||
===================================================== ==================== =====================
|
||||
Transaction Debit Credit
|
||||
===================================================== ==================== =====================
|
||||
Order is placed with two tickets € 500
|
||||
Order is paid partially with a gift card € 200
|
||||
Remainder is paid with a credit card € 300
|
||||
One of the tickets is canceled **-** € 250
|
||||
Refund is made to the credit card **-** € 250
|
||||
**Balance** **€ 250** **€ 250**
|
||||
===================================================== ==================== =====================
|
||||
|
||||
If an order is fully settled, the sums of both columns match. However, as the movements in both columns do not always
|
||||
happen at the same time, at some times during the lifecycle of an order the sums are not balanced, in which case we
|
||||
consider an order to be "pending payment" or "overpaid".
|
||||
|
||||
In the API, the "Debit" column is represented by the "transaction" resource listed on this page.
|
||||
In many cases, the left column *usually* also matches the data returned by the :ref:`rest-invoices` resource, but there
|
||||
are two important differences:
|
||||
|
||||
- pretix may be configured such that an invoice is not always generated for an order. In this case, only the transactions
|
||||
return the full data set.
|
||||
|
||||
- pretix does not enforce a new invoice to be created e.g. when a ticket is changed to a different subevent. However,
|
||||
pretix always creates a new transaction whenever there is a change to a ticket that concerns the **price**, **tax rate**,
|
||||
**product**, or **date** (in an event series).
|
||||
|
||||
The :ref:`rest-orders` themselves are not a good representation of the "Debit" side of the table for accounting
|
||||
purposes since they are not immutable:
|
||||
They will only tell you the current state of the order, not what it was a week ago.
|
||||
|
||||
The "Credit" column is represented by the :ref:`order-payment-resource` and :ref:`order-refund-resource`.
|
||||
|
||||
|
||||
Resource description
|
||||
--------------------
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal ID of the transaction
|
||||
order string Order code the transaction was created from
|
||||
event string Event slug, only present on organizer-level API calls
|
||||
created datetime The creation time of the transaction in the database
|
||||
datetime datetime The time at which the transaction is financially relevant.
|
||||
This is usually the same as created, but may vary for
|
||||
retroactively created transactions after software bugs or
|
||||
for data that preceeds this data model.
|
||||
positionid integer Number of the position within the order this refers to,
|
||||
is ``null`` for transactions that refer to a fee
|
||||
count integer Number of items purchased, is negative for cancellations
|
||||
item integer The internal ID of the item purchased (or ``null`` for fees)
|
||||
variation integer The internal ID of the variation purchased (or ``null``)
|
||||
subevent integer The internal ID of the event series date (or ``null``)
|
||||
price money (string) Gross price of the transaction
|
||||
tax_rate decimal (string) Tax rate applied in transaction
|
||||
tax_rule integer The internal ID of the tax rule used (or ``null``)
|
||||
tax_code string The selected tax code (or ``null``)
|
||||
tax_value money (string) The computed tax value
|
||||
fee_type string The type of fee (or ``null`` for products)
|
||||
internal_type string Additional type classification of the fee (or ``null`` for products)
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 2025.7.0
|
||||
|
||||
This resource was added to the API.
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/transactions/
|
||||
|
||||
Returns a list of all transactions of an event.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/transactions/ 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": 123,
|
||||
"order": "FOO",
|
||||
"count": 1,
|
||||
"created": "2017-12-01T10:00:00Z",
|
||||
"datetime": "2017-12-01T10:00:00Z",
|
||||
"item": null,
|
||||
"variation": null,
|
||||
"positionid": 1,
|
||||
"price": "23.00",
|
||||
"subevent": null,
|
||||
"tax_code": "E",
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": 23,
|
||||
"tax_value": "0.00",
|
||||
"fee_type": null,
|
||||
"internal_type": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||
:query string order: Only return transactions matching the given order code.
|
||||
:query datetime_since: Only return transactions with a datetime at or after the given time.
|
||||
:query datetime_before: Only return transactions with a datetime before the given time.
|
||||
:query created_since: Only return transactions with a creation time at or after the given time.
|
||||
:query created_before: Only return transactions with a creation time before the given time.
|
||||
:query item: Only return transactions that match the given item ID.
|
||||
:query item__in: Only return transactions that match one of the given item IDs (separated with a comma).
|
||||
:query variation: Only return transactions that match the given variation ID.
|
||||
:query variation__in: Only return transactions that match one of the given variation IDs (separated with a comma).
|
||||
:query subevent: Only return transactions that match the given subevent ID.
|
||||
:query subevent__in: Only return transactions that match one of the given subevent IDs (separated with a comma).
|
||||
:query tax_rule: Only return transactions that match the given tax rule ID.
|
||||
:query tax_rule__in: Only return transactions that match one of the given tax rule IDs (separated with a comma).
|
||||
:query tax_code: Only return transactions that match the given tax code.
|
||||
:query tax_code__in: Only return transactions that match one of the given tax codes (separated with a comma).
|
||||
:query tax_rate: Only return transactions that match the given tax rate.
|
||||
:query tax_rate__in: Only return transactions that match one of the given tax rates (separated with a comma).
|
||||
:query fee_type: Only return transactions that match the given fee type.
|
||||
:query fee_type__in: Only return transactions that match one of the given fee types (separated with a comma).
|
||||
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``created``, and ``id``.
|
||||
:param organizer: The ``slug`` field of a valid organizer
|
||||
:param event: The ``slug`` field of a valid event
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/transactions/
|
||||
|
||||
Returns a list of all transactions of an organizer that you have access to.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/transactions/ 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": 123,
|
||||
"event": "sampleconf",
|
||||
"order": "FOO",
|
||||
"count": 1,
|
||||
"created": "2017-12-01T10:00:00Z",
|
||||
"datetime": "2017-12-01T10:00:00Z",
|
||||
"item": null,
|
||||
"variation": null,
|
||||
"positionid": 1,
|
||||
"price": "23.00",
|
||||
"subevent": null,
|
||||
"tax_code": "E",
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": 23,
|
||||
"tax_value": "0.00",
|
||||
"fee_type": null,
|
||||
"internal_type": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||
:query string event: Only return transactions matching the given event slug.
|
||||
:query string order: Only return transactions matching the given order code.
|
||||
:query datetime_since: Only return transactions with a datetime at or after the given time.
|
||||
:query datetime_before: Only return transactions with a datetime before the given time.
|
||||
:query created_since: Only return transactions with a creation time at or after the given time.
|
||||
:query created_before: Only return transactions with a creation time before the given time.
|
||||
:query item: Only return transactions that match the given item ID.
|
||||
:query item__in: Only return transactions that match one of the given item IDs (separated with a comma).
|
||||
:query variation: Only return transactions that match the given variation ID.
|
||||
:query variation__in: Only return transactions that match one of the given variation IDs (separated with a comma).
|
||||
:query subevent: Only return transactions that match the given subevent ID.
|
||||
:query subevent__in: Only return transactions that match one of the given subevent IDs (separated with a comma).
|
||||
:query tax_rule: Only return transactions that match the given tax rule ID.
|
||||
:query tax_rule__in: Only return transactions that match one of the given tax rule IDs (separated with a comma).
|
||||
:query tax_code: Only return transactions that match the given tax code.
|
||||
:query tax_code__in: Only return transactions that match one of the given tax codes (separated with a comma).
|
||||
:query tax_rate: Only return transactions that match the given tax rate.
|
||||
:query tax_rate__in: Only return transactions that match one of the given tax rates (separated with a comma).
|
||||
:query fee_type: Only return transactions that match the given fee type.
|
||||
:query fee_type__in: Only return transactions that match one of the given fee types (separated with a comma).
|
||||
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``created``, and ``id``.
|
||||
:param organizer: The ``slug`` field of a valid organizer
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
|
||||
@@ -14,6 +14,7 @@ The voucher resource contains the following public fields:
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal ID of the voucher
|
||||
created datetime The creation date of the voucher. For vouchers created before pretix 2025.7.0, this is guessed retroactively and might not be accurate.
|
||||
code string The voucher code that is required to redeem the voucher
|
||||
max_usages integer The maximum number of times this voucher can be
|
||||
redeemed (default: 1).
|
||||
@@ -49,8 +50,14 @@ subevent integer ID of the date
|
||||
show_hidden_items boolean Only if set to ``true``, this voucher allows to buy products with the property ``hide_without_voucher``. Defaults to ``true``.
|
||||
all_addons_included boolean If set to ``true``, all add-on products for the product purchased with this voucher are included in the base price.
|
||||
all_bundles_included boolean If set to ``true``, all bundled products for the product purchased with this voucher are added without their designated price.
|
||||
budget money (string) The budget a voucher is allowed to consume before being used up (or ``null``)
|
||||
budget_used money (string) The amount of budget the voucher has already used up.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 2025.7
|
||||
|
||||
The attributes ``created``, ``budget``, and ``budget_used`` have been added.
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
@@ -82,6 +89,7 @@ Endpoints
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"created": "2020-09-18T14:17:40.971519Z",
|
||||
"code": "43K6LKM37FBVR2YG",
|
||||
"max_usages": 1,
|
||||
"redeemed": 0,
|
||||
@@ -99,7 +107,9 @@ Endpoints
|
||||
"subevent": null,
|
||||
"show_hidden_items": false,
|
||||
"all_addons_included": false,
|
||||
"all_bundles_included": false
|
||||
"all_bundles_included": false,
|
||||
"budget": None,
|
||||
"budget_used": "0.00"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -152,6 +162,7 @@ Endpoints
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"created": "2020-09-18T14:17:40.971519Z",
|
||||
"code": "43K6LKM37FBVR2YG",
|
||||
"max_usages": 1,
|
||||
"redeemed": 0,
|
||||
@@ -169,7 +180,9 @@ Endpoints
|
||||
"subevent": null,
|
||||
"show_hidden_items": false,
|
||||
"all_addons_included": false,
|
||||
"all_bundles_included": false
|
||||
"all_bundles_included": false,
|
||||
"budget": None,
|
||||
"budget_used": "0.00"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -222,6 +235,7 @@ Endpoints
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"created": "2020-09-18T14:17:40.971519Z",
|
||||
"code": "43K6LKM37FBVR2YG",
|
||||
"max_usages": 1,
|
||||
"redeemed": 0,
|
||||
@@ -239,7 +253,9 @@ Endpoints
|
||||
"subevent": null,
|
||||
"show_hidden_items": false,
|
||||
"all_addons_included": false,
|
||||
"all_bundles_included": false
|
||||
"all_bundles_included": false,
|
||||
"budget": None,
|
||||
"budget_used": "0.00"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to create a voucher for
|
||||
@@ -313,6 +329,7 @@ Endpoints
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"created": "2020-09-18T14:17:40.971519Z",
|
||||
"code": "43K6LKM37FBVR2YG",
|
||||
…
|
||||
}, …
|
||||
@@ -359,6 +376,7 @@ Endpoints
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"created": "2020-09-18T14:17:40.971519Z",
|
||||
"code": "43K6LKM37FBVR2YG",
|
||||
"max_usages": 1,
|
||||
"redeemed": 0,
|
||||
@@ -376,7 +394,9 @@ Endpoints
|
||||
"subevent": null,
|
||||
"show_hidden_items": false,
|
||||
"all_addons_included": false,
|
||||
"all_bundles_included": false
|
||||
"all_bundles_included": false,
|
||||
"budget": None,
|
||||
"budget_used": "0.00"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
|
||||
@@ -60,6 +60,9 @@ The following values for ``action_types`` are valid with pretix core:
|
||||
* ``pretix.event.added``
|
||||
* ``pretix.event.changed``
|
||||
* ``pretix.event.deleted``
|
||||
* ``pretix.voucher.added``
|
||||
* ``pretix.voucher.changed``
|
||||
* ``pretix.voucher.deleted``
|
||||
* ``pretix.subevent.added``
|
||||
* ``pretix.subevent.changed``
|
||||
* ``pretix.subevent.deleted``
|
||||
|
||||
@@ -178,13 +178,6 @@ You can then implement a view as you would normally do. It will be automatically
|
||||
* Your plugin is enabled
|
||||
* The locale is set correctly
|
||||
|
||||
.. versionchanged:: 1.7
|
||||
|
||||
The ``event_url()`` wrapper has been added in 1.7 to replace the former ``@event_view`` decorator. The
|
||||
``event_url()`` wrapper is optional and using ``url()`` still works, but you will not be able to set the
|
||||
``require_live`` setting any more via the decorator. The ``@event_view`` decorator is now deprecated and
|
||||
does nothing.
|
||||
|
||||
REST API viewsets
|
||||
-----------------
|
||||
|
||||
|
||||
207
doc/development/api/datasync.rst
Normal file
207
doc/development/api/datasync.rst
Normal file
@@ -0,0 +1,207 @@
|
||||
.. highlight:: python
|
||||
:linenothreshold: 5
|
||||
|
||||
Data sync providers
|
||||
===================
|
||||
|
||||
.. warning:: This feature is considered **experimental**. It might change at any time without prior notice.
|
||||
|
||||
pretix provides connectivity to many external services through plugins. A common requirement
|
||||
is unidirectionally sending (order, customer, ticket, ...) data into external systems.
|
||||
The transfer is usually triggered by signals provided by pretix core (e.g. :data:`order_placed`),
|
||||
but performed asynchronously.
|
||||
|
||||
Such plugins should use the :class:`OutboundSyncProvider` API to utilize the queueing, retry and mapping
|
||||
mechanisms as well as the user interface for configuration and monitoring. Sync providers are registered
|
||||
in the :py:attr:`pretix.base.datasync.datasync.datasync_providers` :ref:`registry <registries>`.
|
||||
|
||||
An :class:`OutboundSyncProvider` for subscribing event participants to a mailing list could start
|
||||
like this, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pretix.base.datasync.datasync import (OutboundSyncProvider, datasync_providers)
|
||||
|
||||
@datasync_providers.register
|
||||
class MyListSyncProvider(OutboundSyncProvider):
|
||||
identifier = "my_list"
|
||||
display_name = "My Mailing List Service"
|
||||
# ...
|
||||
|
||||
|
||||
The plugin must register listeners in `signals.py` for all signals that should to trigger a sync and
|
||||
within it has to call :meth:`MyListSyncProvider.enqueue_order` to enqueue the order for synchronization:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@receiver(order_placed, dispatch_uid="mylist_order_placed")
|
||||
def on_order_placed(sender, order, **kwargs):
|
||||
MyListSyncProvider.enqueue_order(order, "order_placed")
|
||||
|
||||
|
||||
Property mappings
|
||||
-----------------
|
||||
|
||||
Most of these plugins need to translate data from some pretix objects (e.g. orders)
|
||||
into an external system's data structures. Sometimes, there is only one reasonable way or the
|
||||
plugin author makes an opinionated decision what information from which objects should be
|
||||
transferred into which data structures in the external system.
|
||||
|
||||
Otherwise, you can use a :class:`PropertyMappingFormSet` to let the user set up a mapping from pretix model fields
|
||||
to external data fields. You could store the mapping information either in the event settings, or in a separate
|
||||
data model. Your implementation of :attr:`OutboundSyncProvider.mappings`
|
||||
needs to provide a list of mappings, which can be e.g. static objects or model instances, as long as they
|
||||
have at least the properties defined in
|
||||
:class:`pretix.base.datasync.datasync.StaticMapping`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# class MyListSyncProvider, contd.
|
||||
def mappings(self):
|
||||
return [
|
||||
StaticMapping(
|
||||
id=1, pretix_model='Order', external_object_type='Contact',
|
||||
pretix_id_field='email', external_id_field='email',
|
||||
property_mappings=self.event.settings.mylist_order_mapping,
|
||||
))
|
||||
]
|
||||
|
||||
|
||||
Currently, we support `orders` and `order positions` as data sources, with the data fields defined in
|
||||
:func:`pretix.base.datasync.sourcefields.get_data_fields`.
|
||||
|
||||
To perform the actual sync, implement :func:`sync_object_with_properties` and optionally
|
||||
:func:`finalize_sync_order`. The former is called for each object to be created according to the ``mappings``.
|
||||
For each order that was enqueued using :func:`enqueue_order`:
|
||||
|
||||
- each Mapping with ``pretix_model == "Order"`` results in one call to :func:`sync_object_with_properties`,
|
||||
- each Mapping with ``pretix_model == "OrderPosition"`` results in one call to
|
||||
:func:`sync_object_with_properties` per order position,
|
||||
- :func:`finalize_sync_order` is called one time after all calls to :func:`sync_object_with_properties`.
|
||||
|
||||
|
||||
Implementation examples
|
||||
-----------------------
|
||||
|
||||
For example implementations, see the test cases in :mod:`tests.base.test_datasync`.
|
||||
|
||||
In :class:`SimpleOrderSync`, a basic data transfer of order data only is
|
||||
shown. Therein, a ``sync_object_with_properties`` method is defined as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pretix.base.datasync.utils import assign_properties
|
||||
|
||||
# class MyListSyncProvider, contd.
|
||||
def sync_object_with_properties(
|
||||
self, external_id_field, id_value, properties: list, inputs: dict,
|
||||
mapping, mapped_objects: dict, **kwargs,
|
||||
):
|
||||
# First, we query the external service if our object-to-sync already exists there.
|
||||
# This is necessary to make sure our method is idempotent, i.e. handles already synced
|
||||
# data gracefully.
|
||||
pre_existing_object = self.fake_api_client.retrieve_object(
|
||||
mapping.external_object_type,
|
||||
external_id_field,
|
||||
id_value
|
||||
)
|
||||
|
||||
# We use the helper function ``assign_properties`` to update a pre-existing object.
|
||||
update_values = assign_properties(
|
||||
new_values=properties,
|
||||
old_values=pre_existing_object or {},
|
||||
is_new=pre_existing_object is None,
|
||||
list_sep=";",
|
||||
)
|
||||
|
||||
# Then we can send our new data to the external service. The specifics of course depends
|
||||
# on your API, e.g. you may need to use different endpoints for creating or updating an
|
||||
# object, or pass the identifier separately instead of in the same dictionary as the
|
||||
# other properties.
|
||||
result = self.fake_api_client.create_or_update_object(mapping.external_object_type, {
|
||||
**update_values,
|
||||
external_id_field: id_value,
|
||||
"_id": pre_existing_object and pre_existing_object.get("_id"),
|
||||
})
|
||||
|
||||
# Finally, return a dictionary containing at least `object_type`, `external_id_field`,
|
||||
# `id_value`, `external_link_href`, and `external_link_display_name` keys.
|
||||
# Further keys may be provided for your internal use. This dictionary is provided
|
||||
# in following calls in the ``mapped_objects`` dict, to allow creating associations
|
||||
# to this object.
|
||||
return {
|
||||
"object_type": mapping.external_object_type,
|
||||
"external_id_field": external_id_field,
|
||||
"id_value": id_value,
|
||||
"external_link_href": f"https://example.org/external-system/{mapping.external_object_type}/{id_value}/",
|
||||
"external_link_display_name": f"Contact #{id_value} - Jane Doe",
|
||||
"my_result": result,
|
||||
}
|
||||
|
||||
.. note:: The result dictionaries of earlier invocations of :func:`sync_object_with_properties` are
|
||||
only provided in subsequent calls of the same sync run, such that a mapping can
|
||||
refer to e.g. the external id of an object created by a preceding mapping.
|
||||
However, the result dictionaries are currently not provided across runs. This will
|
||||
likely change in a future revision of this API, to allow easier integration of external
|
||||
systems that do not allow retrieving/updating data by a pretix-provided key.
|
||||
|
||||
``mapped_objects`` is a dictionary of lists of dictionaries. The keys to the dictionary are
|
||||
the mapping identifiers (``mapping.id``), the lists contain the result dictionaries returned
|
||||
by :func:`sync_object_with_properties`.
|
||||
|
||||
|
||||
In :class:`OrderAndTicketAssociationSync`, an example is given where orders, order positions,
|
||||
and the association between them are transferred.
|
||||
|
||||
|
||||
The OutboundSyncProvider base class
|
||||
-----------------------------------
|
||||
|
||||
.. autoclass:: pretix.base.datasync.datasync.OutboundSyncProvider
|
||||
:members:
|
||||
|
||||
|
||||
Property mapping format
|
||||
-----------------------
|
||||
|
||||
To allow the user to configure property mappings, you can use the PropertyMappingFormSet,
|
||||
which will generate the required ``property_mappings`` value automatically. If you need
|
||||
to specify the property mappings programmatically, you can refer to the description below
|
||||
on their format.
|
||||
|
||||
.. autoclass:: pretix.control.forms.mapping.PropertyMappingFormSet
|
||||
:members: to_property_mappings_json
|
||||
|
||||
A simple JSON-serialized ``property_mappings`` list for mapping some order information can look like this:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"pretix_field": "email",
|
||||
"external_field": "orderemail",
|
||||
"value_map": "",
|
||||
"overwrite": "overwrite",
|
||||
},
|
||||
{
|
||||
"pretix_field": "order_status",
|
||||
"external_field": "status",
|
||||
"value_map": "{\"n\": \"pending\", \"p\": \"paid\", \"e\": \"expired\", \"c\": \"canceled\", \"r\": \"refunded\"}",
|
||||
"overwrite": "overwrite",
|
||||
},
|
||||
{
|
||||
"pretix_field": "order_total",
|
||||
"external_field": "total",
|
||||
"value_map": "",
|
||||
"overwrite": "overwrite",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Translating mappings on Event copy
|
||||
----------------------------------
|
||||
|
||||
Property mappings can contain references to event-specific primary keys. Therefore, plugins must register to the
|
||||
event_copy_data signal and call translate_property_mappings on all property mappings they store.
|
||||
|
||||
.. autofunction:: pretix.base.datasync.utils.translate_property_mappings
|
||||
@@ -23,21 +23,21 @@ There are multiple signals that will be sent out in the ordering cycle:
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:no-index:
|
||||
:members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_expiry_changed, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text
|
||||
:members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_expiry_changed, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, build_invoice_data, invoice_line_text
|
||||
|
||||
Check-ins
|
||||
"""""""""
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:no-index:
|
||||
:members: checkin_created
|
||||
:members: checkin_created, checkin_annulled
|
||||
|
||||
|
||||
Frontend
|
||||
--------
|
||||
|
||||
.. automodule:: pretix.presale.signals
|
||||
:members: html_head, html_footer, footer_link, global_footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, contact_form_fields_overrides, question_form_fields_overrides, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header, seatingframe_html_head
|
||||
:members: html_head, html_footer, footer_link, global_footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, contact_form_fields_overrides, question_form_fields_overrides, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header, seatingframe_html_head, filter_subevents
|
||||
|
||||
|
||||
.. automodule:: pretix.presale.signals
|
||||
|
||||
@@ -10,14 +10,15 @@ Contents:
|
||||
exporter
|
||||
ticketoutput
|
||||
payment
|
||||
payment_2.0
|
||||
email
|
||||
placeholder
|
||||
invoice
|
||||
invoicetransmission
|
||||
shredder
|
||||
import
|
||||
customview
|
||||
cookieconsent
|
||||
auth
|
||||
datasync
|
||||
general
|
||||
quality
|
||||
|
||||
65
doc/development/api/invoicetransmission.rst
Normal file
65
doc/development/api/invoicetransmission.rst
Normal file
@@ -0,0 +1,65 @@
|
||||
.. highlight:: python
|
||||
:linenothreshold: 5
|
||||
|
||||
Writing an invoice transmission plugin
|
||||
======================================
|
||||
|
||||
An invoice transmission provider transports an invoice from the sender to the recipient.
|
||||
There are pre-defined types of invoice transmission in pretix, currently ``"email"``, ``"peppol"``, and ``"it_sdi"``.
|
||||
You can find more information about them at :ref:`rest-transmission-types`.
|
||||
|
||||
New transmission types can not be added by plugins but need to be added to pretix itself.
|
||||
However, plugins can provide implementations for the actual transmission.
|
||||
Please read :ref:`Creating a plugin <pluginsetup>` first, if you haven't already.
|
||||
|
||||
Output registration
|
||||
-------------------
|
||||
|
||||
New invoice transmission providers can be registered through the :ref:`registry <registries>` mechanism
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pretix.base.invoicing.transmission import transmission_providers, TransmissionProvider
|
||||
|
||||
@transmission_providers.new()
|
||||
class SdiTransmissionProvider(TransmissionProvider):
|
||||
identifier = "fatturapa_providerabc"
|
||||
type = "it_sdi"
|
||||
verbose_name = _("FatturaPA through provider ABC")
|
||||
...
|
||||
|
||||
|
||||
The provider class
|
||||
------------------
|
||||
|
||||
.. class:: pretix.base.invoicing.transmission.TransmissionProvider
|
||||
|
||||
.. autoattribute:: identifier
|
||||
|
||||
This is an abstract attribute, you **must** override this!
|
||||
|
||||
.. autoattribute:: type
|
||||
|
||||
This is an abstract attribute, you **must** override this!
|
||||
|
||||
.. autoattribute:: verbose_name
|
||||
|
||||
This is an abstract attribute, you **must** override this!
|
||||
|
||||
.. autoattribute:: priority
|
||||
|
||||
.. autoattribute:: testmode_supported
|
||||
|
||||
.. automethod:: is_ready
|
||||
|
||||
This is an abstract method, you **must** override this!
|
||||
|
||||
.. automethod:: is_available
|
||||
|
||||
This is an abstract method, you **must** override this!
|
||||
|
||||
.. automethod:: transmit
|
||||
|
||||
This is an abstract method, you **must** override this!
|
||||
|
||||
.. automethod:: settings_url
|
||||
@@ -1,129 +0,0 @@
|
||||
.. highlight:: python
|
||||
:linenothreshold: 5
|
||||
|
||||
.. _`payment2.0`:
|
||||
|
||||
Porting a payment provider from pretix 1.x to pretix 2.x
|
||||
========================================================
|
||||
|
||||
In pretix 2.x, we changed large parts of the payment provider API. This documentation details the changes we made
|
||||
and shows you how you can make an existing pretix 1.x payment provider compatible with pretix 2.x
|
||||
|
||||
Conceptual overview
|
||||
-------------------
|
||||
|
||||
In pretix 1.x, an order was always directly connected to a payment provider for the full life of an order. As long as
|
||||
an order was unpaid, this could still be changed in some cases, but once an order was paid, no changes to the payment
|
||||
provider were possible any more. Additionally, the internal state of orders allowed orders only to be fully paid or
|
||||
not paid at all. This leads to a couple of consequences:
|
||||
|
||||
* Payment-related functions (like "execute payment" or "do a refund") always operated on full orders.
|
||||
|
||||
* Changing the total of an order was basically impossible once an order was paid, since there was no concept of
|
||||
partial payments or partial refunds.
|
||||
|
||||
* Payment provider plugins needed to take complicated steps to detect cases that require human intervention, like e.g.
|
||||
|
||||
* An order has expired, no quota is left to revive it, but a payment has been received
|
||||
|
||||
* A payment has been received for a canceled order
|
||||
|
||||
* A payment has been received for an order that has already been paid with a different payment method
|
||||
|
||||
* An external payment service notified us of a refund/dispute
|
||||
|
||||
We noticed that we copied and repeated large portions of code in all our official payment provider plugins, just
|
||||
to deal with some of these cases.
|
||||
|
||||
* Sometimes, there is the need to mark an order as refunded within pretix, without automatically triggering a refund
|
||||
with an external API. Every payment method needed to implement a user interface for this independently.
|
||||
|
||||
* If a refund was not possible automatically, there was no way user to track which payments actually have been refunded
|
||||
manually and which are still left to do.
|
||||
|
||||
* When the payment with one payment provider failed and the user changed to a different payment provider, all
|
||||
information about the first payment was lost from the order object and could only be retrieved from order log data,
|
||||
which also made it hard to design a data shredder API to get rid of this data.
|
||||
|
||||
In pretix 2.x, we introduced two new models, :py:class:`OrderPayment <pretix.base.models.OrderPayment>` and
|
||||
:py:class:`OrderRefund <pretix.base.models.OrderRefund>`. Each instance of these is connected to an order and
|
||||
represents one single attempt to pay or refund a specific amount of money. Each one of these has an individual state,
|
||||
can individually fail or succeed, and carries an amount variable that can differ from the order total.
|
||||
|
||||
This has the following advantages:
|
||||
|
||||
* The system can now detect orders that are over- or underpaid, independent of the payment providers in use.
|
||||
|
||||
* Therefore, we can now allow partial payments, partial refunds, and changing paid orders, and automatically detect
|
||||
the cases listed above and notify the user.
|
||||
|
||||
Payment providers now interact with those payment and refund objects more than with orders.
|
||||
|
||||
Your to-do list
|
||||
---------------
|
||||
|
||||
Payment processing
|
||||
""""""""""""""""""
|
||||
|
||||
* The method ``BasePaymentProvider.order_pending_render`` has been removed and replaced by a new
|
||||
``BasePaymentProvider.payment_pending_render(request, payment)`` method that is passed an ``OrderPayment``
|
||||
object instead of an ``Order``.
|
||||
|
||||
* The method ``BasePaymentProvider.payment_form_render`` now receives a new ``total`` parameter.
|
||||
|
||||
* The method ``BasePaymentProvider.payment_perform`` has been removed and replaced by a new method
|
||||
``BasePaymentProvider.execute_payment(request, payment)`` that is passed an ``OrderPayment``
|
||||
object instead of an ``Order``.
|
||||
|
||||
* The function ``pretix.base.services.mark_order_paid`` has been removed, instead call ``payment.confirm()``
|
||||
on a pending ``OrderPayment`` object. If no further payments are required for this order, this will also
|
||||
mark the order as paid automatically. Note that ``payment.confirm()`` can still throw a ``QuotaExceededException``,
|
||||
however it will still mark the payment as complete (not the order!), so you should catch this exception and
|
||||
inform the user, but not abort the transaction.
|
||||
|
||||
* A new property ``BasePaymentProvider.abort_pending_allowed`` has been introduced. Only if set, the user will
|
||||
be able to retry a payment or switch the payment method when the order currently has a payment object in
|
||||
state ``"pending"``. This replaces ``BasePaymentProvider.order_can_retry``, which no longer exists.
|
||||
|
||||
* The methods ``BasePaymentProvider.retry_prepare`` and ``BasePaymentProvider.order_prepare`` have both been
|
||||
replaced by a new method ``BasePaymentProvider.payment_prepare(request, payment)`` that is passed an ``OrderPayment``
|
||||
object instead of an ``Order``. **Keep in mind that this payment object might have an amount property that
|
||||
differs from the order total, if the order is already partially paid.**
|
||||
|
||||
* The method ``BasePaymentProvider.order_paid_render`` has been removed.
|
||||
|
||||
* The method ``BasePaymentProvider.order_control_render`` has been removed and replaced by a new method
|
||||
``BasePaymentProvider.payment_control_render(request, payment)`` that is passed an ``OrderPayment``
|
||||
object instead of an ``Order``.
|
||||
|
||||
* There's no need to manually deal with excess payments or duplicate payments anymore, just setting the ``OrderPayment``
|
||||
methods to the correct state will do the job.
|
||||
|
||||
Creating refunds
|
||||
""""""""""""""""
|
||||
|
||||
* The methods ``BasePaymentProvider.order_control_refund_render`` and ``BasePaymentProvider.order_control_refund_perform``
|
||||
have been removed.
|
||||
|
||||
* Two new boolean methods ``BasePaymentProvider.payment_refund_supported(payment)`` and ``BasePaymentProvider.payment_partial_refund_supported(payment)``
|
||||
have been introduced. They should be set to return ``True`` if and only if the payment API allows to *automatically*
|
||||
transfer the money back to the customer.
|
||||
|
||||
* A new method ``BasePaymentProvider.execute_refund(refund)`` has been introduced. This method is called using a
|
||||
``OrderRefund`` object in ``"created"`` state and is expected to transfer the money back and confirm success with
|
||||
calling ``refund.done()``. This will only ever be called if either ``BasePaymentProvider.payment_refund_supported(payment)``
|
||||
or ``BasePaymentProvider.payment_partial_refund_supported(payment)`` return ``True``.
|
||||
|
||||
Processing external refunds
|
||||
"""""""""""""""""""""""""""
|
||||
|
||||
* If e.g. a webhook API notifies you that a payment has been disputed or refunded with the external API, you are
|
||||
expected to call ``OrderPayment.create_external_refund(self, amount, execution_date, info='{}')`` on this payment.
|
||||
This will create and return an appropriate ``OrderRefund`` object and send out a notification. However, it will not
|
||||
mark the order as refunded, but will ask the event organizer for a decision.
|
||||
|
||||
Data shredders
|
||||
""""""""""""""
|
||||
|
||||
* The method ``BasePaymentProvider.shred_payment_info`` is no longer passed an order, but instead **either**
|
||||
an ``OrderPayment`` **or** an ``OrderRefund``.
|
||||
@@ -56,6 +56,20 @@ restricted boolean (optional) ``False`` by default, restricts a plugin
|
||||
for an event by system administrators / superusers.
|
||||
experimental boolean (optional) ``False`` by default, marks a plugin as an experimental feature in the plugins list.
|
||||
compatibility string Specifier for compatible pretix versions.
|
||||
level string System level the plugin can be activated at.
|
||||
Set to ``pretix.base.plugins.PLUGIN_LEVEL_EVENT`` for plugins that can be activated
|
||||
at event level and then be active for that event only.
|
||||
Set to ``pretix.base.plugins.PLUGIN_LEVEL_ORGANIZER`` for plugins that can be
|
||||
activated only for the organizer as a whole and are active for any event within
|
||||
that organizer.
|
||||
Set to ``pretix.base.plugins.PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID`` for plugins that
|
||||
can be activated at organizer level but are considered active only within events
|
||||
for which they have also been specifically activated.
|
||||
More levels, e.g. user-level plugins, might be invented in the future.
|
||||
settings_links list List of ``((menu name, submenu name, …), urlname, url_kwargs)`` tuples that point
|
||||
to the plugin's settings.
|
||||
navigation_links list List of ``((menu name, submenu name, …), urlname, url_kwargs)`` tuples that point
|
||||
to the plugin's system pages.
|
||||
================== ==================== ===========================================================
|
||||
|
||||
A working example would be:
|
||||
@@ -63,9 +77,9 @@ A working example would be:
|
||||
.. code-block:: python
|
||||
|
||||
try:
|
||||
from pretix.base.plugins import PluginConfig
|
||||
from pretix.base.plugins import PluginConfig, PLUGIN_LEVEL_EVENT
|
||||
except ImportError:
|
||||
raise RuntimeError("Please use pretix 2.7 or above to run this plugin!")
|
||||
raise RuntimeError("Please use pretix 2025.7 or above to run this plugin!")
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
@@ -79,6 +93,7 @@ A working example would be:
|
||||
version = '1.0.0'
|
||||
category = 'PAYMENT'
|
||||
picture = 'pretix_paypal/paypal_logo.svg'
|
||||
level = PLUGIN_LEVEL_EVENT
|
||||
visible = True
|
||||
featured = False
|
||||
restricted = False
|
||||
@@ -142,14 +157,14 @@ method to make your receivers available:
|
||||
from . import signals # NOQA
|
||||
|
||||
You can optionally specify code that is executed when your plugin is activated for an event
|
||||
in the ``installed`` method:
|
||||
or organizer in the ``installed`` method:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class PaypalApp(AppConfig):
|
||||
…
|
||||
|
||||
def installed(self, event):
|
||||
def installed(self, event_or_organizer):
|
||||
pass # Your code here
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Development setup
|
||||
|
||||
This tutorial helps you to get started hacking with pretix on your own computer. You need this to
|
||||
be able to contribute to pretix, but it might also be helpful if you want to write your own plugins.
|
||||
If you want to install pretix on a server for actual usage, go to the [administrator documentation](https://docs.pretix.eu/self-hosting/) instead.
|
||||
If you want to install pretix on a server for actual usage, go to the `administrator documentation`_ instead.
|
||||
|
||||
Obtain a copy of the source code
|
||||
--------------------------------
|
||||
@@ -221,3 +221,4 @@ your virtual environment.::
|
||||
|
||||
.. _Django's documentation: https://docs.djangoproject.com/en/1.11/ref/django-admin/#runserver
|
||||
.. _pretixdroid: https://github.com/pretix/pretixdroid
|
||||
.. _administrator documentation: https://docs.pretix.eu/self-hosting/
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
.. spelling:word-list::
|
||||
|
||||
AGPL
|
||||
AGPLv3
|
||||
GPL
|
||||
LGPL
|
||||
Apache
|
||||
BSD
|
||||
MIT
|
||||
CLA
|
||||
django
|
||||
i18nfields
|
||||
hierarkey
|
||||
rami.io
|
||||
rami
|
||||
io
|
||||
GmbH
|
||||
|
||||
License FAQ
|
||||
===========
|
||||
|
||||
.. warning::
|
||||
|
||||
This FAQ tries to explain in simpler terms what the license of the pretix open source project does and does not
|
||||
allow. It is based on our interpretation of the license and is not legal advice. The contents of this page are not
|
||||
legally binding, only the original text of the license in the `license file`_ is legally binding.
|
||||
|
||||
How is pretix licensed?
|
||||
-----------------------
|
||||
|
||||
pretix follows the popular dual licensing model. It is available under the `GNU Affero General Public License 3`_ (AGPL)
|
||||
plus some additional terms, as well as under a proprietary license ("pretix Enterprise license") on request.
|
||||
|
||||
How can it be AGPL if there are additional terms?
|
||||
-------------------------------------------------
|
||||
|
||||
Even though it is fairly unknown, the AGPL's section 7 is titled "Additional Terms" and outlines specific conditions
|
||||
under which additional terms can be imposed on an AGPL-licensed work. In our case, we add three additional terms.
|
||||
|
||||
The first additional term for pretix is an additional **permission**. It allows you to do something that the AGPL would
|
||||
generally not allow. As it doesn't restrict your freedoms granted by AGPL, if you don't like it, you can ignore it, and
|
||||
if you distribute pretix further, you can remove it.
|
||||
|
||||
The second and third additional term for pretix are additional terms that restrict or specify other provisions of the
|
||||
license. AGPL specifically requires that these terms can only restrict or specify very specific things and we believe
|
||||
our additional terms are in compliance with that and are thus valid and may not be removed.
|
||||
|
||||
Why did you choose this license model?
|
||||
--------------------------------------
|
||||
|
||||
pretix was born in the open source community and we're deeply committed to building the best open source ticketing
|
||||
solution in the world. It is important to us that pretix is available with a comprehensive feature set under term that
|
||||
are compatible with the `Open Source Definition`_. This enables event organizers from all industries and regions
|
||||
to have access to a self-hosted, privacy-friendly and secure option to host their events.
|
||||
|
||||
However, developing and maintaining pretix is a lot of work. Between 2014 and 2021, we've received external
|
||||
contributions from more than 150 individuals. Not counting translations over 90 % of the development was
|
||||
done by staff engineers of rami.io GmbH, the company that started pretix. While we're very happy to receive many more
|
||||
contributions in the future, we also want to ensure that we continue to be able to pay people working on pretix
|
||||
full-time.
|
||||
|
||||
We believe our model creates a good balance between ensuring pretix is available freely as well as protecting our
|
||||
business interests. Unlike licenses chosen by other projects recently, such as the Server-Side Public License, our
|
||||
choice does not restrict using pretix for any possible use case, it just sets a few rules that you have to play by
|
||||
if you do.
|
||||
|
||||
What do I need to do if I use pretix unmodified?
|
||||
------------------------------------------------
|
||||
|
||||
If you use pretix without any modifications or plugins, you can use it for whatever you want, as long as you keep
|
||||
all copyright notices (including the link to pretix at the bottom of the site) intact.
|
||||
|
||||
You are also allowed to make copies of the unmodified source code and distribute them to others as long as you keep
|
||||
all copyright and license information intact.
|
||||
|
||||
If you install **plugins**, you must follow the same terms as when using a **modified** version (see below).
|
||||
|
||||
What do I need to do if I modify pretix?
|
||||
----------------------------------------
|
||||
|
||||
If you want to modify pretix, you have the right to do so. However, you need to follow the following rules:
|
||||
|
||||
* If you **run it for your own events** (events run by you or your company as well as companies from the same
|
||||
corporate groups) our additional permission allows you to do so **without needing to share your source code
|
||||
modifications** as long as you keep the link to pretix at the bottom of the site intact.
|
||||
|
||||
* If you **run it for others**, for example as part of a Software-as-a-Service offering or a managed hosting service
|
||||
you **must** make the source code **including all your modifications and all installed plugins** available under the
|
||||
same license as pretix to every visitor of your site. You need to do so in a prominent place such as a link at the bottom of the
|
||||
site. You also **must** keep the existing link intact.
|
||||
You **may not** add additional restrictions on the result as a whole. You **may** add additional permissions, but
|
||||
only on the parts you added. You **must** make clear which changes you made and you must not give the impression that
|
||||
your modified version is an official version of pretix.
|
||||
|
||||
* If you **distribute** the modified version, for example as a source code or software package, you **must** license it
|
||||
under the AGPL license with the same additional terms. You **may not** add additional restrictions on the result as a
|
||||
whole. You **may** add additional permissions, but only on the parts you added. You **must** make clear which changes
|
||||
you made and you must not give the impression that your modified version is an official version of pretix.
|
||||
|
||||
Does the AGPL copyleft mechanism extend to plugins?
|
||||
---------------------------------------------------
|
||||
|
||||
Yes. pretix plugins are tightly integrated with pretix, so when running pretix together with a plugin in the same
|
||||
environment they form a `combined work`_ and the copyleft mechanism of AGPL applies.
|
||||
|
||||
Can I create proprietary or secret plugins?
|
||||
-------------------------------------------
|
||||
|
||||
Yes, you can create a proprietary or secret plugin, but it may only ever be **used** in an environment that is covered
|
||||
by the additional permission from our license. As soon as the plugin is installed in an installation that is not covered
|
||||
by our additional permission (e.g. when it is used in a SaaS environment) or covered by an active pretix Enterprise
|
||||
license it **must** be released to the visitors of the site under the same license as pretix (like a modified version
|
||||
of pretix).
|
||||
|
||||
What licenses can plugins use?
|
||||
------------------------------
|
||||
|
||||
Technically, you can distribute a plugin under any free or proprietary license as long as it is distributed separately.
|
||||
However, once it is either **distributed together with pretix or used in an environment not covered by our
|
||||
additional permission** or an active pretix Enterprise license, you **must** release it to all recipients of the
|
||||
distribution or all visitors of your site under the same license as pretix (like a modified version of pretix).
|
||||
|
||||
If you release a plugin publicly, it is therefore most practical to use a license that is `compatible to AGPL`_.
|
||||
This includes most open source licenses such as AGPL, GPL, Apache, 3-clause BSD or MIT.
|
||||
|
||||
Note however that when you license a plugin with pure AGPL, it will be incompatible with our additional permission.
|
||||
Therefore, if you want to use an AGPL-licensed plugin, you'll need to publish the source code of **all** your plugins
|
||||
under AGPL terms **even if you only use it for your own events**. A plugin would add its `own additional permission`_
|
||||
to its license to allow combining it with pretix for this use case.
|
||||
|
||||
To make things less complicated, if you want to distribute a plugin freely, we therefore recommend distributing the
|
||||
plugin under **Apache License 2.0**, like we do for most plugins we distribute as open source.
|
||||
|
||||
What do I need to do if I want to contribute my changes back?
|
||||
-------------------------------------------------------------
|
||||
|
||||
In order to retain the possibility for us to offer pretix in a dual licensing model, we unfortunately need you to sign
|
||||
a Contributor License Agreement (CLA) that gives us permission to use your contribution in all present and future
|
||||
distributions of pretix. We know the bureaucracy sucks. Sorry.
|
||||
|
||||
What if I want to re-use a minor part of pretix in my project?
|
||||
--------------------------------------------------------------
|
||||
|
||||
This is the main part we dislike about AGPL: If you see a specific thing in pretix that you'd like to use in another
|
||||
project, you'll need to distribute your other project under AGPL terms as well which is often not practical.
|
||||
|
||||
In this case, feel free to get in touch with us! We're happy to grant you special permission or pull the component
|
||||
out into a separately, permissively licensed repository. We already did that with `django-hierarkey`_ and
|
||||
`django-i18nfield`_ which have previously been parts of pretix.
|
||||
|
||||
What can I use the name "pretix" for?
|
||||
-------------------------------------
|
||||
|
||||
The name pretix is a registered trademark by rami.io GmbH.
|
||||
|
||||
* You **may** use it to **indicate copyright**, such as in the "powered by pretix" or "based on pretix" line, or when
|
||||
indicating that a distribution is based on pretix.
|
||||
|
||||
* You **may** use it to **indicate compatibility**, for example you are allowed to name your plugin "<name> for pretix"
|
||||
or you may state that an external service is compatible with pretix.
|
||||
|
||||
* You **may not** give the impression that your modified version, plugin or compatible service is official or authorized
|
||||
by rami.io GmbH or pretix unless we specifically allowed you to do so.
|
||||
|
||||
* You **may not** use it to name your modified version of pretix. End-users must be able to easily identify whether
|
||||
a version of pretix is distributed by us.
|
||||
|
||||
* You **may not** use any variations of the name, such as "MyPretix".
|
||||
|
||||
.. _license file: https://github.com/pretix/pretix/blob/master/LICENSE
|
||||
.. _GNU Affero General Public License 3: https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
.. _compatible to AGPL: https://www.gnu.org/licenses/license-list.en.html#GPLCompatibleLicenses
|
||||
.. _Open Source Definition: https://opensource.org/osd
|
||||
.. _combined work: https://www.gnu.org/licenses/gpl-faq.html#GPLPlugins
|
||||
.. _own additional permission: https://www.gnu.org/licenses/gpl-faq.html#GPLIncompatibleLibs
|
||||
.. _django-hierarkey: https://github.com/raphaelm/django-hierarkey
|
||||
.. _django-i18nfield: https://github.com/raphaelm/django-i18nfield
|
||||
@@ -6,4 +6,4 @@ sphinxcontrib-images
|
||||
sphinxcontrib-jquery
|
||||
sphinxcontrib-spelling==8.*
|
||||
sphinxemoji
|
||||
pyenchant==3.2.*
|
||||
pyenchant==3.3.*
|
||||
|
||||
@@ -7,4 +7,4 @@ sphinxcontrib-images
|
||||
sphinxcontrib-jquery
|
||||
sphinxcontrib-spelling==8.*
|
||||
sphinxemoji
|
||||
pyenchant==3.2.*
|
||||
pyenchant==3.3.*
|
||||
|
||||
@@ -28,30 +28,30 @@ classifiers = [
|
||||
dependencies = [
|
||||
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab
|
||||
"babel",
|
||||
"BeautifulSoup4==4.13.*",
|
||||
"BeautifulSoup4==4.14.*",
|
||||
"bleach==6.2.*",
|
||||
"celery==5.5.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=44.0.0",
|
||||
"css-inline==0.14.*",
|
||||
"css-inline==0.17.*",
|
||||
"defusedcsv>=1.1.0",
|
||||
"Django[argon2]==4.2.*,>=4.2.15",
|
||||
"django-bootstrap3==25.1",
|
||||
"Django[argon2]==4.2.*,>=4.2.24",
|
||||
"django-bootstrap3==25.2",
|
||||
"django-compressor==4.5.1",
|
||||
"django-countries==7.6.*",
|
||||
"django-filter==25.1",
|
||||
"django-formset-js-improved==0.5.0.3",
|
||||
"django-formset-js-improved==0.5.0.4",
|
||||
"django-formtools==2.5.1",
|
||||
"django-hierarkey==1.2.*",
|
||||
"django-hierarkey==2.0.*,>=2.0.1",
|
||||
"django-hijack==3.7.*",
|
||||
"django-i18nfield==1.10.*",
|
||||
"django-i18nfield==1.11.*",
|
||||
"django-libsass==0.9",
|
||||
"django-localflavor==4.0",
|
||||
"django-localflavor==5.0",
|
||||
"django-markup",
|
||||
"django-oauth-toolkit==2.3.*",
|
||||
"django-otp==1.6.*",
|
||||
"django-phonenumber-field==7.3.*",
|
||||
"django-redis==5.4.*",
|
||||
"django-redis==6.0.*",
|
||||
"django-scopes==2.0.*",
|
||||
"django-statici18n==2.6.*",
|
||||
"djangorestframework==3.16.*",
|
||||
@@ -64,34 +64,34 @@ dependencies = [
|
||||
"kombu==5.5.*",
|
||||
"libsass==0.23.*",
|
||||
"lxml",
|
||||
"markdown==3.8", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
||||
"markdown==3.9", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
||||
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
||||
"mt-940==4.30.*",
|
||||
"oauthlib==3.2.*",
|
||||
"oauthlib==3.3.*",
|
||||
"openpyxl==3.1.*",
|
||||
"packaging",
|
||||
"paypalrestsdk==1.13.*",
|
||||
"paypal-checkout-serversdk==1.0.*",
|
||||
"PyJWT==2.10.*",
|
||||
"phonenumberslite==9.0.*",
|
||||
"Pillow==11.2.*",
|
||||
"Pillow==11.3.*",
|
||||
"pretix-plugin-build",
|
||||
"protobuf==6.30.*",
|
||||
"protobuf==6.32.*",
|
||||
"psycopg2-binary",
|
||||
"pycountry",
|
||||
"pycparser==2.22",
|
||||
"pycparser==2.23",
|
||||
"pycryptodome==3.23.*",
|
||||
"pypdf==5.4.*",
|
||||
"pypdf==6.0.*",
|
||||
"python-bidi==0.6.*", # Support for Arabic in reportlab
|
||||
"python-dateutil==2.9.*",
|
||||
"pytz",
|
||||
"pytz-deprecation-shim==0.1.*",
|
||||
"pyuca",
|
||||
"qrcode==8.2",
|
||||
"redis==5.2.*",
|
||||
"redis==6.4.*",
|
||||
"reportlab==4.4.*",
|
||||
"requests==2.31.*",
|
||||
"sentry-sdk==2.29.*",
|
||||
"requests==2.32.*",
|
||||
"sentry-sdk==2.41.*",
|
||||
"sepaxml==2.6.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
@@ -100,30 +100,30 @@ dependencies = [
|
||||
"ua-parser==1.0.*",
|
||||
"vat_moss_forked==2020.3.20.0.11.0",
|
||||
"vobject==0.9.*",
|
||||
"webauthn==2.5.*",
|
||||
"webauthn==2.7.*",
|
||||
"zeep==4.3.*"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
memcached = ["pylibmc"]
|
||||
dev = [
|
||||
"aiohttp==3.12.*",
|
||||
"aiohttp==3.13.*",
|
||||
"coverage",
|
||||
"coveralls",
|
||||
"fakeredis==2.26.*",
|
||||
"flake8==7.2.*",
|
||||
"fakeredis==2.32.*",
|
||||
"flake8==7.3.*",
|
||||
"freezegun",
|
||||
"isort==6.0.*",
|
||||
"isort==6.1.*",
|
||||
"pep8-naming==0.15.*",
|
||||
"potypo",
|
||||
"pytest-asyncio>=0.24",
|
||||
"pytest-cache",
|
||||
"pytest-cov",
|
||||
"pytest-django==4.*",
|
||||
"pytest-mock==3.14.*",
|
||||
"pytest-mock==3.15.*",
|
||||
"pytest-sugar",
|
||||
"pytest-xdist==3.7.*",
|
||||
"pytest==8.3.*",
|
||||
"pytest-xdist==3.8.*",
|
||||
"pytest==8.4.*",
|
||||
"responses",
|
||||
]
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ coverage:
|
||||
coverage run -m py.test
|
||||
|
||||
npminstall:
|
||||
# keep this in sync with setup.py!
|
||||
# keep this in sync with pretix/_build.py!
|
||||
mkdir -p pretix/static.dist/node_prefix/
|
||||
cp -r pretix/static/npm_dir/* pretix/static.dist/node_prefix/
|
||||
npm install --prefix=pretix/static.dist/node_prefix
|
||||
npm ci --prefix=pretix/static.dist/node_prefix
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -19,4 +19,4 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "2025.6.0.dev0"
|
||||
__version__ = "2025.9.0.dev0"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -115,6 +115,7 @@ ALL_LANGUAGES = [
|
||||
('sk', _('Slovak')),
|
||||
('sv', _('Swedish')),
|
||||
('es', _('Spanish')),
|
||||
('es-419', _('Spanish (Latin America)')),
|
||||
('tr', _('Turkish')),
|
||||
('uk', _('Ukrainian')),
|
||||
]
|
||||
@@ -172,6 +173,12 @@ EXTRA_LANG_INFO = {
|
||||
'name': 'Norwegian Bokmal',
|
||||
'name_local': 'norsk (bokmål)',
|
||||
},
|
||||
'es-419': {
|
||||
'bidi': False,
|
||||
'code': 'es-419',
|
||||
'name': 'Spanish (Latin America)',
|
||||
'name_local': 'Español',
|
||||
},
|
||||
}
|
||||
|
||||
django.conf.locale.LANG_INFO.update(EXTRA_LANG_INFO)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -39,7 +39,7 @@ def npm_install():
|
||||
node_prefix = os.path.join(here, 'static.dist', 'node_prefix')
|
||||
os.makedirs(node_prefix, exist_ok=True)
|
||||
shutil.copytree(os.path.join(here, 'static', 'npm_dir'), node_prefix, dirs_exist_ok=True)
|
||||
subprocess.check_call('npm install', shell=True, cwd=node_prefix)
|
||||
subprocess.check_call('npm ci', shell=True, cwd=node_prefix)
|
||||
npm_installed = True
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.17 on 2025-06-24 14:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixapi", "0012_oauthapplication_post_logout_redirect_uris"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="webhookcallretry",
|
||||
name="retry_not_before",
|
||||
field=models.DateTimeField(),
|
||||
),
|
||||
]
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -157,7 +157,7 @@ class WebHookCallRetry(models.Model):
|
||||
id = models.BigAutoField(primary_key=True)
|
||||
webhook = models.ForeignKey('WebHook', on_delete=models.CASCADE, related_name='retries')
|
||||
logentry = models.ForeignKey('pretixbase.LogEntry', on_delete=models.CASCADE, related_name='webhook_retries')
|
||||
retry_not_before = models.DateTimeField(auto_now_add=True)
|
||||
retry_not_before = models.DateTimeField()
|
||||
retry_count = models.PositiveIntegerField(default=0)
|
||||
action_type = models.CharField(max_length=255)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -104,3 +104,14 @@ class MiniCheckinListSerializer(I18nAwareModelSerializer):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class CheckinRPCAnnulInputSerializer(serializers.Serializer):
|
||||
lists = serializers.PrimaryKeyRelatedField(required=True, many=True, queryset=CheckinList.objects.none())
|
||||
nonce = serializers.CharField(required=True, allow_null=False)
|
||||
datetime = serializers.DateTimeField(required=False, allow_null=True)
|
||||
error_explanation = serializers.CharField(required=False, allow_null=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['lists'].child_relation.queryset = CheckinList.objects.filter(event__in=self.context['events']).select_related('event')
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -50,6 +50,7 @@ from rest_framework.relations import SlugRelatedField
|
||||
from pretix.api.serializers import (
|
||||
CompatibleJSONField, SalesChannelMigrationMixin,
|
||||
)
|
||||
from pretix.api.serializers.fields import PluginsField
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.api.serializers.settings import SettingsSerializer
|
||||
from pretix.base.models import (
|
||||
@@ -61,6 +62,9 @@ from pretix.base.models.items import (
|
||||
ItemMetaProperty, SubEventItem, SubEventItemVariation,
|
||||
)
|
||||
from pretix.base.models.tax import CustomRulesValidator
|
||||
from pretix.base.plugins import (
|
||||
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
|
||||
)
|
||||
from pretix.base.services.seating import (
|
||||
SeatProtected, generate_seats, validate_plan_change,
|
||||
)
|
||||
@@ -126,22 +130,6 @@ class SeatCategoryMappingField(Field):
|
||||
}
|
||||
|
||||
|
||||
class PluginsField(Field):
|
||||
|
||||
def to_representation(self, obj):
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
|
||||
return sorted([
|
||||
p.module for p in get_all_plugins()
|
||||
if not p.name.startswith('.') and getattr(p, 'visible', True) and p.module in obj.get_plugins()
|
||||
])
|
||||
|
||||
def to_internal_value(self, data):
|
||||
return {
|
||||
'plugins': data
|
||||
}
|
||||
|
||||
|
||||
class TimeZoneField(ChoiceField):
|
||||
def get_attribute(self, instance):
|
||||
return instance.cache.get_or_set(
|
||||
@@ -283,17 +271,28 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
|
||||
plugins_available = {
|
||||
p.module: p for p in get_all_plugins(self.instance)
|
||||
p.module: p for p in get_all_plugins(event=self.instance)
|
||||
if not p.name.startswith('.') and getattr(p, 'visible', True)
|
||||
}
|
||||
current_plugins = self.instance.get_plugins() if self.instance and self.instance.pk else []
|
||||
settings_holder = self.instance if self.instance and self.instance.pk else self.context['organizer']
|
||||
|
||||
allowed_levels = (PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID)
|
||||
for plugin in value.get('plugins'):
|
||||
if plugin not in plugins_available:
|
||||
raise ValidationError(_('Unknown plugin: \'{name}\'.').format(name=plugin))
|
||||
if getattr(plugins_available[plugin], 'restricted', False):
|
||||
if plugin not in settings_holder.settings.allowed_restricted_plugins:
|
||||
raise ValidationError(_('Restricted plugin: \'{name}\'.').format(name=plugin))
|
||||
level = getattr(plugins_available[plugin], 'level', PLUGIN_LEVEL_EVENT)
|
||||
if level not in allowed_levels:
|
||||
raise ValidationError('Plugin cannot be enabled on this level: \'{name}\'.'.format(name=plugin))
|
||||
|
||||
if level == PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID and plugin not in self.context['organizer'].get_plugins():
|
||||
if plugin not in current_plugins:
|
||||
# Technically, this is allowed, but consumers might be confused if the API call doesn't do anything
|
||||
# so we prevent this change.
|
||||
raise ValidationError('Plugin should be enabled on organizer level first: \'{name}\'.'.format(name=plugin))
|
||||
|
||||
return value
|
||||
|
||||
@@ -685,8 +684,26 @@ class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = TaxRule
|
||||
fields = ('id', 'name', 'rate', 'code', 'price_includes_tax', 'eu_reverse_charge', 'home_country',
|
||||
'internal_name', 'keep_gross_if_rate_changes', 'custom_rules')
|
||||
fields = ('id', 'name', 'default', 'rate', 'code', 'price_includes_tax', 'eu_reverse_charge', 'home_country',
|
||||
'internal_name', 'keep_gross_if_rate_changes', 'custom_rules', 'default')
|
||||
|
||||
def create(self, validated_data):
|
||||
if "default" not in validated_data and not self.context["event"].tax_rules.exists():
|
||||
validated_data["default"] = True
|
||||
return super().create(validated_data)
|
||||
|
||||
def save(self, **kwargs):
|
||||
if self.validated_data.get("default"):
|
||||
if self.instance and self.instance.pk:
|
||||
self.context["event"].tax_rules.exclude(pk=self.instance.pk).update(default=False)
|
||||
else:
|
||||
self.context["event"].tax_rules.update(default=False)
|
||||
return super().save(**kwargs)
|
||||
|
||||
def validate_default(self, value):
|
||||
if not value and self.instance.default:
|
||||
raise ValidationError("You can't remove the default property, instead set it on another tax rule.")
|
||||
return value
|
||||
|
||||
|
||||
class EventSettingsSerializer(SettingsSerializer):
|
||||
@@ -712,6 +729,8 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'allow_modifications_after_checkin',
|
||||
'last_order_modification_date',
|
||||
'show_quota_left',
|
||||
'tax_rule_payment',
|
||||
'tax_rule_cancellation',
|
||||
'waiting_list_enabled',
|
||||
'waiting_list_auto_disable',
|
||||
'waiting_list_hours',
|
||||
@@ -786,6 +805,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'invoice_reissue_after_modify',
|
||||
'invoice_include_free',
|
||||
'invoice_generate',
|
||||
'invoice_period',
|
||||
'invoice_numbers_consecutive',
|
||||
'invoice_numbers_prefix',
|
||||
'invoice_numbers_prefix_cancellations',
|
||||
@@ -942,6 +962,8 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
|
||||
'reusable_media_type_nfc_mf0aes',
|
||||
'reusable_media_type_nfc_mf0aes_random_uid',
|
||||
'system_question_order',
|
||||
'tax_rule_payment',
|
||||
'tax_rule_cancellation',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -19,45 +19,16 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.http import QueryDict
|
||||
from pytz import common_timezones
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from pretix.api.serializers.forms import form_field_to_serializer_field
|
||||
from pretix.base.exporter import OrganizerLevelExportMixin
|
||||
from pretix.base.models import ScheduledEventExport, ScheduledOrganizerExport
|
||||
from pretix.base.timeframes import DateFrameField, SerializerDateFrameField
|
||||
|
||||
|
||||
class FormFieldWrapperField(serializers.Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.form_field = kwargs.pop('form_field')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
return self.form_field.widget.format_value(value)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
d = self.form_field.widget.value_from_datadict({'name': data}, {}, 'name')
|
||||
d = self.form_field.clean(d)
|
||||
return d
|
||||
|
||||
|
||||
simple_mappings = (
|
||||
(forms.DateField, serializers.DateField, ()),
|
||||
(forms.TimeField, serializers.TimeField, ()),
|
||||
(forms.SplitDateTimeField, serializers.DateTimeField, ()),
|
||||
(forms.DateTimeField, serializers.DateTimeField, ()),
|
||||
(forms.DecimalField, serializers.DecimalField, ('max_digits', 'decimal_places', 'min_value', 'max_value')),
|
||||
(forms.FloatField, serializers.FloatField, ()),
|
||||
(forms.IntegerField, serializers.IntegerField, ()),
|
||||
(forms.EmailField, serializers.EmailField, ()),
|
||||
(forms.UUIDField, serializers.UUIDField, ()),
|
||||
(forms.URLField, serializers.URLField, ()),
|
||||
(forms.BooleanField, serializers.BooleanField, ()),
|
||||
)
|
||||
from pretix.base.timeframes import SerializerDateFrameField
|
||||
|
||||
|
||||
class SerializerDescriptionField(serializers.Field):
|
||||
@@ -81,13 +52,6 @@ class ExporterSerializer(serializers.Serializer):
|
||||
input_parameters = SerializerDescriptionField(source='_serializer')
|
||||
|
||||
|
||||
class PrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
|
||||
def to_representation(self, value):
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
return super().to_representation(value)
|
||||
|
||||
|
||||
class JobRunSerializer(serializers.Serializer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
ex = kwargs.pop('exporter')
|
||||
@@ -102,59 +66,7 @@ class JobRunSerializer(serializers.Serializer):
|
||||
many=True
|
||||
)
|
||||
for k, v in ex.export_form_fields.items():
|
||||
for m_from, m_to, m_kwargs in simple_mappings:
|
||||
if isinstance(v, m_from):
|
||||
self.fields[k] = m_to(
|
||||
required=v.required,
|
||||
allow_null=not v.required,
|
||||
validators=v.validators,
|
||||
**{kwarg: getattr(v, kwargs, None) for kwarg in m_kwargs}
|
||||
)
|
||||
break
|
||||
|
||||
if isinstance(v, forms.NullBooleanField):
|
||||
self.fields[k] = serializers.BooleanField(
|
||||
required=v.required,
|
||||
allow_null=True,
|
||||
validators=v.validators,
|
||||
)
|
||||
if isinstance(v, forms.ModelMultipleChoiceField):
|
||||
self.fields[k] = PrimaryKeyRelatedField(
|
||||
queryset=v.queryset,
|
||||
required=v.required,
|
||||
allow_empty=not v.required,
|
||||
validators=v.validators,
|
||||
many=True
|
||||
)
|
||||
elif isinstance(v, forms.ModelChoiceField):
|
||||
self.fields[k] = PrimaryKeyRelatedField(
|
||||
queryset=v.queryset,
|
||||
required=v.required,
|
||||
allow_null=not v.required,
|
||||
validators=v.validators,
|
||||
)
|
||||
elif isinstance(v, forms.MultipleChoiceField):
|
||||
self.fields[k] = serializers.MultipleChoiceField(
|
||||
choices=v.choices,
|
||||
required=v.required,
|
||||
allow_empty=not v.required,
|
||||
validators=v.validators,
|
||||
)
|
||||
elif isinstance(v, forms.ChoiceField):
|
||||
self.fields[k] = serializers.ChoiceField(
|
||||
choices=v.choices,
|
||||
required=v.required,
|
||||
allow_null=not v.required,
|
||||
validators=v.validators,
|
||||
)
|
||||
elif isinstance(v, DateFrameField):
|
||||
self.fields[k] = SerializerDateFrameField(
|
||||
required=v.required,
|
||||
allow_null=not v.required,
|
||||
validators=v.validators,
|
||||
)
|
||||
else:
|
||||
self.fields[k] = FormFieldWrapperField(form_field=v, required=v.required, allow_null=not v.required)
|
||||
self.fields[k] = form_field_to_serializer_field(v)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if isinstance(data, QueryDict):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -109,3 +109,19 @@ class UploadedFileField(serializers.Field):
|
||||
return None
|
||||
request = self.context['request']
|
||||
return request.build_absolute_uri(url)
|
||||
|
||||
|
||||
class PluginsField(serializers.Field):
|
||||
|
||||
def to_representation(self, obj):
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
|
||||
return sorted([
|
||||
p.module for p in get_all_plugins()
|
||||
if not p.name.startswith('.') and getattr(p, 'visible', True) and p.module in obj.get_plugins()
|
||||
])
|
||||
|
||||
def to_internal_value(self, data):
|
||||
return {
|
||||
'plugins': data
|
||||
}
|
||||
|
||||
115
src/pretix/api/serializers/forms.py
Normal file
115
src/pretix/api/serializers/forms.py
Normal file
@@ -0,0 +1,115 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django import forms
|
||||
from rest_framework import serializers
|
||||
|
||||
from pretix.base.timeframes import DateFrameField, SerializerDateFrameField
|
||||
|
||||
simple_mappings = (
|
||||
(forms.DateField, serializers.DateField, ()),
|
||||
(forms.TimeField, serializers.TimeField, ()),
|
||||
(forms.SplitDateTimeField, serializers.DateTimeField, ()),
|
||||
(forms.DateTimeField, serializers.DateTimeField, ()),
|
||||
(forms.DecimalField, serializers.DecimalField, ('max_digits', 'decimal_places', 'min_value', 'max_value')),
|
||||
(forms.FloatField, serializers.FloatField, ()),
|
||||
(forms.IntegerField, serializers.IntegerField, ()),
|
||||
(forms.EmailField, serializers.EmailField, ()),
|
||||
(forms.UUIDField, serializers.UUIDField, ()),
|
||||
(forms.URLField, serializers.URLField, ()),
|
||||
(forms.BooleanField, serializers.BooleanField, ()),
|
||||
)
|
||||
|
||||
|
||||
class PrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
|
||||
def to_representation(self, value):
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
return super().to_representation(value)
|
||||
|
||||
|
||||
class FormFieldWrapperField(serializers.Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.form_field = kwargs.pop('form_field')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
return self.form_field.widget.format_value(value)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
d = self.form_field.widget.value_from_datadict({'name': data}, {}, 'name')
|
||||
d = self.form_field.clean(d)
|
||||
return d
|
||||
|
||||
|
||||
def form_field_to_serializer_field(field):
|
||||
for m_from, m_to, m_kwargs in simple_mappings:
|
||||
if isinstance(field, m_from):
|
||||
return m_to(
|
||||
required=field.required,
|
||||
allow_null=not field.required,
|
||||
validators=field.validators,
|
||||
**{kwarg: getattr(field, kwarg, None) for kwarg in m_kwargs}
|
||||
)
|
||||
|
||||
if isinstance(field, forms.NullBooleanField):
|
||||
return serializers.BooleanField(
|
||||
required=field.required,
|
||||
allow_null=True,
|
||||
validators=field.validators,
|
||||
)
|
||||
if isinstance(field, forms.ModelMultipleChoiceField):
|
||||
return PrimaryKeyRelatedField(
|
||||
queryset=field.queryset,
|
||||
required=field.required,
|
||||
allow_empty=not field.required,
|
||||
validators=field.validators,
|
||||
many=True
|
||||
)
|
||||
elif isinstance(field, forms.ModelChoiceField):
|
||||
return PrimaryKeyRelatedField(
|
||||
queryset=field.queryset,
|
||||
required=field.required,
|
||||
allow_null=not field.required,
|
||||
validators=field.validators,
|
||||
)
|
||||
elif isinstance(field, forms.MultipleChoiceField):
|
||||
return serializers.MultipleChoiceField(
|
||||
choices=field.choices,
|
||||
required=field.required,
|
||||
allow_empty=not field.required,
|
||||
validators=field.validators,
|
||||
)
|
||||
elif isinstance(field, forms.ChoiceField):
|
||||
return serializers.ChoiceField(
|
||||
choices=field.choices,
|
||||
required=field.required,
|
||||
allow_null=not field.required,
|
||||
validators=field.validators,
|
||||
)
|
||||
elif isinstance(field, DateFrameField):
|
||||
return SerializerDateFrameField(
|
||||
required=field.required,
|
||||
allow_null=not field.required,
|
||||
validators=field.validators,
|
||||
)
|
||||
else:
|
||||
return FormFieldWrapperField(form_field=field, required=field.required, allow_null=not field.required)
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -505,6 +505,11 @@ class QuestionSerializer(I18nAwareModelSerializer):
|
||||
Question._clean_identifier(self.context['event'], value, self.instance)
|
||||
return value
|
||||
|
||||
def validate_type(self, value):
|
||||
if self.instance:
|
||||
self.instance.clean_type_change(self.instance.type, value)
|
||||
return value
|
||||
|
||||
def validate_dependency_question(self, value):
|
||||
if value:
|
||||
if value.type not in (Question.TYPE_CHOICE, Question.TYPE_BOOLEAN, Question.TYPE_CHOICE_MULTIPLE):
|
||||
@@ -545,7 +550,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
|
||||
if full_data.get('show_during_checkin') and full_data.get('type') in Question.SHOW_DURING_CHECKIN_UNSUPPORTED:
|
||||
raise ValidationError(_('This type of question cannot be shown during check-in.'))
|
||||
|
||||
Question.clean_items(event, full_data.get('items'))
|
||||
Question.clean_items(event, full_data.get('items') or [])
|
||||
return data
|
||||
|
||||
def validate_options(self, value):
|
||||
@@ -561,7 +566,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
|
||||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
options_data = validated_data.pop('options') if 'options' in validated_data else []
|
||||
items = validated_data.pop('items')
|
||||
items = validated_data.pop('items', [])
|
||||
|
||||
question = Question.objects.create(**validated_data)
|
||||
question.items.set(items)
|
||||
@@ -577,7 +582,7 @@ class QuotaSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Quota
|
||||
fields = ('id', 'name', 'size', 'items', 'variations', 'subevent', 'closed', 'close_when_sold_out',
|
||||
'release_after_exit', 'available', 'available_number')
|
||||
'release_after_exit', 'available', 'available_number', 'ignore_for_event_availability')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -42,6 +42,7 @@ from rest_framework.reverse import reverse
|
||||
|
||||
from pretix.api.serializers import CompatibleJSONField
|
||||
from pretix.api.serializers.event import SubEventSerializer
|
||||
from pretix.api.serializers.forms import form_field_to_serializer_field
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.api.serializers.item import (
|
||||
InlineItemVariationSerializer, ItemSerializer, QuestionSerializer,
|
||||
@@ -49,6 +50,7 @@ from pretix.api.serializers.item import (
|
||||
from pretix.api.signals import order_api_details, orderposition_api_details
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.invoicing.transmission import get_transmission_types
|
||||
from pretix.base.models import (
|
||||
CachedFile, Checkin, Customer, Invoice, InvoiceAddress, InvoiceLine, Item,
|
||||
ItemVariation, Order, OrderPosition, Question, QuestionAnswer,
|
||||
@@ -56,7 +58,7 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.models.orders import (
|
||||
BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund,
|
||||
PrintLog, RevokedTicketSecret,
|
||||
PrintLog, RevokedTicketSecret, Transaction,
|
||||
)
|
||||
from pretix.base.pdf import get_images, get_variables
|
||||
from pretix.base.services.cart import error_messages
|
||||
@@ -102,6 +104,13 @@ class CountryField(serializers.Field):
|
||||
return str(src) if src else None
|
||||
|
||||
|
||||
class TransmissionInfoSerializer(serializers.Serializer):
|
||||
def __init__(self, *args, transmission_type, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for k, v in transmission_type.invoice_address_form_fields.items():
|
||||
self.fields[k] = form_field_to_serializer_field(v)
|
||||
|
||||
|
||||
class InvoiceAddressSerializer(I18nAwareModelSerializer):
|
||||
country = CompatibleCountryField(source='*')
|
||||
name = serializers.CharField(required=False)
|
||||
@@ -109,7 +118,8 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = InvoiceAddress
|
||||
fields = ('last_modified', 'is_business', 'company', 'name', 'name_parts', 'street', 'zipcode', 'city', 'country',
|
||||
'state', 'vat_id', 'vat_id_validated', 'custom_field', 'internal_reference')
|
||||
'state', 'vat_id', 'vat_id_validated', 'custom_field', 'internal_reference', 'transmission_type',
|
||||
'transmission_info')
|
||||
read_only_fields = ('last_modified',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -147,6 +157,48 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
|
||||
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
|
||||
)
|
||||
|
||||
if data.get("transmission_type"):
|
||||
for t in get_transmission_types():
|
||||
if data.get("transmission_type") == t.identifier:
|
||||
if not t.is_available(self.context["request"].event, data.get("country"), data.get("is_business")):
|
||||
raise ValidationError({
|
||||
"transmission_type": "The selected transmission type is not available for this country or address type."
|
||||
})
|
||||
|
||||
ts = TransmissionInfoSerializer(transmission_type=t, data=data.get("transmission_info", {}))
|
||||
try:
|
||||
ts.is_valid(raise_exception=True)
|
||||
except ValidationError as e:
|
||||
raise ValidationError(
|
||||
{"transmission_info": e.detail}
|
||||
)
|
||||
data["transmission_info"] = ts.validated_data
|
||||
|
||||
required_fields = t.invoice_address_form_fields_required(data.get("country"), data.get("is_business"))
|
||||
for r in required_fields:
|
||||
if r in self.fields:
|
||||
if not data.get(r):
|
||||
raise ValidationError(
|
||||
{r: "This field is required for the selected type of invoice transmission."}
|
||||
)
|
||||
else:
|
||||
if not ts.validated_data.get(r):
|
||||
raise ValidationError(
|
||||
{"transmission_info": {r: "This field is required for the selected type of invoice transmission."}}
|
||||
)
|
||||
break # do not call else branch of for loop
|
||||
elif t.exclusive:
|
||||
if t.is_available(self.context["request"].event, data.get("country"), data.get("is_business")):
|
||||
raise ValidationError({
|
||||
"transmission_type": "The transmission type '%s' must be used for this country or address type." % (
|
||||
t.identifier,
|
||||
)
|
||||
})
|
||||
else:
|
||||
raise ValidationError(
|
||||
{"transmission_type": "Unknown transmission type."}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -1600,7 +1652,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
self.context['event'].currency)
|
||||
is_split_taxes = fee_data.pop('_split_taxes_like_products', False)
|
||||
|
||||
if is_split_taxes:
|
||||
if is_split_taxes and order.total:
|
||||
d = defaultdict(lambda: Decimal('0.00'))
|
||||
trz = TaxRule.zero()
|
||||
for p in pos_map.values():
|
||||
@@ -1705,12 +1757,14 @@ class LinePositionField(serializers.IntegerField):
|
||||
|
||||
class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
|
||||
position = LinePositionField(read_only=True)
|
||||
event_date_from = serializers.DateTimeField(read_only=True, source="period_start")
|
||||
event_date_to = serializers.DateTimeField(read_only=True, source="period_end")
|
||||
|
||||
class Meta:
|
||||
model = InvoiceLine
|
||||
fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from',
|
||||
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_code', 'tax_name', 'fee_type',
|
||||
'fee_internal_type', 'event_location')
|
||||
'event_date_to', 'period_start', 'period_end', 'gross_value', 'tax_value', 'tax_rate', 'tax_code',
|
||||
'tax_name', 'fee_type', 'fee_internal_type', 'event_location')
|
||||
|
||||
|
||||
class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
@@ -1725,12 +1779,13 @@ class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
model = Invoice
|
||||
fields = ('event', 'order', 'number', 'is_cancellation', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
|
||||
'invoice_from_city', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
|
||||
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode',
|
||||
'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
|
||||
'custom_field', 'date', 'refers', 'locale',
|
||||
'invoice_to', 'invoice_to_is_business', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street',
|
||||
'invoice_to_zipcode', 'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id',
|
||||
'invoice_to_beneficiary', 'invoice_to_transmission_info', 'custom_field', 'date', 'refers', 'locale',
|
||||
'introductory_text', 'additional_text', 'payment_provider_text', 'payment_provider_stamp',
|
||||
'footer_text', 'lines', 'foreign_currency_display', 'foreign_currency_rate',
|
||||
'foreign_currency_rate_date', 'internal_reference')
|
||||
'foreign_currency_rate_date', 'internal_reference', 'transmission_type', 'transmission_provider',
|
||||
'transmission_status', 'transmission_date')
|
||||
|
||||
|
||||
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
|
||||
@@ -1783,3 +1838,23 @@ class BlockedTicketSecretSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = BlockedTicketSecret
|
||||
fields = ('id', 'secret', 'updated', 'blocked')
|
||||
|
||||
|
||||
class TransactionSerializer(I18nAwareModelSerializer):
|
||||
order = serializers.SlugRelatedField(slug_field="code", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Transaction
|
||||
fields = (
|
||||
"id", "order", "created", "datetime", "positionid", "count", "item", "variation",
|
||||
"subevent", "price", "tax_rate", "tax_rule", "tax_code", "tax_value", "fee_type",
|
||||
"internal_type"
|
||||
)
|
||||
|
||||
|
||||
class OrganizerTransactionSerializer(TransactionSerializer):
|
||||
event = serializers.SlugRelatedField(source="order.event", slug_field="slug", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Transaction
|
||||
fields = TransactionSerializer.Meta.fields + ("event",)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -83,6 +83,7 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
|
||||
|
||||
def create(self, validated_data):
|
||||
ocm = self.context['ocm']
|
||||
check_quotas = self.context.get('check_quotas', True)
|
||||
|
||||
try:
|
||||
ocm.add_position(
|
||||
@@ -96,7 +97,7 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
|
||||
valid_until=validated_data.get('valid_until'),
|
||||
)
|
||||
if self.context.get('commit', True):
|
||||
ocm.commit()
|
||||
ocm.commit(check_quotas=check_quotas)
|
||||
return validated_data['order'].positions.order_by('-positionid').first()
|
||||
else:
|
||||
return OrderPosition() # fake to appease DRF
|
||||
@@ -310,6 +311,7 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
ocm = self.context['ocm']
|
||||
check_quotas = self.context.get('check_quotas', True)
|
||||
current_seat = {'seat_guid': instance.seat.seat_guid} if instance.seat else None
|
||||
item = validated_data.get('item', instance.item)
|
||||
variation = validated_data.get('variation', instance.variation)
|
||||
@@ -356,7 +358,7 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
|
||||
ocm.change_ticket_secret(instance, secret)
|
||||
|
||||
if self.context.get('commit', True):
|
||||
ocm.commit()
|
||||
ocm.commit(check_quotas=check_quotas)
|
||||
instance.refresh_from_db()
|
||||
except OrderError as e:
|
||||
raise ValidationError(str(e))
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -24,6 +24,7 @@ from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -32,6 +33,7 @@ from rest_framework.exceptions import ValidationError
|
||||
|
||||
from pretix.api.auth.devicesecurity import get_all_security_profiles
|
||||
from pretix.api.serializers import AsymmetricField
|
||||
from pretix.api.serializers.fields import PluginsField
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.api.serializers.order import CompatibleJSONField
|
||||
from pretix.api.serializers.settings import SettingsSerializer
|
||||
@@ -43,6 +45,10 @@ from pretix.base.models import (
|
||||
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
|
||||
)
|
||||
from pretix.base.models.seating import SeatingPlanLayoutValidator
|
||||
from pretix.base.plugins import (
|
||||
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
|
||||
PLUGIN_LEVEL_ORGANIZER,
|
||||
)
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.base.settings import validate_organizer_settings
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
@@ -53,13 +59,47 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class OrganizerSerializer(I18nAwareModelSerializer):
|
||||
public_url = serializers.SerializerMethodField('get_organizer_url', read_only=True)
|
||||
plugins = PluginsField(required=False, source='*')
|
||||
name = serializers.CharField(read_only=True)
|
||||
slug = serializers.CharField(read_only=True)
|
||||
|
||||
def get_organizer_url(self, organizer):
|
||||
return build_absolute_uri(organizer, 'presale:organizer.index')
|
||||
|
||||
class Meta:
|
||||
model = Organizer
|
||||
fields = ('name', 'slug', 'public_url')
|
||||
fields = ('name', 'slug', 'public_url', 'plugins')
|
||||
|
||||
def validate_plugins(self, value):
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
|
||||
plugins_available = {
|
||||
p.module: p for p in get_all_plugins(organizer=self.instance)
|
||||
if not p.name.startswith('.') and getattr(p, 'visible', True)
|
||||
}
|
||||
settings_holder = self.instance
|
||||
|
||||
allowed_levels = (PLUGIN_LEVEL_ORGANIZER, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID)
|
||||
for plugin in value.get('plugins'):
|
||||
if plugin not in plugins_available:
|
||||
raise ValidationError(_('Unknown plugin: \'{name}\'.').format(name=plugin))
|
||||
if getattr(plugins_available[plugin], 'restricted', False):
|
||||
if plugin not in settings_holder.settings.allowed_restricted_plugins:
|
||||
raise ValidationError(_('Restricted plugin: \'{name}\'.').format(name=plugin))
|
||||
if getattr(plugins_available[plugin], 'level', PLUGIN_LEVEL_EVENT) not in allowed_levels:
|
||||
raise ValidationError('Plugin cannot be enabled on this level: \'{name}\'.'.format(name=plugin))
|
||||
|
||||
return value
|
||||
|
||||
@transaction.atomic
|
||||
def update(self, instance, validated_data):
|
||||
plugins = validated_data.pop('plugins', None)
|
||||
organizer = super().update(instance, validated_data)
|
||||
# Plugins
|
||||
if plugins is not None:
|
||||
organizer.set_active_plugins(plugins)
|
||||
organizer.save()
|
||||
return organizer
|
||||
|
||||
|
||||
class SeatingPlanSerializer(I18nAwareModelSerializer):
|
||||
@@ -444,6 +484,7 @@ class OrganizerSettingsSerializer(SettingsSerializer):
|
||||
'reusable_media_type_nfc_mf0aes',
|
||||
'reusable_media_type_nfc_mf0aes_autocreate_giftcard',
|
||||
'reusable_media_type_nfc_mf0aes_autocreate_giftcard_currency',
|
||||
'reusable_media_type_nfc_mf0aes_random_uid',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -19,6 +19,8 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from decimal import Decimal
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
@@ -64,14 +66,15 @@ class SeatGuidField(serializers.CharField):
|
||||
|
||||
class VoucherSerializer(I18nAwareModelSerializer):
|
||||
seat = SeatGuidField(allow_null=True, required=False)
|
||||
budget_used = serializers.DecimalField(read_only=True, max_digits=13, decimal_places=2, min_value=Decimal('0.00'))
|
||||
|
||||
class Meta:
|
||||
model = Voucher
|
||||
fields = ('id', 'code', 'max_usages', 'redeemed', 'min_usages', 'valid_until', 'block_quota',
|
||||
fields = ('id', 'created', 'code', 'max_usages', 'redeemed', 'min_usages', 'valid_until', 'block_quota',
|
||||
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
|
||||
'tag', 'comment', 'subevent', 'show_hidden_items', 'seat', 'all_addons_included',
|
||||
'all_bundles_included')
|
||||
read_only_fields = ('id', 'redeemed')
|
||||
'all_bundles_included', 'budget', 'budget_used')
|
||||
read_only_fields = ('id', 'redeemed', 'budget_used')
|
||||
list_serializer_class = VoucherListSerializer
|
||||
|
||||
def validate(self, data):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -21,22 +21,22 @@
|
||||
#
|
||||
from datetime import timedelta
|
||||
|
||||
from django.dispatch import Signal, receiver
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.api.models import ApiCall, WebHookCall
|
||||
from pretix.base.signals import EventPluginSignal, periodic_task
|
||||
from pretix.base.signals import EventPluginSignal, GlobalSignal, periodic_task
|
||||
from pretix.helpers.periodic import minimum_interval
|
||||
|
||||
register_webhook_events = Signal()
|
||||
register_webhook_events = GlobalSignal()
|
||||
"""
|
||||
This signal is sent out to get all known webhook events. Receivers should return an
|
||||
instance of a subclass of ``pretix.api.webhooks.WebhookEvent`` or a list of such
|
||||
instances.
|
||||
"""
|
||||
|
||||
register_device_security_profile = Signal()
|
||||
register_device_security_profile = GlobalSignal()
|
||||
"""
|
||||
This signal is sent out to get all known device security_profiles. Receivers should
|
||||
return an instance of a subclass of ``pretix.api.auth.devicesecurity.BaseSecurityProfile``
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -66,6 +66,7 @@ orga_router.register(r'orders', order.OrganizerOrderViewSet)
|
||||
orga_router.register(r'invoices', order.InvoiceViewSet)
|
||||
orga_router.register(r'scheduled_exports', exporters.ScheduledOrganizerExportViewSet)
|
||||
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
|
||||
orga_router.register(r'transactions', order.OrganizerTransactionViewSet)
|
||||
|
||||
team_router = routers.DefaultRouter()
|
||||
team_router.register(r'members', organizer.TeamMemberViewSet)
|
||||
@@ -83,6 +84,7 @@ event_router.register(r'quotas', item.QuotaViewSet)
|
||||
event_router.register(r'vouchers', voucher.VoucherViewSet)
|
||||
event_router.register(r'orders', order.EventOrderViewSet)
|
||||
event_router.register(r'orderpositions', order.OrderPositionViewSet)
|
||||
event_router.register(r'transactions', order.TransactionViewSet)
|
||||
event_router.register(r'invoices', order.InvoiceViewSet)
|
||||
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets')
|
||||
event_router.register(r'blockedsecrets', order.BlockedSecretViewSet, basename='blockedsecrets')
|
||||
@@ -130,6 +132,8 @@ urlpatterns = [
|
||||
name="checkinrpc.redeem"),
|
||||
re_path(r'^organizers/(?P<organizer>[^/]+)/checkinrpc/search/$', checkin.CheckinRPCSearchView.as_view(),
|
||||
name="checkinrpc.search"),
|
||||
re_path(r'^organizers/(?P<organizer>[^/]+)/checkinrpc/annul/$', checkin.CheckinRPCAnnulView.as_view(),
|
||||
name="checkinrpc.annul"),
|
||||
re_path(r'^organizers/(?P<organizer>[^/]+)/settings/$', organizer.OrganizerSettingsView.as_view(),
|
||||
name="organizer.settings"),
|
||||
re_path(r'^organizers/(?P<organizer>[^/]+)/giftcards/(?P<giftcard>[^/]+)/', include(giftcard_router.urls)),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -20,12 +20,13 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import operator
|
||||
from datetime import timedelta
|
||||
from functools import reduce
|
||||
|
||||
import django_filters
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError as BaseValidationError
|
||||
from django.db import transaction
|
||||
from django.db import connection, transaction
|
||||
from django.db.models import (
|
||||
Count, Exists, F, Max, OrderBy, OuterRef, Prefetch, Q, Subquery,
|
||||
prefetch_related_objects,
|
||||
@@ -39,17 +40,19 @@ from django.utils.translation import gettext
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from packaging.version import parse
|
||||
from rest_framework import views, viewsets
|
||||
from rest_framework import status, views, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.exceptions import (
|
||||
NotFound, PermissionDenied, ValidationError,
|
||||
)
|
||||
from rest_framework.fields import DateTimeField
|
||||
from rest_framework.generics import ListAPIView
|
||||
from rest_framework.permissions import SAFE_METHODS
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.serializers.checkin import (
|
||||
CheckinListSerializer, CheckinRPCRedeemInputSerializer,
|
||||
MiniCheckinListSerializer,
|
||||
CheckinListSerializer, CheckinRPCAnnulInputSerializer,
|
||||
CheckinRPCRedeemInputSerializer, MiniCheckinListSerializer,
|
||||
)
|
||||
from pretix.api.serializers.item import QuestionSerializer
|
||||
from pretix.api.serializers.order import (
|
||||
@@ -66,6 +69,8 @@ from pretix.base.models.orders import PrintLog
|
||||
from pretix.base.services.checkin import (
|
||||
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
|
||||
)
|
||||
from pretix.base.signals import checkin_annulled
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
with scopes_disabled():
|
||||
class CheckinListFilter(FilterSet):
|
||||
@@ -813,7 +818,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['expand'] = self.request.query_params.getlist('expand')
|
||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
|
||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
|
||||
return ctx
|
||||
|
||||
def get_filterset_kwargs(self):
|
||||
@@ -832,9 +837,9 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
def get_queryset(self, ignore_status=False, ignore_products=False):
|
||||
qs = _checkin_list_position_queryset(
|
||||
[self.checkinlist],
|
||||
ignore_status=self.request.query_params.get('ignore_status', 'false') == 'true' or ignore_status,
|
||||
ignore_status=self.request.query_params.get('ignore_status', 'false').lower() == 'true' or ignore_status,
|
||||
ignore_products=ignore_products,
|
||||
pdf_data=self.request.query_params.get('pdf_data', 'false') == 'true',
|
||||
pdf_data=self.request.query_params.get('pdf_data', 'false').lower() == 'true',
|
||||
expand=self.request.query_params.getlist('expand'),
|
||||
)
|
||||
|
||||
@@ -876,7 +881,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
expand=self.request.query_params.getlist('expand'),
|
||||
pdf_data=self.request.query_params.get('pdf_data', 'false') == 'true',
|
||||
pdf_data=self.request.query_params.get('pdf_data', 'false').lower() == 'true',
|
||||
questions_supported=self.request.data.get('questions_supported', True),
|
||||
canceled_supported=self.request.data.get('canceled_supported', False),
|
||||
request=self.request, # this is not clean, but we need it in the serializers for URL generation
|
||||
@@ -911,7 +916,7 @@ class CheckinRPCRedeemView(views.APIView):
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
expand=self.request.query_params.getlist('expand'),
|
||||
pdf_data=self.request.query_params.get('pdf_data', 'false') == 'true',
|
||||
pdf_data=self.request.query_params.get('pdf_data', 'false').lower() == 'true',
|
||||
questions_supported=s.validated_data['questions_supported'],
|
||||
use_order_locale=s.validated_data['use_order_locale'],
|
||||
canceled_supported=True,
|
||||
@@ -989,9 +994,9 @@ class CheckinRPCSearchView(ListAPIView):
|
||||
def get_queryset(self, ignore_status=False, ignore_products=False):
|
||||
qs = _checkin_list_position_queryset(
|
||||
self.lists,
|
||||
ignore_status=self.request.query_params.get('ignore_status', 'false') == 'true' or ignore_status,
|
||||
ignore_status=self.request.query_params.get('ignore_status', 'false').lower() == 'true' or ignore_status,
|
||||
ignore_products=ignore_products,
|
||||
pdf_data=self.request.query_params.get('pdf_data', 'false') == 'true',
|
||||
pdf_data=self.request.query_params.get('pdf_data', 'false').lower() == 'true',
|
||||
expand=self.request.query_params.getlist('expand'),
|
||||
)
|
||||
|
||||
@@ -999,3 +1004,79 @@ class CheckinRPCSearchView(ListAPIView):
|
||||
qs = qs.none()
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class CheckinRPCAnnulView(views.APIView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||
events = self.request.auth.get_events_with_permission(('can_change_orders', 'can_checkin_orders'))
|
||||
elif self.request.user.is_authenticated:
|
||||
events = self.request.user.get_events_with_permission(('can_change_orders', 'can_checkin_orders'), self.request).filter(
|
||||
organizer=self.request.organizer
|
||||
)
|
||||
else:
|
||||
raise ValueError("unknown authentication method")
|
||||
|
||||
s = CheckinRPCAnnulInputSerializer(data=request.data, context={'events': events})
|
||||
s.is_valid(raise_exception=True)
|
||||
|
||||
with transaction.atomic():
|
||||
try:
|
||||
qs = Checkin.all.all()
|
||||
if isinstance(request.auth, Device):
|
||||
qs = qs.filter(device=request.auth)
|
||||
ci = qs.select_for_update(
|
||||
of=OF_SELF,
|
||||
).select_related("position", "position__order", "position__order__event").get(
|
||||
list__in=s.validated_data['lists'],
|
||||
nonce=s.validated_data['nonce'],
|
||||
)
|
||||
if connection.features.has_select_for_update_of and ci.position_id:
|
||||
# Lock position as well, can't do it with of= above because relation is nullable
|
||||
OrderPosition.objects.select_for_update(of=OF_SELF).get(pk=ci.position_id)
|
||||
|
||||
if not ci.successful or not ci.position:
|
||||
raise ValidationError("Cannot annul an unsuccessful checkin")
|
||||
except Checkin.DoesNotExist:
|
||||
raise NotFound("No check-in found based on nonce")
|
||||
except Checkin.MultipleObjectsReturned:
|
||||
raise ValidationError("Multiple check-ins found based on nonce")
|
||||
|
||||
annulment_time = s.validated_data.get("datetime") or now()
|
||||
|
||||
if annulment_time - ci.datetime > timedelta(minutes=15):
|
||||
# Compare to sent datetime, which makes this cheatable, but allows offline annulment of checkins
|
||||
ci.position.order.log_action('pretix.event.checkin.annulment.ignored', data={
|
||||
'checkin': ci.pk,
|
||||
'position': ci.position.id,
|
||||
'positionid': ci.position.positionid,
|
||||
'datetime': annulment_time,
|
||||
'error_explanation': s.validated_data.get("error_explanation"),
|
||||
'type': ci.type,
|
||||
'list': ci.list_id,
|
||||
}, user=request.user, auth=request.auth)
|
||||
return Response({
|
||||
"non_field_errors": ["Annulment is not allowed more than 15 minutes after check-in"]
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if ci.device and ci.device != request.auth:
|
||||
return Response({
|
||||
"non_field_errors": ["Annulment is only allowed from the same device"]
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
ci.successful = False
|
||||
ci.error_reason = Checkin.REASON_ANNULLED
|
||||
ci.error_explanation = s.validated_data.get("error_explanation")
|
||||
ci.save(update_fields=["successful", "error_reason", "error_explanation"])
|
||||
ci.position.order.log_action('pretix.event.checkin.annulled', data={
|
||||
'checkin': ci.pk,
|
||||
'position': ci.position.id,
|
||||
'positionid': ci.position.positionid,
|
||||
'datetime': annulment_time,
|
||||
'error_explanation': s.validated_data.get("error_explanation"),
|
||||
'type': ci.type,
|
||||
'list': ci.list_id,
|
||||
}, user=request.user, auth=request.auth)
|
||||
checkin_annulled.send(ci.position.order.event, checkin=ci)
|
||||
|
||||
return Response({"status": "ok"}, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -580,6 +580,11 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
)
|
||||
super().perform_destroy(instance)
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx["event"] = self.request.event
|
||||
return ctx
|
||||
|
||||
|
||||
class ItemMetaPropertiesViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemMetaPropertiesSerializer
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -485,8 +485,17 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
|
||||
super().perform_destroy(instance)
|
||||
|
||||
|
||||
class NumberInFilter(django_filters.BaseInFilter, django_filters.NumberFilter):
|
||||
pass
|
||||
|
||||
|
||||
with scopes_disabled():
|
||||
class QuotaFilter(FilterSet):
|
||||
items__in = NumberInFilter(
|
||||
field_name='items__id',
|
||||
lookup_expr='in',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Quota
|
||||
fields = {
|
||||
@@ -508,7 +517,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
return self.request.event.quotas.all()
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
queryset = self.filter_queryset(self.get_queryset()).distinct()
|
||||
|
||||
page = self.paginate_queryset(queryset)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -57,9 +57,9 @@ from pretix.api.serializers.order import (
|
||||
BlockedTicketSecretSerializer, InvoiceSerializer, OrderCreateSerializer,
|
||||
OrderPaymentCreateSerializer, OrderPaymentSerializer,
|
||||
OrderPositionSerializer, OrderRefundCreateSerializer,
|
||||
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer,
|
||||
PrintLogSerializer, RevokedTicketSecretSerializer,
|
||||
SimulatedOrderSerializer,
|
||||
OrderRefundSerializer, OrderSerializer, OrganizerTransactionSerializer,
|
||||
PriceCalcSerializer, PrintLogSerializer, RevokedTicketSecretSerializer,
|
||||
SimulatedOrderSerializer, TransactionSerializer,
|
||||
)
|
||||
from pretix.api.serializers.orderchange import (
|
||||
BlockNameSerializer, OrderChangeOperationSerializer,
|
||||
@@ -80,6 +80,7 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.models.orders import (
|
||||
BlockedTicketSecret, PrintLog, QuestionAnswer, RevokedTicketSecret,
|
||||
Transaction,
|
||||
)
|
||||
from pretix.base.payment import PaymentException
|
||||
from pretix.base.pdf import get_images
|
||||
@@ -87,7 +88,7 @@ from pretix.base.secrets import assign_ticket_secret
|
||||
from pretix.base.services import tickets
|
||||
from pretix.base.services.invoices import (
|
||||
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
|
||||
regenerate_invoice,
|
||||
regenerate_invoice, transmit_invoice,
|
||||
)
|
||||
from pretix.base.services.mail import SendMailException
|
||||
from pretix.base.services.orders import (
|
||||
@@ -227,7 +228,7 @@ class OrderViewSetMixin:
|
||||
def get_queryset(self):
|
||||
qs = self.get_base_queryset()
|
||||
if 'fees' not in self.request.GET.getlist('exclude'):
|
||||
if self.request.query_params.get('include_canceled_fees', 'false') == 'true':
|
||||
if self.request.query_params.get('include_canceled_fees', 'false').lower() == 'true':
|
||||
fqs = OrderFee.all
|
||||
else:
|
||||
fqs = OrderFee.objects
|
||||
@@ -245,11 +246,11 @@ class OrderViewSetMixin:
|
||||
return qs
|
||||
|
||||
def _positions_prefetch(self, request):
|
||||
if request.query_params.get('include_canceled_positions', 'false') == 'true':
|
||||
if request.query_params.get('include_canceled_positions', 'false').lower() == 'true':
|
||||
opq = OrderPosition.all
|
||||
else:
|
||||
opq = OrderPosition.objects
|
||||
if request.query_params.get('pdf_data', 'false') == 'true' and getattr(request, 'event', None):
|
||||
if request.query_params.get('pdf_data', 'false').lower() == 'true' and getattr(request, 'event', None):
|
||||
prefetch_related_objects([request.organizer], 'meta_properties')
|
||||
prefetch_related_objects(
|
||||
[request.event],
|
||||
@@ -343,7 +344,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
|
||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
|
||||
return ctx
|
||||
|
||||
def get_base_queryset(self):
|
||||
@@ -742,7 +743,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||
user=request.user if request.user.is_authenticated else None,
|
||||
auth=request.auth,
|
||||
)
|
||||
order_placed.send(self.request.event, order=order)
|
||||
order_placed.send(self.request.event, order=order, bulk=False)
|
||||
if order.status == Order.STATUS_PAID:
|
||||
order_paid.send(self.request.event, order=order)
|
||||
order.log_action(
|
||||
@@ -763,7 +764,13 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||
) and not order.invoices.last()
|
||||
invoice = None
|
||||
if gen_invoice:
|
||||
invoice = generate_invoice(order, trigger_pdf=True)
|
||||
try:
|
||||
invoice = generate_invoice(order, trigger_pdf=True)
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
|
||||
# Refresh serializer only after running signals
|
||||
prefetch_related_objects([order], self._positions_prefetch(request))
|
||||
@@ -942,6 +949,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||
@action(detail=True, methods=['POST'])
|
||||
def change(self, request, **kwargs):
|
||||
order = self.get_object()
|
||||
check_quotas = self.request.query_params.get('check_quotas', 'true').lower() == 'true'
|
||||
|
||||
serializer = OrderChangeOperationSerializer(
|
||||
context={'order': order, **self.get_serializer_context()},
|
||||
@@ -1007,7 +1015,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||
elif serializer.validated_data.get('recalculate_taxes') == 'keep_gross':
|
||||
ocm.recalculate_taxes(keep='gross')
|
||||
|
||||
ocm.commit()
|
||||
ocm.commit(check_quotas=check_quotas)
|
||||
except OrderError as e:
|
||||
raise ValidationError(str(e))
|
||||
|
||||
@@ -1085,17 +1093,18 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
|
||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
|
||||
ctx['check_quotas'] = self.request.query_params.get('check_quotas', 'true').lower() == 'true'
|
||||
return ctx
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.query_params.get('include_canceled_positions', 'false') == 'true':
|
||||
if self.request.query_params.get('include_canceled_positions', 'false').lower() == 'true':
|
||||
qs = OrderPosition.all
|
||||
else:
|
||||
qs = OrderPosition.objects
|
||||
|
||||
qs = qs.filter(order__event=self.request.event)
|
||||
if self.request.query_params.get('pdf_data', 'false') == 'true':
|
||||
if self.request.query_params.get('pdf_data', 'false').lower() == 'true':
|
||||
prefetch_related_objects([self.request.organizer], 'meta_properties')
|
||||
prefetch_related_objects(
|
||||
[self.request.event],
|
||||
@@ -1660,6 +1669,9 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
else:
|
||||
mark_refunded = request.data.get('mark_canceled', False)
|
||||
|
||||
if not isinstance(request.data.get("comment", ""), str):
|
||||
return Response({'comment': 'Invalid type.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if payment.state != OrderPayment.PAYMENT_STATE_CONFIRMED:
|
||||
return Response({'detail': 'Invalid state of payment.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -1686,6 +1698,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
amount=amount,
|
||||
provider=payment.provider,
|
||||
info='{}',
|
||||
comment=request.data.get("comment"),
|
||||
)
|
||||
payment.order.log_action('pretix.event.order.refund.created', {
|
||||
'local_id': r.local_id,
|
||||
@@ -1888,6 +1901,12 @@ class RetryException(APIException):
|
||||
default_code = 'retry_later'
|
||||
|
||||
|
||||
class CurrentlyInflightException(APIException):
|
||||
status_code = 409
|
||||
default_detail = 'The requested action is already in progress.'
|
||||
default_code = 'currently_inflight'
|
||||
|
||||
|
||||
class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = InvoiceSerializer
|
||||
queryset = Invoice.objects.none()
|
||||
@@ -1936,13 +1955,52 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
|
||||
return resp
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def transmit(self, request, **kwargs):
|
||||
invoice = self.get_object()
|
||||
if invoice.shredded:
|
||||
raise PermissionDenied('The invoice file is no longer stored on the server.')
|
||||
|
||||
if invoice.transmission_status != Invoice.TRANSMISSION_STATUS_PENDING:
|
||||
raise PermissionDenied('The invoice is not in pending state.')
|
||||
|
||||
transmit_invoice.apply_async(args=(self.request.event.pk, invoice.pk, False))
|
||||
return Response(status=204)
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def retransmit(self, request, **kwargs):
|
||||
invoice = self.get_object()
|
||||
if invoice.shredded:
|
||||
raise PermissionDenied('The invoice file is no longer stored on the server.')
|
||||
|
||||
with transaction.atomic(durable=True):
|
||||
invoice = Invoice.objects.select_for_update(of=OF_SELF).get(pk=invoice.pk)
|
||||
|
||||
if invoice.transmission_status == Invoice.TRANSMISSION_STATUS_INFLIGHT:
|
||||
raise CurrentlyInflightException()
|
||||
|
||||
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_PENDING
|
||||
invoice.transmission_date = now()
|
||||
invoice.save(update_fields=["transmission_status", "transmission_date"])
|
||||
invoice.order.log_action(
|
||||
'pretix.event.order.invoice.retransmitted',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={
|
||||
'invoice': invoice.pk,
|
||||
'full_invoice_no': invoice.full_invoice_no,
|
||||
}
|
||||
)
|
||||
transmit_invoice.apply_async(args=(self.request.event.pk, invoice.pk, True))
|
||||
return Response(status=204)
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def regenerate(self, request, **kwargs):
|
||||
inv = self.get_object()
|
||||
if inv.canceled:
|
||||
raise ValidationError('The invoice has already been canceled.')
|
||||
if not inv.event.settings.invoice_regenerate_allowed:
|
||||
raise PermissionDenied('Invoices may not be changed after they are created.')
|
||||
if not inv.regenerate_allowed:
|
||||
raise PermissionDenied('Invoice may not be regenerated.')
|
||||
elif inv.shredded:
|
||||
raise PermissionDenied('The invoice file is no longer stored on the server.')
|
||||
elif inv.sent_to_organizer:
|
||||
@@ -2030,3 +2088,61 @@ class BlockedSecretViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
return BlockedTicketSecret.objects.filter(event=self.request.event)
|
||||
|
||||
|
||||
with scopes_disabled():
|
||||
class TransactionFilter(FilterSet):
|
||||
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
|
||||
event = django_filters.CharFilter(field_name='order__event', lookup_expr='slug__iexact')
|
||||
datetime_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte')
|
||||
datetime_before = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='lt')
|
||||
created_since = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='gte')
|
||||
created_before = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='lt')
|
||||
|
||||
class Meta:
|
||||
model = Transaction
|
||||
fields = {
|
||||
'item': ['exact', 'in'],
|
||||
'variation': ['exact', 'in'],
|
||||
'subevent': ['exact', 'in'],
|
||||
'tax_rule': ['exact', 'in'],
|
||||
'tax_code': ['exact', 'in'],
|
||||
'tax_rate': ['exact', 'in'],
|
||||
'fee_type': ['exact', 'in'],
|
||||
}
|
||||
|
||||
|
||||
class TransactionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = TransactionSerializer
|
||||
queryset = Transaction.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering = ('datetime', 'pk')
|
||||
ordering_fields = ('datetime', 'created', 'id',)
|
||||
filterset_class = TransactionFilter
|
||||
permission = 'can_view_orders'
|
||||
|
||||
def get_queryset(self):
|
||||
return Transaction.objects.filter(order__event=self.request.event).select_related("order")
|
||||
|
||||
|
||||
class OrganizerTransactionViewSet(TransactionViewSet):
|
||||
serializer_class = OrganizerTransactionSerializer
|
||||
permission = None
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Transaction.objects.filter(
|
||||
order__event__organizer=self.request.organizer
|
||||
).select_related("order", "order__event")
|
||||
|
||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||
qs = qs.filter(
|
||||
order__event__in=self.request.auth.get_events_with_permission("can_view_orders"),
|
||||
)
|
||||
elif self.request.user.is_authenticated:
|
||||
qs = qs.filter(
|
||||
order__event__in=self.request.user.get_events_with_permission("can_view_orders", request=self.request)
|
||||
)
|
||||
else:
|
||||
raise PermissionDenied("Unknown authentication scheme")
|
||||
|
||||
return qs
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -19,7 +19,9 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import operator
|
||||
from decimal import Decimal
|
||||
from functools import reduce
|
||||
|
||||
import django_filters
|
||||
from django.contrib.auth.hashers import make_password
|
||||
@@ -48,15 +50,18 @@ from pretix.api.serializers.organizer import (
|
||||
TeamInviteSerializer, TeamMemberSerializer, TeamSerializer,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
Customer, Device, GiftCard, GiftCardTransaction, Membership,
|
||||
MembershipType, Organizer, SalesChannel, SeatingPlan, Team, TeamAPIToken,
|
||||
TeamInvite, User,
|
||||
Customer, Device, Event, GiftCard, GiftCardTransaction, LogEntry,
|
||||
Membership, MembershipType, Organizer, SalesChannel, SeatingPlan, Team,
|
||||
TeamAPIToken, TeamInvite, User,
|
||||
)
|
||||
from pretix.base.plugins import (
|
||||
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
|
||||
)
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
|
||||
|
||||
class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
class OrganizerViewSet(mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = OrganizerSerializer
|
||||
queryset = Organizer.objects.none()
|
||||
lookup_field = 'slug'
|
||||
@@ -65,6 +70,7 @@ class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
filter_backends = (TotalOrderingFilter,)
|
||||
ordering = ('slug',)
|
||||
ordering_fields = ('name', 'slug')
|
||||
write_permission = "can_change_organizer_settings"
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_authenticated:
|
||||
@@ -83,6 +89,67 @@ class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
else:
|
||||
return Organizer.objects.filter(pk=self.request.auth.team.organizer_id)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
|
||||
original_data = self.get_serializer(instance=serializer.instance).data
|
||||
|
||||
current_plugins_value = serializer.instance.get_plugins()
|
||||
updated_plugins_value = serializer.validated_data.get('plugins', None)
|
||||
|
||||
super().perform_update(serializer)
|
||||
|
||||
if serializer.data == original_data:
|
||||
# Performance optimization: If nothing was changed, we do not need to save or log anything.
|
||||
# This costs us a few cycles on save, but avoids thousands of lines in our log.
|
||||
return
|
||||
|
||||
if updated_plugins_value is not None and set(updated_plugins_value) != set(current_plugins_value):
|
||||
enabled = {m: 'enabled' for m in updated_plugins_value if m not in current_plugins_value}
|
||||
disabled = {m: 'disabled' for m in current_plugins_value if m not in updated_plugins_value}
|
||||
changed = merge_dicts(enabled, disabled)
|
||||
|
||||
plugins_available = {
|
||||
p.module: p
|
||||
for p in get_all_plugins(organizer=serializer.instance)
|
||||
if not p.name.startswith('.') and getattr(p, 'visible', True)
|
||||
}
|
||||
qs = []
|
||||
for module in disabled:
|
||||
pluginmeta = plugins_available[module]
|
||||
level = getattr(pluginmeta, 'level', PLUGIN_LEVEL_EVENT)
|
||||
if level == PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID:
|
||||
qs.append(Q(plugins__regex='(^|,)' + module + '(,|$)'))
|
||||
|
||||
if qs:
|
||||
events_to_disable = set(self.request.organizer.events.filter(
|
||||
reduce(operator.or_, qs)
|
||||
).values_list("pk", flat=True))
|
||||
logentries_to_save = []
|
||||
events_to_save = []
|
||||
|
||||
for e in self.request.organizer.events.filter(pk__in=events_to_disable):
|
||||
for module in disabled:
|
||||
if module in e.get_plugins():
|
||||
logentries_to_save.append(
|
||||
e.log_action('pretix.event.plugins.disabled', user=self.request.user, auth=self.request.auth,
|
||||
data={'plugin': module}, save=False)
|
||||
)
|
||||
e.disable_plugin(module)
|
||||
events_to_save.append(e)
|
||||
|
||||
Event.objects.bulk_update(events_to_save, fields=["plugins"])
|
||||
LogEntry.objects.bulk_create(logentries_to_save)
|
||||
|
||||
for module, operation in changed.items():
|
||||
serializer.instance.log_action(
|
||||
'pretix.organizer.plugins.' + operation,
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={'plugin': module}
|
||||
)
|
||||
|
||||
|
||||
class SeatingPlanViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = SeatingPlanSerializer
|
||||
@@ -479,7 +546,8 @@ class DeviceViewSet(mixins.CreateModelMixin,
|
||||
|
||||
|
||||
class OrganizerSettingsView(views.APIView):
|
||||
permission = 'can_change_organizer_settings'
|
||||
permission = None
|
||||
write_permission = 'can_change_organizer_settings'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
@@ -78,6 +78,13 @@ class WebhookEvent:
|
||||
"""
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
@property
|
||||
def help_text(self) -> str:
|
||||
"""
|
||||
A human-readable description
|
||||
"""
|
||||
return ""
|
||||
|
||||
|
||||
def get_all_webhook_events():
|
||||
global _ALL_EVENTS
|
||||
@@ -97,9 +104,10 @@ def get_all_webhook_events():
|
||||
|
||||
|
||||
class ParametrizedWebhookEvent(WebhookEvent):
|
||||
def __init__(self, action_type, verbose_name):
|
||||
def __init__(self, action_type, verbose_name, help_text=""):
|
||||
self._action_type = action_type
|
||||
self._verbose_name = verbose_name
|
||||
self._help_text = help_text
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
@@ -110,6 +118,10 @@ class ParametrizedWebhookEvent(WebhookEvent):
|
||||
def verbose_name(self):
|
||||
return self._verbose_name
|
||||
|
||||
@property
|
||||
def help_text(self):
|
||||
return self._help_text
|
||||
|
||||
|
||||
class ParametrizedOrderWebhookEvent(ParametrizedWebhookEvent):
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
@@ -161,6 +173,19 @@ class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedVoucherWebhookEvent(ParametrizedWebhookEvent):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
# do not use content_object, this is also called in deletion
|
||||
return {
|
||||
'notification_id': logentry.pk,
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
'event': logentry.event.slug,
|
||||
'voucher': logentry.object_id,
|
||||
'action': logentry.action_type,
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedSubEventWebhookEvent(ParametrizedWebhookEvent):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
@@ -346,8 +371,9 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
),
|
||||
ParametrizedItemWebhookEvent(
|
||||
'pretix.event.item.*',
|
||||
_('Product changed (including product added or deleted and including changes to nested objects like '
|
||||
'variations or bundles)'),
|
||||
_('Product changed'),
|
||||
_('This includes product added or deleted and changes to nested objects like '
|
||||
'variations or bundles.'),
|
||||
),
|
||||
ParametrizedEventWebhookEvent(
|
||||
'pretix.event.live.activated',
|
||||
@@ -381,6 +407,19 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
'pretix.event.orders.waitinglist.voucher_assigned',
|
||||
_('Waiting list entry received voucher'),
|
||||
),
|
||||
ParametrizedVoucherWebhookEvent(
|
||||
'pretix.voucher.added',
|
||||
_('Voucher added'),
|
||||
),
|
||||
ParametrizedVoucherWebhookEvent(
|
||||
'pretix.voucher.changed',
|
||||
_('Voucher changed'),
|
||||
_('Only includes explicit changes to the voucher, not e.g. an increase of the number of redemptions.')
|
||||
),
|
||||
ParametrizedVoucherWebhookEvent(
|
||||
'pretix.voucher.deleted',
|
||||
_('Voucher deleted'),
|
||||
),
|
||||
ParametrizedCustomerWebhookEvent(
|
||||
'pretix.customer.created',
|
||||
_('Customer account created'),
|
||||
@@ -400,8 +439,12 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
def notify_webhooks(logentry_ids: list):
|
||||
if not isinstance(logentry_ids, list):
|
||||
logentry_ids = [logentry_ids]
|
||||
qs = LogEntry.all.select_related('event', 'event__organizer', 'organizer').filter(id__in=logentry_ids)
|
||||
_org, _at, webhooks = None, None, None
|
||||
qs = LogEntry.all.select_related(
|
||||
'event', 'event__organizer', 'organizer'
|
||||
).order_by(
|
||||
'action_type', 'organizer_id', 'event_id',
|
||||
).filter(id__in=logentry_ids)
|
||||
_org, _at, _ev, webhooks = None, None, None, None
|
||||
for logentry in qs:
|
||||
if not logentry.organizer:
|
||||
break # We need to know the organizer
|
||||
@@ -411,7 +454,7 @@ def notify_webhooks(logentry_ids: list):
|
||||
if not notification_type:
|
||||
break # Ignore, no webhooks for this event type
|
||||
|
||||
if _org != logentry.organizer or _at != logentry.action_type or webhooks is None:
|
||||
if _org != logentry.organizer or _at != logentry.action_type or _ev != logentry.event_id or webhooks is None:
|
||||
_org = logentry.organizer
|
||||
_at = logentry.action_type
|
||||
|
||||
@@ -476,7 +519,7 @@ def send_webhook(self, logentry_id: int, action_type: str, webhook_id: int, retr
|
||||
300, # + 5 minutes
|
||||
1200, # + 20 minutes
|
||||
3600, # + 60 minutes
|
||||
1440, # + 4 hours
|
||||
14400, # + 4 hours
|
||||
21600, # + 6 hours
|
||||
43200, # + 12 hours
|
||||
43200, # + 24 hours
|
||||
@@ -527,8 +570,10 @@ def send_webhook(self, logentry_id: int, action_type: str, webhook_id: int, retr
|
||||
if retry_count >= len(retry_intervals):
|
||||
return 'retry-given-up'
|
||||
elif retry_intervals[retry_count] < retry_celery_cutoff:
|
||||
send_webhook.apply_async(args=(logentry_id, action_type, webhook_id, retry_count + 1),
|
||||
countdown=retry_intervals[retry_count])
|
||||
send_webhook.apply_async(
|
||||
args=(logentry_id, action_type, webhook_id, retry_count + 1),
|
||||
countdown=retry_intervals[retry_count]
|
||||
)
|
||||
return 'retry-via-celery'
|
||||
else:
|
||||
webhook.retries.update_or_create(
|
||||
@@ -555,7 +600,10 @@ def send_webhook(self, logentry_id: int, action_type: str, webhook_id: int, retr
|
||||
if retry_count >= len(retry_intervals):
|
||||
return 'retry-given-up'
|
||||
elif retry_intervals[retry_count] < retry_celery_cutoff:
|
||||
send_webhook.apply_async(args=(logentry_id, action_type, webhook_id, retry_count + 1))
|
||||
send_webhook.apply_async(
|
||||
args=(logentry_id, action_type, webhook_id, retry_count + 1),
|
||||
countdown=retry_intervals[retry_count]
|
||||
)
|
||||
return 'retry-via-celery'
|
||||
else:
|
||||
webhook.retries.update_or_create(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user