mirror of
https://github.com/pretix/pretix.git
synced 2025-12-05 21:32:28 +00:00
Compare commits
435 Commits
hierarkey-
...
update-dja
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efd5ca5e25 | ||
|
|
e1f5678d7c | ||
|
|
609b7c82ee | ||
|
|
8d66e1e732 | ||
|
|
c925f094f2 | ||
|
|
5caaa8586d | ||
|
|
1b1cf1557d | ||
|
|
35d8a7eec5 | ||
|
|
d428c3e1a4 | ||
|
|
63850f3139 | ||
|
|
04c8270d43 | ||
|
|
74a960e239 | ||
|
|
5a1bcae085 | ||
|
|
051eb78312 | ||
|
|
15808e55fd | ||
|
|
c886c0b415 | ||
|
|
47472447eb | ||
|
|
1a40215e91 | ||
|
|
d3fde85c39 | ||
|
|
40bd66cb86 | ||
|
|
bdd94b1f8a | ||
|
|
1c907f6a6f | ||
|
|
39e3ed9c25 | ||
|
|
4b5711253e | ||
|
|
bd554c7c29 | ||
|
|
2261951b15 | ||
|
|
0f82e1cae6 | ||
|
|
b0760157ce | ||
|
|
de2dec9089 | ||
|
|
446c8e622b | ||
|
|
703be2ebb8 | ||
|
|
a56fbc896c | ||
|
|
7b6f5df985 | ||
|
|
d2087907d5 | ||
|
|
cbc2e611a2 | ||
|
|
02126a48fe | ||
|
|
be9af94131 | ||
|
|
dbe1944996 | ||
|
|
6181bdc2e9 | ||
|
|
fe40d1c491 | ||
|
|
9f263fbe4f | ||
|
|
fdd34f387a | ||
|
|
bfab523d83 | ||
|
|
8f69cb166d | ||
|
|
2fc7c23960 | ||
|
|
b0911c9e42 | ||
|
|
a5aa1030e5 | ||
|
|
681e682e73 | ||
|
|
db7518735a | ||
|
|
9c80f3038a | ||
|
|
4dc5bbae06 | ||
|
|
e997ca4242 | ||
|
|
278b4301e5 | ||
|
|
b648f9c46c | ||
|
|
9ce16b60d2 | ||
|
|
f4a7604632 | ||
|
|
7cebb3e93f | ||
|
|
c82726e13d | ||
|
|
2fcfc336d0 | ||
|
|
39ff84b2e2 | ||
|
|
44804f05f3 | ||
|
|
5e828ab8af | ||
|
|
313f4f326b | ||
|
|
ed43bf327e | ||
|
|
30aabc6253 | ||
|
|
5eade62121 | ||
|
|
2669afa1f8 | ||
|
|
d42c6f9b72 | ||
|
|
34f064ca33 | ||
|
|
ad8d0a270c | ||
|
|
363fcc3b56 | ||
|
|
9521ec2c52 | ||
|
|
688d341baf | ||
|
|
cdd4001378 | ||
|
|
d8d56ff020 | ||
|
|
44b3647689 | ||
|
|
818bb76e89 | ||
|
|
8c01cad06b | ||
|
|
86ca7c4440 | ||
|
|
d7b6856322 | ||
|
|
e2d9cbb41d | ||
|
|
57bc7563da | ||
|
|
7741e9f936 | ||
|
|
2f08bb465a | ||
|
|
4fb048e3a9 | ||
|
|
82af3012bd | ||
|
|
11425f21e6 | ||
|
|
55f35a998b | ||
|
|
53cfce2ce7 | ||
|
|
68ce335034 | ||
|
|
6ce5c1a26a | ||
|
|
ae4540acd7 | ||
|
|
a814d31c9b | ||
|
|
ef9863518b | ||
|
|
eb740204d4 | ||
|
|
5583298322 | ||
|
|
74b06435a0 | ||
|
|
a26b0c5512 | ||
|
|
095e07b3f1 | ||
|
|
b2eb1b6231 | ||
|
|
9d838f1d9c | ||
|
|
cbf6bd29b0 | ||
|
|
0e84df9af2 | ||
|
|
7feacc8a1a | ||
|
|
5ada22dd15 | ||
|
|
6d56011695 | ||
|
|
da167eacd5 | ||
|
|
5df0c55daa | ||
|
|
b01e798b48 | ||
|
|
0256ee76db | ||
|
|
e99eecb8be | ||
|
|
d1ae579a6f | ||
|
|
90d3f50eba | ||
|
|
c1b6d660a4 | ||
|
|
0b88b63597 | ||
|
|
0cc6439748 | ||
|
|
ad53c48d0f | ||
|
|
59a5c11ef6 | ||
|
|
1cb2d443f9 | ||
|
|
a0dbf6c5db | ||
|
|
fd9d03786b | ||
|
|
7041d40972 | ||
|
|
0b46982e6d | ||
|
|
29906c6288 | ||
|
|
3380bd3e82 | ||
|
|
6ae8e7cbb6 | ||
|
|
23c2d9266e | ||
|
|
ba155faaa3 | ||
|
|
fd177fa89f | ||
|
|
0b051c1400 | ||
|
|
af8d0f0b65 | ||
|
|
1b7bba195c | ||
|
|
f056f77dc0 | ||
|
|
ee4e7f618f | ||
|
|
cd450f1780 | ||
|
|
fc876978b2 | ||
|
|
d8efaa47f7 | ||
|
|
f0c3514588 | ||
|
|
e1ad4d9dba | ||
|
|
3ab587883e | ||
|
|
b02e1a1515 | ||
|
|
41780add40 | ||
|
|
b07a61e4f1 | ||
|
|
dead2a9bed | ||
|
|
94389c3913 | ||
|
|
3e972eddbf | ||
|
|
cdeb1e86bd | ||
|
|
9a69b76880 | ||
|
|
7d5df2b69e | ||
|
|
d203eee5ab | ||
|
|
0d07615006 | ||
|
|
1fbf75d89f | ||
|
|
f38c36c034 | ||
|
|
1488960c54 | ||
|
|
9655f3d15d | ||
|
|
4ef01df9ea | ||
|
|
7ce2d7ccb4 | ||
|
|
04fae9ea14 | ||
|
|
4b9f1712f0 | ||
|
|
9461ac27f9 | ||
|
|
1e0ede529c | ||
|
|
e386ed4352 | ||
|
|
8676033fe1 | ||
|
|
b9ca68c0be | ||
|
|
b2aca033e0 | ||
|
|
2d0b657d0f | ||
|
|
d54807073a | ||
|
|
ca1efc0a58 | ||
|
|
3746077881 | ||
|
|
04075cfc45 | ||
|
|
bdd47ee32b | ||
|
|
ff42c1fe5c | ||
|
|
0ba5c0c143 | ||
|
|
f6aa17a0ff | ||
|
|
5882a728bf | ||
|
|
11acd4ca53 | ||
|
|
40db7d939f | ||
|
|
5563183255 | ||
|
|
dfd52f05ff | ||
|
|
15ea7c65e4 | ||
|
|
0ab633ce7b | ||
|
|
cd0c6b2b0f | ||
|
|
d7be7dc1c3 | ||
|
|
aa1044cf91 | ||
|
|
3df5c890a8 | ||
|
|
0775d09df4 | ||
|
|
bccfefecf1 | ||
|
|
a78c8c910f | ||
|
|
cdc265c409 | ||
|
|
0f230be785 | ||
|
|
b077417eef | ||
|
|
0ee028a9da | ||
|
|
0c05296df3 | ||
|
|
9098eb2a26 | ||
|
|
b68f68740c | ||
|
|
c8f90c9645 | ||
|
|
98bed10e25 | ||
|
|
0f7928268b | ||
|
|
6cf916d0e6 | ||
|
|
df9147d207 | ||
|
|
0f25a1d6c8 | ||
|
|
d9572420eb | ||
|
|
bdf6b6cfa8 | ||
|
|
cdb69281ad | ||
|
|
855b8c800e | ||
|
|
3562952e6d | ||
|
|
068f82457d | ||
|
|
8a3da37b45 | ||
|
|
71f2c8093f | ||
|
|
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 |
@@ -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.
|
||||
|
||||
@@ -421,3 +421,94 @@ Annulment of a check-in
|
||||
: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.
|
||||
|
||||
|
||||
Check-in history
|
||||
----------------
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal ID of the check-in
|
||||
successful boolean Whether the check-in was successful
|
||||
error_reason string Category of reason why the check-in was unsuccessful. Currently
|
||||
``"canceled"``, ``"invalid"``, ``"unpaid"`` ``"product"``,
|
||||
``"rules"``, ``"revoked"``, ``"incomplete"``, ``"already_redeemed"``,
|
||||
``"ambiguous"``, ``"error"``, ``"blocked"``, ``"unapproved"``,
|
||||
``"invalid_time"``, ``"annulled"`` or ``null``
|
||||
error_explanation string Additional, human-readable reason for the check-in to be unsuccessful (or ``null``)
|
||||
position integer Internal ID of the order position (or ``null`` for unknown scans)
|
||||
datetime datetime Logical time when the check-in happened
|
||||
created datetime Time when the check-in appeared on the server
|
||||
list integer Internal ID of the check-in list
|
||||
auto_checked_in boolean Whether the check-in was performed by the system automatically
|
||||
gate integer Internal ID of the gate (or ``null``)
|
||||
device integer Internal ID of the device (or ``null``)
|
||||
device_id integer Organizer-internal ID of the device (or ``null``)
|
||||
type string Type of check-in, currently ``"entry"`` or ``"exit"``
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkins/
|
||||
|
||||
Returns a list of all check-in events within a given event.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/checkins/ 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": 1,
|
||||
"successful": true,
|
||||
"error_reason": null,
|
||||
"error_explanation": null,
|
||||
"position": 1234,
|
||||
"datetime": "2017-12-25T12:45:23Z",
|
||||
"created": "2017-12-25T12:45:23Z",
|
||||
"list": 2,
|
||||
"auto_checked_in": false,
|
||||
"gate": null,
|
||||
"device": null,
|
||||
"device_id": null,
|
||||
"type": "entry",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||
:query datetime created_since: Only return check-ins that have been created since the given date (inclusive).
|
||||
:query datetime created_before: Only return check-ins that have been created before the given date (exclusive).
|
||||
:query datetime datetime_since: Only return check-ins that have happened since the given date (inclusive).
|
||||
:query datetime datetime_before: Only return check-ins that have happened before the given date (exclusive).
|
||||
:query boolean successful: Only return check-ins that have (not) been successful.
|
||||
:query boolean error_reason: Only return check-ins with a specific error reason.
|
||||
:query integer list: Only return check-ins from a specific list.
|
||||
:query string type: Only return check-ins of a specific type.
|
||||
:query integer gate: Only return check-ins from a specific gate.
|
||||
:query integer device: Only return check-ins from a specific device.
|
||||
:query boolean auto_checked_in: Only return check-ins that are (not) auto-checked in.
|
||||
: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 the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
|
||||
@@ -424,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)/
|
||||
|
||||
@@ -19,6 +19,7 @@ at :ref:`plugin-docs`.
|
||||
item_bundles
|
||||
item_add-ons
|
||||
item_meta_properties
|
||||
item_program_times
|
||||
questions
|
||||
question_options
|
||||
quotas
|
||||
|
||||
@@ -22,10 +22,13 @@ invoice_from_name string Sender address:
|
||||
invoice_from string Sender address: Address lines
|
||||
invoice_from_zipcode string Sender address: ZIP code
|
||||
invoice_from_city string Sender address: City
|
||||
invoice_from_state string Sender address: State (only used in some countries)
|
||||
invoice_from_country string Sender address: Country code
|
||||
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
|
||||
@@ -35,6 +38,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
|
||||
@@ -77,17 +81,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
|
||||
@@ -110,6 +109,12 @@ 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``).
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
@@ -121,6 +126,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
|
||||
--------------------
|
||||
@@ -159,11 +234,13 @@ List of all invoices
|
||||
"invoice_from": "Demo street 12",
|
||||
"invoice_from_zipcode":"",
|
||||
"invoice_from_city":"Demo town",
|
||||
"invoice_from_state":"CA",
|
||||
"invoice_from_country":"US",
|
||||
"invoice_from_tax_id":"",
|
||||
"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",
|
||||
@@ -172,6 +249,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,
|
||||
@@ -193,6 +271,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",
|
||||
@@ -204,7 +284,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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -299,11 +383,13 @@ Fetching individual invoices
|
||||
"invoice_from": "Demo street 12",
|
||||
"invoice_from_zipcode":"",
|
||||
"invoice_from_city":"Demo town",
|
||||
"invoice_from_state":"CA",
|
||||
"invoice_from_country":"US",
|
||||
"invoice_from_tax_id":"",
|
||||
"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",
|
||||
@@ -312,6 +398,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,
|
||||
@@ -333,6 +420,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",
|
||||
@@ -344,7 +433,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
|
||||
@@ -449,3 +542,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
|
||||
223
doc/api/resources/item_program_times.rst
Normal file
223
doc/api/resources/item_program_times.rst
Normal file
@@ -0,0 +1,223 @@
|
||||
Item program times
|
||||
==================
|
||||
|
||||
Resource description
|
||||
--------------------
|
||||
|
||||
Program times for products (items) that can be set in addition to event times, e.g. to display seperate schedules within an event.
|
||||
Note that ``program_times`` are not available for items inside event series.
|
||||
The program times resource contains the following public fields:
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal ID of the program time
|
||||
start datetime The start date time for this program time slot.
|
||||
end datetime The end date time for this program time slot.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: TODO
|
||||
|
||||
The resource has been added.
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/
|
||||
|
||||
Returns a list of all program times for a given item.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/items/11/program_times/ 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": 3,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 2,
|
||||
"start": "2025-08-14T22:00:00Z",
|
||||
"end": "2025-08-15T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"start": "2025-08-12T22:00:00Z",
|
||||
"end": "2025-08-13T22:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"start": "2025-08-15T22:00:00Z",
|
||||
"end": "2025-08-17T22:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param item: The ``id`` field of the item to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event/item does not exist **or** you have no permission to view this resource.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/(id)/
|
||||
|
||||
Returns information on one program time, identified by its ID.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/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: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"start": "2025-08-15T22:00:00Z",
|
||||
"end": "2025-10-27T23:00:00Z"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param item: The ``id`` field of the item to fetch
|
||||
:param id: The ``id`` field of the program time to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/
|
||||
|
||||
Creates a new program time
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"start": "2025-08-15T10:00:00Z",
|
||||
"end": "2025-08-15T22:00:00Z"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 17,
|
||||
"start": "2025-08-15T10:00:00Z",
|
||||
"end": "2025-08-15T22:00:00Z"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event/item to create a program time for
|
||||
:param event: The ``slug`` field of the event to create a program time for
|
||||
:param item: The ``id`` field of the item to create a program time for
|
||||
:statuscode 201: no error
|
||||
:statuscode 400: The program time could not be created due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this resource.
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/(id)/
|
||||
|
||||
Update a program time. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
|
||||
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
|
||||
want to change.
|
||||
|
||||
You can change all fields of the resource except the ``id`` field.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
Content-Length: 94
|
||||
|
||||
{
|
||||
"start": "2025-08-14T10:00:00Z"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"start": "2025-08-14T10:00:00Z",
|
||||
"end": "2025-08-15T12:00:00Z"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the item to modify
|
||||
:param id: The ``id`` field of the program time to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The program time could not be modified due to invalid submitted data
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
|
||||
|
||||
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/items/(id)/program_times/(id)/
|
||||
|
||||
Delete a program time.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Vary: Accept
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the item to modify
|
||||
:param id: The ``id`` field of the program time to delete
|
||||
:statuscode 204: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
|
||||
@@ -139,6 +139,10 @@ has_variations boolean Shows whether
|
||||
variations list of objects A list with one object for each variation of this item.
|
||||
Can be empty. Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
program_times list of objects A list with one object for each program time of this item.
|
||||
Can be empty. Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
Not available for items in event series.
|
||||
├ id integer Internal ID of the variation
|
||||
├ value multi-lingual string The "name" of the variation
|
||||
├ default_price money (string) The price set directly for this variation or ``null``
|
||||
@@ -225,6 +229,10 @@ meta_data object Values set fo
|
||||
|
||||
The ``hidden_if_item_available_mode`` attributes has been added.
|
||||
|
||||
.. versionchanged:: 2025.9
|
||||
|
||||
The ``program_times`` attribute has been added.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
@@ -232,9 +240,11 @@ Please note that an item either always has variations or never has. Once created
|
||||
change to an item without and vice versa. To create an item with variations ensure that you POST an item with at least
|
||||
one variation.
|
||||
|
||||
Also note that ``variations``, ``bundles``, and ``addons`` are only supported on ``POST``. To update/delete variations,
|
||||
bundles, and add-ons please use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT``
|
||||
with nested ``variations``, ``bundles`` and/or ``addons``.
|
||||
Also note that ``variations``, ``bundles``, ``addons`` and ``program_times`` are only supported on ``POST``. To update/delete variations,
|
||||
bundles, add-ons and program times please use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT``
|
||||
with nested ``variations``, ``bundles``, ``addons`` and/or ``program_times``.
|
||||
|
||||
``program_times`` is not available to items in event series.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
@@ -373,7 +383,8 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -525,7 +536,8 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": []
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -653,7 +665,13 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": [
|
||||
{
|
||||
"start": "2025-08-14T22:00:00Z",
|
||||
"end": "2025-08-15T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -773,7 +791,13 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": [
|
||||
{
|
||||
"start": "2025-08-14T22:00:00Z",
|
||||
"end": "2025-08-15T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event to create an item for
|
||||
@@ -789,8 +813,9 @@ Endpoints
|
||||
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
|
||||
want to change.
|
||||
|
||||
You can change all fields of the resource except the ``has_variations``, ``variations`` and the ``addon`` field. If
|
||||
you need to update/delete variations or add-ons please use the nested dedicated endpoints.
|
||||
You can change all fields of the resource except the ``has_variations``, ``variations``, ``addon`` and the
|
||||
``program_times`` field. If you need to update/delete variations, add-ons or program times, please use the nested
|
||||
dedicated endpoints.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@@ -924,7 +949,8 @@ Endpoints
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"bundles": []
|
||||
"bundles": [],
|
||||
"program_times": []
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
|
||||
@@ -41,6 +41,7 @@ expires datetime The order will
|
||||
payment_date date **DEPRECATED AND INACCURATE** Date of payment receipt
|
||||
payment_provider string **DEPRECATED AND INACCURATE** Payment provider used for this order
|
||||
total money (string) Total value of this order
|
||||
tax_rounding_mode string Tax rounding mode, see :ref:`algorithms-rounding`
|
||||
comment string Internal comment on this order
|
||||
api_meta object Meta data for that order. Only available through API, no guarantees
|
||||
on the content structure. You can use this to save references to your system.
|
||||
@@ -65,11 +66,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
|
||||
@@ -142,6 +148,14 @@ 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.
|
||||
|
||||
.. versionchanged:: 2025.10
|
||||
|
||||
The ``tax_rounding_mode`` attribute has been added.
|
||||
|
||||
.. _order-position-resource:
|
||||
|
||||
Order position resource
|
||||
@@ -349,6 +363,7 @@ List of all orders
|
||||
"payment_provider": "banktransfer",
|
||||
"fees": [],
|
||||
"total": "23.00",
|
||||
"tax_rounding_mode": "line",
|
||||
"comment": "",
|
||||
"custom_followup_at": null,
|
||||
"checkin_attention": false,
|
||||
@@ -368,7 +383,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": [
|
||||
{
|
||||
@@ -407,6 +424,7 @@ List of all orders
|
||||
"seat": null,
|
||||
"checkins": [
|
||||
{
|
||||
"id": 1337,
|
||||
"list": 44,
|
||||
"type": "entry",
|
||||
"gate": null,
|
||||
@@ -590,6 +608,7 @@ Fetching individual orders
|
||||
"payment_provider": "banktransfer",
|
||||
"fees": [],
|
||||
"total": "23.00",
|
||||
"tax_rounding_mode": "line",
|
||||
"comment": "",
|
||||
"api_meta": {},
|
||||
"custom_followup_at": null,
|
||||
@@ -610,7 +629,9 @@ Fetching individual orders
|
||||
"state": "",
|
||||
"internal_reference": "",
|
||||
"vat_id": "EU123456789",
|
||||
"vat_id_validated": false
|
||||
"vat_id_validated": false,
|
||||
"transmission_type": "email",
|
||||
"transmission_info": {}
|
||||
},
|
||||
"positions": [
|
||||
{
|
||||
@@ -649,6 +670,7 @@ Fetching individual orders
|
||||
"seat": null,
|
||||
"checkins": [
|
||||
{
|
||||
"id": 1337,
|
||||
"list": 44,
|
||||
"type": "entry",
|
||||
"gate": null,
|
||||
@@ -996,6 +1018,7 @@ Creating orders
|
||||
provider will not be called to do anything about this (i.e. if you pass a bank account to a debit provider, *no*
|
||||
charge will be created), this is just informative in case you *handled the payment already*.
|
||||
* ``payment_date`` (optional) – Date and time of the completion of the payment.
|
||||
* ``tax_rounding_mode`` (optional)
|
||||
* ``comment`` (optional)
|
||||
* ``custom_followup_at`` (optional)
|
||||
* ``checkin_attention`` (optional)
|
||||
@@ -1015,8 +1038,10 @@ Creating orders
|
||||
* ``internal_reference``
|
||||
* ``vat_id``
|
||||
* ``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!
|
||||
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``
|
||||
|
||||
@@ -1041,6 +1066,7 @@ Creating orders
|
||||
* ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
|
||||
* ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected)
|
||||
* ``use_reusable_medium`` (optional, causes the new ticket to take over the given reusable medium, identified by its ID)
|
||||
* ``discount`` (optional, only possible if ``price`` is set; attention: if this is set to not-``null`` on any position, automatic calculation of discounts will not run)
|
||||
* ``answers``
|
||||
|
||||
* ``question``
|
||||
@@ -1617,6 +1643,7 @@ List of all order positions
|
||||
"blocked": null,
|
||||
"checkins": [
|
||||
{
|
||||
"id": 1337,
|
||||
"list": 44,
|
||||
"type": "entry",
|
||||
"gate": null,
|
||||
@@ -1745,6 +1772,7 @@ Fetching individual positions
|
||||
"seat": null,
|
||||
"checkins": [
|
||||
{
|
||||
"id": 1337,
|
||||
"list": 44,
|
||||
"type": "entry",
|
||||
"gate": null,
|
||||
@@ -2489,6 +2517,7 @@ Order payment endpoints
|
||||
|
||||
{
|
||||
"amount": "23.00",
|
||||
"comment": "Overpayment",
|
||||
"mark_canceled": false
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ 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.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
@@ -53,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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -87,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
|
||||
@@ -95,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
|
||||
------------------
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@ available_number integer Number of avail
|
||||
slightly out of date. ``null`` means unlimited.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 2025.7
|
||||
|
||||
The attribute ``ignore_for_event_availability`` has been added.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ budget money (string) The budget a vo
|
||||
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
|
||||
---------
|
||||
|
||||
@@ -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,3 +178,124 @@ Flowchart
|
||||
---------
|
||||
|
||||
.. image:: /images/cart_pricing.png
|
||||
|
||||
|
||||
.. _`algorithms-rounding`:
|
||||
|
||||
Rounding of taxes
|
||||
-----------------
|
||||
|
||||
pretix internally always stores taxes on a per-line level, like this:
|
||||
|
||||
========== ========== =========== ======= =============
|
||||
Product Tax rate Net price Tax Gross price
|
||||
========== ========== =========== ======= =============
|
||||
Ticket A 19 % 84.03 15.97 100.00
|
||||
Ticket B 19 % 84.03 15.97 100.00
|
||||
Ticket C 19 % 84.03 15.97 100.00
|
||||
Ticket D 19 % 84.03 15.97 100.00
|
||||
Ticket E 19 % 84.03 15.97 100.00
|
||||
Sum 420.15 79.85 500.00
|
||||
========== ========== =========== ======= =============
|
||||
|
||||
Whether the net price is computed from the gross price or vice versa is configured on the tax rule and may differ for every line.
|
||||
|
||||
The line-based computation has a few significant advantages:
|
||||
|
||||
- We can report both net and gross prices for every individual ticket.
|
||||
|
||||
- We can report both net and gross prices for every filter imaginable, such as the gross sum of all sales of Ticket A
|
||||
or the net sum of all sales for a specific date in an event series. All numbers will be exact.
|
||||
|
||||
- When splitting the order into two, both net price and gross price are split without any changes in rounding.
|
||||
|
||||
The main disadvantage is that the tax looks "wrong" when computed from the sum. Taking the sum of net prices (420.15)
|
||||
and multiplying it with the tax rate (19%) yields a tax amount of 79.83 (instead of 79.85) and a gross sum of 499.98
|
||||
(instead of 500.00). This becomes a problem when juristictions, data formats, or external systems expect this calculation
|
||||
to work on the level of the entire order. A prominent example is the EN 16931 standard for e-invoicing that
|
||||
does not allow the computation as created by pretix.
|
||||
|
||||
However, calculating the tax rate from the net total has significant disadvantages:
|
||||
|
||||
- It is impossible to guarantee a stable gross price this way, i.e. if you advertise a price of €100 per ticket to
|
||||
consumers, they will be confused when they only need to pay €499.98 for 5 tickets.
|
||||
|
||||
- Some prices are impossible, e.g. you cannot sell a ticket for a gross price of €99.99 at a 19% tax rate, since there
|
||||
is no two-decimal net price that would be computed to a gross price of €99.99.
|
||||
|
||||
- When splitting an order into two, the combined of the new orders is not guaranteed to be the same as the total of the
|
||||
original order. Therefore, additional payments or refunds of very small amounts might be necessary.
|
||||
|
||||
To allow organizers to make their own choices on this matter, pretix provides the following options:
|
||||
|
||||
Compute taxes for every line individually
|
||||
"""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Algorithm identifier: ``line``
|
||||
|
||||
This is our original algorithm where the tax value is rounded for every line individually.
|
||||
|
||||
**This is our current default algorithm and we recommend it whenever you do not have different requirements** (see below).
|
||||
For the example above:
|
||||
|
||||
========== ========== =========== ======= =============
|
||||
Product Tax rate Net price Tax Gross price
|
||||
========== ========== =========== ======= =============
|
||||
Ticket A 19 % 84.03 15.97 100.00
|
||||
Ticket B 19 % 84.03 15.97 100.00
|
||||
Ticket C 19 % 84.03 15.97 100.00
|
||||
Ticket D 19 % 84.03 15.97 100.00
|
||||
Ticket E 19 % 84.03 15.97 100.00
|
||||
Sum 420.15 79.85 500.00
|
||||
========== ========== =========== ======= =============
|
||||
|
||||
|
||||
Compute taxes based on net total
|
||||
""""""""""""""""""""""""""""""""
|
||||
|
||||
Algorithm identifier: ``sum_by_net``
|
||||
|
||||
In this algorithm, the tax value and gross total are computed from the sum of the net prices. To accomplish this within
|
||||
our data model, the gross price and tax of some of the tickets will be changed by the minimum currency unit (e.g. €0.01).
|
||||
The net price of the tickets always stay the same.
|
||||
|
||||
**This is the algorithm intended by EN 16931 invoices and our recommendation to use for e-invoicing when (primarily) business customers are involved.**
|
||||
|
||||
The main downside is that it might be confusing when selling to consumers, since the amounts to be paid change in unexpected ways.
|
||||
For the example above, the customer expects to pay 5 times 100.00, but they are are in fact charged 499.98:
|
||||
|
||||
========== ========== =========== ============================== ==============================
|
||||
Product Tax rate Net price Tax Gross price
|
||||
========== ========== =========== ============================== ==============================
|
||||
Ticket A 19 % 84.03 15.96 (incl. -0.01 rounding) 99.99 (incl. -0.01 rounding)
|
||||
Ticket B 19 % 84.03 15.96 (incl. -0.01 rounding) 99.99 (incl. -0.01 rounding)
|
||||
Ticket C 19 % 84.03 15.97 100.00
|
||||
Ticket D 19 % 84.03 15.97 100.00
|
||||
Ticket E 19 % 84.03 15.97 100.00
|
||||
Sum 420.15 78.83 499.98
|
||||
========== ========== =========== ============================== ==============================
|
||||
|
||||
Compute taxes based on net total with stable gross prices
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Algorithm identifier: ``sum_by_net_keep_gross``
|
||||
|
||||
In this algorithm, the tax value and gross total are computed from the sum of the net prices. However, the net prices
|
||||
of some of the tickets will be changed automatically by the minimum currency unit (e.g. €0.01) such that the resulting
|
||||
gross prices stay the same.
|
||||
|
||||
**This is less confusing to consumers and the end result is still compliant to EN 16931, so we recommend this for e-invoicing when (primarily) consumers are involved.**
|
||||
|
||||
The main downside is that it might be confusing when selling to business customers, since the prices of the identical tickets appear to be different.
|
||||
Full computation for the example above:
|
||||
|
||||
========== ========== ============================= ============================== =============
|
||||
Product Tax rate Net price Tax Gross price
|
||||
========== ========== ============================= ============================== =============
|
||||
Ticket A 19 % 84.04 (incl. 0.01 rounding) 15.96 (incl. -0.01 rounding) 100.00
|
||||
Ticket B 19 % 84.04 (incl. 0.01 rounding) 15.96 (incl. -0.01 rounding) 100.00
|
||||
Ticket C 19 % 84.03 15.97 100.00
|
||||
Ticket D 19 % 84.03 15.97 100.00
|
||||
Ticket E 19 % 84.03 15.97 100.00
|
||||
Sum 420.17 79.83 500.00
|
||||
========== ========== ============================= ============================== =============
|
||||
|
||||
@@ -11,19 +11,22 @@ is unidirectionally sending (order, customer, ticket, ...) data into external sy
|
||||
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.
|
||||
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 registering event participants in a mailing list could start
|
||||
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
|
||||
from pretix.base.datasync.datasync import (OutboundSyncProvider, datasync_providers)
|
||||
|
||||
@datasync_providers.register
|
||||
class MyListSyncProvider(OutboundSyncProvider):
|
||||
identifier = "my_list"
|
||||
display_name = "My Mailing List Service"
|
||||
# ...c
|
||||
# ...
|
||||
|
||||
|
||||
The plugin must register listeners in `signals.py` for all signals that should to trigger a sync and
|
||||
@@ -36,7 +39,10 @@ within it has to call :meth:`MyListSyncProvider.enqueue_order` to enqueue the or
|
||||
MyListSyncProvider.enqueue_order(order, "order_placed")
|
||||
|
||||
|
||||
Furthermore, most of these plugins need to translate data from some pretix objects (e.g. orders)
|
||||
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.
|
||||
@@ -86,6 +92,7 @@ shown. Therein, a ``sync_object_with_properties`` method is defined as follows:
|
||||
|
||||
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,
|
||||
|
||||
@@ -23,7 +23,7 @@ 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
|
||||
"""""""""
|
||||
@@ -37,7 +37,7 @@ 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
|
||||
|
||||
@@ -13,6 +13,7 @@ Contents:
|
||||
email
|
||||
placeholder
|
||||
invoice
|
||||
invoicetransmission
|
||||
shredder
|
||||
import
|
||||
customview
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 49 KiB |
@@ -23,6 +23,7 @@ partition "For every cart position" {
|
||||
--> "Store as line_price (gross), tax_rate"
|
||||
}
|
||||
--> "Apply discount engine"
|
||||
--> "Apply tax rounding"
|
||||
--> "Store as price (gross)"
|
||||
|
||||
@enduml
|
||||
|
||||
@@ -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.*
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "pretix"
|
||||
dynamic = ["version"]
|
||||
description = "Reinventing presales, one ticket at a time"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
license = {file = "LICENSE"}
|
||||
keywords = ["tickets", "web", "shop", "ecommerce"]
|
||||
authors = [
|
||||
@@ -22,29 +22,30 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Framework :: Django :: 4.2",
|
||||
"Framework :: Django :: 5.2",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab
|
||||
"babel",
|
||||
"BeautifulSoup4==4.13.*",
|
||||
"BeautifulSoup4==4.14.*",
|
||||
"bleach==6.2.*",
|
||||
"celery==5.5.*",
|
||||
"celery==5.6.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=44.0.0",
|
||||
"css-inline==0.17.*",
|
||||
"css-inline==0.18.*",
|
||||
"defusedcsv>=1.1.0",
|
||||
"Django[argon2]==4.2.*,>=4.2.15",
|
||||
"dnspython==2.*",
|
||||
"Django[argon2]==5.2.*",
|
||||
"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==5.0",
|
||||
"django-markup",
|
||||
@@ -61,10 +62,10 @@ dependencies = [
|
||||
"importlib_metadata==8.*", # Polyfill, we can probably drop this once we require Python 3.10+
|
||||
"isoweek",
|
||||
"jsonschema",
|
||||
"kombu==5.5.*",
|
||||
"kombu==5.6.*",
|
||||
"libsass==0.23.*",
|
||||
"lxml",
|
||||
"markdown==3.8.2", # 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.3.*",
|
||||
@@ -76,12 +77,12 @@ dependencies = [
|
||||
"phonenumberslite==9.0.*",
|
||||
"Pillow==11.3.*",
|
||||
"pretix-plugin-build",
|
||||
"protobuf==6.31.*",
|
||||
"protobuf==6.33.*",
|
||||
"psycopg2-binary",
|
||||
"pycountry",
|
||||
"pycparser==2.22",
|
||||
"pycparser==2.23",
|
||||
"pycryptodome==3.23.*",
|
||||
"pypdf==6.0.*",
|
||||
"pypdf==6.4.*",
|
||||
"python-bidi==0.6.*", # Support for Arabic in reportlab
|
||||
"python-dateutil==2.9.*",
|
||||
"pytz",
|
||||
@@ -90,37 +91,36 @@ dependencies = [
|
||||
"qrcode==8.2",
|
||||
"redis==6.4.*",
|
||||
"reportlab==4.4.*",
|
||||
"requests==2.31.*",
|
||||
"sentry-sdk==2.34.*",
|
||||
"sepaxml==2.6.*",
|
||||
"requests==2.32.*",
|
||||
"sentry-sdk==2.47.*",
|
||||
"sepaxml==2.7.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
"tlds>=2020041600",
|
||||
"tqdm==4.*",
|
||||
"ua-parser==1.0.*",
|
||||
"vat_moss_forked==2020.3.20.0.11.0",
|
||||
"vobject==0.9.*",
|
||||
"webauthn==2.6.*",
|
||||
"webauthn==2.7.*",
|
||||
"zeep==4.3.*"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
memcached = ["pylibmc"]
|
||||
dev = [
|
||||
"aiohttp==3.12.*",
|
||||
"aiohttp==3.13.*",
|
||||
"coverage",
|
||||
"coveralls",
|
||||
"fakeredis==2.31.*",
|
||||
"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.8.*",
|
||||
"pytest==8.4.*",
|
||||
|
||||
@@ -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.7.0.dev0"
|
||||
__version__ = "2025.11.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.
|
||||
|
||||
@@ -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,23 @@
|
||||
# Generated by Django 4.2.24 on 2025-11-14 16:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixapi", "0013_alter_webhookcallretry_retry_not_before"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="webhook",
|
||||
name="target_url",
|
||||
field=models.URLField(max_length=1024),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="webhookcall",
|
||||
name="target_url",
|
||||
field=models.URLField(max_length=1024),
|
||||
),
|
||||
]
|
||||
@@ -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.
|
||||
@@ -114,7 +114,7 @@ class OAuthRefreshToken(AbstractRefreshToken):
|
||||
class WebHook(models.Model):
|
||||
organizer = models.ForeignKey('pretixbase.Organizer', on_delete=models.CASCADE, related_name='webhooks')
|
||||
enabled = models.BooleanField(default=True, verbose_name=_("Enable webhook"))
|
||||
target_url = models.URLField(verbose_name=_("Target URL"), max_length=255)
|
||||
target_url = models.URLField(verbose_name=_("Target URL"), max_length=1024)
|
||||
all_events = models.BooleanField(default=True, verbose_name=_("All events (including newly created ones)"))
|
||||
limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True)
|
||||
comment = models.CharField(verbose_name=_("Comment"), max_length=255, null=True, blank=True)
|
||||
@@ -140,7 +140,7 @@ class WebHookEventListener(models.Model):
|
||||
class WebHookCall(models.Model):
|
||||
webhook = models.ForeignKey('WebHook', on_delete=models.CASCADE, related_name='calls')
|
||||
datetime = models.DateTimeField(auto_now_add=True)
|
||||
target_url = models.URLField(max_length=255)
|
||||
target_url = models.URLField(max_length=1024)
|
||||
action_type = models.CharField(max_length=255)
|
||||
is_retry = models.BooleanField(default=False)
|
||||
execution_time = models.FloatField(null=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.
|
||||
@@ -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
|
||||
|
||||
@@ -796,6 +795,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'invoice_address_asked',
|
||||
'invoice_address_required',
|
||||
'invoice_address_vatid',
|
||||
'invoice_address_vatid_required_countries',
|
||||
'invoice_address_company_required',
|
||||
'invoice_address_beneficiary',
|
||||
'invoice_address_custom_field',
|
||||
@@ -806,6 +806,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',
|
||||
@@ -820,6 +821,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'invoice_address_from',
|
||||
'invoice_address_from_zipcode',
|
||||
'invoice_address_from_city',
|
||||
'invoice_address_from_state',
|
||||
'invoice_address_from_country',
|
||||
'invoice_address_from_tax_id',
|
||||
'invoice_address_from_vat_id',
|
||||
@@ -829,6 +831,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'invoice_eu_currencies',
|
||||
'invoice_logo_image',
|
||||
'invoice_renderer_highlight_order_code',
|
||||
'tax_rounding',
|
||||
'cancel_allow_user',
|
||||
'cancel_allow_user_until',
|
||||
'cancel_allow_user_unpaid_keep',
|
||||
@@ -941,6 +944,7 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
|
||||
'invoice_address_asked',
|
||||
'invoice_address_required',
|
||||
'invoice_address_vatid',
|
||||
'invoice_address_vatid_required_countries',
|
||||
'invoice_address_company_required',
|
||||
'invoice_address_beneficiary',
|
||||
'invoice_address_custom_field',
|
||||
@@ -951,6 +955,7 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
|
||||
'invoice_address_from',
|
||||
'invoice_address_from_zipcode',
|
||||
'invoice_address_from_city',
|
||||
'invoice_address_from_state',
|
||||
'invoice_address_from_country',
|
||||
'invoice_address_from_tax_id',
|
||||
'invoice_address_from_vat_id',
|
||||
|
||||
@@ -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.
|
||||
@@ -47,8 +47,9 @@ from pretix.api.serializers.event import MetaDataField
|
||||
from pretix.api.serializers.fields import UploadedFileField
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.base.models import (
|
||||
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemVariation,
|
||||
ItemVariationMetaValue, Question, QuestionOption, Quota, SalesChannel,
|
||||
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemProgramTime,
|
||||
ItemVariation, ItemVariationMetaValue, Question, QuestionOption, Quota,
|
||||
SalesChannel,
|
||||
)
|
||||
|
||||
|
||||
@@ -187,6 +188,12 @@ class InlineItemAddOnSerializer(serializers.ModelSerializer):
|
||||
'position', 'price_included', 'multi_allowed')
|
||||
|
||||
|
||||
class InlineItemProgramTimeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemProgramTime
|
||||
fields = ('start', 'end')
|
||||
|
||||
|
||||
class ItemBundleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemBundle
|
||||
@@ -212,6 +219,37 @@ class ItemBundleSerializer(serializers.ModelSerializer):
|
||||
return data
|
||||
|
||||
|
||||
class ItemProgramTimeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemProgramTime
|
||||
fields = ('id', 'start', 'end')
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
|
||||
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
|
||||
full_data.update(data)
|
||||
|
||||
start = full_data.get('start')
|
||||
if not start:
|
||||
raise ValidationError(_("The program start must not be empty."))
|
||||
|
||||
end = full_data.get('end')
|
||||
if not end:
|
||||
raise ValidationError(_("The program end must not be empty."))
|
||||
|
||||
if start > end:
|
||||
raise ValidationError(_("The program end must not be before the program start."))
|
||||
|
||||
event = self.context['event']
|
||||
if event.has_subevents:
|
||||
raise ValidationError({
|
||||
_("You cannot use program times on an event series.")
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ItemAddOnSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemAddOn
|
||||
@@ -250,6 +288,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
addons = InlineItemAddOnSerializer(many=True, required=False)
|
||||
bundles = InlineItemBundleSerializer(many=True, required=False)
|
||||
variations = InlineItemVariationSerializer(many=True, required=False)
|
||||
program_times = InlineItemProgramTimeSerializer(many=True, required=False)
|
||||
tax_rate = ItemTaxRateField(source='*', read_only=True)
|
||||
meta_data = MetaDataField(required=False, source='*')
|
||||
picture = UploadedFileField(required=False, allow_null=True, allowed_types=(
|
||||
@@ -271,7 +310,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
'available_from', 'available_from_mode', 'available_until', 'available_until_mode',
|
||||
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
|
||||
'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations',
|
||||
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
|
||||
'addons', 'bundles', 'program_times', 'original_price', 'require_approval', 'generate_tickets',
|
||||
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'hidden_if_item_available_mode', 'allow_waitinglist',
|
||||
'issue_giftcard', 'meta_data',
|
||||
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
|
||||
@@ -294,9 +333,9 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data):
|
||||
raise ValidationError(_('Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use the '
|
||||
'dedicated nested endpoint.'))
|
||||
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data or 'program_times' in data):
|
||||
raise ValidationError(_('Updating add-ons, bundles, program times or variations via PATCH/PUT is not '
|
||||
'supported. Please use the dedicated nested endpoint.'))
|
||||
|
||||
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order'))
|
||||
Item.clean_available(data.get('available_from'), data.get('available_until'))
|
||||
@@ -347,6 +386,13 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
ItemAddOn.clean_max_min_count(addon_data.get('max_count', 0), addon_data.get('min_count', 0))
|
||||
return value
|
||||
|
||||
def validate_program_times(self, value):
|
||||
if not self.instance:
|
||||
for program_time_data in value:
|
||||
ItemProgramTime.clean_start_end(self, start=program_time_data.get('start', None),
|
||||
end=program_time_data.get('end', None))
|
||||
return value
|
||||
|
||||
@cached_property
|
||||
def item_meta_properties(self):
|
||||
return {
|
||||
@@ -364,6 +410,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
variations_data = validated_data.pop('variations') if 'variations' in validated_data else {}
|
||||
addons_data = validated_data.pop('addons') if 'addons' in validated_data else {}
|
||||
bundles_data = validated_data.pop('bundles') if 'bundles' in validated_data else {}
|
||||
program_times_data = validated_data.pop('program_times') if 'program_times' in validated_data else {}
|
||||
meta_data = validated_data.pop('meta_data', None)
|
||||
picture = validated_data.pop('picture', None)
|
||||
require_membership_types = validated_data.pop('require_membership_types', [])
|
||||
@@ -398,6 +445,8 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
ItemAddOn.objects.create(base_item=item, **addon_data)
|
||||
for bundle_data in bundles_data:
|
||||
ItemBundle.objects.create(base_item=item, **bundle_data)
|
||||
for program_time_data in program_times_data:
|
||||
ItemProgramTime.objects.create(item=item, **program_time_data)
|
||||
|
||||
# Meta data
|
||||
if meta_data is not None:
|
||||
@@ -550,7 +599,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):
|
||||
@@ -566,7 +615,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)
|
||||
|
||||
@@ -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,10 +50,12 @@ 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,
|
||||
ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule, Voucher,
|
||||
CachedFile, Checkin, Customer, Device, Invoice, InvoiceAddress,
|
||||
InvoiceLine, Item, ItemVariation, Order, OrderPosition, Question,
|
||||
QuestionAnswer, ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule,
|
||||
Voucher,
|
||||
)
|
||||
from pretix.base.models.orders import (
|
||||
BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund,
|
||||
@@ -62,10 +65,13 @@ from pretix.base.pdf import get_images, get_variables
|
||||
from pretix.base.services.cart import error_messages
|
||||
from pretix.base.services.locking import LOCK_TRUST_WINDOW, lock_objects
|
||||
from pretix.base.services.pricing import (
|
||||
apply_discounts, get_line_price, get_listed_price, is_included_for_free,
|
||||
apply_discounts, apply_rounding, get_line_price, get_listed_price,
|
||||
is_included_for_free,
|
||||
)
|
||||
from pretix.base.services.quotas import QuotaAvailability
|
||||
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
|
||||
from pretix.base.settings import (
|
||||
COUNTRIES_WITH_STATE_IN_ADDRESS, ROUNDING_MODES,
|
||||
)
|
||||
from pretix.base.signals import register_ticket_outputs
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
@@ -102,6 +108,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 +122,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 +161,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
|
||||
|
||||
|
||||
@@ -273,7 +329,7 @@ class AnswerSerializer(I18nAwareModelSerializer):
|
||||
return data
|
||||
|
||||
|
||||
class CheckinSerializer(I18nAwareModelSerializer):
|
||||
class InlineCheckinSerializer(I18nAwareModelSerializer):
|
||||
device_id = serializers.SlugRelatedField(
|
||||
source='device',
|
||||
slug_field='device_id',
|
||||
@@ -285,6 +341,21 @@ class CheckinSerializer(I18nAwareModelSerializer):
|
||||
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'device_id', 'type')
|
||||
|
||||
|
||||
class CheckinSerializer(I18nAwareModelSerializer):
|
||||
device_id = serializers.SlugRelatedField(
|
||||
source='device',
|
||||
slug_field='device_id',
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Checkin
|
||||
fields = (
|
||||
'id', 'successful', 'error_reason', 'error_explanation', 'position', 'datetime', 'list', 'created',
|
||||
'auto_checked_in', 'gate', 'device', 'device_id', 'type'
|
||||
)
|
||||
|
||||
|
||||
class PrintLogSerializer(serializers.ModelSerializer):
|
||||
device_id = serializers.SlugRelatedField(
|
||||
source='device',
|
||||
@@ -508,7 +579,7 @@ class OrderPositionPluginDataField(serializers.Field):
|
||||
|
||||
|
||||
class OrderPositionSerializer(I18nAwareModelSerializer):
|
||||
checkins = CheckinSerializer(many=True, read_only=True)
|
||||
checkins = InlineCheckinSerializer(many=True, read_only=True)
|
||||
print_logs = PrintLogSerializer(many=True, read_only=True)
|
||||
answers = AnswerSerializer(many=True)
|
||||
downloads = PositionDownloadsField(source='*', read_only=True)
|
||||
@@ -781,14 +852,15 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
list_serializer_class = OrderListSerializer
|
||||
fields = (
|
||||
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
||||
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
|
||||
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
||||
'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date', 'plugin_data',
|
||||
'payment_provider', 'fees', 'total', 'tax_rounding_mode', 'comment', 'custom_followup_at', 'invoice_address',
|
||||
'positions', 'downloads', 'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds',
|
||||
'require_approval', 'sales_channel', 'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date',
|
||||
'plugin_data',
|
||||
)
|
||||
read_only_fields = (
|
||||
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
|
||||
'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer',
|
||||
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date'
|
||||
'payment_provider', 'fees', 'total', 'tax_rounding_mode', 'positions', 'downloads', 'customer',
|
||||
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date',
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -953,7 +1025,7 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
|
||||
'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled',
|
||||
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until',
|
||||
'requested_valid_from', 'use_reusable_medium')
|
||||
'requested_valid_from', 'use_reusable_medium', 'discount')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -1049,6 +1121,10 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
|
||||
)
|
||||
|
||||
if data.get('price') is None and data.get('discount'):
|
||||
raise ValidationError(
|
||||
{'discount': ['You can only specify a discount if you do the price computation, but price is not set.']}
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
@@ -1103,11 +1179,13 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
queryset=SalesChannel.objects.none(),
|
||||
required=False,
|
||||
)
|
||||
tax_rounding_mode = serializers.ChoiceField(choices=ROUNDING_MODES, allow_null=True, required=False,)
|
||||
locale = serializers.ChoiceField(choices=[], required=False, allow_null=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['positions'].child.fields['voucher'].queryset = self.context['event'].vouchers.all()
|
||||
self.fields['positions'].child.fields['discount'].queryset = self.context['event'].discounts.all()
|
||||
self.fields['customer'].queryset = self.context['event'].organizer.customers.all()
|
||||
self.fields['expires'].required = False
|
||||
self.fields["sales_channel"].queryset = self.context["event"].organizer.sales_channels.all()
|
||||
@@ -1118,7 +1196,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
|
||||
'invoice_address', 'positions', 'checkin_attention', 'checkin_text', 'payment_info', 'payment_date',
|
||||
'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at',
|
||||
'require_approval', 'valid_if_pending', 'expires', 'api_meta')
|
||||
'require_approval', 'valid_if_pending', 'expires', 'api_meta', 'tax_rounding_mode')
|
||||
|
||||
def validate_payment_provider(self, pp):
|
||||
if pp is None:
|
||||
@@ -1515,19 +1593,22 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
pos.voucher_budget_use = max(listed_price - price_after_voucher, Decimal('0.00'))
|
||||
|
||||
order_positions = [pos_data['__instance'] for pos_data in positions_data]
|
||||
discount_results = apply_discounts(
|
||||
self.context['event'],
|
||||
order.sales_channel,
|
||||
[
|
||||
(cp.item_id, cp.subevent_id, cp.subevent.date_from if cp.subevent_id else None, cp.price,
|
||||
bool(cp.addon_to), cp.is_bundled, pos._voucher_discount)
|
||||
for cp in order_positions
|
||||
]
|
||||
)
|
||||
for cp, (new_price, discount) in zip(order_positions, discount_results):
|
||||
if new_price != pos.price and pos._auto_generated_price:
|
||||
pos.price = new_price
|
||||
pos.discount = discount
|
||||
if not any([p.get("discount") for p in positions_data]):
|
||||
# If any discount is set by the client (i.e. pretixPOS), we do not recalculate but believe the client
|
||||
# to avoid differences in end results.
|
||||
discount_results = apply_discounts(
|
||||
self.context['event'],
|
||||
order.sales_channel,
|
||||
[
|
||||
(cp.item_id, cp.subevent_id, cp.subevent.date_from if cp.subevent_id else None, cp.price,
|
||||
bool(cp.addon_to), cp.is_bundled, pos._voucher_discount)
|
||||
for cp in order_positions
|
||||
]
|
||||
)
|
||||
for cp, (new_price, discount) in zip(order_positions, discount_results):
|
||||
if new_price != pos.price and pos._auto_generated_price:
|
||||
pos.price = new_price
|
||||
pos.discount = discount
|
||||
|
||||
# Save instances
|
||||
for pos_data in positions_data:
|
||||
@@ -1641,7 +1722,31 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
else:
|
||||
f.save()
|
||||
|
||||
order.total += sum([f.value for f in fees])
|
||||
rounding_mode = validated_data.get("tax_rounding_mode")
|
||||
if not rounding_mode:
|
||||
if isinstance(self.context.get("auth"), Device):
|
||||
# Safety fallback to avoid differences in tax reporting
|
||||
brand = self.context.get("auth").software_brand or ""
|
||||
if "pretixPOS" in brand or "pretixKIOSK" in brand:
|
||||
rounding_mode = "line"
|
||||
if not rounding_mode:
|
||||
rounding_mode = self.context["event"].settings.tax_rounding
|
||||
changed = apply_rounding(
|
||||
rounding_mode,
|
||||
self.context["event"].currency,
|
||||
[*pos_map.values(), *fees]
|
||||
)
|
||||
for line in changed:
|
||||
if isinstance(line, OrderPosition):
|
||||
line.save(update_fields=[
|
||||
"price", "price_includes_rounding_correction", "tax_value", "tax_value_includes_rounding_correction"
|
||||
])
|
||||
elif isinstance(line, OrderFee):
|
||||
line.save(update_fields=[
|
||||
"value", "value_includes_rounding_correction", "tax_value", "tax_value_includes_rounding_correction"
|
||||
])
|
||||
|
||||
order.total = sum([c.price for c in pos_map.values()]) + sum([f.value for f in fees])
|
||||
if simulate:
|
||||
order.fees = fees
|
||||
order.positions = pos_map.values()
|
||||
@@ -1705,12 +1810,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):
|
||||
@@ -1724,13 +1831,14 @@ class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
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_from_city', 'invoice_from_state', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
|
||||
'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):
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -92,6 +92,7 @@ event_router.register(r'taxrules', event.TaxRuleViewSet)
|
||||
event_router.register(r'seats', event.SeatViewSet)
|
||||
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
|
||||
event_router.register(r'checkinlists', checkin.CheckinListViewSet)
|
||||
event_router.register(r'checkins', checkin.CheckinViewSet)
|
||||
event_router.register(r'cartpositions', cart.CartPositionViewSet)
|
||||
event_router.register(r'scheduled_exports', exporters.ScheduledEventExportViewSet)
|
||||
event_router.register(r'exporters', exporters.EventExportersViewSet, basename='exporters')
|
||||
@@ -111,6 +112,7 @@ item_router = routers.DefaultRouter()
|
||||
item_router.register(r'variations', item.ItemVariationViewSet)
|
||||
item_router.register(r'addons', item.ItemAddOnViewSet)
|
||||
item_router.register(r'bundles', item.ItemBundleViewSet)
|
||||
item_router.register(r'program_times', item.ItemProgramTimeViewSet)
|
||||
|
||||
order_router = routers.DefaultRouter()
|
||||
order_router.register(r'payments', order.PaymentViewSet)
|
||||
|
||||
@@ -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.
|
||||
@@ -56,7 +56,8 @@ from pretix.api.serializers.checkin import (
|
||||
)
|
||||
from pretix.api.serializers.item import QuestionSerializer
|
||||
from pretix.api.serializers.order import (
|
||||
CheckinListOrderPositionSerializer, FailedCheckinSerializer,
|
||||
CheckinListOrderPositionSerializer, CheckinSerializer,
|
||||
FailedCheckinSerializer,
|
||||
)
|
||||
from pretix.api.views import RichOrderingFilter
|
||||
from pretix.api.views.order import OrderPositionFilter
|
||||
@@ -96,6 +97,16 @@ with scopes_disabled():
|
||||
)
|
||||
return queryset.filter(expr)
|
||||
|
||||
class CheckinFilter(FilterSet):
|
||||
created_since = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='gte')
|
||||
created_before = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='lt')
|
||||
datetime_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte')
|
||||
datetime_before = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='lt')
|
||||
|
||||
class Meta:
|
||||
model = Checkin
|
||||
fields = ['successful', 'error_reason', 'list', 'type', 'gate', 'device', 'auto_checked_in']
|
||||
|
||||
|
||||
class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = CheckinListSerializer
|
||||
@@ -818,7 +829,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):
|
||||
@@ -837,9 +848,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'),
|
||||
)
|
||||
|
||||
@@ -881,7 +892,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
|
||||
@@ -916,7 +927,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,
|
||||
@@ -994,9 +1005,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'),
|
||||
)
|
||||
|
||||
@@ -1080,3 +1091,25 @@ class CheckinRPCAnnulView(views.APIView):
|
||||
checkin_annulled.send(ci.position.order.event, checkin=ci)
|
||||
|
||||
return Response({"status": "ok"}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class CheckinViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = CheckinSerializer
|
||||
queryset = Checkin.all.none()
|
||||
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
|
||||
filterset_class = CheckinFilter
|
||||
ordering = ('created', 'id')
|
||||
ordering_fields = ('created', 'datetime', 'id',)
|
||||
permission = 'can_view_orders'
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Checkin.all.filter().select_related(
|
||||
"position",
|
||||
"device",
|
||||
)
|
||||
return qs
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
@@ -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.
|
||||
@@ -40,19 +40,19 @@ from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.item import (
|
||||
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
|
||||
ItemSerializer, ItemVariationSerializer, QuestionOptionSerializer,
|
||||
QuestionSerializer, QuotaSerializer,
|
||||
ItemProgramTimeSerializer, ItemSerializer, ItemVariationSerializer,
|
||||
QuestionOptionSerializer, QuestionSerializer, QuotaSerializer,
|
||||
)
|
||||
from pretix.api.views import ConditionalListView
|
||||
from pretix.base.models import (
|
||||
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation,
|
||||
Question, QuestionOption, Quota,
|
||||
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemProgramTime,
|
||||
ItemVariation, Question, QuestionOption, Quota,
|
||||
)
|
||||
from pretix.base.services.quotas import QuotaAvailability
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
@@ -279,6 +279,59 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
|
||||
|
||||
class ItemProgramTimeViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemProgramTimeSerializer
|
||||
queryset = ItemProgramTime.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
|
||||
ordering_fields = ('id',)
|
||||
ordering = ('id',)
|
||||
permission = None
|
||||
write_permission = 'can_change_items'
|
||||
|
||||
@cached_property
|
||||
def item(self):
|
||||
return get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.event.has_subevents:
|
||||
raise ValidationError('You cannot use program times on an event series.')
|
||||
return self.item.program_times.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['item'] = self.item
|
||||
return ctx
|
||||
|
||||
def perform_create(self, serializer):
|
||||
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
serializer.save(item=item)
|
||||
item.log_action(
|
||||
'pretix.event.item.program_times.added',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.item.log_action(
|
||||
'pretix.event.item.program_times.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
super().perform_destroy(instance)
|
||||
instance.item.log_action(
|
||||
'pretix.event.item.program_times.removed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={'start': instance.start, 'end': instance.end}
|
||||
)
|
||||
|
||||
|
||||
class ItemAddOnViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemAddOnSerializer
|
||||
queryset = ItemAddOn.objects.none()
|
||||
@@ -514,7 +567,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
write_permission = 'can_change_items'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.quotas.all()
|
||||
return self.request.event.quotas.select_related('subevent').prefetch_related('items', 'variations').all()
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset()).distinct()
|
||||
|
||||
@@ -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.
|
||||
@@ -88,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 (
|
||||
@@ -228,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
|
||||
@@ -246,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],
|
||||
@@ -344,7 +344,8 @@ 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['auth'] = self.request.auth
|
||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
|
||||
return ctx
|
||||
|
||||
def get_base_queryset(self):
|
||||
@@ -743,7 +744,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(
|
||||
@@ -764,7 +765,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))
|
||||
@@ -943,7 +950,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') == 'true'
|
||||
check_quotas = self.request.query_params.get('check_quotas', 'true').lower() == 'true'
|
||||
|
||||
serializer = OrderChangeOperationSerializer(
|
||||
context={'order': order, **self.get_serializer_context()},
|
||||
@@ -1087,18 +1094,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],
|
||||
@@ -1663,6 +1670,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)
|
||||
|
||||
@@ -1689,6 +1699,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,
|
||||
@@ -1891,6 +1902,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()
|
||||
@@ -1939,13 +1956,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:
|
||||
|
||||
@@ -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={
|
||||
@@ -653,7 +721,7 @@ class MembershipViewSet(viewsets.ModelViewSet):
|
||||
def get_queryset(self):
|
||||
return Membership.objects.filter(
|
||||
customer__organizer=self.request.organizer
|
||||
)
|
||||
).select_related('customer')
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_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.
|
||||
@@ -19,6 +19,7 @@
|
||||
# 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.db import transaction
|
||||
from django.db.models import F, Q
|
||||
from django.utils.timezone import now
|
||||
@@ -64,8 +65,13 @@ class VoucherViewSet(viewsets.ModelViewSet):
|
||||
permission = 'can_view_vouchers'
|
||||
write_permission = 'can_change_vouchers'
|
||||
|
||||
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
|
||||
def get_queryset(self):
|
||||
return self.request.event.vouchers.select_related('seat').all()
|
||||
return Voucher.annotate_budget_used(
|
||||
self.request.event.vouchers
|
||||
).select_related(
|
||||
'item', 'quota', 'seat', 'variation'
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def create(self, request, *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.
|
||||
@@ -43,6 +43,7 @@ from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
|
||||
from pretix.base.signals import periodic_task
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.celery import get_task_priority
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_ALL_EVENTS = None
|
||||
@@ -78,6 +79,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 +105,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 +119,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 +174,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 +372,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 +408,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 +440,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 +455,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
|
||||
|
||||
@@ -431,7 +475,10 @@ def notify_webhooks(logentry_ids: list):
|
||||
)
|
||||
|
||||
for wh in webhooks:
|
||||
send_webhook.apply_async(args=(logentry.id, notification_type.action_type, wh.pk))
|
||||
send_webhook.apply_async(
|
||||
args=(logentry.id, notification_type.action_type, wh.pk),
|
||||
priority=get_task_priority("notifications", logentry.organizer_id),
|
||||
)
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=60, acks_late=True, autoretry_for=(DatabaseError,),)
|
||||
|
||||
@@ -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.
|
||||
@@ -43,7 +43,7 @@ class PretixBaseConfig(AppConfig):
|
||||
from . import exporter # NOQA
|
||||
from . import payment # NOQA
|
||||
from . import exporters # NOQA
|
||||
from . import invoice # NOQA
|
||||
from .invoicing import pdf, transmission, email, peppol, national # NOQA
|
||||
from . import notifications # NOQA
|
||||
from . import email # NOQA
|
||||
from .services import auth, checkin, currencies, datasync, export, mail, tickets, cart, modelimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
|
||||
|
||||
@@ -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.
|
||||
@@ -112,23 +112,6 @@ def oidc_validate_and_complete_config(config):
|
||||
scope="openid",
|
||||
))
|
||||
|
||||
for scope in config["scope"].split(" "):
|
||||
if scope not in provider_config.get("scopes_supported", []):
|
||||
raise ValidationError(_('You are requesting scope "{scope}" but provider only supports these: {scopes}.').format(
|
||||
scope=scope,
|
||||
scopes=", ".join(provider_config.get("scopes_supported", []))
|
||||
))
|
||||
|
||||
if "claims_supported" in provider_config:
|
||||
claims_supported = provider_config.get("claims_supported", [])
|
||||
for k, v in config.items():
|
||||
if k.endswith('_field') and v:
|
||||
if v not in claims_supported: # https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
|
||||
raise ValidationError(_('You are requesting field "{field}" but provider only supports these: {fields}.').format(
|
||||
field=v,
|
||||
fields=", ".join(provider_config.get("claims_supported", []))
|
||||
))
|
||||
|
||||
if "token_endpoint_auth_methods_supported" in provider_config:
|
||||
token_endpoint_auth_methods_supported = provider_config.get("token_endpoint_auth_methods_supported",
|
||||
["client_secret_basic"])
|
||||
|
||||
@@ -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.
|
||||
@@ -38,13 +38,13 @@ from pretix.base.datasync.sourcefields import (
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.logentrytype_registry import make_link
|
||||
from pretix.base.models.datasync import OrderSyncQueue, OrderSyncResult
|
||||
from pretix.base.signals import EventPluginRegistry
|
||||
from pretix.base.signals import PluginAwareRegistry
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
datasync_providers = EventPluginRegistry({"identifier": lambda o: o.identifier})
|
||||
datasync_providers = PluginAwareRegistry({"identifier": lambda o: o.identifier})
|
||||
|
||||
|
||||
class BaseSyncError(Exception):
|
||||
@@ -106,7 +106,7 @@ class OutboundSyncProvider:
|
||||
return str(cls.identifier)
|
||||
|
||||
@classmethod
|
||||
def enqueue_order(cls, order, triggered_by, not_before=None):
|
||||
def enqueue_order(cls, order, triggered_by, not_before=None, immediate=False):
|
||||
"""
|
||||
Adds an order to the sync queue. May only be called on derived classes which define an ``identifier`` attribute.
|
||||
|
||||
@@ -119,10 +119,14 @@ class OutboundSyncProvider:
|
||||
:param order: the Order that should be synced
|
||||
:param triggered_by: the reason why the order should be synced, e.g. name of the signal
|
||||
(currently only used internally for logging)
|
||||
:param immediate: whether a new sync task should run immediately for this order, instead
|
||||
of waiting for the next periodic_task interval
|
||||
:return: Return a tuple (queue_item, created), where created is a boolean
|
||||
specifying whether a new queue item was created.
|
||||
"""
|
||||
if not hasattr(cls, 'identifier'):
|
||||
raise TypeError('Call this method on a derived class that defines an "identifier" attribute.')
|
||||
OrderSyncQueue.objects.update_or_create(
|
||||
queue_item, created = OrderSyncQueue.objects.update_or_create(
|
||||
order=order,
|
||||
sync_provider=cls.identifier,
|
||||
in_flight=False,
|
||||
@@ -133,6 +137,10 @@ class OutboundSyncProvider:
|
||||
"need_manual_retry": None,
|
||||
},
|
||||
)
|
||||
if immediate:
|
||||
from pretix.base.services.datasync import sync_single
|
||||
sync_single.apply_async(args=(queue_item.pk,))
|
||||
return queue_item, created
|
||||
|
||||
@classmethod
|
||||
def get_external_link_info(cls, event, external_link_href, external_link_display_name):
|
||||
@@ -252,9 +260,15 @@ class OutboundSyncProvider:
|
||||
except KeyError:
|
||||
with language(self.event.settings.locale):
|
||||
raise SyncConfigError([_(
|
||||
'Field "{field_name}" is not valid for {available_inputs}. Please check your {provider_name} settings.'
|
||||
).format(key=key, available_inputs="/".join(inputs.keys()), provider_name=self.display_name)])
|
||||
input = inputs[field.required_input]
|
||||
'Field "{field_name}" does not exist. Please check your {provider_name} settings.'
|
||||
).format(field_name=key, provider_name=self.display_name)])
|
||||
try:
|
||||
input = inputs[field.required_input]
|
||||
except KeyError:
|
||||
with language(self.event.settings.locale):
|
||||
raise SyncConfigError([_(
|
||||
'Field "{field_name}" requires {required_input}, but only got {available_inputs}. Please check your {provider_name} settings.'
|
||||
).format(field_name=key, required_input=field.required_input, available_inputs=", ".join(inputs.keys()), provider_name=self.display_name)])
|
||||
val = field.getter(input)
|
||||
if isinstance(val, list):
|
||||
if field.enum_opts and mapping_entry.get("value_map"):
|
||||
@@ -377,7 +391,7 @@ class OutboundSyncProvider:
|
||||
def sync_order(self, order):
|
||||
if not self.should_sync_order(order):
|
||||
logger.debug("Skipping order %r", order)
|
||||
return
|
||||
return {}
|
||||
|
||||
logger.debug("Syncing order %r", order)
|
||||
positions = list(
|
||||
|
||||
@@ -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,10 +24,11 @@ from collections import namedtuple
|
||||
from functools import partial
|
||||
|
||||
from django.db.models import Max, Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.models import Checkin, InvoiceAddress, Order, Question
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
|
||||
def get_answer(op, question_identifier=None):
|
||||
@@ -102,6 +103,14 @@ def normalize_email(email):
|
||||
return None
|
||||
|
||||
|
||||
def get_email_domain(email):
|
||||
if email:
|
||||
local, host = email.split("@")
|
||||
return host
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
ORDER_POSITION = 'position'
|
||||
ORDER = 'order'
|
||||
EVENT = 'event'
|
||||
@@ -111,10 +120,23 @@ AVAILABLE_MODELS = {
|
||||
'Order': (ORDER, EVENT),
|
||||
}
|
||||
|
||||
DataFieldCategory = namedtuple(
|
||||
'DataFieldCategory',
|
||||
field_names=('sort_index', 'label',),
|
||||
)
|
||||
|
||||
CAT_ORDER_POSITION = DataFieldCategory(10, _('Order position details'))
|
||||
CAT_ATTENDEE = DataFieldCategory(11, _('Attendee details'))
|
||||
CAT_QUESTIONS = DataFieldCategory(12, _('Questions'))
|
||||
CAT_PRODUCT = DataFieldCategory(20, _('Product details'))
|
||||
CAT_ORDER = DataFieldCategory(21, _('Order details'))
|
||||
CAT_INVOICE_ADDRESS = DataFieldCategory(22, _('Invoice address'))
|
||||
CAT_EVENT = DataFieldCategory(30, _('Event information'))
|
||||
CAT_EVENT_OR_SUBEVENT = DataFieldCategory(31, pgettext_lazy('subevent', 'Event or date information'))
|
||||
|
||||
DataFieldInfo = namedtuple(
|
||||
'DataFieldInfo',
|
||||
field_names=('required_input', 'key', 'label', 'type', 'enum_opts', 'getter', 'deprecated'),
|
||||
field_names=('required_input', 'category', 'key', 'label', 'type', 'enum_opts', 'getter', 'deprecated'),
|
||||
defaults=[False]
|
||||
)
|
||||
|
||||
@@ -148,6 +170,7 @@ def get_data_fields(event, for_model=None):
|
||||
[
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_name",
|
||||
_("Attendee name"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -159,6 +182,7 @@ def get_data_fields(event, for_model=None):
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_name_" + k,
|
||||
_("Attendee") + ": " + label,
|
||||
Question.TYPE_STRING,
|
||||
@@ -178,6 +202,7 @@ def get_data_fields(event, for_model=None):
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_email",
|
||||
_("Attendee email"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -189,6 +214,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_or_order_email",
|
||||
_("Attendee or order email"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -201,6 +227,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_company",
|
||||
_("Attendee company"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -209,6 +236,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_street",
|
||||
_("Attendee address street"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -217,6 +245,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_zipcode",
|
||||
_("Attendee address ZIP code"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -225,6 +254,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_city",
|
||||
_("Attendee address city"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -233,6 +263,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_country",
|
||||
_("Attendee address country"),
|
||||
Question.TYPE_COUNTRYCODE,
|
||||
@@ -243,6 +274,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_INVOICE_ADDRESS,
|
||||
"invoice_address_company",
|
||||
_("Invoice address company"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -251,6 +283,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_INVOICE_ADDRESS,
|
||||
"invoice_address_name",
|
||||
_("Invoice address name"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -261,6 +294,7 @@ def get_data_fields(event, for_model=None):
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_INVOICE_ADDRESS,
|
||||
"invoice_address_name_" + k,
|
||||
_("Invoice address") + ": " + label,
|
||||
Question.TYPE_STRING,
|
||||
@@ -278,6 +312,7 @@ def get_data_fields(event, for_model=None):
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_INVOICE_ADDRESS,
|
||||
"invoice_address_street",
|
||||
_("Invoice address street"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -286,6 +321,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_INVOICE_ADDRESS,
|
||||
"invoice_address_zipcode",
|
||||
_("Invoice address ZIP code"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -294,6 +330,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_INVOICE_ADDRESS,
|
||||
"invoice_address_city",
|
||||
_("Invoice address city"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -302,6 +339,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_INVOICE_ADDRESS,
|
||||
"invoice_address_country",
|
||||
_("Invoice address country"),
|
||||
Question.TYPE_COUNTRYCODE,
|
||||
@@ -310,6 +348,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"email",
|
||||
_("Order email"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -318,6 +357,16 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"email_domain",
|
||||
_("Order email domain"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: get_email_domain(normalize_email(order.email)),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"order_code",
|
||||
_("Order code"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -326,6 +375,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"event_order_code",
|
||||
_("Event and order code"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -334,6 +384,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"order_total",
|
||||
_("Order total"),
|
||||
Question.TYPE_NUMBER,
|
||||
@@ -342,6 +393,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_PRODUCT,
|
||||
"product",
|
||||
_("Product and variation name"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -353,14 +405,25 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_PRODUCT,
|
||||
"product_id",
|
||||
_("Product ID"),
|
||||
Question.TYPE_NUMBER,
|
||||
None,
|
||||
lambda position: str(position.item.pk),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_PRODUCT,
|
||||
"product_is_admission",
|
||||
_("Product is admission product"),
|
||||
Question.TYPE_BOOLEAN,
|
||||
None,
|
||||
lambda position: bool(position.item.admission),
|
||||
),
|
||||
DataFieldInfo(
|
||||
EVENT,
|
||||
CAT_EVENT,
|
||||
"event_slug",
|
||||
_("Event short form"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -369,6 +432,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
EVENT,
|
||||
CAT_EVENT,
|
||||
"event_name",
|
||||
_("Event name"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -377,6 +441,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
EVENT_OR_SUBEVENT,
|
||||
CAT_EVENT_OR_SUBEVENT,
|
||||
"event_date_from",
|
||||
_("Event start date"),
|
||||
Question.TYPE_DATETIME,
|
||||
@@ -385,6 +450,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
EVENT_OR_SUBEVENT,
|
||||
CAT_EVENT_OR_SUBEVENT,
|
||||
"event_date_to",
|
||||
_("Event end date"),
|
||||
Question.TYPE_DATETIME,
|
||||
@@ -393,6 +459,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ORDER_POSITION,
|
||||
"voucher_code",
|
||||
_("Voucher code"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -401,6 +468,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ORDER_POSITION,
|
||||
"ticket_id",
|
||||
_("Order code and position number"),
|
||||
Question.TYPE_STRING,
|
||||
@@ -409,6 +477,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ORDER_POSITION,
|
||||
"ticket_price",
|
||||
_("Ticket price"),
|
||||
Question.TYPE_NUMBER,
|
||||
@@ -417,6 +486,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"order_status",
|
||||
_("Order status"),
|
||||
Question.TYPE_CHOICE,
|
||||
@@ -425,6 +495,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ORDER_POSITION,
|
||||
"ticket_status",
|
||||
_("Ticket status"),
|
||||
Question.TYPE_CHOICE,
|
||||
@@ -433,6 +504,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"order_date",
|
||||
_("Order date and time"),
|
||||
Question.TYPE_DATETIME,
|
||||
@@ -441,6 +513,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"payment_date",
|
||||
_("Payment date and time"),
|
||||
Question.TYPE_DATETIME,
|
||||
@@ -449,24 +522,58 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"order_locale",
|
||||
_("Order language code"),
|
||||
_("Order locale"),
|
||||
Question.TYPE_CHOICE,
|
||||
[(lc, lc) for lc in event.settings.locales],
|
||||
lambda order: [order.locale],
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ORDER_POSITION,
|
||||
"position_id",
|
||||
_("Order position ID"),
|
||||
Question.TYPE_NUMBER,
|
||||
None,
|
||||
lambda op: str(op.pk),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_ORDER,
|
||||
"presale_order_url",
|
||||
_("Order link"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ORDER_POSITION,
|
||||
"presale_ticket_url",
|
||||
_("Ticket link"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda op: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': op.order.code,
|
||||
'secret': op.web_secret,
|
||||
'position': op.positionid
|
||||
}
|
||||
),
|
||||
),
|
||||
]
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ORDER_POSITION,
|
||||
"checkin_date_" + str(cl.pk),
|
||||
_("Check-in datetime on list {}").format(cl.name),
|
||||
Question.TYPE_DATETIME,
|
||||
@@ -478,6 +585,7 @@ def get_data_fields(event, for_model=None):
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_QUESTIONS,
|
||||
"question_" + q.identifier,
|
||||
_("Question: {name}").format(name=str(q.question)),
|
||||
q.type,
|
||||
@@ -491,6 +599,7 @@ def get_data_fields(event, for_model=None):
|
||||
src_fields += [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_name_given_name",
|
||||
_("Attendee") + ": " + _("Given name") + " (⚠️ auto-generated, not recommended)",
|
||||
Question.TYPE_STRING,
|
||||
@@ -500,6 +609,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_INVOICE_ADDRESS,
|
||||
"invoice_address_name_given_name",
|
||||
_("Invoice address") + ": " + _("Given name") + " (⚠️ auto-generated, not recommended)",
|
||||
Question.TYPE_STRING,
|
||||
@@ -513,6 +623,7 @@ def get_data_fields(event, for_model=None):
|
||||
src_fields += [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
CAT_ATTENDEE,
|
||||
"attendee_name_family_name",
|
||||
_("Attendee") + ": " + _("Family name") + " (⚠️ auto-generated, not recommended)",
|
||||
Question.TYPE_STRING,
|
||||
@@ -522,6 +633,7 @@ def get_data_fields(event, for_model=None):
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
CAT_INVOICE_ADDRESS,
|
||||
"invoice_address_name_family_name",
|
||||
_("Invoice address") + ": " + _("Family name") + " (⚠️ auto-generated, not recommended)",
|
||||
Question.TYPE_STRING,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user