Compare commits

...

298 Commits

Author SHA1 Message Date
Raphael Michel
663de9239d Bump to 4.17.1 2023-03-06 14:37:42 +01:00
Raphael Michel
ef7dd65194 [SECURITY] Enforce session validation on oauth authorize endpoint 2023-03-06 14:37:27 +01:00
Raphael Michel
b3ae675a14 Bump version to 4.17.0 2023-02-27 09:24:48 +01:00
Mie Frydensbjerg
3639577841 Translations: Update Danish
Currently translated at 33.6% (1740 of 5177 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/da/

powered by weblate
2023-02-27 09:24:16 +01:00
Raphael Michel
26202e82bc Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5177 of 5177 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-02-27 09:24:16 +01:00
Raphael Michel
f81704be7b Translations: Update German
Currently translated at 100.0% (5177 of 5177 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-02-27 09:24:16 +01:00
Raphael Michel
24b1c94140 Docs: Change reocmmended indexes 2023-02-27 09:01:09 +01:00
Raphael Michel
aa85bae35f Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2023-02-27 09:00:38 +01:00
Toon Toetenel
e1c10c4b01 Translations: Update Dutch (informal) (nl_Informal)
Currently translated at 77.0% (3988 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl_Informal/

powered by weblate
2023-02-27 08:59:41 +01:00
Toon Toetenel
211200cb4d Translations: Update Dutch
Currently translated at 87.2% (4516 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-27 08:59:41 +01:00
Toon Toetenel
37a07192da Translations: Update Dutch
Currently translated at 86.5% (4478 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-27 08:59:41 +01:00
Raphael Michel
7d4b575150 Ensure total ordering of paginated lists (#3061) 2023-02-24 10:51:51 +01:00
Raphael Michel
c2d720b3b9 Fix location field in private ICS files 2023-02-24 10:28:34 +01:00
Raphael Michel
6a8ebcca1a Add support for X-Forwarded-Host 2023-02-24 10:28:22 +01:00
Raphael Michel
79c7b53efa Self-service order change: Enforce hidden variations 2023-02-22 16:38:34 +01:00
Raphael Michel
8a5463320a Bump pytest-xdist to 3.2.* 2023-02-22 12:59:16 +01:00
Raphael Michel
0bb99c911b Bump sentry-sdk to 1.15.* 2023-02-22 12:59:16 +01:00
Raphael Michel
04899c9540 Bump protobuf to 4.22.* 2023-02-22 12:59:16 +01:00
Raphael Michel
ae8c6058cd Bump redis to 4.5.* 2023-02-22 12:59:16 +01:00
Raphael Michel
4200562814 Translations: Update Dutch
Currently translated at 85.4% (4420 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-22 12:48:23 +01:00
Toon Toetenel
b0e580447f Translations: Update Dutch
Currently translated at 85.4% (4420 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-22 12:48:23 +01:00
Toon Toetenel
c6e650c69a Translations: Update Dutch
Currently translated at 85.3% (4416 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-22 12:48:23 +01:00
Maurice Kaag
bf49297c7e Translations: Update French
Currently translated at 49.6% (2570 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-02-22 12:48:23 +01:00
danijossnet
c4ea99646e Translations: Update Greek
Currently translated at 54.4% (2819 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/el/

powered by weblate
2023-02-22 12:48:23 +01:00
Simon Breum
78037e18d2 Translations: Update Danish
Currently translated at 33.5% (1736 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/da/

powered by weblate
2023-02-22 12:48:23 +01:00
danijossnet
57ddc14fda Translations: Update Greek
Currently translated at 54.4% (2818 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/el/

powered by weblate
2023-02-22 12:48:23 +01:00
Raphael Michel
f08333814f Add OrderPosition.ignore_from_quota_while_blocked (#3119) 2023-02-22 12:44:51 +01:00
Raphael Michel
175921fbaf Badges: Add new export page format 70x36 2023-02-22 12:22:38 +01:00
Raphael Michel
7d53150779 Fix crash on invalid download URL 2023-02-19 15:45:20 +01:00
Raphael Michel
285150354a Add search field to scrollable choice fields 2023-02-17 16:37:58 +01:00
Raphael Michel
093eaace43 Temporarily work around pypdf bug py-pdf/pypdf#1640 2023-02-17 16:37:56 +01:00
Raphael Michel
3c0bd0629b Fix item-meta typeahead with variations 2023-02-17 11:39:09 +01:00
Raphael Michel
b58546c3c8 Do not hide "free" if there is an original price 2023-02-17 10:43:49 +01:00
Raphael Michel
50f9cfd402 Widget: Keep language in no-cookie-flow 2023-02-17 09:58:47 +01:00
Raphael Michel
72aaf24a40 Fix failing tests after Stripe provider change 2023-02-17 09:58:36 +01:00
Raphael Michel
83b10ecd23 Use 24h time for Greek locale 2023-02-17 09:35:06 +01:00
Raphael Michel
bb8657637f Widget: Deprecate "style" parameter, prefer "list-type"
"style" is problematic since it clashes with an HTML5 attribute
2023-02-17 09:24:57 +01:00
Raphael Michel
076f279a60 Fix voucher item validation 2023-02-16 14:46:15 +01:00
Martin Gross
75f1ee9191 Doc: Fix Order Creation Sample Resource: Use GB instead of UK 2023-02-16 14:43:21 +01:00
Raphael Michel
ca9ddd7d98 PDF: Add machine-readable PDF string for every barcode 2023-02-16 09:35:00 +01:00
Raphael Michel
c63bc46d3b Add validation for voucher products not being addon products 2023-02-15 18:06:04 +01:00
Raphael Michel
24fd8f404e Payment list export: Add date range 2023-02-15 16:22:44 +01:00
Raphael Michel
6c7415a7ff Use now() instead of utcnow() 2023-02-15 15:05:13 +01:00
Raphael Michel
b094c6861d Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5173 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-02-15 13:49:34 +01:00
Raphael Michel
3a14a3da49 Translations: Update German
Currently translated at 100.0% (5173 of 5173 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-02-15 13:49:34 +01:00
Raphael Michel
6adc8b6876 Translations: Extend wordlist 2023-02-15 13:39:47 +01:00
Raphael Michel
440db4883c Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-02-15 13:25:35 +01:00
Raphael Michel
71e08012f5 Stripe: Add refund ID to export 2023-02-15 13:22:36 +01:00
Raphael Michel
2ba9514b6f Invoicing: Allow to show exchange rates based on sources/rules (#3122) 2023-02-15 13:22:04 +01:00
Raphael Michel
e358bacfa3 Expose some payment details in exports 2023-02-15 13:21:55 +01:00
Ismael Menéndez Fernández
fd78e31861 Translations: Update Galician
Currently translated at 11.0% (573 of 5168 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/gl/

powered by weblate
2023-02-15 13:06:48 +01:00
Ismael Menéndez Fernández
da387f4af2 Translations: Update Spanish
Currently translated at 57.1% (2955 of 5168 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2023-02-15 13:06:48 +01:00
Raphael Michel
c2c7e58fd6 Add BasePaymentProvider.refund_matching_id 2023-02-15 12:03:47 +01:00
Raphael Michel
f09878df9f Add missing refund.done webhooks 2023-02-15 11:05:12 +01:00
Raphael Michel
a416ec09d7 Translations: Fix Typo 2023-02-15 11:00:23 +01:00
Martin Gross
20581cd31c API: Expose organizer and event URL (Z#23116269) (#3121) 2023-02-14 14:47:18 +01:00
Raphael Michel
e33fbaf9c0 Order payment: Auto-select payment method if there is only one 2023-02-14 14:04:20 +01:00
Raphael Michel
1399f97e1e Question step: Fix validateion for required photo 2023-02-14 13:54:20 +01:00
Raphael Michel
9b60dde718 Bump Django to 3.2.18 2023-02-14 11:15:19 +01:00
Raphael Michel
35fb20fe76 Fix PDF variable for validity end date 2023-02-14 11:01:54 +01:00
Raphael Michel
ccebbb6307 OrderChangeManager: Fix type annotation 2023-02-14 10:29:34 +01:00
Raphael Michel
63dde340b6 Order import: Accept upper-case languages 2023-02-14 10:29:25 +01:00
Raphael Michel
5ae3b27e83 Item validity: Compute month ranges one day shorter 2023-02-14 09:39:03 +01:00
Raphael Michel
04cb7d3ec5 Copy validity when copying items 2023-02-14 09:08:07 +01:00
Raphael Michel
538e1b4089 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5168 of 5168 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-02-13 16:21:35 +01:00
Raphael Michel
37d58c53a1 Translations: Update German
Currently translated at 100.0% (5168 of 5168 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-02-13 16:21:35 +01:00
Raphael Michel
d6dc21ff89 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-02-13 14:59:05 +01:00
Raphael Michel
f63408504e Allow to define ticket validity through a product (#3105) 2023-02-13 14:46:52 +01:00
Raphael Michel
fdadda9910 Docs: Fix search page 2023-02-13 14:12:29 +01:00
Raphael Michel
399643fd5d Check-in list exporter: Make date filter behave more consistently 2023-02-13 12:07:46 +01:00
Raphael Michel
d22b90beaa Update unclear German translation 2023-02-13 12:06:03 +01:00
Raphael Michel
6d20500f52 Use reproducible ordering for meta properties 2023-02-13 12:05:26 +01:00
Raphael Michel
2607b18833 Update pretix logo to refreshed version (#3114)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-02-13 11:42:08 +01:00
Raphael Michel
f22698fdbf Fix broken settings page after 28eb730fd 2023-02-10 16:41:50 +01:00
Raphael Michel
6941dda489 Doc: Upgrade to Sphinx 6 (#3113) 2023-02-10 16:37:57 +01:00
Jan Felix Wiebe
c99df679e7 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5135 of 5135 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-02-10 16:36:55 +01:00
Jan Felix Wiebe
dfb2f18ac3 Translations: Update German
Currently translated at 100.0% (5135 of 5135 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-02-10 16:36:55 +01:00
Felix Schäfer
e4b9877444 Docs: Remove duplicate entry in attribute table (#3111) 2023-02-10 09:20:18 +01:00
Julian Rother
28eb730fdd Separate mail template for incomplete payment notifications (#2999)
* Separate mail template for incomplete payment notifications

* Update src/pretix/base/settings.py

Co-authored-by: Richard Schreiber <wiffbi@gmail.com>

* Update src/pretix/control/forms/event.py

Co-authored-by: Richard Schreiber <wiffbi@gmail.com>

---------

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
2023-02-09 22:12:35 +01:00
Raphael Michel
c44ff6244d Order change: Warn about actions that might generate a new secret 2023-02-09 17:10:27 +01:00
Raphael Michel
37e633812b Translations: Update German (informal) (de_Informal)
Currently translated at 99.0% (203 of 205 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/de_Informal/

powered by weblate
2023-02-09 15:06:14 +01:00
Raphael Michel
1b7bb1f3e6 Translations: Update German
Currently translated at 99.0% (203 of 205 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/de/

powered by weblate
2023-02-09 15:06:14 +01:00
Raphael Michel
34f8503251 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5135 of 5135 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-02-09 15:06:14 +01:00
Raphael Michel
9b049db2f8 Translations: Update German
Currently translated at 100.0% (5135 of 5135 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-02-09 15:06:14 +01:00
Maurice Kaag
ef560c0f68 Translations: Update French
Currently translated at 65.0% (132 of 203 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/fr/

powered by weblate
2023-02-09 15:06:14 +01:00
Maurice Kaag
efb41c5220 Translations: Update French
Currently translated at 50.4% (2571 of 5097 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-02-09 15:06:14 +01:00
Raphael Michel
18986caa49 Attendee ticket page: Allow to hide prices (#3104)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-02-09 14:37:10 +01:00
Raphael Michel
fba55f0292 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-02-09 14:36:26 +01:00
Raphael Michel
80ee99d46a Checkout confirmation: Don't show payment box for free orders 2023-02-09 13:54:46 +01:00
Raphael Michel
2893f72d5b Widget: Don't set CSP header on non-HTML resources 2023-02-09 13:54:46 +01:00
Iria Costas
ba0edd6261 Translations: Update Spanish
Currently translated at 57.8% (2947 of 5097 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2023-02-09 09:55:07 +01:00
Maurice Kaag
04d34fac8c Translations: Update French
Currently translated at 49.5% (2525 of 5097 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-02-09 09:55:07 +01:00
Raphael Michel
cef7b33c4b Fix styling of dl in alert 2023-02-09 09:47:10 +01:00
Raphael Michel
6902725f3c New check-in features (#3022) 2023-02-09 09:46:46 +01:00
Raphael Michel
7b0d07065f Fix isort issues 2023-02-08 17:43:21 +01:00
Raphael Michel
04b0a3d5d4 Remove debuggin statement 2023-02-08 14:45:58 +01:00
Raphael Michel
fd16e4e78e Fix data-display-dependency for membership types 2023-02-08 14:44:50 +01:00
Raphael Michel
ac16adba4c Device security profiles: Improve logging 2023-02-08 13:35:51 +01:00
Raphael Michel
289e0096e8 API: Add logging for replaying by idempotency key 2023-02-08 13:35:51 +01:00
Raphael Michel
df41caacf7 API: Clear permission errors from idempotency storage when chaning permissions 2023-02-08 13:35:51 +01:00
Thomas Vranken
50f001221c Translations: Update Dutch
Currently translated at 86.2% (4394 of 5097 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-08 11:24:53 +01:00
BerkieBb
a4dca6b10d Translations: Update Dutch
Currently translated at 86.1% (4393 of 5097 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-08 11:24:53 +01:00
Thomas Vranken
e7019c6913 Translations: Update Dutch
Currently translated at 86.1% (4393 of 5097 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-08 11:24:53 +01:00
BerkieBb
f03aa71a02 Translations: Update Dutch
Currently translated at 86.1% (4392 of 5097 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-08 11:24:53 +01:00
Raphael Michel
ec51078047 Quick setup: Mark tickets personalized 2023-02-08 10:33:10 +01:00
Raphael Michel
66c1a321b0 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5097 of 5097 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-02-07 18:27:24 +01:00
Raphael Michel
14f129bc51 Translations: Update German
Currently translated at 100.0% (5097 of 5097 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-02-07 18:27:24 +01:00
Raphael Michel
97ec07139e Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-02-07 18:02:02 +01:00
Raphael Michel
ee720cd9db Event list/calendar: Use "buy now" instead of "book now" for available events (#3097) 2023-02-07 18:01:21 +01:00
Raphael Michel
e8785f4117 Check-in rule editor: Add duplicate option 2023-02-07 17:12:12 +01:00
Raphael Michel
accb9f8b13 Check-in rule editor: Fix reactivity issue in select2 component 2023-02-07 17:12:12 +01:00
Phin Wolkwitz
98d290992c Docs: Add comment to webhook API (#3096) 2023-02-07 16:38:33 +01:00
Raphael Michel
9a56874083 Subevents: Validate time order of product availability 2023-02-07 15:29:11 +01:00
ser8phin
82dd417a8e Webhooks: Add comment field (#3095) 2023-02-07 15:17:19 +01:00
Richard Schreiber
ba2c6e1e58 Event index: Increase white-space for products and headlines (#3092) 2023-02-07 15:03:28 +01:00
Thomas Vranken
38b8269f14 Translations: Update Dutch
Currently translated at 86.1% (4389 of 5093 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-02-07 15:03:19 +01:00
Raphael Michel
749f5c7e6c Invoices: Visibly mark paid invoices (#3094) 2023-02-07 11:34:38 +01:00
pajowu
d1e8504481 Use local name in control variation list heading (#3091) 2023-02-07 10:07:19 +01:00
Raphael Michel
108408366e Fix localization issue in scheduled emails 2023-02-07 10:05:26 +01:00
Raphael Michel
4543d8093f Add webhooks for changes to items (#3087) 2023-02-06 17:52:42 +01:00
Richard Schreiber
513a90f976 Subevent list: Add meta-data filter (Z#23114466) (#3083)
Co-authored-by: Raphael Michel <michel@rami.io>
2023-02-06 17:51:47 +01:00
Raphael Michel
6f61155deb Bump to stripe==5.1.* 2023-02-06 16:49:50 +01:00
Raphael Michel
714ce28b6a DateFrameWidget: Fix bug with open-ended timeframes 2023-02-06 13:15:45 +01:00
Raphael Michel
b3bcad38a8 DateFrameWidget: Improve dealing with required fields 2023-02-06 13:15:22 +01:00
Raphael Michel
90978e5cab Update from PyPDF2 to pypdf 2023-02-06 10:09:30 +01:00
Raphael Michel
84fb481cdb Fix #3090 -- Add note on pgloader version to migration guide 2023-02-06 10:04:58 +01:00
Raphael Michel
854b41c955 Downgrade PyPDF2 to 2.12 again 2023-02-03 15:00:55 +01:00
Raphael Michel
79ee89bde9 Widget: Add data-id attributes to items, variations and categories 2023-02-03 13:42:05 +01:00
Richard Schreiber
d8d31bab51 PDF-Export: limit pagesize precision in badges nup-placement (#3085) 2023-02-03 11:59:43 +01:00
Raphael Michel
d47bebb403 Update openpyxl to 3.1.* 2023-02-03 09:35:21 +01:00
Raphael Michel
32927bfd4f Update pycryptodome to 3.17.* 2023-02-03 09:35:21 +01:00
dependabot[bot]
41c8d646d9 Bump @babel/core from 7.20.7 to 7.20.12 in /src/pretix/static/npm_dir (#3080)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 09:35:19 +01:00
juliusstoerrle
c828873d21 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5093 of 5093 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-02-03 09:31:50 +01:00
Richard Schreiber
b4e372ce04 PDF: Add support for line height (#3066)
Co-authored-by: Raphael Michel <michel@rami.io>
2023-02-02 19:07:10 +01:00
Raphael Michel
7b301b6027 Self-service order change: Don't create invoice too early 2023-02-01 17:23:28 +01:00
Raphael Michel
68430f01a3 OrderPayment.fail(): Return whether fail was successful 2023-02-01 15:45:20 +01:00
Raphael Michel
363c62a6ca Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5093 of 5093 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-02-01 14:55:00 +01:00
Raphael Michel
a7f32b8647 Translations: Update German
Currently translated at 100.0% (5093 of 5093 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-02-01 14:55:00 +01:00
Raphael Michel
8786397910 Add words to translation word lists 2023-02-01 14:52:03 +01:00
Raphael Michel
d078a42250 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-02-01 14:31:33 +01:00
Raphael Michel
f1a73cd440 Update django to 3.2.17 2023-02-01 13:22:54 +01:00
Raphael Michel
8bba1a2ea6 Fix #1251 -- Event list/calendar: Show "event almost sold out" state (#3063)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-02-01 13:20:06 +01:00
Raphael Michel
aeb5c52bfe Ignore deprecation warning from PyPDF2 2023-02-01 13:18:50 +01:00
Raphael Michel
a684aca212 Revert "Fix test failing due to tz"
This reverts commit 1d46a96821.
2023-02-01 13:16:18 +01:00
Raphael Michel
59d46ddded Revert "First steps into pytz deprecation"
This reverts commit e4e7d50659.
2023-02-01 13:15:18 +01:00
Raphael Michel
e4e7d50659 First steps into pytz deprecation 2023-02-01 13:12:24 +01:00
Raphael Michel
1d46a96821 Fix test failing due to tz 2023-02-01 12:27:22 +01:00
Raphael Michel
8b81ef6f43 Fix test classes 2023-02-01 12:27:15 +01:00
Raphael Michel
cb734510ac Bump PyPDF2 to 3.0.* 2023-02-01 12:16:30 +01:00
Raphael Michel
b29f5c69ed Update isort to 5.12.* 2023-02-01 12:16:01 +01:00
Raphael Michel
56ce37225c Update pytest-rerunfailures to 11.* 2023-02-01 12:15:28 +01:00
Raphael Michel
afae6fdd45 Improve error messages when event or organizer can't be deleted (#3074) 2023-02-01 11:43:07 +01:00
Mauro Amico
4cad8eae93 Translations: Update Italian
Currently translated at 18.2% (928 of 5085 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/it/

powered by weblate
2023-02-01 11:43:00 +01:00
Raphael Michel
c5b7ff66b7 Fix incorrecly used error messages 2023-01-31 22:11:53 +01:00
Raphael Michel
04e16bbb39 Prevent generation of very long team names 2023-01-31 18:13:51 +01:00
Raphael Michel
eea48af60a PDF: Fix Arabic string rendering 2023-01-31 17:11:47 +01:00
Raphael Michel
fc63f60960 Fix #3064 -- Banktransfer: Incorrect timeout handling 2023-01-31 10:44:47 +01:00
Raphael Michel
3b94125471 Voucher product autocompletion: Allow to search for internal name 2023-01-30 18:20:28 +01:00
Raphael Michel
00d901b04b Test on Python 3.11, drop Python 3.7-3.8 (#2982) 2023-01-30 17:54:19 +01:00
Richard Schreiber
a7f9e100d2 Clean up localization or error messages in cart (#3049) 2023-01-30 17:24:09 +01:00
Raphael Michel
59f409b1c6 Widget: Add missing "var" statement 2023-01-30 17:07:27 +01:00
Raphael Michel
e03bebf5ab Bump to 4.17.0.dev0 2023-01-30 13:51:58 +01:00
Raphael Michel
065e6d4024 Bump version to 4.16.0 2023-01-30 13:50:27 +01:00
Raphael Michel
f99e1dd5be Deprecate MySQL support (#3017) 2023-01-30 13:28:30 +01:00
Raphael Michel
25949c6c2b Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5085 of 5085 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-30 12:53:16 +01:00
Raphael Michel
6fe33077e9 Translations: Update German
Currently translated at 100.0% (5085 of 5085 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-30 12:53:16 +01:00
Raphael Michel
29b8ee8408 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-01-30 12:33:49 +01:00
Christophe Piret
15273ba32e Translations: Update French
Currently translated at 49.7% (2526 of 5081 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-01-30 11:54:24 +01:00
Raphael Michel
6ff5b4431c Clean up timezone handling in calendar 2023-01-27 16:45:36 +01:00
Christian Kohlstedde
a82ce69633 Docs: Typo fixes (#3067) 2023-01-27 15:45:09 +01:00
Ismael Menéndez Fernández
53156a4181 Translations: Update Galician
Currently translated at 11.1% (569 of 5081 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/gl/

powered by weblate
2023-01-27 13:48:02 +01:00
Ismael Menéndez Fernández
30142b013e Translations: Update Spanish
Currently translated at 58.1% (2954 of 5081 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2023-01-27 13:48:02 +01:00
Christophe Piret
c4bdfe7537 Translations: Update French
Currently translated at 49.6% (2523 of 5081 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-01-27 13:48:02 +01:00
Mossroy
0972123614 Translations: Update French
Currently translated at 49.6% (2523 of 5081 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-01-27 13:48:02 +01:00
juliusstoerrle
cf71c4ed2b Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5081 of 5081 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-27 13:48:02 +01:00
Raphael Michel
31e5d00093 Fix typo in best_availability_state computation 2023-01-26 21:32:10 +01:00
Raphael Michel
0eba0f5e3e Organizer index: Fix incorrect display of sold-out events 2023-01-26 15:06:54 +01:00
Richard Schreiber
ce79647289 Shop header: Fix logo link outline to contain image (Z#23115320) 2023-01-26 07:41:50 +01:00
Raphael Michel
acc34c29f7 Box office: SHow payment type "cash" 2023-01-25 17:27:22 +01:00
Raphael Michel
ee6fbbf648 Check-in list: Use new optimized query for present people 2023-01-25 17:16:11 +01:00
Raphael Michel
57fa29a0e9 API: Fix default ordering of check-in list positions 2023-01-25 16:36:08 +01:00
Raphael Michel
5d42dc97c2 API: Use a more sane default ordering for checkin-list 2023-01-25 14:35:20 +01:00
Raphael Michel
ddf0d551f3 Box office payments: Fall back to cardType for ZVT 2023-01-25 12:35:14 +01:00
Raphael Michel
a5570dc475 Checkin: Prefer shorter explanation sin logic explainer 2023-01-25 12:27:27 +01:00
Raphael Michel
3c1f3a26cf Always make explicit which tables to lock (#3058)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-01-25 11:44:11 +01:00
Raphael Michel
8ca128912e Fix TypeError in failing bulk-checkin-action 2023-01-25 11:17:03 +01:00
Raphael Michel
b9d8429da8 Fix ignored parameter in 9eb2d4301 2023-01-25 10:13:46 +01:00
Raphael Michel
034a32b048 Fix incorrect detail in 9eb2d4301 2023-01-25 10:13:00 +01:00
Raphael Michel
9eb2d43016 Fix performance and logic issues in auto-exit-all 2023-01-25 09:50:36 +01:00
Richard Schreiber
f81b7bcf53 PPv2: fix missing p-tag in payment confirmation 2023-01-24 19:44:36 +01:00
Raphael Michel
234f9d43c5 PPv2: Improve visibility of last step in paying orders (#3046)
Co-authored-by: Martin Gross <gross@rami.io>
2023-01-24 19:28:34 +01:00
Raphael Michel
7f09b4c903 Check-in list: Do not show auto-exits as auto-entries 2023-01-24 18:59:59 +01:00
Raphael Michel
3bc8450d4f Email shredder: Also shred attendee emails and incoming bounces 2023-01-24 18:18:29 +01:00
Raphael Michel
fdcad926f9 Changing orders: Default to not notifying the user (#3056) 2023-01-24 16:16:29 +01:00
Raphael Michel
433262f6fc Prepare for DeleteView change in Django 4.0 2023-01-24 14:16:01 +01:00
Fabian
50596b7543 bump debian version (#3055) 2023-01-23 21:37:17 +01:00
Raphael Michel
988188b00a Scheduled exports: Fix missing event context, fix form initial 2023-01-23 11:31:54 +01:00
Raphael Michel
fdc15a753c Scheduled exports: Set owner to cc instead of to if there is an explicit recipient (#3045) 2023-01-23 11:10:47 +01:00
Raphael Michel
785cc49a2e Bank transfer: Fix SEPA debits not shown on organizer level 2023-01-20 16:59:25 +01:00
Raphael Michel
863fd3065a Optimize CheckinList.inside_count (#3043) 2023-01-20 16:02:19 +01:00
Raphael Michel
ac361a8f47 Scheduled exports: Use proper JSON encoder 2023-01-20 12:59:38 +01:00
Raphael Michel
56d928d5ec Widget: Do not declare products "FREE" if they have mandatory addons (#3041) 2023-01-20 09:15:14 +01:00
Richard Schreiber
6c3e745d5d Control: Remove empty help-text for colorpickers with no-contrast (#3042) 2023-01-20 08:50:08 +01:00
Raphael Michel
b29efb9694 Scheduled exports: Add required transaction 2023-01-19 18:41:46 +01:00
Raphael Michel
5ee1213dbf Gift card list export: Use date picker 2023-01-19 17:46:06 +01:00
Raphael Michel
c29dc49819 Scheduled exports: Lock exports while setting their new time 2023-01-19 16:31:47 +01:00
Raphael Michel
8b74f791f4 Export schedule: Fix computation of start time on same day 2023-01-19 14:34:27 +01:00
Raphael Michel
4d75438a11 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5081 of 5081 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-19 13:37:58 +01:00
Raphael Michel
781002b27e Translations: Update German
Currently translated at 100.0% (5081 of 5081 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-19 13:37:58 +01:00
Raphael Michel
f7c0e8c8d0 Translations: Extend German word list 2023-01-19 13:20:14 +01:00
Raphael Michel
70a3516725 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-01-19 11:47:34 +01:00
Raphael Michel
3133e18b22 Fix isort issue 2023-01-19 11:46:56 +01:00
Raphael Michel
3257c59117 Delete checkins when deleting orders 2023-01-19 11:46:51 +01:00
Raphael Michel
19d1a8de71 Scheduled exports (#3033) 2023-01-19 11:46:30 +01:00
Richard Schreiber
0bb5af191b Product list: Fix add-to-cart-button being shown on seating-only event (#3038) 2023-01-19 10:56:48 +01:00
Raphael Michel
8fe56b7278 Export: Fix date range validation 2023-01-19 10:51:18 +01:00
Martin Gross
df432b1958 Presale: Set "Contact Event Organizer"-mailto href to _blank 2023-01-18 17:29:18 +01:00
Raphael Michel
54434f07a9 Email settings: Order languages of preview like form 2023-01-18 12:23:43 +01:00
Raphael Michel
0ecbee48ae Stripe: Catch failing promise on JS level 2023-01-18 11:47:28 +01:00
Maurice Kaag
ff2fa43ba1 Translations: Update French
Currently translated at 63.3% (128 of 202 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/fr/

powered by weblate
2023-01-18 11:39:23 +01:00
Maurice Kaag
3a1cefbbe7 Translations: Update French
Currently translated at 48.9% (2464 of 5029 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-01-18 11:39:23 +01:00
Raphael Michel
7aa433e9af Translations: Update French
Currently translated at 46.8% (2354 of 5029 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-01-18 11:39:23 +01:00
Maurice Kaag
c5a5d13158 Translations: Update French
Currently translated at 62.8% (127 of 202 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/fr/

powered by weblate
2023-01-18 11:39:23 +01:00
Maurice Kaag
2e256e30be Translations: Update French
Currently translated at 46.8% (2354 of 5029 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-01-18 11:39:23 +01:00
Raphael Michel
0fbc0c3ffb Refresh order status after applying gift card 2023-01-17 15:26:31 +01:00
Richard Schreiber
93950d3fac Presale: separate multiple lines by comma in ical event location (#3037) 2023-01-17 13:17:58 +01:00
pretix translation bot
e8269ed1bf Update translations (#3031)
Co-authored-by: Raphael Michel <michel@rami.io>
2023-01-13 15:43:06 +01:00
Raphael Michel
3fa1fbf6e2 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-01-13 15:07:59 +01:00
Raphael Michel
8114b47c8c API: Support for date ranges in exports 2023-01-13 13:48:45 +01:00
Raphael Michel
dcf5e67196 Fix minor issues in DateFrameField/DateFrameWidget 2023-01-13 13:30:12 +01:00
Raphael Michel
bf4569b080 Exports: Add predefined timeframes (#3027)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-01-13 13:14:08 +01:00
Aurélia BOUYGE
95979143d7 Translations: Update French
Currently translated at 45.7% (2284 of 4995 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2023-01-13 11:20:00 +01:00
tree
4c5e77c2ef Translations: Update Czech
Currently translated at 85.1% (172 of 202 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/cs/

powered by weblate
2023-01-13 11:20:00 +01:00
tree
95b4f08aeb Translations: Update Czech
Currently translated at 26.7% (1336 of 4995 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/cs/

powered by weblate
2023-01-13 11:20:00 +01:00
Raphael Michel
d6605e668b Bump Pillow to 9.4.* 2023-01-13 11:19:21 +01:00
Raphael Michel
6ee348548f Bump django-compressor to 4.3.* 2023-01-13 11:18:39 +01:00
Raphael Michel
fca8e48f6a Bump arabic-reshaper to 3.0.0 2023-01-13 11:18:20 +01:00
Raphael Michel
5a295934f7 Update select2 from 4.0.6-rc.1 to 4.0.13 2023-01-13 10:49:54 +01:00
Raphael Michel
4385b41e8b Item typeahead: Allow search by internal name 2023-01-13 10:40:39 +01:00
Raphael Michel
92dacfb966 Only run new validation on newly uploaded files 2023-01-12 19:00:40 +01:00
Raphael Michel
d1acbad181 Export: Fix issue showing error messages 2023-01-12 18:06:23 +01:00
Richard Schreiber
d0676765a4 Checkout: remove Indonesia from zipcode-validation (#3029) 2023-01-12 16:57:13 +01:00
Raphael Michel
9dd3b12625 Validate image size in pixels at upload time (#3003) 2023-01-12 16:30:28 +01:00
Raphael Michel
738301d2af CI: Fix syntax error 2023-01-12 15:13:58 +01:00
Raphael Michel
f7f29e8a55 Do not read language from session any more (deprecated since Django 3.0) 2023-01-12 15:00:37 +01:00
Raphael Michel
ad69ec293f CI: Use own codecov upload token to prevent rate limit issue (#3028) 2023-01-12 13:36:13 +01:00
Raphael Michel
3443296a28 Device list: Hide revoked devices by default (#2996) 2023-01-12 13:35:43 +01:00
Richard Schreiber
7a69e00d39 Control: improve settings-icon for non-personalized tickets 2023-01-12 11:00:43 +01:00
Raphael Michel
bddc91d595 Export: Fix handling of form validation errors 2023-01-12 09:56:45 +01:00
Raphael Michel
0c0d8b2c55 ItemDataExporter: Fix off-by-one 2023-01-12 09:45:17 +01:00
Raphael Michel
c018921a18 Translate label of JSONExporter 2023-01-11 15:10:14 +01:00
Raphael Michel
f33aa3fdba Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (4995 of 4995 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-11 13:54:57 +01:00
Raphael Michel
7b55f85663 Translations: Update German
Currently translated at 100.0% (4995 of 4995 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-11 13:54:57 +01:00
Raphael Michel
fb9909ca83 Translations: Update German
Currently translated at 100.0% (4995 of 4995 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-11 13:54:57 +01:00
Raphael Michel
35e8bab7a5 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (4995 of 4995 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-11 13:54:57 +01:00
Raphael Michel
bf34e73121 Translations: Update German
Currently translated at 99.9% (4994 of 4995 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-11 13:54:57 +01:00
Raphael Michel
39e2715f3c Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (4995 of 4995 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-11 13:54:57 +01:00
Raphael Michel
97d2b015cf Translations: Update German
Currently translated at 99.9% (4994 of 4995 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-11 13:54:57 +01:00
Raphael Michel
ca30a07da3 Update translation wordlists 2023-01-11 13:48:22 +01:00
Raphael Michel
81d31ce64c Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-01-11 13:07:47 +01:00
Raphael Michel
0ae66ab7f6 Reorganize UI for exporters (#3025)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-01-11 12:34:56 +01:00
Raphael Michel
cb4af51c01 Sendmail: Fix issue loading old logs 2023-01-10 17:36:53 +01:00
Raphael Michel
6b44cae607 Fix incorrect handling of admission/personalized in API PATCH 2023-01-10 17:28:58 +01:00
Raphael Michel
1a4d4029c9 Sendmail: Fix incorrect placeholder promoted for waiting list 2023-01-10 13:28:22 +01:00
Raphael Michel
3563653d55 Payment step: Fix edge case when redeeming gift cards with service fees 2023-01-10 13:17:27 +01:00
Raphael Michel
e4c9afa87a Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (4961 of 4961 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-10 12:34:37 +01:00
Raphael Michel
6938397a6a Translations: Update German
Currently translated at 100.0% (4961 of 4961 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-10 12:34:37 +01:00
Raphael Michel
24e5b593ea Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-01-10 12:04:50 +01:00
Raphael Michel
cd237d4c19 Sendmail: Improve wording 2023-01-10 12:04:03 +01:00
Raphael Michel
9b1d7cc522 Sendmail: Abstract away to allow more types of recipients (#2994)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-01-10 12:03:50 +01:00
Raphael Michel
d07948613a Validate tax rates to be between 0 and 100 2023-01-10 11:48:42 +01:00
Raphael Michel
eadc1b4812 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (4953 of 4953 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-09 16:01:58 +01:00
Raphael Michel
787d4ec06b Translations: Update German
Currently translated at 100.0% (4953 of 4953 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-09 16:01:58 +01:00
Richard Schreiber
ca1d13421f Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (4953 of 4953 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-09 16:01:58 +01:00
Richard Schreiber
495ae25b9e Translations: Update German
Currently translated at 100.0% (4953 of 4953 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-09 16:01:58 +01:00
Raphael Michel
d98accdd2d Translations: Update German
Currently translated at 100.0% (4953 of 4953 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-09 16:01:58 +01:00
Raphael Michel
746ced9e93 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (4953 of 4953 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-01-09 16:01:58 +01:00
Raphael Michel
d72bbffc51 Translations: Update German
Currently translated at 100.0% (4953 of 4953 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-01-09 16:01:58 +01:00
Raphael Michel
8503623472 Add word to German wordlist 2023-01-09 15:32:36 +01:00
Raphael Michel
4f097e279a Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-01-09 14:58:25 +01:00
Raphael Michel
603225d042 Separate personalization from admission (#2990)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-01-09 14:57:35 +01:00
Raphael Michel
e5528f7784 Writable API for ticket layouts (#3004)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-01-09 13:44:01 +01:00
Raphael Michel
2e702b87de Do not show empty invoice address fields on confirmation 2023-01-09 11:24:30 +01:00
Raphael Michel
59730ff501 Stripe: Rename SOFORT's public name 2023-01-09 10:03:37 +01:00
Raphael Michel
280c24528f API: Fix crash when creating item variations with require_membership_types 2023-01-09 10:03:19 +01:00
Raphael Michel
ff09ed422c Prevent requiring a membership without selecting any types 2023-01-06 23:17:00 +01:00
Raphael Michel
b3be64b9f3 Bank transfer: Small parser improvement 2023-01-05 09:41:35 +01:00
Raphael Michel
018c3d70e3 API: Allow to set order of check-in lists 2023-01-04 18:29:35 +01:00
Raphael Michel
a2f2d25169 Allow users with can_checkin_orders permission to use the bulk actions of the check-in list view 2023-01-04 18:13:24 +01:00
Raphael Michel
4747a4c480 Order change view: Remove a few buttons for read-only users 2023-01-04 18:13:24 +01:00
dependabot[bot]
ed9a9246e3 Bump @babel/core from 7.20.5 to 7.20.7 in /src/pretix/static/npm_dir (#3002)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-02 11:42:41 +01:00
Raphael Michel
6e63d34932 Cart: Prevent TypeError mixing seated and unseated lines 2023-01-02 10:33:51 +01:00
Raphael Michel
db06ed132a PPv2: Fix invalid cart payments in edge case (PRETIXEU-7QG) 2023-01-02 10:19:31 +01:00
Raphael Michel
ddbe38ca53 API: Do not crash if invalid data type is given for name_parts 2023-01-02 10:17:09 +01:00
Raphael Michel
d3698b3e2f Widget: Annotate parts of widget source code 2022-12-22 11:36:22 +01:00
Fazenda Dengo
ff828ecc92 Translations: Update Portuguese (Portugal)
Currently translated at 85.6% (4228 of 4934 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_PT/

powered by weblate
2022-12-22 10:47:53 +01:00
Fazenda Dengo
d0236572f0 Translations: Update Portuguese (Brazil)
Currently translated at 12.9% (637 of 4934 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_BR/

powered by weblate
2022-12-22 10:47:53 +01:00
Raphael Michel
8ea6f3bc7d Fix #2499 -- Incorrect type detection order in RelativeDateWrapper 2022-12-21 15:34:31 +01:00
443 changed files with 221295 additions and 154907 deletions

View File

@@ -26,10 +26,10 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Set up Python 3.11
uses: actions/setup-python@v1
with:
python-version: 3.9
python-version: 3.11
- uses: actions/cache@v1
with:
path: ~/.cache/pip

View File

@@ -24,10 +24,10 @@ jobs:
name: Check gettext syntax
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Set up Python 3.11
uses: actions/setup-python@v1
with:
python-version: 3.9
python-version: 3.11
- uses: actions/cache@v1
with:
path: ~/.cache/pip
@@ -50,10 +50,10 @@ jobs:
name: Spellcheck
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Set up Python 3.11
uses: actions/setup-python@v1
with:
python-version: 3.9
python-version: 3.11
- uses: actions/cache@v1
with:
path: ~/.cache/pip

View File

@@ -24,10 +24,10 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Set up Python 3.11
uses: actions/setup-python@v1
with:
python-version: 3.9
python-version: 3.11
- uses: actions/cache@v1
with:
path: ~/.cache/pip
@@ -45,10 +45,10 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Set up Python 3.11
uses: actions/setup-python@v1
with:
python-version: 3.9
python-version: 3.11
- uses: actions/cache@v1
with:
path: ~/.cache/pip
@@ -66,10 +66,10 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Set up Python 3.11
uses: actions/setup-python@v1
with:
python-version: 3.9
python-version: 3.11
- name: Install Dependencies
run: pip3 install licenseheaders
- name: Run licenseheaders

View File

@@ -24,22 +24,22 @@ jobs:
name: Tests
strategy:
matrix:
python-version: ["3.7", "3.9", "3.10"]
python-version: ["3.9", "3.10", "3.11"]
database: [sqlite, postgres, mysql]
exclude:
- database: mysql
python-version: "3.10"
- database: mysql
python-version: "3.9"
- database: mysql
python-version: "3.11"
- database: sqlite
python-version: "3.7"
python-version: "3.9"
- database: sqlite
python-version: "3.10"
steps:
- uses: actions/checkout@v2
- uses: getong/mariadb-action@v1.1
with:
mariadb version: '10.4'
mariadb version: '10.10'
mysql database: 'pretix'
mysql root password: ''
if: matrix.database == 'mysql'
@@ -81,5 +81,6 @@ jobs:
uses: codecov/codecov-action@v1
with:
file: src/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
if: matrix.database == 'postgres' && matrix.python-version == '3.10'
if: matrix.database == 'postgres' && matrix.python-version == '3.11'

View File

@@ -1,4 +1,4 @@
FROM python:3.9-bullseye
FROM python:3.11-bullseye
RUN apt-get update && \
apt-get install -y --no-install-recommends \

View File

@@ -18,67 +18,82 @@
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
{% endblock %}
{# FAVICON #}
{% if favicon %}
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{% endif %}
{# CANONICAL URL #}
{% if theme_canonical_url %}
{#- CSS #}
{%- for css in css_files %}
{%- if css|attr("rel") %}
<link rel="{{ css.rel }}" href="{{ pathto(css.filename, 1) }}" type="text/css"{% if css.title is not none %} title="{{ css.title }}"{% endif %} />
{%- else %}
<link rel="stylesheet" href="{{ pathto(css, 1) }}" type="text/css" />
{%- endif %}
{%- endfor %}
{%- for cssfile in extra_css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{%- endfor -%}
{#- FAVICON
favicon_url is the only context var necessary since Sphinx 4.
In Sphinx<4, we use favicon but need to prepend path info.
#}
{%- set _favicon_url = favicon_url | default(pathto('_static/' + (favicon or ""), 1)) %}
{%- if favicon_url or favicon %}
<link rel="shortcut icon" href="{{ _favicon_url }}"/>
{%- endif %}
{#- CANONICAL URL (deprecated) #}
{%- if theme_canonical_url and not pageurl %}
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
{% endif %}
{%- endif -%}
{# CSS #}
{#- CANONICAL URL #}
{%- if pageurl %}
<link rel="canonical" href="{{ pageurl|e }}" />
{%- endif -%}
{# OPENSEARCH #}
{% if not embedded %}
{% if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{% endif %}
{#- JAVASCRIPTS #}
{%- block scripts %}
<!--[if lt IE 9]>
<script src="{{ pathto('_static/js/html5shiv.min.js', 1) }}"></script>
<![endif]-->
{%- if not embedded %}
{# XXX Sphinx 1.8.0 made this an external js-file, quick fix until we refactor the template to inherert more blocks directly from sphinx #}
{%- for scriptfile in script_files %}
{{ js_tag(scriptfile) }}
{%- endfor %}
<script src="{{ pathto('_static/js/theme.js', 1) }}"></script>
{% endif %}
{# RTD hosts this file, so just load on non RTD builds #}
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
{% for cssfile in css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{% endfor %}
{% for cssfile in extra_css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{% endfor %}
{#- OPENSEARCH #}
{%- if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml"
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{%- endif %}
{%- endif %}
{%- endblock %}
{%- block linktags %}
{%- if hasdoc('about') %}
<link rel="author" title="{{ _('About these documents') }}"
href="{{ pathto('about') }}"/>
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
{%- endif %}
{%- if hasdoc('genindex') %}
<link rel="index" title="{{ _('Index') }}"
href="{{ pathto('genindex') }}"/>
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
{%- endif %}
{%- if hasdoc('search') %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/>
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
{%- endif %}
{%- if hasdoc('copyright') %}
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}"/>
{%- endif %}
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}"/>
{%- if parents %}
<link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}"/>
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
{%- endif %}
{%- if next %}
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}"/>
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
{%- endif %}
{%- if prev %}
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}"/>
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
{%- endif %}
{%- endblock %}
{%- block extrahead %} {% endblock %}
{# Keep modernizr in head - http://modernizr.com/docs/#installing #}
<script src="{{ pathto('_static/js/modernizr.min.js', 1) }}"></script>
</head>
<body class="wy-body-for-nav" role="document">
@@ -92,16 +107,14 @@
<div class="wy-side-nav-search">
{% block sidebartitle %}
{% if logo and theme_logo_only %}
<a href="{{ pathto('index') }}">
{% else %}
<a href="{{ pathto('index') }}" class="icon icon-home"> {{ project }}
{% endif %}
{% if logo %}
{# Not strictly valid HTML, but it's the only way to display/scale it properly, without weird scripting or heaps of work #}
<img src="{{ pathto('_static/' + logo, 1) }}" class="logo" />
{% endif %}
{# the logo helper function was removed in Sphinx 6 and deprecated since Sphinx 4 #}
{# the master_doc variable was renamed to root_doc in Sphinx 4 (master_doc still exists in later Sphinx versions) #}
{%- set _logo_url = logo_url|default(pathto('_static/' + (logo or ""), 1)) %}
{%- set _root_doc = root_doc|default(master_doc) %}
<a href="{{ pathto(_root_doc) }}"{% if not theme_logo_only %} class="icon icon-home"{% endif %}>
{%- if logo or logo_url %}
<img src="{{ _logo_url }}" class="logo" alt="{{ _('Logo') }}"/>
{%- endif %}
</a>
{% include "searchbox.html" %}

View File

@@ -5,31 +5,37 @@
Template for the search page.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
:license: BSD, see https://github.com/sphinx-doc/sphinx/blob/master/LICENSE for details.
#}
{%- extends "layout.html" %}
{% set title = _('Search') %}
{% set script_files = script_files + ['_static/searchtools.js'] %}
{% set display_vcs_links = False %}
{%- block scripts %}
{{ super() }}
<script src="{{ pathto('_static/searchtools.js', 1) }}"></script>
<script src="{{ pathto('_static/language_data.js', 1) }}"></script>
{%- endblock %}
{% block footer %}
<script type="text/javascript">
<script>
jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
</script>
{# this is used when loading the search index using $.ajax fails,
such as on Chrome for documents on localhost #}
<script type="text/javascript" id="searchindexloader"></script>
<script id="searchindexloader"></script>
{{ super() }}
{% endblock %}
{% block body %}
<noscript>
<div id="fallback" class="admonition warning">
<p class="last">
{% trans %}Please activate JavaScript to enable the search
{% trans trimmed %}Please activate JavaScript to enable the search
functionality.{% endtrans %}
</p>
</div>
</noscript>
{% if search_performed %}
{# Translators: Search is a noun, not a verb #}
<h2>{{ _('Search Results') }}</h2>
{% if not search_results %}
<p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
@@ -47,4 +53,4 @@
</ul>
{% endif %}
</div>
{% endblock %}
{% endblock %}

View File

@@ -2,7 +2,7 @@
.. _`config`:
.. spelling:: Galera
.. spelling:word-list:: Galera
Configuration file
==================
@@ -84,7 +84,7 @@ Example::
Enables or disables the "keep me logged in" button. Defaults to ``on``.
``ecb_rates``
By default, pretix periodically downloads a XML file from the European Central Bank to retrieve exchange rates
By default, pretix periodically downloads currency rates from the European Central Bank as well as other authorities
that are used to print tax amounts in the customer currency on invoices for some currencies. Set to ``off`` to
disable this feature. Defaults to ``on``.
@@ -106,6 +106,11 @@ Example::
proxy that actively removes and re-adds the header to make sure the correct value is set.
Defaults to ``off``.
``trust_x_forwarded_host``
Specifies whether the ``X-Forwarded-Host`` header can be trusted. Only set to ``on`` if you have a reverse
proxy that actively removes and re-adds the header to make sure the correct value is set.
Defaults to ``off``.
``csp_log``
Log violations of the Content Security Policy (CSP). Defaults to ``on``.
@@ -141,7 +146,7 @@ Database settings
Example::
[database]
backend=mysql
backend=postgresql
name=pretix
user=pretix
password=abcd
@@ -149,7 +154,7 @@ Example::
port=3306
``backend``
One of ``mysql``, ``sqlite3``, ``oracle`` and ``postgresql``.
One of ``mysql`` (deprecated), ``sqlite3`` and ``postgresql``.
Default: ``sqlite3``.
If you use MySQL, be sure to create your database using
@@ -163,7 +168,7 @@ Example::
Connection details for the database connection. Empty by default.
``galera``
Indicates if the database backend is a MySQL/MariaDB Galera cluster and
(Deprecated) Indicates if the database backend is a MySQL/MariaDB Galera cluster and
turns on some optimizations/special case handlers. Default: ``False``
.. _`config-replica`:
@@ -194,7 +199,7 @@ Example::
[urls]
media=/media/
static=/media/
static=/static/
``media``
The URL to be used to serve user-uploaded content. You should not need to modify

View File

@@ -14,4 +14,5 @@ This documentation is for everyone who wants to install pretix on a server.
maintainance
scaling
errors
mysql2postgres
indexes

View File

@@ -45,8 +45,8 @@ Here is the currently recommended set of commands::
CREATE INDEX CONCURRENTLY pretix_addidx_order_comment
ON pretixbase_order
USING gin (upper("comment") gin_trgm_ops);
CREATE INDEX CONCURRENTLY pretix_addidx_order_event_date
ON public.pretixbase_order (event_id, datetime);
CREATE INDEX CONCURRENTLY pretix_addidx_order_event_date_id
ON public.pretixbase_order (event_id, datetime, id);
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_name
ON pretixbase_orderposition
USING gin (upper("attendee_name_cached") gin_trgm_ops);
@@ -66,10 +66,10 @@ Here is the currently recommended set of commands::
ON public.pretixbase_orderposition (upper((attendee_email)::text));
CREATE INDEX CONCURRENTLY pretix_addidx_voucher_code_upper
ON public.pretixbase_voucher (upper((code)::text));
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_date
ON public.pretixbase_logentry (event_id, datetime);
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_cid_date
ON public.pretixbase_logentry (event_id, content_type_id, datetime);
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_date_id
ON public.pretixbase_logentry (event_id, datetime, id);
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_cid_date_id
ON public.pretixbase_logentry (event_id, content_type_id, datetime, id);
Also, if you use our ``pretix-shipping`` plugin::

View File

@@ -14,7 +14,7 @@ This has some trade-offs in terms of performance and isolation but allows a rath
get it right. If you're not feeling comfortable managing a Linux server, check out our hosting and service
offers at `pretix.eu`_.
We tested this guide on the Linux distribution **Debian 8.0** but it should work very similar on other
We tested this guide on the Linux distribution **Debian 11.0** but it should work very similar on other
modern distributions, especially on all systemd-based ones.
Requirements
@@ -26,7 +26,7 @@ installation guides):
* `Docker`_
* A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for
* A HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections
* A `PostgreSQL`_ 9.6+, `MySQL`_ 5.7+, or MariaDB 10.2.7+ database server
* A `PostgreSQL`_ 9.6+ database server
* A `redis`_ server
We also recommend that you use a firewall, although this is not a pretix-specific recommendation. If you're new to
@@ -58,9 +58,6 @@ directory writable to the user that runs pretix inside the docker container::
Database
--------
.. warning:: **Please use PostgreSQL for all new installations**. If you need to go for MySQL, make sure you run
**MySQL 5.7 or newer** or **MariaDB 10.2.7 or newer**.
Next, we need a database and a database user. We can create these with any kind of database managing tool or directly on
our database's shell. Please make sure that UTF8 is used as encoding for the best compatibility. You can check this with
the following command::
@@ -86,13 +83,6 @@ Restart PostgreSQL after you changed these files::
If you have a firewall running, you should also make sure that port 5432 is reachable from the ``172.17.0.1/16`` subnet.
For MySQL, you can either also use network-based connections or mount the ``/var/run/mysqld/mysqld.sock`` socket into the docker container.
When using MySQL, make sure you set the character set of the database to ``utf8mb4``, e.g. like this::
mysql > CREATE DATABASE pretix DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
You will also need to make sure that ``sql_mode`` in your ``my.cnf`` file does **not** include ``ONLY_FULL_GROUP_BY``.
Redis
-----
@@ -152,15 +142,13 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
trust_x_forwarded_proto=on
[database]
; Replace postgresql with mysql for MySQL
backend=postgresql
name=pretix
user=pretix
; Replace with the password you chose above
password=*********
; In most docker setups, 172.17.0.1 is the address of the docker host. Adjust
; this to wherever your database is running, e.g. the name of a linked container
; or of a mounted MySQL socket.
; this to wherever your database is running, e.g. the name of a linked container.
host=172.17.0.1
[mail]
@@ -212,8 +200,6 @@ named ``/etc/systemd/system/pretix.service`` with the following content::
[Install]
WantedBy=multi-user.target
When using MySQL and socket mounting, you'll need the additional flag ``-v /var/run/mysqld:/var/run/mysqld`` in the command.
You can now run the following commands
to enable and start the service::
@@ -339,7 +325,6 @@ workers, e.g. ``docker run … taskworker -Q notifications --concurrency 32``.
.. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/
.. _Let's Encrypt: https://letsencrypt.org/
.. _pretix.eu: https://pretix.eu/
.. _MySQL: https://dev.mysql.com/doc/refman/5.7/en/linux-installation-apt-repo.html
.. _PostgreSQL: https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-20-04
.. _redis: https://blog.programster.org/debian-8-install-redis-server/
.. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall

View File

@@ -1,6 +1,6 @@
.. highlight:: ini
.. spelling:: SQL
.. spelling:word-list:: SQL
General remarks
===============

View File

@@ -12,7 +12,7 @@ solution with many things readily set-up, look at :ref:`dockersmallscale`.
get it right. If you're not feeling comfortable managing a Linux server, check out our hosting and service
offers at `pretix.eu`_.
We tested this guide on the Linux distribution **Debian 10.0** but it should work very similar on other
We tested this guide on the Linux distribution **Debian 11.6** but it should work very similar on other
modern distributions, especially on all systemd-based ones.
Requirements
@@ -23,7 +23,7 @@ installation guides):
* A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for
* A HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections
* A `PostgreSQL`_ 9.6+, `MySQL`_ 5.7+, or MariaDB 10.2.7+ database server
* A `PostgreSQL`_ 11+ database server
* A `redis`_ server
* A `nodejs`_ installation
@@ -47,9 +47,6 @@ In this guide, all code lines prepended with a ``#`` symbol are commands that yo
Database
--------
.. warning:: **Please use PostgreSQL for all new installations**. If you need to go for MySQL, make sure you run
**MySQL 5.7 or newer** or **MariaDB 10.2.7 or newer**.
Having the database server installed, we still need a database and a database user. We can create these with any kind
of database managing tool or directly on our database's shell. Please make sure that UTF8 is used as encoding for the
best compatibility. You can check this with the following command::
@@ -61,12 +58,6 @@ For PostgreSQL database creation, we would do::
# sudo -u postgres createuser pretix
# sudo -u postgres createdb -O pretix pretix
When using MySQL, make sure you set the character set of the database to ``utf8mb4``, e.g. like this::
mysql > CREATE DATABASE pretix DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
You will also need to make sure that ``sql_mode`` in your ``my.cnf`` file does **not** include ``ONLY_FULL_GROUP_BY``.
Package dependencies
--------------------
@@ -74,7 +65,7 @@ To build and run pretix, you will need the following debian packages::
# apt-get install git build-essential python-dev python3-venv python3 python3-pip \
python3-dev libxml2-dev libxslt1-dev libffi-dev zlib1g-dev libssl-dev \
gettext libpq-dev libmariadb-dev libjpeg-dev libopenjp2-7-dev
gettext libpq-dev libjpeg-dev libopenjp2-7-dev
Config file
-----------
@@ -97,16 +88,12 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
trust_x_forwarded_proto=on
[database]
; For MySQL, replace with "mysql"
backend=postgresql
name=pretix
user=pretix
; For MySQL, enter the user password. For PostgreSQL on the same host,
; we don't need one because we can use peer authentification if our
; PostgreSQL user matches our unix user.
; For PostgreSQL on the same host, we don't need a password because we can use
; peer authentication if our PostgreSQL user matches our unix user.
password=
; For MySQL, use local socket, e.g. /var/run/mysqld/mysqld.sock
; For a remote host, supply an IP address
; For local postgres authentication, you can leave it empty
host=
@@ -140,11 +127,7 @@ We now install pretix, its direct dependencies and gunicorn::
(venv)$ pip3 install pretix gunicorn
If you're running MySQL, also install the client library::
(venv)$ pip3 install mysqlclient
Note that you need Python 3.7 or newer. You can find out your Python version using ``python -V``.
Note that you need Python 3.9 or newer. You can find out your Python version using ``python -V``.
We also need to create a data directory::
@@ -344,7 +327,6 @@ Then, proceed like after any plugin installation::
.. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/
.. _Let's Encrypt: https://letsencrypt.org/
.. _pretix.eu: https://pretix.eu/
.. _MySQL: https://dev.mysql.com/doc/refman/5.7/en/linux-installation-apt-repo.html
.. _PostgreSQL: https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-20-04
.. _redis: https://blog.programster.org/debian-8-install-redis-server/
.. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall

View File

@@ -17,11 +17,11 @@ Backups
There are essentially two things which you should create backups of:
Database
Your SQL database (MySQL or PostgreSQL). This is critical and you should **absolutely
always create automatic backups of your database**. There are tons of tutorials on the
internet on how to do this, and the exact process depends on the choice of your database.
For MySQL, see ``mysqldump`` and for PostgreSQL, see the ``pg_dump`` tool. You probably
want to create a cronjob that does the backups for you on a regular schedule.
Your SQL database. This is critical and you should **absolutely always create automatic
backups of your database**. There are tons of tutorials on the internet on how to do this,
and the exact process depends on the choice of your database. For PostgreSQL, see the
``pg_dump`` tool. You probably want to create a cronjob that does the backups for you on a
regular schedule.
Data directory
The data directory of your pretix configuration might contain some things that you should

View File

@@ -0,0 +1,156 @@
.. highlight:: none
Migrating from MySQL/MariaDB to PostgreSQL
==========================================
Our recommended database for all production installations is PostgreSQL. Support for MySQL/MariaDB will be removed in
pretix 5.0.
In order to follow this guide, your pretix installation needs to be a version that fully supports MySQL/MariaDB. If you
already upgraded to pretix 5.0, downgrade back to the last 4.x release using ``pip``.
.. note:: We have tested this guide carefully, but we can't assume any liability for its correctness. The data loss
risk should be low as long as pretix is not running while you do the migration. If you are a pretix Enterprise
customer, feel free to reach out in advance if you want us to support you along the way.
Update database schema
----------------------
Before you start, make sure your database schema is up to date::
# sudo -u pretix -s
$ source /var/pretix/venv/bin/activate
(venv)$ python -m pretix migrate
Install PostgreSQL
------------------
Now, install and set up a PostgreSQL server. For a local installation on Debian or Ubuntu, use::
# apt install postgresql
Having the database server installed, we still need a database and a database user. We can create these with any kind
of database managing tool or directly on our database's shell. Please make sure that UTF8 is used as encoding for the
best compatibility. You can check this with the following command::
# sudo -u postgres psql -c 'SHOW SERVER_ENCODING'
Without Docker
""""""""""""""
For our standard manual installation, create the database and user like this::
# sudo -u postgres createuser pretix
# sudo -u postgres createdb -O pretix pretix
With Docker
"""""""""""
For our standard docker installation, create the database and user like this::
# sudo -u postgres createuser -P pretix
# sudo -u postgres createdb -O pretix pretix
Make sure that your database listens on the network. If PostgreSQL on the same same host as docker, but not inside a docker container, we recommend that you just listen on the Docker interface by changing the following line in ``/etc/postgresql/<version>/main/postgresql.conf``::
listen_addresses = 'localhost,172.17.0.1'
You also need to add a new line to ``/etc/postgresql/<version>/main/pg_hba.conf`` to allow network connections to this user and database::
host pretix pretix 172.17.0.1/16 md5
Restart PostgreSQL after you changed these files::
# systemctl restart postgresql
If you have a firewall running, you should also make sure that port 5432 is reachable from the ``172.17.0.1/16`` subnet.
Of course, instead of all this you can also run a PostgreSQL docker container and link it to the pretix container.
Stop pretix
-----------
To prevent any more changes to your data, stop pretix from running::
# systemctl stop pretix-web pretix-worker
Change configuration
--------------------
Change the database configuration in your ``/etc/pretix/pretix.cfg`` file::
[database]
backend=postgresql
name=pretix
user=pretix
password= ; only required for docker or remote database, can be kept empty for local auth
host= ; set to 172.17.0.1 in docker setup, keep empty for local auth
Create database schema
-----------------------
To create the schema in your new PostgreSQL database, use the following commands::
# sudo -u pretix -s
$ source /var/pretix/venv/bin/activate
(venv)$ python -m pretix migrate
Migrate your data
-----------------
Install ``pgloader``::
# apt install pgloader
.. note::
If you are using Ubuntu 20.04, the ``pgloader`` version from the repositories seems to be incompatible with PostgreSQL
12+. You can install ``pgloader`` from the `PostgreSQL repositories`_ instead.
See also `this discussion <https://github.com/pretix/pretix/issues/3090>`_.
Create a new file ``/tmp/pretix.load``, replacing the MySQL and PostgreSQL connection strings with the correct user names, passwords, and/or database names::
LOAD DATABASE
FROM mysql://pretix:password@localhost/pretix -- replace with mysql://username:password@hostname/dbname
INTO postgresql:///pretix -- replace with dbname
WITH data only, include no drop, truncate, disable triggers,
create no indexes, drop indexes, reset sequences
ALTER SCHEMA 'pretix' RENAME TO 'public' -- replace pretix with the name of the MySQL database
ALTER TABLE NAMES MATCHING ~/.*/
SET SCHEMA 'public'
SET timezone TO '+00:00'
SET PostgreSQL PARAMETERS
maintenance_work_mem to '128MB',
work_mem to '12MB';
Then, run::
# sudo -u postgres pgloader /tmp/pretix.load
The output should end with a table summarizing the results for every table. You can ignore warnings about type casts
and missing constraints.
Afterwards, delete the file again::
# rm -rf /tmp/pretix.load
Start pretix
------------
Now, restart pretix. Maybe stop your MySQL server as a verification step that you are no longer using it::
# systemctl stop mariadb
# systemctl start pretix-web pretix-worker
And you're done! After you've verified everything has been copied correctly, you can delete the old MySQL database.
.. note:: Don't forget to update your backup process to back up your PostgreSQL database instead of your MySQL database now.
.. _PostgreSQL repositories: https://wiki.postgresql.org/wiki/Apt

View File

@@ -42,7 +42,7 @@ A pretix installation usually consists of the following components which run per
* ``pretix-worker`` is a Celery-based application that processes tasks that should be run asynchronously outside of the web application process.
* A **SQL database** keeps all the important data and processes the actual transactions. We recommend using PostgreSQL, but MySQL/MariaDB works as well.
* A **PostgreSQL database** keeps all the important data and processes the actual transactions.
* A **web server** that terminates TLS and HTTP connections and forwards them to ``pretix-web``. In some cases, e.g. when serving static files, the web servers might return a response directly. We recommend using ``nginx``.
@@ -74,7 +74,7 @@ We recommend reading up on tuning your web server for high concurrency. For ngin
processes and the number of connections each worker process accepts. Double-check that TLS session caching works, because TLS
handshakes can get really expensive.
During a traffic peak, your web server will be able to make us of more CPU resources, while memory usage will stay comparatively low,
During a traffic peak, your web server will be able to make use of more CPU resources, while memory usage will stay comparatively low,
so if you invest in more hardware here, invest in more and faster CPU cores.
Make sure that pretix' static files (such as CSS and JavaScript assets) as well as user-uploaded media files (event logos, etc)

View File

@@ -192,6 +192,9 @@ Relative date *either* String in ISO 8601 ``"2017-12-27"``,
File URL in responses, ``file:`` ``"https://…"``, ``"file:…"``
specifiers in requests
(see below).
Date range *either* two dates separated ``2022-03-18/2022-03-23``, ``2022-03-18/``,
by ``/`` *or* the name of a ``/2022-03-23``, ``week_this``, ``week_next``,
defined range. ``month_this``
===================== ============================ ===================================
Query parameters

View File

@@ -107,9 +107,9 @@ You can supply a valid access token as a ``Bearer``-type token in the ``Authoriz
.. sourcecode:: http
:emphasize-lines: 3
GET /api/v1/organizers/ HTTP/1.1
Host: pretix.eu
Authorization: Bearer i3ytqTSRWsKp16fqjekHXa4tdM4qNC
GET /api/v1/organizers/ HTTP/1.1
Host: pretix.eu
Authorization: Bearer i3ytqTSRWsKp16fqjekHXa4tdM4qNC
Refreshing an access token
--------------------------

View File

@@ -1,4 +1,4 @@
.. spelling:: checkin
.. spelling:word-list:: checkin
.. _rest-checkin:
@@ -203,6 +203,8 @@ Checking a ticket in
* ``invalid`` - Ticket is not known.
* ``unpaid`` - Ticket is not paid for.
* ``blocked`` - Ticket has been blocked.
* ``invalid_time`` - Ticket is not valid at this time.
* ``canceled`` Ticket is canceled or expired.
* ``already_redeemed`` - Ticket already has been redeemed.
* ``product`` - Tickets with this product may not be scanned at this device.

View File

@@ -1,4 +1,4 @@
.. spelling:: checkin
.. spelling:word-list:: checkin
.. _rest-checkinlists:
@@ -98,6 +98,8 @@ Endpoints
:query string ends_after: Exclude all check-in lists attached to a sub-event that is already in the past at the given time.
:query string expand: Expand a field into a full object. Currently only ``subevent`` is supported. Can be passed multiple times.
:query string exclude: Exclude a field from the output, e.g. ``checkin_count``. Can be used as a performance optimization. Can be passed multiple times.
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id``, ``name``, and ``subevent__date_from``,
Default: ``subevent__date_from,name``
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:statuscode 200: no error
@@ -362,7 +364,7 @@ Endpoints
Stores a failed check-in. Only necessary for statistical purposes if you perform scan validation offline.
:<json boolean error_reason: One of ``canceled``, ``invalid``, ``unpaid``, ``product``, ``rules``, ``revoked``,
``incomplete``, ``already_redeemed``, or ``error``. Required.
``incomplete``, ``already_redeemed``, ``blocked``, ``invalid_time``, or ``error``. Required.
:<json raw_barcode: The raw barcode you scanned. Required.
:<json datetime: Date and time of the scan. Optional.
:<json type: Type of scan, defaults to ``"entry"``.
@@ -741,6 +743,8 @@ Order position endpoints
* ``invalid`` - Ticket code not known.
* ``unpaid`` - Ticket is not paid for.
* ``blocked`` - Ticket has been blocked.
* ``invalid_time`` - Ticket is not valid at this time.
* ``canceled`` Ticket is canceled or expired. This reason is only sent when your request sets.
``canceled_supported`` to ``true``, otherwise these orders return ``unpaid``.
* ``already_redeemed`` - Ticket already has been redeemed.

View File

@@ -1,4 +1,4 @@
.. spelling:: fullname
.. spelling:word-list:: fullname
.. _`rest-devices`:

View File

@@ -1,4 +1,4 @@
.. spelling::
.. spelling:word-list::
geo
lat
@@ -49,6 +49,7 @@ valid_keys object Cryptographic k
only contained in detail views. Value can be cached.
sales_channels list A list of sales channels this event is available for
sale on.
public_url string The public, customer-facing URL of the event (read-only).
===================================== ========================== =======================================================
@@ -65,6 +66,10 @@ Endpoints
The ``search`` query parameter has been added to filter events by their slug, name, or location in any language.
.. versionchanged:: 4.17
The ``public_url`` field has been added.
.. http:get:: /api/v1/organizers/(organizer)/events/
Returns a list of all events within a given organizer the authenticated user/token has access to.
@@ -123,7 +128,8 @@ Endpoints
"web",
"pretixpos",
"resellers"
]
],
"public_url": "https://pretix.eu/bigevents/sampleconf/"
}
]
}
@@ -211,7 +217,8 @@ Endpoints
"web",
"pretixpos",
"resellers"
]
],
"public_url": "https://pretix.eu/bigevents/sampleconf/"
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -307,7 +314,8 @@ Endpoints
"web",
"pretixpos",
"resellers"
]
],
"public_url": "https://pretix.eu/bigevents/sampleconf/"
}
:param organizer: The ``slug`` field of the organizer of the event to create.
@@ -411,7 +419,8 @@ Endpoints
"web",
"pretixpos",
"resellers"
]
],
"public_url": "https://pretix.eu/bigevents/sampleconf/"
}
:param organizer: The ``slug`` field of the organizer of the event to create.
@@ -485,7 +494,8 @@ Endpoints
"web",
"pretixpos",
"resellers"
]
],
"public_url": "https://pretix.eu/bigevents/sampleconf/"
}
:param organizer: The ``slug`` field of the organizer of the event to update

View File

@@ -1,4 +1,4 @@
.. spelling:: checkin
.. spelling:word-list:: checkin
Data exporters
==============

View File

@@ -42,6 +42,7 @@ introductory_text string Text to be prin
additional_text string Text to be printed below the product list
payment_provider_text string Text to be printed below the product list with
payment information
payment_provider_stamp string Short text to be visibly printed to indicate payment status
footer_text string Text to be printed in the page footer area
lines list of objects The actual invoice contents
├ position integer Number of the line within an invoice.
@@ -178,6 +179,7 @@ Endpoints
"internal_reference": "",
"additional_text": "We are looking forward to see you on our conference!",
"payment_provider_text": "Please transfer the money to our account ABC…",
"payment_provider_stamp": null,
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [
{
@@ -268,6 +270,7 @@ Endpoints
"internal_reference": "",
"additional_text": "We are looking forward to see you on our conference!",
"payment_provider_text": "Please transfer the money to our account ABC…",
"payment_provider_stamp": null,
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [
{

View File

@@ -24,6 +24,9 @@ active boolean If ``false``, t
description multi-lingual string A public description of the variation. May contain
Markdown syntax or can be ``null``.
position integer An integer, used for sorting
checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if such
a variation is being scanned.
require_approval boolean If ``true``, orders with this variation will need to be
approved by the event organizer before they can be
paid.
@@ -48,7 +51,7 @@ meta_data object Values set for
.. versionchanged:: 4.16
The ``meta_data`` attribute has been added.
The ``meta_data`` and ``checkin_attention`` attributes have been added.
Endpoints
---------
@@ -84,6 +87,7 @@ Endpoints
"en": "S"
},
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -107,6 +111,7 @@ Endpoints
"en": "L"
},
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -159,6 +164,7 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -197,6 +203,7 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -225,6 +232,7 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -284,6 +292,7 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": false,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,

View File

@@ -11,141 +11,165 @@ The item resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the item
name multi-lingual string The item's visible name
internal_name string An optional name that is only used in the backend
default_price money (string) The item price that is applied if the price is not
overwritten by variations or other options.
category integer The ID of the category this item belongs to
(or ``null``).
active boolean If ``false``, the item is hidden from all public lists
and will not be sold.
description multi-lingual string A public description of the item. May contain Markdown
syntax or can be ``null``.
free_price boolean If ``true``, customers can change the price at which
they buy the product (however, the price can't be set
lower than the price defined by ``default_price`` or
otherwise).
tax_rate decimal (string) The VAT rate to be applied for this item (read-only,
set through ``tax_rule``).
tax_rule integer The internal ID of the applied tax rule (or ``null``).
admission boolean ``true`` for items that grant admission to the event
(such as primary tickets) and ``false`` for others
(such as add-ons or merchandise).
position integer An integer, used for sorting
picture file A product picture to be displayed in the shop
(can be ``null``).
sales_channels list of strings Sales channels this product is available on, such as
``"web"`` or ``"resellers"``. Defaults to ``["web"]``.
available_from datetime The first date time at which this item can be bought
(or ``null``).
available_until datetime The last date time at which this item can be bought
(or ``null``).
hidden_if_available integer The internal ID of a quota object, or ``null``. If
set, this item won't be shown publicly as long as this
quota is available.
require_voucher boolean If ``true``, this item can only be bought using a
voucher that is specifically assigned to this item.
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
redemption process, but not in the normal shop
frontend.
allow_cancel boolean If ``false``, customers cannot cancel orders containing
this item.
min_per_order integer This product can only be bought if it is included at
least this many times in the order (or ``null`` for no
limitation).
max_per_order integer This product can only be bought if it is included at
most this many times in the order (or ``null`` for no
limitation).
checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if such
a product is being scanned.
original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
require_approval boolean If ``true``, orders with this product will need to be
approved by the event organizer before they can be
paid.
require_bundling boolean If ``true``, this item is only available as part of bundles.
require_membership boolean If ``true``, booking this item requires an active membership.
require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this product will
be hidden from users without a valid membership.
require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
grant_membership_type integer If set to the internal ID of a membership type, purchasing this item will
create a membership of the given type.
grant_membership_duration_like_event boolean If ``true``, the membership created through ``grant_membership_type`` will derive
its term from ``date_from`` to ``date_to`` of the purchased (sub)event.
grant_membership_duration_days integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of
days for the membership.
grant_membership_duration_months integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of
calendar months for the membership.
generate_tickets boolean If ``false``, tickets are never generated for this
product, regardless of other settings. If ``true``,
tickets are generated even if this is a
non-admission or add-on product, regardless of event
settings. If this is ``null``, regular ticketing
rules apply.
allow_waitinglist boolean If ``false``, no waiting list will be shown for this
product when it is sold out.
issue_giftcard boolean If ``true``, buying this product will yield a gift card.
show_quota_left boolean Publicly show how many tickets are still available.
If this is ``null``, the event default is used.
has_variations boolean Shows whether or not this item has variations.
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.
├ 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``
├ price money (string) The price used for this variation. This is either the
same as ``default_price`` if that value is set or equal
to the item's ``default_price``.
├ original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
├ active boolean If ``false``, this variation will not be sold or shown.
├ description multi-lingual string A public description of the variation. May contain
├ require_membership boolean If ``true``, booking this variation requires an active membership.
├ require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this variation will
be hidden from users without a valid membership.
├ require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
Markdown syntax or can be ``null``.
├ sales_channels list of strings Sales channels this variation is available on, such as
``"web"`` or ``"resellers"``. Defaults to all existing sales channels.
The item-level list takes precedence, i.e. a sales
channel needs to be on both lists for the item to be
available.
available_from datetime The first date time at which this variation can be bought
(or ``null``).
├ available_until datetime The last date time at which this variation can be bought
(or ``null``).
hide_without_voucher boolean If ``true``, this variation is only shown during the voucher
redemption process, but not in the normal shop
frontend.
meta_data object Values set for event-specific meta data parameters.
└ position integer An integer, used for sorting
addons list of objects Definition of add-ons that can be chosen for this item.
Only writable during creation,
use separate endpoint to modify this later.
├ addon_category integer Internal ID of the item category the add-on can be
chosen from.
min_count integer The minimal number of add-ons that need to be chosen.
max_count integer The maximal number of add-ons that can be chosen.
├ position integer An integer, used for sorting
multi_allowed boolean Adding the same item multiple times is allowed
└ price_included boolean Adding this add-on to the item is free
bundles list of objects Definition of bundles that are included in this item.
Only writable during creation,
use separate endpoint to modify this later.
├ bundled_item integer Internal ID of the item that is included.
├ bundled_variation integer Internal ID of the variation of the item (or ``null``).
count integer Number of items included
└ designated_price money (string) Designated price of the bundled product. This will be
used to split the price of the base item e.g. for mixed
taxation. This is not added to the price.
meta_data object Values set for event-specific meta data parameters.
===================================== ========================== =======================================================
======================================= ========================== =======================================================
Field Type Description
======================================= ========================== =======================================================
id integer Internal ID of the item
name multi-lingual string The item's visible name
internal_name string An optional name that is only used in the backend
default_price money (string) The item price that is applied if the price is not
overwritten by variations or other options.
category integer The ID of the category this item belongs to
(or ``null``).
active boolean If ``false``, the item is hidden from all public lists
and will not be sold.
description multi-lingual string A public description of the item. May contain Markdown
syntax or can be ``null``.
free_price boolean If ``true``, customers can change the price at which
they buy the product (however, the price can't be set
lower than the price defined by ``default_price`` or
otherwise).
tax_rate decimal (string) The VAT rate to be applied for this item (read-only,
set through ``tax_rule``).
tax_rule integer The internal ID of the applied tax rule (or ``null``).
admission boolean ``true`` for items that grant admission to the event
(such as primary tickets) and ``false`` for others
(such as add-ons or merchandise).
personalized boolean ``true`` for items that require personalization according
to event settings. Only affects system-level fields, not
custom questions. Currently only allowed for products with
``admission`` set to ``true``. For backwards compatibility,
when creating new items and this field is not given, it defaults
to the same value as ``admission``.
position integer An integer, used for sorting
picture file A product picture to be displayed in the shop
(can be ``null``).
sales_channels list of strings Sales channels this product is available on, such as
``"web"`` or ``"resellers"``. Defaults to ``["web"]``.
available_from datetime The first date time at which this item can be bought
(or ``null``).
available_until datetime The last date time at which this item can be bought
(or ``null``).
hidden_if_available integer The internal ID of a quota object, or ``null``. If
set, this item won't be shown publicly as long as this
quota is available.
require_voucher boolean If ``true``, this item can only be bought using a
voucher that is specifically assigned to this item.
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
redemption process, but not in the normal shop
frontend.
allow_cancel boolean If ``false``, customers cannot cancel orders containing
this item.
min_per_order integer This product can only be bought if it is included at
least this many times in the order (or ``null`` for no
limitation).
max_per_order integer This product can only be bought if it is included at
most this many times in the order (or ``null`` for no
limitation).
checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if such
a product is being scanned.
original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
require_approval boolean If ``true``, orders with this product will need to be
approved by the event organizer before they can be
paid.
require_bundling boolean If ``true``, this item is only available as part of bundles.
require_membership boolean If ``true``, booking this item requires an active membership.
require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this product will
be hidden from users without a valid membership.
require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
grant_membership_type integer If set to the internal ID of a membership type, purchasing this item will
create a membership of the given type.
grant_membership_duration_like_event boolean If ``true``, the membership created through ``grant_membership_type`` will derive
its term from ``date_from`` to ``date_to`` of the purchased (sub)event.
grant_membership_duration_days integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of
days for the membership.
grant_membership_duration_months integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of
calendar months for the membership.
validity_mode string If ``null``, tickets generated for this product do not
have special validity behavior, but follow event configuration and
can be limited e.g. through check-in rules. Other values are ``"fixed"`` and ``"dynamic"``
validity_fixed_from datetime If ``validity_mode`` is ``"fixed"``, this is the start of validity for issued tickets.
validity_fixed_until datetime If ``validity_mode`` is ``"fixed"``, this is the end of validity for issued tickets.
validity_dynamic_duration_minutes integer If ``validity_mode`` is ``"dynamic"``, this is the "minutes" component of the ticket validity duration.
validity_dynamic_duration_hours integer If ``validity_mode`` is ``"dynamic"``, this is the "hours" component of the ticket validity duration.
validity_dynamic_duration_days integer If ``validity_mode`` is ``"dynamic"``, this is the "days" component of the ticket validity duration.
validity_dynamic_duration_months integer If ``validity_mode`` is ``"dynamic"``, this is the "months" component of the ticket validity duration.
validity_dynamic_start_choice boolean If ``validity_mode`` is ``"dynamic"`` and this is ``true``, customers can choose the start of validity.
validity_dynamic_start_choice_day_limit boolean If ``validity_mode`` is ``"dynamic"`` and ``validity_dynamic_start_choice`` is ``true``,
this is the maximum number of days the start can be in the future.
generate_tickets boolean If ``false``, tickets are never generated for this
product, regardless of other settings. If ``true``,
tickets are generated even if this is a
non-admission or add-on product, regardless of event
settings. If this is ``null``, regular ticketing
rules apply.
allow_waitinglist boolean If ``false``, no waiting list will be shown for this
product when it is sold out.
issue_giftcard boolean If ``true``, buying this product will yield a gift card.
show_quota_left boolean Publicly show how many tickets are still available.
If this is ``null``, the event default is used.
has_variations boolean Shows whether or not this item has variations.
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.
├ 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``
├ price money (string) The price used for this variation. This is either the
same as ``default_price`` if that value is set or equal
to the item's ``default_price``.
original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
├ active boolean If ``false``, this variation will not be sold or shown.
description multi-lingual string A public description of the variation. May contain
├ checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if such
a variation is being scanned.
├ require_approval boolean If ``true``, orders with this variation will need to be
approved by the event organizer before they can be
paid.
require_membership boolean If ``true``, booking this variation requires an active membership.
require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this variation will
be hidden from users without a valid membership.
require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
Markdown syntax or can be ``null``.
├ sales_channels list of strings Sales channels this variation is available on, such as
``"web"`` or ``"resellers"``. Defaults to all existing sales channels.
The item-level list takes precedence, i.e. a sales
channel needs to be on both lists for the item to be
available.
available_from datetime The first date time at which this variation can be bought
(or ``null``).
├ available_until datetime The last date time at which this variation can be bought
(or ``null``).
├ hide_without_voucher boolean If ``true``, this variation is only shown during the voucher
redemption process, but not in the normal shop
frontend.
├ meta_data object Values set for event-specific meta data parameters.
└ position integer An integer, used for sorting
addons list of objects Definition of add-ons that can be chosen for this item.
Only writable during creation,
use separate endpoint to modify this later.
├ addon_category integer Internal ID of the item category the add-on can be
chosen from.
├ min_count integer The minimal number of add-ons that need to be chosen.
├ max_count integer The maximal number of add-ons that can be chosen.
├ position integer An integer, used for sorting
├ multi_allowed boolean Adding the same item multiple times is allowed
└ price_included boolean Adding this add-on to the item is free
bundles list of objects Definition of bundles that are included in this item.
Only writable during creation,
use separate endpoint to modify this later.
├ bundled_item integer Internal ID of the item that is included.
├ bundled_variation integer Internal ID of the variation of the item (or ``null``).
├ count integer Number of items included
└ designated_price money (string) Designated price of the bundled product. This will be
used to split the price of the base item e.g. for mixed
taxation. This is not added to the price.
meta_data object Values set for event-specific meta data parameters.
======================================= ========================== =======================================================
.. versionchanged:: 4.0
@@ -158,7 +182,12 @@ meta_data object Values set for
.. versionchanged:: 4.16
The ``variations[x].meta_data`` attribute has been added.
The ``variations[x].meta_data`` and ``variations[x].checkin_attention`` attributes have been added.
The ``personalized`` attribute has been added.
.. versionchanged:: 4.17
The ``validity_*`` attributes have been added.
Notes
-----
@@ -213,6 +242,7 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"personalized": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
@@ -238,6 +268,14 @@ Endpoints
"grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0,
"grant_membership_duration_months": 0,
"validity_fixed_from": null,
"validity_fixed_until": null,
"validity_dynamic_duration_minutes": null,
"validity_dynamic_duration_hours": null,
"validity_dynamic_duration_days": null,
"validity_dynamic_duration_months": null,
"validity_dynamic_start_choice": false,
"validity_dynamic_start_choice_day_limit": null,
"variations": [
{
"value": {"en": "Student"},
@@ -245,6 +283,8 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
@@ -261,6 +301,8 @@ Endpoints
"price": "23.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
@@ -329,6 +371,7 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"personalized": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
@@ -354,6 +397,14 @@ Endpoints
"grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0,
"grant_membership_duration_months": 0,
"validity_fixed_from": null,
"validity_fixed_until": null,
"validity_dynamic_duration_minutes": null,
"validity_dynamic_duration_hours": null,
"validity_dynamic_duration_days": null,
"validity_dynamic_duration_months": null,
"validity_dynamic_start_choice": false,
"validity_dynamic_start_choice_day_limit": null,
"variations": [
{
"value": {"en": "Student"},
@@ -361,6 +412,8 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"description": null,
@@ -377,6 +430,8 @@ Endpoints
"price": "23.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
@@ -426,6 +481,7 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"personalized": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
@@ -450,6 +506,14 @@ Endpoints
"grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0,
"grant_membership_duration_months": 0,
"validity_fixed_from": null,
"validity_fixed_until": null,
"validity_dynamic_duration_minutes": null,
"validity_dynamic_duration_hours": null,
"validity_dynamic_duration_days": null,
"validity_dynamic_duration_months": null,
"validity_dynamic_start_choice": false,
"validity_dynamic_start_choice_day_limit": null,
"variations": [
{
"value": {"en": "Student"},
@@ -457,6 +521,8 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
@@ -473,6 +539,8 @@ Endpoints
"price": "23.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
@@ -510,6 +578,7 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"personalized": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
@@ -535,6 +604,14 @@ Endpoints
"grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0,
"grant_membership_duration_months": 0,
"validity_fixed_from": null,
"validity_fixed_until": null,
"validity_dynamic_duration_minutes": null,
"validity_dynamic_duration_hours": null,
"validity_dynamic_duration_days": null,
"validity_dynamic_duration_months": null,
"validity_dynamic_start_choice": false,
"validity_dynamic_start_choice_day_limit": null,
"variations": [
{
"value": {"en": "Student"},
@@ -542,6 +619,8 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
@@ -558,6 +637,8 @@ Endpoints
"price": "23.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
@@ -626,6 +707,7 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"personalized": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
@@ -651,6 +733,14 @@ Endpoints
"grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0,
"grant_membership_duration_months": 0,
"validity_fixed_from": null,
"validity_fixed_until": null,
"validity_dynamic_duration_minutes": null,
"validity_dynamic_duration_hours": null,
"validity_dynamic_duration_days": null,
"validity_dynamic_duration_months": null,
"validity_dynamic_start_choice": false,
"validity_dynamic_start_choice_day_limit": null,
"variations": [
{
"value": {"en": "Student"},
@@ -658,6 +748,8 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
@@ -674,6 +766,8 @@ Endpoints
"price": "23.00",
"original_price": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],

View File

@@ -1,4 +1,4 @@
.. spelling::
.. spelling:word-list::
checkins
pdf
@@ -91,6 +91,10 @@ require_approval boolean If ``true`` and
needs approval by an organizer before it can
continue. If ``true`` and the order is canceled,
this order has been denied by the event organizer.
valid_if_pending boolean If ``true`` and the order is pending, this order
is still treated like a paid order for most purposes,
such as check-in. This may be used e.g. for trusted
customers who only need to pay after the event.
url string The full URL to the order confirmation page
payments list of objects List of payment processes (see below)
refunds list of objects List of refund processes (see below)
@@ -122,6 +126,10 @@ last_modified datetime Last modificati
The ``include`` query parameter has been added.
.. versionchanged:: 4.16
The ``valid_if_pending`` attribute has been added.
.. _order-position-resource:
@@ -159,6 +167,9 @@ secret string Secret code pri
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
subevent integer ID of the date inside an event series this position belongs to (or ``null``).
discount integer ID of a discount that has been used during the creation of this position in some way (or ``null``).
blocked list of strings A list of strings, or ``null``. Whenever not ``null``, the ticket may not be used (e.g. for check-in).
valid_from datetime The ticket will not be valid before this time. Can be ``null``.
valid_until datetime The ticket will not be valid after this time. Can be ``null``.
pseudonymization_id string A random ID, e.g. for use in lead scanning apps
checkins list of objects List of **successful** check-ins with this ticket
├ id integer Internal ID of the check-in event
@@ -186,6 +197,10 @@ pdf_data object Data object req
``pdf_data=true`` query parameter to your request.
===================================== ========================== =======================================================
.. versionchanged:: 4.16
The attributes ``blocked``, ``valid_from`` and ``valid_until`` have been added.
.. _order-payment-resource:
Order payment resource
@@ -294,6 +309,7 @@ List of all orders
"custom_followup_at": null,
"checkin_attention": false,
"require_approval": false,
"valid_if_pending": false,
"invoice_address": {
"last_modified": "2017-12-01T10:00:00Z",
"is_business": true,
@@ -336,6 +352,9 @@ List of all orders
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"valid_from": null,
"valid_until": null,
"blocked": null,
"discount": null,
"pseudonymization_id": "MQLJvANO3B",
"seat": null,
@@ -467,6 +486,7 @@ Fetching individual orders
"custom_followup_at": null,
"checkin_attention": false,
"require_approval": false,
"valid_if_pending": false,
"invoice_address": {
"last_modified": "2017-12-01T10:00:00Z",
"company": "Sample company",
@@ -509,6 +529,9 @@ Fetching individual orders
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"valid_from": null,
"valid_until": null,
"blocked": null,
"discount": null,
"pseudonymization_id": "MQLJvANO3B",
"seat": null,
@@ -640,6 +663,8 @@ Updating order fields
* ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address)
* ``valid_if_pending``
**Example request**:
.. sourcecode:: http
@@ -815,6 +840,7 @@ Creating orders
* does not support or validate memberships
You can supply the following fields of the resource:
* ``code`` (optional) Only ``A-Z`` and ``0-9``, but without ``O`` and ``1``.
@@ -845,6 +871,7 @@ Creating orders
* ``custom_followup_at`` (optional)
* ``checkin_attention`` (optional)
* ``require_approval`` (optional)
* ``valid_if_pending`` (optional)
* ``invoice_address`` (optional)
* ``company``
@@ -880,6 +907,9 @@ Creating orders
* ``secret`` (optional)
* ``addon_to`` (optional, see below)
* ``subevent`` (optional)
* ``valid_from`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
* ``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)
* ``answers``
* ``question``
@@ -950,7 +980,7 @@ Creating orders
"street": "Sesam Street 12",
"zipcode": "12345",
"city": "Sample City",
"country": "UK",
"country": "GB",
"state": "",
"internal_reference": "",
"vat_id": ""
@@ -1448,6 +1478,9 @@ List of all order positions
"seat": null,
"addon_to": null,
"subevent": null,
"valid_from": null,
"valid_until": null,
"blocked": null,
"checkins": [
{
"list": 44,
@@ -1554,6 +1587,9 @@ Fetching individual positions
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"valid_from": null,
"valid_until": null,
"blocked": null,
"discount": null,
"pseudonymization_id": "MQLJvANO3B",
"seat": null,
@@ -1659,6 +1695,10 @@ Manipulating individual positions
The ``PATCH`` method now supports changing items, variations, subevents, seats, prices, and tax rules.
The ``POST`` endpoint to add individual positions has been added.
.. versionadded:: 4.16
The endpoints to manage blocks have been added.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/
Updates specific fields on an order position. Currently, only the following fields are supported:
@@ -1697,6 +1737,10 @@ Manipulating individual positions
* ``tax_rule``
* ``valid_from``
* ``valid_until``
Changing parameters such as ``item`` or ``price`` will **not** automatically trigger creation of a new invoice,
you need to take care of that yourself.
@@ -1771,6 +1815,10 @@ Manipulating individual positions
and ``option_identifiers`` will be ignored. As a special case, you can submit the magic value
``"file:keep"`` as the answer to a file question to keep the current value without re-uploading it.
* ``valid_from``
* ``valid_until``
This will **not** automatically trigger creation of a new invoice, you need to take care of that yourself.
**Example request**:
@@ -1834,6 +1882,82 @@ Manipulating individual positions
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested order position does not exist.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/add_block/
Blocks an order position from being used. The block name either needs to be ``"admin"`` or start with ``"api:"``. It
may only contain letters, numbers, dots and underscores. ``"admin"`` represents the regular block that can be set
in the backend user interface.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23/add_block/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"name": "api:block1"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
(Full order position resource, see above.)
:param organizer: The ``slug`` field of the organizer of the event
:param event: The ``slug`` field of the event
:param code: The ``id`` field of the order position to update
:statuscode 200: no error
:statuscode 400: The order position could not be updated due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to update this order position.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/remove_block/
Unblocks an order position from being used. The block name either needs to be ``"admin"`` or start with ``"api:"``. It
may only contain letters, numbers, dots and underscores. ``"admin"`` represents the regular block that can be set
in the backend user interface. Blocks set by plugins cannot be lifted through this API.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23/remove_block/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"name": "api:block1"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
(Full order position resource, see above.)
:param organizer: The ``slug`` field of the organizer of the event
:param event: The ``slug`` field of the event
:param code: The ``id`` field of the order position to update
:statuscode 200: no error
:statuscode 400: The order position could not be updated due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to update this order position.
Changing order contents
-----------------------
@@ -1852,7 +1976,7 @@ otherwise, such as splitting an order or changing fees.
* ``patch_positions``: A list of objects with the two keys ``position`` specifying an order position ID and
``body`` specifying the desired changed values of the position (``item``, ``variation``, ``subevent``, ``seat``,
``price``, ``tax_rule``).
``price``, ``tax_rule``, ``valid_from``, ``valid_until``).
* ``cancel_positions``: A list of objects with the single key ``position`` specifying an order position ID.
@@ -2541,3 +2665,57 @@ With some non-default ticket secret generation methods, a list of revoked ticket
: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.
Blocked ticket secrets
----------------------
With some non-default ticket secret generation methods, a list of blocked ticket secrets is required for proper validation.
This endpoint returns all secrets that are currently blocked **or have been blocked before and are now unblocked**, so
be sure to check the ``blocked`` attribute for its actual value. The list is currently always ordered with the most
recently updated ones first.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/blockedsecrets/
Returns a list of all blocked or historically blocked secrets within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/blockedsecrets/ 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
X-Page-Generated: 2017-12-01T10:00:00Z
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1234,
"secret": "k24fiuwvu8kxz3y1",
"blocked": true,
"updated": "2017-12-01T10:00:00Z",
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query datetime updated_since: Only return records that have been updated since the given date.
:query boolean blocked: Only return blocked / non-blocked records.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:resheader X-Page-Generated: The server time at the beginning of the operation. If you're using this API to fetch
differences, this is the value you want to use as ``updated_since`` in your next call.
: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.

View File

@@ -17,12 +17,18 @@ Field Type Description
name string The organizer's full name, i.e. the name of an
organization or company.
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).
===================================== ========================== =======================================================
Endpoints
---------
.. versionchanged:: 4.17
The ``public_url`` field has been added.
.. http:get:: /api/v1/organizers/
Returns a list of all organizers the authenticated user/token has access to.
@@ -51,6 +57,7 @@ Endpoints
{
"name": "Big Events LLC",
"slug": "Big Events",
"public_url": "https://pretix.eu/bigevents/"
}
]
}
@@ -84,6 +91,7 @@ Endpoints
{
"name": "Big Events LLC",
"slug": "Big Events",
"public_url": "https://pretix.eu/bigevents/"
}
:param organizer: The ``slug`` field of the organizer to fetch

View File

@@ -1,4 +1,4 @@
.. spelling::
.. spelling:word-list::
checkin
datetime

View File

@@ -1,4 +1,4 @@
.. spelling:: checkin
.. spelling:word-list:: checkin
Data shredders
==============

View File

@@ -1,4 +1,4 @@
.. spelling::
.. spelling:word-list::
geo
lat

View File

@@ -1,4 +1,4 @@
.. spelling:: fullname checkin
.. spelling:word-list:: fullname checkin
.. _`rest-teams`:

View File

@@ -26,6 +26,7 @@ limit_events list of strings If ``all_events
action_types list of strings A list of action type filters that limit the
notifications sent to this webhook. See below for
valid values
comment string Internal comment on this webhook, default ``null``
===================================== ========================== =======================================================
The following values for ``action_types`` are valid with pretix core:
@@ -56,6 +57,7 @@ The following values for ``action_types`` are valid with pretix core:
* ``pretix.subevent.added``
* ``pretix.subevent.changed``
* ``pretix.subevent.deleted``
* ``pretix.event.item.*``
* ``pretix.event.live.activated``
* ``pretix.event.live.deactivated``
* ``pretix.event.testmode.activated``
@@ -98,7 +100,8 @@ Endpoints
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
"comment": null
}
]
}
@@ -135,7 +138,8 @@ Endpoints
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
"comment": null
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -162,7 +166,8 @@ Endpoints
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
"comment": "Called for changes"
}
**Example response**:
@@ -179,7 +184,8 @@ Endpoints
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
"comment": "Called for changes"
}
:param organizer: The ``slug`` field of the organizer to create a webhook for
@@ -224,7 +230,8 @@ Endpoints
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
"comment": null
}
:param organizer: The ``slug`` field of the organizer to modify

View File

@@ -13,10 +13,6 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
from docutils.parsers.rst.directives.admonitions import BaseAdmonition
from sphinx.util import compat
compat.make_admonition = BaseAdmonition # See https://github.com/spinus/sphinxcontrib-images/issues/41
import sys
import os
@@ -28,12 +24,13 @@ from datetime import date
sys.path.insert(0, os.path.abspath('../src'))
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.testutils.settings")
django.setup()
try:
import enchant
import enchant # noqa
HAS_PYENCHANT = True
except:
HAS_PYENCHANT = False
@@ -41,7 +38,7 @@ except:
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@@ -52,6 +49,7 @@ extensions = [
'sphinx.ext.coverage',
'sphinxcontrib.httpdomain',
'sphinxcontrib.images',
'sphinxcontrib.jquery',
'sphinxemoji.sphinxemoji',
]
if HAS_PYENCHANT:
@@ -64,7 +62,7 @@ templates_path = ['_templates']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
@@ -79,19 +77,20 @@ copyright = '2014-{}, Raphael Michel'.format(date.today().year)
#
# The short X.Y version.
from pretix import __version__
version = '.'.join(__version__.split('.')[:2])
# The full version, including alpha/beta/rc tags.
release = __version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -99,34 +98,34 @@ exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# keep_warnings = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#html_theme = ""
# html_theme = ""
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -136,14 +135,14 @@ html_theme_options = {
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
@@ -152,7 +151,7 @@ html_logo = 'images/logo-white.svg'
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -166,18 +165,18 @@ html_static_path = [
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
@@ -192,24 +191,24 @@ html_domain_indices = False
html_use_index = False
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'pretixdoc'
@@ -217,47 +216,46 @@ htmlhelp_basename = 'pretixdoc'
html_theme = 'pretix_theme'
html_theme_path = [os.path.abspath('_themes')]
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# The paper size ('letterpaper' or 'a4paper').
'papersize': 'a4paper',
# The font size ('10pt', '11pt' or '12pt').
# The font size ('10pt', '11pt' or '12pt').
'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'pretix.tex', 'pretix Documentation',
'Raphael Michel', 'manual'),
('index', 'pretix.tex', 'pretix Documentation',
'Raphael Michel', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
@@ -270,7 +268,7 @@ man_pages = [
]
# If true, show URL addresses after external links.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
@@ -279,22 +277,22 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'pretix', 'pretix Documentation',
'Raphael Michel', 'pretix', 'One line description of project.',
'Miscellaneous'),
('index', 'pretix', 'pretix Documentation',
'Raphael Michel', 'pretix', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# texinfo_no_detailmenu = False
images_config = {
@@ -314,12 +312,13 @@ if HAS_PYENCHANT:
# String specifying a file containing a list of words known to be spelled
# correctly but that do not appear in the language dictionary selected by
# spelling_lang. The file should contain one word per line.
spelling_word_list_filename='spelling_wordlist.txt'
spelling_word_list_filename = 'spelling_wordlist.txt'
# Boolean controlling whether suggestions for misspelled words are printed.
# Defaults to False.
spelling_show_suggestions=True
spelling_show_suggestions = True
# List of filter classes to be added to the tokenizer that produces words to be checked.
from checkin_filter import CheckinFilter
spelling_filters=[CheckinFilter]
spelling_filters = [CheckinFilter]

View File

@@ -76,6 +76,10 @@ The exporter class
This is an abstract attribute, you **must** override this!
.. autoattribute:: description
.. autoattribute:: category
.. autoattribute:: export_form_fields
.. automethod:: render

View File

@@ -61,7 +61,7 @@ Backend
item_formsets, order_search_filter_q, order_search_forms
.. automodule:: pretix.base.signals
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events, orderposition_blocked_display
Vouchers
""""""""

View File

@@ -102,6 +102,8 @@ The provider class
.. automethod:: render_invoice_text
.. automethod:: render_invoice_stamp
.. automethod:: order_change_allowed
.. automethod:: payment_prepare
@@ -120,6 +122,8 @@ The provider class
.. automethod:: refund_control_render
.. automethod:: refund_control_render_short
.. automethod:: new_refund_control_form_render
.. automethod:: new_refund_control_form_process
@@ -130,6 +134,8 @@ The provider class
.. automethod:: matching_id
.. automethod:: refund_matching_id
.. automethod:: shred_payment_info
.. automethod:: cancel_payment

View File

@@ -55,7 +55,6 @@ visible boolean (optional) ``True`` by default, can hide a plugin s
restricted boolean (optional) ``False`` by default, restricts a plugin such that it can only be enabled
for an event by system administrators / superusers.
experimental boolean (optional) ``False`` by default, marks a plugin as an experimental feature in the plugins list.
picture string (optional) Path to a picture resolvable through the static file system.
compatibility string Specifier for compatible pretix versions.
================== ==================== ===========================================================

View File

@@ -1,4 +1,4 @@
.. spelling:: Rebase rebasing
.. spelling:word-list:: Rebase rebasing
Coding style and quality
========================

View File

@@ -1,7 +1,7 @@
.. highlight:: python
:linenothreshold: 5
.. spelling:: answ contrib
.. spelling:word-list:: answ contrib
Data model
==========

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 278 KiB

View File

@@ -23,30 +23,50 @@ partition "data-based check" {
"Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then
-right->[no] "Return error CANCELED"
else
-down->[yes] "Is the product part of the check-in list?"
-down->[yes] "Is one or more block set on the ticket?"
--> if "" then
-right->[no] "Return error PRODUCT"
-right->[no] "Return error BLOCKED"
else
-down->[yes] "Is the subevent part of the check-in list?"
-down->[yes] "If this is not an exit, is the valid_from/valid_until\nconstraint on the ticket fulfilled?"
--> if "" then
-right->[no] "Return error INVALID"
note bottom: TODO\ninconsistent\nwith online\ncheck
-right->[no] "Return error INVALID_TIME"
else
-down->[yes] "Is the order in status PAID?"
-down->[yes] "Is the product part of the check-in list?"
--> if "" then
-right->[no] "Does the check-in list include pending orders?"
-right->[no] "Return error PRODUCT"
else
-down->[yes] "Is the subevent part of the check-in list?"
--> if "" then
-right->[no] "Return error UNPAID "
-right->[no] "Return error INVALID"
note bottom: TODO\ninconsistent\nwith online\ncheck
else
-down->[yes] "Is ignore_unpaid set?\n(Has the operator confirmed\nthe checkin?)"
-down->[yes] "Is the order in status PAID?"
--> if "" then
-right->[no] "Return error UNPAID "
-right->[no] "Is Order.require_approval set?"
--> if "" then
-->[yes] "Return error UNPAID "
else
-right->[no] "Is Order.valid_if_pending set?"
--> if "" then
-->[yes] "Is this an entry or exit?"
else
-->[no] "Does the check-in list include pending orders?"
--> if "" then
-->[no] "Return error UNPAID "
else
-->[yes] "Is ignore_unpaid set on the request?\n(Has the operator confirmed\nthe checkin?)"
--> if "" then
-->[no] "Return error UNPAID "
else
-->[yes] "Is this an entry or exit?"
endif
endif
endif
endif
else
-down->[yes] "Is this an entry or exit?"
endif
endif
else
-down->[yes] "Is this an entry or exit?"
endif
endif
endif
@@ -98,16 +118,26 @@ partition "dataless check" {
--> if "" then
-right->[yes] "Return error REVOKED"
else
-down->[no] "Is the product part of the check-in list? "
-down->[yes] "Is the ticket secret on the block list?"
--> if "" then
-right->[no] "Return error PRODUCT "
-right->[yes] "Return error BLOCKED "
else
-down->[yes] "Is the subevent part of the check-in list? "
-down->[yes] "If this is not an exit, is the valid_from/valid_until\nconstraint on the ticket fulfilled? "
--> if "" then
-right->[no] "Return error INVALID "
note bottom: TODO\ninconsistent\nwith online\ncheck
-right->[no] "Return error INVALID_TIME "
else
--> "Is this an entry or exit? "
-down->[no] "Is the product part of the check-in list? "
--> if "" then
-right->[no] "Return error PRODUCT "
else
-down->[yes] "Is the subevent part of the check-in list? "
--> if "" then
-right->[no] "Return error INVALID "
note bottom: TODO\ninconsistent\nwith online\ncheck
else
--> "Is this an entry or exit? "
endif
endif
endif
endif
endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -40,29 +40,49 @@ endif
"Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then
-right->[no] "Return error CANCELED"
else
-down->[yes] "Is the product part of the check-in list?"
-down->[yes] "Is one or more block set on the ticket?"
--> if "" then
-right->[no] "Return error PRODUCT"
-right->[no] "Return error BLOCKED"
else
-down->[yes] "Is the subevent part of the check-in list?"
-down->[yes] "If this is not an exit, is the valid_from/valid_until\nconstraint on the ticket fulfilled?"
--> if "" then
-right->[no] "Return error PRODUCT "
-right->[no] "Return error INVALID_TIME"
else
-down->[yes] "Is the order in status PAID\nor is this a forced upload?"
-down->[yes] "Is the product part of the check-in list?"
--> if "" then
-right->[no] "Does the check-in list include pending orders?"
-right->[no] "Return error PRODUCT"
else
-down->[yes] "Is the subevent part of the check-in list?"
--> if "" then
-right->[no] "Return error UNPAID "
-right->[no] "Return error PRODUCT "
else
-down->[yes] "Is ignore_unpaid set?\n(Has the operator confirmed\nthe checkin?)"
-down->[yes] "Is the order in status PAID\nor is this a forced upload?"
--> if "" then
-right->[no] "Return error UNPAID "
-right->[no] "Is Order.require_approval set?"
--> if "" then
-->[no] "Is Order.valid_if_pending set?"
--> if "" then
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
else
-right->[no] "Does the check-in list include pending orders?"
--> if "" then
-right->[no] "Return error UNPAID "
else
-down->[yes] "Is ignore_unpaid set on the request?\n(Has the operator confirmed\nthe checkin?)"
--> if "" then
-right->[no] "Return error UNPAID "
else
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
endif
endif
endif
else
-->[yes] "Return error UNPAID "
endif
else
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
endif
endif
else
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
endif
endif
endif

View File

@@ -1,69 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="254.15625"
height="109.59375"
viewBox="0 0 254.15625 109.59375"
version="1.1"
id="svg5"
sodipodi:docname="logo-white.svg"
inkscape:version="0.92.1 r"><metadata
id="metadata9">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1364"
inkscape:window-height="676"
id="namedview7"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="1"
inkscape:cx="56.462442"
inkscape:cy="54.796875"
inkscape:window-x="0"
inkscape:window-y="72"
inkscape:window-maximized="0"
inkscape:current-layer="svg5" />
id=&quot;svg2&quot;
version=&quot;1.1&quot;&gt;
<defs
id="defs4" />
<g
id="layer1"
transform="translate(-277.78125,-568.75)">
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;enable-background:accumulate"
d="m 20,20 v 34.09375 c 11.43679,0 20.71875,9.28196 20.71875,20.71875 C 40.71875,86.24928 31.43679,95.5 20,95.5 v 34.09375 h 146.6875 v -9.5 h 3 v 9.5 H 274.15625 V 95.5 c -0.0105,2e-5 -0.0208,0 -0.0312,0 -11.43678,0 -20.71875,-9.25072 -20.71875,-20.6875 0,-11.43679 9.28197,-20.71875 20.71875,-20.71875 0.0105,0 0.0208,-2e-5 0.0312,0 V 20 H 169.6875 v 9.09375 h -3 V 20 Z m 146.6875,16.09375 h 3 v 14 h -3 z m 41.44141,12.833984 c 2.79067,0 5.02343,1.92774 5.02343,4.3125 0,2.38476 -2.23276,4.363282 -5.02343,4.363282 -2.73994,0 -4.97266,-1.978522 -4.97266,-4.363282 0,-2.38476 2.23272,-4.3125 4.97266,-4.3125 z m -13.22852,4.210938 v 8.017578 h 3.95899 v 6.291016 h -3.95899 v 12.279296 c 0,2.02959 0.71015,2.791016 2.13086,2.791016 0.71035,0 1.06703,-0.10181 1.82813,-0.40625 v 5.935547 c -0.71036,0.40591 -2.38661,0.964844 -4.61915,0.964844 -6.13949,0 -8.98046,-3.753876 -8.98046,-8.472657 V 67.447266 h -2.8418 V 61.15625 h 2.8418 V 55.574219 Z M 166.6875,57.09375 h 3 v 14 h -3 z m -74.568359,3.554688 c 8.473509,0 14.207029,4.515688 14.207029,14.105468 0,8.62573 -5.02336,14.105469 -12.07617,14.105469 -1.72514,0 -3.147072,-0.20329 -3.857422,-0.40625 V 99.414062 H 80.751953 V 62.728516 c 2.58772,-1.21775 6.090268,-2.080081 11.367188,-2.080078 z m 49.863279,0 c 8.57499,0 12.63436,5.935363 12.12696,15.220703 l -15.93165,2.234375 c 0.60888,2.94289 2.18061,4.414062 5.68165,4.414062 3.24732,0 5.78445,-0.711556 7.30664,-1.472656 l 2.13086,5.886719 c -2.38476,1.16701 -5.58034,2.080078 -10.6543,2.080078 -8.93017,0 -13.64844,-6.037993 -13.64844,-14.257813 0,-8.21981 4.41329,-14.105468 12.98828,-14.105468 z m -17.92187,0.0059 c 0.8928,0.01358 1.82795,0.04496 2.80468,0.0957 l -1.67578,6.697266 c -1.77589,-0.86257 -3.50104,-0.913692 -4.76953,-0.457032 v 21.513672 h -9.64062 v -25.77539 c 2.79702,-1.376314 7.03166,-2.16926 13.28125,-2.074219 z m 79.24804,0.501953 h 9.64063 v 27.347656 h -9.64063 z m 13.23438,0 h 10.04687 l 3.29883,6.849609 h 0.10156 l 3.60157,-6.849609 h 8.98047 l -7.96485,12.632812 8.72656,14.714844 H 232.67969 L 229.17773,80.9434 h -0.10156 l -3.65234,7.560547 h -9.74219 l 8.57422,-14.105468 z m -74.9668,5.023438 c -2.84142,0 -4.41381,2.585948 -4.10937,7.355468 l 7.76367,-1.166015 c 0,-4.16064 -1.2188,-6.189454 -3.6543,-6.189453 z m -49.507811,0.09961 c -0.71035,0 -1.219131,0.101686 -1.675781,0.253906 v 16.439453 c 0.35517,0.15221 0.863828,0.253906 1.523438,0.253906 3.4503,0 4.871093,-2.840514 4.871093,-8.421874 0,-5.733571 -1.21772,-8.525391 -4.71875,-8.525391 z M 166.6875,78.09375 h 3 v 14 h -3 z m 0,21 h 3 v 14 h -3 z"
transform="translate(257.78125,548.75)"
id="rect3888"
inkscape:connector-curvature="0" />
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="254.156" height="109.594" version="1.1"><g transform="scale(1.9856)"><path d="M36.29 23.21c-.35 0-.61.06-.83.13v8.29c.19.06.45.13.77.13 1.73 0 2.46-1.44 2.46-4.26s-.64-4.29-2.4-4.29z" fill="#f8f8f8"/><path d="M61.22 23.15c-1.44 0-2.21 1.31-2.08 3.71l3.9-.58c.03-2.11-.58-3.14-1.82-3.14z" fill="#f8f8f8"/><path d="M127.39 17.1c.35-.03.61-.29.61-.64V.64c0-.35-.29-.64-.64-.64H75.94v2.48c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12V0H.64C.29 0 0 .29 0 .64v15.82c0 .35.26.61.61.64 5.47.32 9.82 4.86 9.82 10.43 0 5.57-4.35 10.08-9.82 10.37-.35.03-.61.29-.61.64v15.82c0 .35.29.64.64.64h73.22-.16v-2.48c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12V55h-.16 51.58c.35 0 .64-.29.64-.64V38.54c0-.35-.26-.61-.61-.64-5.47-.32-9.82-4.83-9.82-10.4s4.35-10.11 9.82-10.4zM37.41 34.57c-.86 0-1.6-.1-1.95-.19v5.54H30.6v-18.5c1.31-.61 3.07-1.06 5.73-1.06 4.26 0 7.17 2.27 7.17 7.1 0 4.35-2.53 7.1-6.08 7.1zm15.58-10.78c-.9-.45-1.76-.45-2.4-.22v10.85h-4.86V21.43c1.41-.7 3.55-1.09 6.69-1.06.45 0 .93.03 1.41.06L53 23.79Zm14.56 4.26-8.03 1.12c.32 1.47 1.09 2.21 2.85 2.21 1.63 0 2.91-.35 3.68-.74l1.09 2.98c-1.22.58-2.82 1.06-5.38 1.06-4.51 0-6.88-3.04-6.88-7.17s2.21-7.1 6.53-7.1c4.35-.03 6.4 2.98 6.14 7.65zm8.38 18.56c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12v-5.12c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zm0-11.01c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12V8.34c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zM90.11 23.8h-2.02v6.18c0 1.02.35 1.41 1.09 1.41.35 0 .54-.06.93-.19v2.98c-.35.19-1.22.48-2.34.48-3.1 0-4.51-1.89-4.51-4.26v-6.59h-1.44v-3.17h1.44v-2.82l4.86-1.22v4.03h1.98v3.17zm7.07 10.62h-4.86V20.66h4.86zm-2.43-15.58c-1.38 0-2.5-.99-2.5-2.21s1.12-2.18 2.5-2.18 2.53.96 2.53 2.18c0 1.22-1.12 2.21-2.53 2.21zm12.35 15.58-1.76-3.81h-.06l-1.82 3.81h-4.9l4.32-7.1-3.87-6.66h5.06l1.66 3.46h.06l1.82-3.46h4.51l-4 6.37 4.38 7.42-5.41-.03z" fill="#f8f8f8"/></g></svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,4 +1,4 @@
.. spelling::
.. spelling:word-list::
AGPL
AGPLv3

View File

@@ -1,4 +1,4 @@
.. spelling::
.. spelling:word-list::
Analytics
List of plugins

View File

@@ -1,5 +1,5 @@
.. highlight:: ini
.. spelling::
.. spelling:word-list::
IdP
skIDentity

View File

@@ -17,9 +17,13 @@ Field Type Description
id integer Internal layout ID
name string Internal layout description
default boolean ``true`` if this is the default layout
layout object Layout specification for libpretixprint
layout list Dynamic layout specification. Each list element
corresponds to one dynamic element of the layout.
The current version of the schema in use can be found
`here`_.
Submitting invalid content can lead to application errors.
background URL Background PDF file
item_assignments list of objects Products this layout is assigned to
item_assignments list of objects Products this layout is assigned to (currently read-only)
├ sales_channel string Sales channel (defaults to ``web``).
└ item integer Item ID
===================================== ========================== =======================================================
@@ -58,7 +62,7 @@ Endpoints
"name": "Default layout",
"default": true,
"layout": {…},
"background": {},
"background": null,
"item_assignments": []
}
]
@@ -96,7 +100,7 @@ Endpoints
"name": "Default layout",
"default": true,
"layout": {…},
"background": {},
"background": null,
"item_assignments": []
}
@@ -147,3 +151,122 @@ Endpoints
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/ticketlayouts/
Creates a new ticket layout
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/ticketlayouts/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"name": "Default layout",
"default": true,
"layout": […],
"background": null,
"item_assignments": []
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": "Default layout",
"default": true,
"layout": […],
"background": null,
"item_assignments": []
}
:param organizer: The ``slug`` field of the organizer of the event to create a layout for
:param event: The ``slug`` field of the event to create a layout for
:statuscode 201: no error
:statuscode 400: The layout 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)/ticketlayouts/(id)/
Update a layout. 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.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/ticketlayouts/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"name": "Default layout"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": "Default layout",
"default": true,
"layout": […],
"background": null,
"item_assignments": []
}
: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 layout to modify
:statuscode 200: no error
:statuscode 400: The layout 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)/ticketlayouts/(id)/
Delete a layout.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/ticketlayouts/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 layout 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.
.. _here: https://github.com/pretix/pretix/blob/master/src/pretix/static/schema/pdf-layout.schema.json

View File

@@ -1,10 +1,10 @@
sphinx==2.3.*
jinja2==3.0.*
sphinx==6.1.*
jinja2==3.1.*
sphinx-rtd-theme
sphinxcontrib-httpdomain
sphinxcontrib-images
sphinxcontrib-spelling==4.*
sphinxcontrib-jquery
sphinxcontrib-spelling==7.*
sphinxemoji
pygments-markdown-lexer
# See https://github.com/rfk/pyenchant/pull/130
git+https://github.com/raphaelm/pyenchant.git@patch-1#egg=pyenchant
pyenchant==3.2.*

View File

@@ -1,11 +1,11 @@
-e ../src/
sphinx==2.3.*
jinja2==3.0.*
sphinx==6.1.*
jinja2==3.1.*
sphinx-rtd-theme
sphinxcontrib-httpdomain
sphinxcontrib-images
sphinxcontrib-spelling==4.*
sphinxcontrib-jquery
sphinxcontrib-spelling==7.*
sphinxemoji
pygments-markdown-lexer
# See https://github.com/rfk/pyenchant/pull/130
git+https://github.com/raphaelm/pyenchant.git@patch-1#egg=pyenchant
pyenchant==3.2.*

View File

@@ -97,6 +97,7 @@ overpayment
param
passphrase
percental
personalization
pluggable
positionid
pre

View File

@@ -1,4 +1,4 @@
.. spelling::
.. spelling:word-list::
Warengutschein
Wertgutschein

View File

@@ -1,7 +1,7 @@
Invoice settings
================
.. spelling:: Inv
.. spelling:word-list:: Inv
The settings at "Settings" → "Invoice" allow you to specify if and how pretix should generate invoices for your orders.

View File

@@ -153,9 +153,9 @@ If you want to include all your public events, you can just reference your organ
There is an optional ``style`` parameter that let's you choose between a monthly calendar view, a week view and a list
view. If you do not set it, the choice will be taken from your organizer settings::
<pretix-widget event="https://pretix.eu/demo/series/" style="list"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" style="calendar"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" style="week"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" list-type="list"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" list-type="calendar"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" list-type="week"></pretix-widget>
If you have more than 100 events, the system might refuse to show a list view and always show a calendar for performance
reasons instead.
@@ -164,7 +164,7 @@ You can see an example here:
.. raw:: html
<pretix-widget event="https://pretix.eu/demo/series/" style="calendar"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" list-type="calendar"></pretix-widget>
<noscript>
<div class="pretix-widget">
<div class="pretix-widget-info-message">
@@ -176,7 +176,7 @@ You can see an example here:
You can filter events by meta data attributes. You can create those attributes in your order profile and set their values in both event and series date
settings. For example, if you set up a meta data property called "Promoted" that you set to "Yes" on some events, you can pass a filter like this::
<pretix-widget event="https://pretix.eu/demo/series/" style="list" filter="attr[Promoted]=Yes"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" list-type="list" filter="attr[Promoted]=Yes"></pretix-widget>
pretix Button
-------------

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -1,36 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 149.59399 149.59399"
version="1.1"
id="svg2"
height="159.56693"
width="159.56693">
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-257.78125,-548.74975)"
id="layer1">
<path
id="rect3888"
transform="matrix(0.93749999,0,0,0.93749999,257.78125,548.74975)"
d="M 21.333984 21.333984 L 21.333984 57.699219 C 33.470966 57.699219 43.320312 67.601506 43.320312 79.800781 C 43.320312 92.000035 33.470966 101.86719 21.333984 101.86719 L 21.333984 138.23438 L 94.226562 138.23438 L 94.226562 128.09961 L 97.410156 128.09961 L 97.410156 138.23438 L 138.23438 138.23438 L 138.23438 101.86719 C 138.22328 101.86721 138.21216 101.86719 138.20117 101.86719 C 126.0642 101.86719 116.21289 92.000035 116.21289 79.800781 C 116.21289 67.601506 126.0642 57.699219 138.20117 57.699219 C 138.21237 57.699219 138.22339 57.699197 138.23438 57.699219 L 138.23438 21.333984 L 97.410156 21.333984 L 97.410156 31.033203 L 94.226562 31.033203 L 94.226562 21.333984 L 21.333984 21.333984 z M 94.226562 38.5 L 97.410156 38.5 L 97.410156 53.433594 L 94.226562 53.433594 L 94.226562 38.5 z M 94.226562 60.900391 L 97.410156 60.900391 L 97.410156 75.833984 L 94.226562 75.833984 L 94.226562 60.900391 z M 67.044922 64.027344 C 76.359333 64.027344 82.662109 68.991742 82.662109 79.533203 C 82.662109 89.014942 77.139434 95.039062 69.386719 95.039062 C 67.490377 95.039062 65.927327 94.814901 65.146484 94.591797 L 65.146484 106.64062 L 54.550781 106.64062 L 54.550781 66.314453 C 57.395304 64.97585 61.244324 64.027344 67.044922 64.027344 z M 66.990234 70.216797 C 66.209392 70.216797 65.648458 70.328766 65.146484 70.496094 L 65.146484 88.568359 C 65.536906 88.735677 66.097199 88.845703 66.822266 88.845703 C 70.61497 88.845703 72.175781 85.725087 72.175781 79.589844 C 72.175781 73.287273 70.838704 70.216797 66.990234 70.216797 z M 94.226562 83.300781 L 97.410156 83.300781 L 97.410156 98.234375 L 94.226562 98.234375 L 94.226562 83.300781 z M 94.226562 105.69922 L 97.410156 105.69922 L 97.410156 120.63281 L 94.226562 120.63281 L 94.226562 105.69922 z "
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#3b1c4a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.06666672;marker:none;enable-background:accumulate" />
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="159.567" height="159.567" version="1.1" viewBox="0 0 149.594 149.594"><g transform="matrix(.9375 0 0 .9375 20.413 20.413)"><path d="M45.52 48.98c-.74 0-1.28.13-1.75.27v17.43c.4.13.94.27 1.61.27 3.63 0 5.18-3.03 5.18-8.95s-1.35-9.02-5.05-9.02z" fill="#3b1c4a"/><path d="M114.72 36.13c.74-.07 1.28-.61 1.28-1.35V1.35c0-.74-.61-1.35-1.35-1.35H75.99v5.38c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V0H1.35C.61 0 0 .61 0 1.35v33.44c0 .74.54 1.28 1.28 1.35 11.51.67 20.66 10.23 20.66 21.94s-9.15 21.2-20.66 21.8c-.74.07-1.28.61-1.28 1.35v33.44c0 .74.61 1.35 1.35 1.35h70.48v-5.38c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09v5.38h38.66c.74 0 1.35-.61 1.35-1.35V81.23c0-.74-.54-1.28-1.28-1.35-11.51-.67-20.66-10.16-20.66-21.87s9.15-21.26 20.66-21.87zM47.87 72.87c-1.82 0-3.36-.2-4.1-.4v11.64H33.54V45.22C36.3 43.94 40 43 45.58 43c8.95 0 15.07 4.78 15.07 14.94 0 9.15-5.32 14.94-12.78 14.94zm28.12 25.3c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V87.4c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V64.12c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09zm0-23.15c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V40.97c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V17.69c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09z" fill="#3b1c4a"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1,72 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="600"
height="400"
id="svg2"
version="1.1"
inkscape:version="0.92.1 r"
sodipodi:docname="logo.svg"
inkscape:export-filename="/tmp/LOGO.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
viewBox="0 0 562.50001 375.00002">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.9899495"
inkscape:cx="133.36756"
inkscape:cy="276.10571"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1916"
inkscape:window-height="1041"
inkscape:window-x="1920"
inkscape:window-y="18"
inkscape:window-maximized="0"
fit-margin-top="20"
fit-margin-left="20"
fit-margin-right="20"
fit-margin-bottom="20" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-259.03125,-322.09374)">
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#3b1c4a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.91138947;marker:none;enable-background:accumulate"
d="m 297.38548,404.85558 v 65.16643 c 21.86016,0 39.6016,17.74144 39.6016,39.60159 0,21.86015 -17.74144,39.54187 -39.6016,39.54187 v 65.16644 h 280.37693 v -18.1582 h 5.73417 v 18.1582 h 199.68046 v -65.16644 c -0.02,4e-5 -0.0397,0 -0.0596,0 -21.86015,0 -39.6016,-17.68172 -39.6016,-39.54187 0,-21.86015 17.74145,-39.60159 39.6016,-39.60159 0.02,0 0.0397,-4e-5 0.0596,0 V 404.85558 H 583.49658 v 17.38169 h -5.73417 V 404.85558 Z M 577.76241,435.617 h 5.73417 v 26.75945 h -5.73417 z m 79.21068,24.53074 c 5.33405,0 9.60172,3.68466 9.60172,8.24287 0,4.5582 -4.26767,8.33993 -9.60172,8.33993 -5.2371,0 -9.50469,-3.78173 -9.50469,-8.33993 0,-4.55821 4.26759,-8.24287 9.50469,-8.24287 z m -25.28486,8.04874 v 15.32472 h 7.56717 v 12.02458 h -7.56717 v 23.47051 c 0,3.87934 1.35737,5.33473 4.0729,5.33473 1.35775,0 2.03951,-0.19461 3.49427,-0.77651 v 11.34514 c -1.35777,0.77585 -4.56174,1.84419 -8.829,1.84419 -11.73495,0 -17.16515,-7.17511 -17.16515,-16.19455 v -25.02351 h -5.43179 V 483.5212 h 5.43179 v -10.66944 z m -53.92582,7.5597 h 5.73417 v 26.75945 h -5.73417 z m -142.52917,6.79439 c 16.19618,0 27.15517,8.63124 27.15517,26.96104 0,16.48713 -9.6016,26.96105 -23.08227,26.96105 -3.29741,0 -6.01528,-0.38857 -7.37304,-0.77651 v 20.95062 H 413.50612 V 486.5264 c 4.94614,-2.32759 11.64087,-3.97583 21.72712,-3.97583 z m 95.30815,0 c 16.39014,0 24.14917,11.34479 23.17933,29.09269 l -30.45158,4.27076 c 1.1638,5.62501 4.16799,8.437 10.85985,8.437 6.20689,0 11.05633,-1.36007 13.96583,-2.81482 l 4.0729,11.2518 c -4.5582,2.23062 -10.6662,3.97584 -20.36451,3.97584 -17.06903,0 -26.08749,-11.54096 -26.08749,-27.25223 0,-15.71126 8.43552,-26.96104 24.82567,-26.96104 z m -34.25568,0.0113 c 1.70649,0.026 3.49392,0.0859 5.36084,0.18292 l -3.20307,12.80108 c -3.39442,-1.6487 -6.69185,-1.74642 -9.11643,-0.87356 v 41.121 h -18.42698 v -49.2668 c 5.3462,-2.63068 13.44024,-4.1463 25.38564,-3.96465 z m 151.47387,0.95943 h 18.42699 v 52.27202 h -18.42699 z m 25.29605,0 h 19.20348 l 6.30535,13.09227 h 0.19412 l 6.884,-13.09227 h 17.16517 l -15.22393,24.14622 16.67986,28.1258 h -20.3645 l -6.69361,-14.45115 h -0.19412 l -6.98104,14.45115 h -18.62112 l 16.38867,-26.96104 z m -143.29075,9.60175 c -5.43106,0 -8.43651,4.94275 -7.85461,14.05916 l 14.8394,-2.22871 c 0,-7.9526 -2.3296,-11.83045 -6.98479,-11.83045 z m -94.6287,0.19038 c -1.35776,0 -2.33024,0.19437 -3.20308,0.48532 v 31.4222 c 0.67888,0.29093 1.65112,0.48531 2.91189,0.48531 6.59487,0 9.31055,-5.42933 9.31055,-16.09748 0,-10.95908 -2.32753,-16.29535 -9.01936,-16.29535 z m 142.62623,22.58194 h 5.73417 v 26.75946 h -5.73417 z m 0,40.13918 h 5.73417 v 26.75946 h -5.73417 z"
id="rect3888"
inkscape:connector-curvature="0"
inkscape:export-filename="/tmp/LOGO.png"
inkscape:export-xdpi="88"
inkscape:export-ydpi="88" />
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="600" height="400" version="1.1" viewBox="0 0 562.5 375"><g transform="translate(-259.031 -322.094)"><g transform="matrix(3.79525 0 0 3.79525 297.317 405.22)"><path d="M36.29 23.21c-.35 0-.61.06-.83.13v8.29c.19.06.45.13.77.13 1.73 0 2.46-1.44 2.46-4.26s-.64-4.29-2.4-4.29z" fill="#3b1c4a"/><path d="M61.22 23.15c-1.44 0-2.21 1.31-2.08 3.71l3.9-.58c.03-2.11-.58-3.14-1.82-3.14z" fill="#3b1c4a"/><path d="M127.39 17.1c.35-.03.61-.29.61-.64V.64c0-.35-.29-.64-.64-.64H75.81v2.48c0 .55-.45.99-.99.99s-.99-.45-.99-.99V0H.64C.29 0 0 .29 0 .64v15.82c0 .35.26.61.61.64 5.47.32 9.82 4.86 9.82 10.43 0 5.57-4.35 10.08-9.82 10.37-.35.03-.61.29-.61.64v15.82c0 .35.29.64.64.64h73.22-.03v-2.48c0-.57.43-.99.99-.99s.99.45.99.99V55h-.03 51.58c.35 0 .64-.29.64-.64V38.54c0-.35-.26-.61-.61-.64-5.47-.32-9.82-4.83-9.82-10.4s4.35-10.11 9.82-10.4zM37.41 34.57c-.86 0-1.6-.1-1.95-.19v5.54H30.6v-18.5c1.31-.61 3.07-1.06 5.73-1.06 4.26 0 7.17 2.27 7.17 7.1 0 4.35-2.53 7.1-6.08 7.1zm15.58-10.78c-.9-.45-1.76-.45-2.4-.22v10.85h-4.86V21.43c1.41-.7 3.55-1.09 6.69-1.06.45 0 .93.03 1.41.06L53 23.79Zm14.56 4.26-8.03 1.12c.32 1.47 1.09 2.21 2.85 2.21 1.63 0 2.91-.35 3.68-.74l1.09 2.98c-1.22.58-2.82 1.06-5.38 1.06-4.51 0-6.88-3.04-6.88-7.17s2.21-7.1 6.53-7.1c4.35-.03 6.4 2.98 6.14 7.65zm8.26 18.56c0 .55-.45.99-.99.99s-.99-.45-.99-.99v-5.12c0-.55.45-.99.99-.99s.99.46.99.99zm0-11.07c0 .53-.46.99-.99.99s-.99-.46-.99-.99v-5.12c0-.57.43-.99.99-.99s.99.45.99.99zm0-11.01c0 .55-.45.99-.99.99s-.99-.45-.99-.99v-5.12c0-.55.45-.99.99-.99s.99.46.99.99zm0-11.07c0 .53-.46.99-.99.99s-.99-.46-.99-.99V8.34c0-.57.43-.99.99-.99s.99.45.99.99zm14.3 10.34h-2.02v6.18c0 1.02.35 1.41 1.09 1.41.35 0 .54-.06.93-.19v2.98c-.35.19-1.22.48-2.34.48-3.1 0-4.51-1.89-4.51-4.26v-6.59h-1.44v-3.17h1.44v-2.82l4.86-1.22v4.03h1.98v3.17zm7.07 10.62h-4.86V20.66h4.86zm-2.43-15.58c-1.38 0-2.5-.99-2.5-2.21s1.12-2.18 2.5-2.18 2.53.96 2.53 2.18c0 1.22-1.12 2.21-2.53 2.21zm12.35 15.58-1.76-3.81h-.06l-1.82 3.81h-4.9l4.32-7.1-3.87-6.66h5.06l1.66 3.46h.06l1.82-3.46h4.51l-4 6.37 4.38 7.42-5.41-.03z" fill="#3b1c4a"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,68 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="294.15625"
height="149.59375"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="logo_draft_white.svg"
inkscape:export-filename="/home/raphael/proj/pretix/pretix/logo_draft.png"
inkscape:export-xdpi="88.529999"
inkscape:export-ydpi="88.529999">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.9899495"
inkscape:cx="121.06383"
inkscape:cy="277.43904"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="956"
inkscape:window-height="1041"
inkscape:window-x="2880"
inkscape:window-y="18"
inkscape:window-maximized="0"
fit-margin-top="20"
fit-margin-left="20"
fit-margin-right="20"
fit-margin-bottom="20" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-257.78125,-548.75)">
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;enable-background:accumulate"
d="M 20 20 L 20 54.09375 C 31.43679 54.09375 40.71875 63.37571 40.71875 74.8125 C 40.71875 86.24928 31.43679 95.5 20 95.5 L 20 129.59375 L 166.6875 129.59375 L 166.6875 120.09375 L 169.6875 120.09375 L 169.6875 129.59375 L 274.15625 129.59375 L 274.15625 95.5 C 274.14575 95.50002 274.1354 95.5 274.125 95.5 C 262.68822 95.5 253.40625 86.24928 253.40625 74.8125 C 253.40625 63.37571 262.68822 54.09375 274.125 54.09375 C 274.1355 54.09375 274.14585 54.09373 274.15625 54.09375 L 274.15625 20 L 169.6875 20 L 169.6875 29.09375 L 166.6875 29.09375 L 166.6875 20 L 20 20 z M 166.6875 36.09375 L 169.6875 36.09375 L 169.6875 50.09375 L 166.6875 50.09375 L 166.6875 36.09375 z M 208.12891 48.927734 C 210.91958 48.927734 213.15234 50.855474 213.15234 53.240234 C 213.15234 55.624994 210.91958 57.603516 208.12891 57.603516 C 205.38897 57.603516 203.15625 55.624994 203.15625 53.240234 C 203.15625 50.855474 205.38897 48.927734 208.12891 48.927734 z M 194.90039 53.138672 L 194.90039 61.15625 L 198.85938 61.15625 L 198.85938 67.447266 L 194.90039 67.447266 L 194.90039 79.726562 C 194.90039 81.756152 195.61054 82.517578 197.03125 82.517578 C 197.7416 82.517578 198.09828 82.415768 198.85938 82.111328 L 198.85938 88.046875 C 198.14902 88.452785 196.47277 89.011719 194.24023 89.011719 C 188.10074 89.011719 185.25977 85.257843 185.25977 80.539062 L 185.25977 67.447266 L 182.41797 67.447266 L 182.41797 61.15625 L 185.25977 61.15625 L 185.25977 55.574219 L 194.90039 53.138672 z M 166.6875 57.09375 L 169.6875 57.09375 L 169.6875 71.09375 L 166.6875 71.09375 L 166.6875 57.09375 z M 92.119141 60.648438 C 100.59265 60.648438 106.32617 65.164126 106.32617 74.753906 C 106.32617 83.379636 101.30281 88.859375 94.25 88.859375 C 92.52486 88.859375 91.102928 88.656085 90.392578 88.453125 L 90.392578 99.414062 L 80.751953 99.414062 L 80.751953 62.728516 C 83.339673 61.510766 86.842221 60.648435 92.119141 60.648438 z M 141.98242 60.648438 C 150.55741 60.648438 154.61678 66.583801 154.10938 75.869141 L 138.17773 78.103516 C 138.78661 81.046406 140.35834 82.517578 143.85938 82.517578 C 147.1067 82.517578 149.64383 81.806022 151.16602 81.044922 L 153.29688 86.931641 C 150.91212 88.098651 147.71654 89.011719 142.64258 89.011719 C 133.71241 89.011719 128.99414 82.973726 128.99414 74.753906 C 128.99414 66.534096 133.40743 60.648438 141.98242 60.648438 z M 124.06055 60.654297 C 124.95335 60.667874 125.8885 60.69926 126.86523 60.75 L 125.18945 67.447266 C 123.41356 66.584696 121.68841 66.533574 120.41992 66.990234 L 120.41992 88.503906 L 110.7793 88.503906 L 110.7793 62.728516 C 113.57632 61.352202 117.81096 60.559256 124.06055 60.654297 z M 203.30859 61.15625 L 212.94922 61.15625 L 212.94922 88.503906 L 203.30859 88.503906 L 203.30859 61.15625 z M 216.54297 61.15625 L 226.58984 61.15625 L 229.88867 68.005859 L 229.99023 68.005859 L 233.5918 61.15625 L 242.57227 61.15625 L 234.60742 73.789062 L 243.33398 88.503906 L 232.67969 88.503906 L 229.17773 80.943359 L 229.07617 80.943359 L 225.42383 88.503906 L 215.68164 88.503906 L 224.25586 74.398438 L 216.54297 61.15625 z M 141.57617 66.179688 C 138.73475 66.179688 137.16236 68.765636 137.4668 73.535156 L 145.23047 72.369141 C 145.23047 68.208501 144.01167 66.179687 141.57617 66.179688 z M 92.068359 66.279297 C 91.358009 66.279297 90.849228 66.380983 90.392578 66.533203 L 90.392578 82.972656 C 90.747748 83.124866 91.256406 83.226562 91.916016 83.226562 C 95.366316 83.226562 96.787109 80.386048 96.787109 74.804688 C 96.787109 69.071117 95.569389 66.279297 92.068359 66.279297 z M 166.6875 78.09375 L 169.6875 78.09375 L 169.6875 92.09375 L 166.6875 92.09375 L 166.6875 78.09375 z M 166.6875 99.09375 L 169.6875 99.09375 L 169.6875 113.09375 L 166.6875 113.09375 L 166.6875 99.09375 z "
transform="translate(257.78125,548.75)"
id="rect3888" />
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="294.156" height="149.594" version="1.1"><g transform="translate(-257.781 -548.75)"><g transform="matrix(1.9856 0 0 1.9856 277.781 568.75)"><path d="M36.29 23.21c-.35 0-.61.06-.83.13v8.29c.19.06.45.13.77.13 1.73 0 2.46-1.44 2.46-4.26s-.64-4.29-2.4-4.29z" fill="#f8f8f8"/><path d="M61.22 23.15c-1.44 0-2.21 1.31-2.08 3.71l3.9-.58c.03-2.11-.58-3.14-1.82-3.14z" fill="#f8f8f8"/><path d="M127.39 17.1c.35-.03.61-.29.61-.64V.64c0-.35-.29-.64-.64-.64H75.94v2.48c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12V0H.64C.29 0 0 .29 0 .64v15.82c0 .35.26.61.61.64 5.47.32 9.82 4.86 9.82 10.43 0 5.57-4.35 10.08-9.82 10.37-.35.03-.61.29-.61.64v15.82c0 .35.29.64.64.64h73.22-.16v-2.48c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12V55h-.16 51.58c.35 0 .64-.29.64-.64V38.54c0-.35-.26-.61-.61-.64-5.47-.32-9.82-4.83-9.82-10.4s4.35-10.11 9.82-10.4zM37.41 34.57c-.86 0-1.6-.1-1.95-.19v5.54H30.6v-18.5c1.31-.61 3.07-1.06 5.73-1.06 4.26 0 7.17 2.27 7.17 7.1 0 4.35-2.53 7.1-6.08 7.1zm15.58-10.78c-.9-.45-1.76-.45-2.4-.22v10.85h-4.86V21.43c1.41-.7 3.55-1.09 6.69-1.06.45 0 .93.03 1.41.06L53 23.79Zm14.56 4.26-8.03 1.12c.32 1.47 1.09 2.21 2.85 2.21 1.63 0 2.91-.35 3.68-.74l1.09 2.98c-1.22.58-2.82 1.06-5.38 1.06-4.51 0-6.88-3.04-6.88-7.17s2.21-7.1 6.53-7.1c4.35-.03 6.4 2.98 6.14 7.65zm8.38 18.56c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12v-5.12c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zm0-11.01c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12V8.34c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zM90.11 23.8h-2.02v6.18c0 1.02.35 1.41 1.09 1.41.35 0 .54-.06.93-.19v2.98c-.35.19-1.22.48-2.34.48-3.1 0-4.51-1.89-4.51-4.26v-6.59h-1.44v-3.17h1.44v-2.82l4.86-1.22v4.03h1.98v3.17zm7.07 10.62h-4.86V20.66h4.86zm-2.43-15.58c-1.38 0-2.5-.99-2.5-2.21s1.12-2.18 2.5-2.18 2.53.96 2.53 2.18c0 1.22-1.12 2.21-2.53 2.21zm12.35 15.58-1.76-3.81h-.06l-1.82 3.81h-4.9l4.32-7.1-3.87-6.66h5.06l1.66 3.46h.06l1.82-3.46h4.51l-4 6.37 4.38 7.42-5.41-.03z" fill="#f8f8f8"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -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__ = "4.16.0.dev0"
__version__ = "4.17.1"

View File

@@ -19,9 +19,12 @@
# 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 logging
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
class FullAccessSecurityProfile:
identifier = 'full'
@@ -36,7 +39,13 @@ class AllowListSecurityProfile:
def is_allowed(self, request):
key = (request.method, f"{request.resolver_match.namespace}:{request.resolver_match.url_name}")
return key in self.allowlist
if key in self.allowlist:
return True
else:
logger.info(
f'Request {key} not allowed in profile {self.identifier}'
)
return False
class PretixScanSecurityProfile(AllowListSecurityProfile):
@@ -65,6 +74,7 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:checkinlistpos-list'),
('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:order-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('GET', 'api-v1:event.settings'),
@@ -99,6 +109,7 @@ class PretixScanNoSyncNoSearchSecurityProfile(AllowListSecurityProfile):
('POST', 'api-v1:checkinlist-failed_checkins'),
('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
@@ -133,6 +144,7 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:checkinlistpos-list'),
('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
@@ -199,6 +211,7 @@ class PretixPosSecurityProfile(AllowListSecurityProfile):
('POST', 'plugins:pretix_posbackend:stripeterminal.paymentintent'),
('PUT', 'plugins:pretix_posbackend:file.upload'),
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:event.settings'),
('GET', 'plugins:pretix_seating:event.event'),
('GET', 'plugins:pretix_seating:event.event.subevent'),

View File

@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>.
#
import json
import logging
from hashlib import sha1
from django.conf import settings
@@ -32,6 +33,9 @@ from rest_framework import status
from pretix.api.models import ApiCall
from pretix.base.models import Organizer
from pretix.helpers import OF_SELF
logger = logging.getLogger(__name__)
class IdempotencyMiddleware:
@@ -56,7 +60,7 @@ class IdempotencyMiddleware:
idempotency_key = request.headers.get('X-Idempotency-Key', '')
with transaction.atomic():
call, created = ApiCall.objects.select_for_update().get_or_create(
call, created = ApiCall.objects.select_for_update(of=OF_SELF).get_or_create(
auth_hash=auth_hash,
idempotency_key=idempotency_key,
defaults={
@@ -96,6 +100,9 @@ class IdempotencyMiddleware:
return resp
else:
if call.locked:
logger.info(
f'Concurrent request with idempotency key {idempotency_key} blocked.'
)
r = JsonResponse(
{'detail': 'Concurrent request with idempotency key.'},
status=status.HTTP_409_CONFLICT,
@@ -110,6 +117,7 @@ class IdempotencyMiddleware:
content=content,
status=call.response_code,
)
logger.info(f'API response replayed from idempotency store for key {idempotency_key} [{call.response_code}]')
for k, v in json.loads(call.response_headers).values():
r[k] = v
return r

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2023-02-07 12:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixapi', '0009_auto_20221217_1847'),
]
operations = [
migrations.AddField(
model_name='webhook',
name='comment',
field=models.CharField(max_length=255, null=True),
),
]

View File

@@ -112,6 +112,7 @@ class WebHook(models.Model):
target_url = models.URLField(verbose_name=_("Target URL"), max_length=255)
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)
class Meta:
ordering = ('id',)

View File

@@ -19,9 +19,19 @@
# 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 rest_framework.filters import OrderingFilter
from rest_framework.pagination import PageNumberPagination
from pretix.helpers import get_deterministic_ordering
class Pagination(PageNumberPagination):
page_size_query_param = 'page_size'
max_page_size = 50
class TotalOrderingFilter(OrderingFilter):
def get_ordering(self, request, queryset, view):
o = super().get_ordering(request, queryset, view)
o = get_deterministic_ordering(queryset.model, o)
return o

View File

@@ -59,6 +59,7 @@ from pretix.base.settings import (
LazyI18nStringList, validate_event_settings,
)
from pretix.base.signals import api_event_settings_fields
from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger(__name__)
@@ -164,6 +165,10 @@ class EventSerializer(I18nAwareModelSerializer):
timezone = TimeZoneField(required=False, choices=[(a, a) for a in common_timezones])
valid_keys = ValidKeysField(source='*', read_only=True)
best_availability_state = serializers.IntegerField(allow_null=True, read_only=True)
public_url = serializers.SerializerMethodField('get_event_url', read_only=True)
def get_event_url(self, event):
return build_absolute_uri(event, 'presale:event.index')
class Meta:
model = Event
@@ -171,7 +176,7 @@ class EventSerializer(I18nAwareModelSerializer):
'date_to', 'date_admission', 'is_public', 'presale_start',
'presale_end', 'location', 'geo_lat', 'geo_lon', 'has_subevents', 'meta_data', 'seating_plan',
'plugins', 'seat_category_mapping', 'timezone', 'item_meta_properties', 'valid_keys',
'sales_channels', 'best_availability_state')
'sales_channels', 'best_availability_state', 'public_url')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -662,6 +667,7 @@ class EventSettingsSerializer(SettingsSerializer):
'show_times',
'show_items_outside_presale_period',
'display_net_prices',
'hide_prices_from_attendees',
'presale_start_show_date',
'locales',
'locale',

View File

@@ -22,8 +22,10 @@
from django import forms
from django.http import QueryDict
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.base.exporter import OrganizerLevelExportMixin
from pretix.base.timeframes import DateFrameField, SerializerDateFrameField
class FormFieldWrapperField(serializers.Field):
@@ -142,6 +144,12 @@ class JobRunSerializer(serializers.Serializer):
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)
@@ -151,5 +159,40 @@ class JobRunSerializer(serializers.Serializer):
for k, v in self.fields.items():
if isinstance(v, serializers.ManyRelatedField) and k not in data:
data[k] = []
for fk in self.fields.keys():
# Backwards compatibility for exports that used to take e.g. (date_from, date_to) or (event_date_from, event_date_to)
# and now only take date_range.
if fk.endswith("_range") and isinstance(self.fields[fk], SerializerDateFrameField) and fk not in data:
if fk.replace("_range", "_from") in data:
d_from = data.pop(fk.replace("_range", "_from"))
if d_from:
d_from = serializers.DateField().to_internal_value(d_from)
else:
d_from = None
if fk.replace("_range", "_to") in data:
d_to = data.pop(fk.replace("_range", "_to"))
if d_to:
d_to = serializers.DateField().to_internal_value(d_to)
else:
d_to = None
data[fk] = f'{d_from.isoformat() if d_from else ""}/{d_to.isoformat() if d_to else ""}'
data = super().to_internal_value(data)
return data
def is_valid(self, raise_exception=False):
super().is_valid(raise_exception=raise_exception)
fields_keys = set(self.fields.keys())
input_keys = set(self.initial_data.keys())
additional_fields = input_keys - fields_keys
if bool(additional_fields):
self._errors['fields'] = ['Additional fields not allowed: {}.'.format(list(additional_fields))]
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors)

View File

@@ -60,8 +60,8 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price', 'require_approval',
'require_membership', 'require_membership_types',
'require_membership_hidden', 'available_from', 'available_until',
'require_membership', 'require_membership_types', 'require_membership_hidden',
'checkin_attention', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher', 'meta_data')
def __init__(self, *args, **kwargs):
@@ -84,8 +84,8 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price', 'require_approval',
'require_membership', 'require_membership_types',
'require_membership_hidden', 'available_from', 'available_until',
'require_membership', 'require_membership_types', 'require_membership_hidden',
'checkin_attention', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher', 'meta_data')
def __init__(self, *args, **kwargs):
@@ -95,8 +95,12 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
@transaction.atomic
def create(self, validated_data):
meta_data = validated_data.pop('meta_data', None)
require_membership_types = validated_data.pop('require_membership_types', [])
variation = ItemVariation.objects.create(**validated_data)
if require_membership_types:
variation.require_membership_types.add(*require_membership_types)
# Meta data
if meta_data is not None:
for key, value in meta_data.items():
@@ -230,7 +234,7 @@ class ItemSerializer(I18nAwareModelSerializer):
class Meta:
model = Item
fields = ('id', 'category', 'name', 'internal_name', 'active', 'sales_channels', 'description',
'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission',
'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission', 'personalized',
'position', 'picture', 'available_from', 'available_until',
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
@@ -238,7 +242,9 @@ class ItemSerializer(I18nAwareModelSerializer):
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
'grant_membership_duration_like_event', 'grant_membership_duration_days',
'grant_membership_duration_months')
'grant_membership_duration_months', 'validity_mode', 'validity_fixed_from', 'validity_fixed_until',
'validity_dynamic_duration_minutes', 'validity_dynamic_duration_hours', 'validity_dynamic_duration_days',
'validity_dynamic_duration_months', 'validity_dynamic_start_choice', 'validity_dynamic_start_choice_day_limit')
read_only_fields = ('has_variations',)
def __init__(self, *args, **kwargs):
@@ -258,6 +264,15 @@ class ItemSerializer(I18nAwareModelSerializer):
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'))
if data.get('personalized') and not data.get('admission'):
raise ValidationError(_('Only admission products can currently be personalized.'))
if data.get('admission') and 'personalized' not in data and not self.instance:
# Backwards compatibility
data['personalized'] = True
elif 'admission' in data and not data['admission']:
data['personalized'] = False
if data.get('issue_giftcard'):
if data.get('tax_rule') and data.get('tax_rule').rate > 0:
raise ValidationError(

View File

@@ -52,7 +52,8 @@ from pretix.base.models import (
SubEvent, TaxRule, Voucher,
)
from pretix.base.models.orders import (
CartPosition, OrderFee, OrderPayment, OrderRefund, RevokedTicketSecret,
BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund,
RevokedTicketSecret,
)
from pretix.base.pdf import get_images, get_variables
from pretix.base.services.cart import error_messages
@@ -118,6 +119,10 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
raise ValidationError(
{'name': ['Do not specify name if you specified name_parts.']}
)
if data.get('name_parts') and not isinstance(data.get('name_parts'), dict):
raise ValidationError({'name_parts': ['Invalid data type']})
if data.get('name_parts') and '_scheme' not in data.get('name_parts'):
data['name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
@@ -296,7 +301,9 @@ class FailedCheckinSerializer(I18nAwareModelSerializer):
class OrderDownloadsField(serializers.Field):
def to_representation(self, instance: Order):
if instance.status != Order.STATUS_PAID:
if instance.status != Order.STATUS_PENDING or instance.require_approval or not instance.event.settings.ticket_download_pending:
if instance.status != Order.STATUS_PENDING or instance.require_approval or (
not instance.valid_if_pending and not instance.event.settings.ticket_download_pending
):
return []
request = self.context['request']
@@ -320,7 +327,9 @@ class OrderDownloadsField(serializers.Field):
class PositionDownloadsField(serializers.Field):
def to_representation(self, instance: OrderPosition):
if instance.order.status != Order.STATUS_PAID:
if instance.order.status != Order.STATUS_PENDING or instance.order.require_approval or not instance.order.event.settings.ticket_download_pending:
if instance.order.status != Order.STATUS_PENDING or instance.order.require_approval or (
not instance.order.valid_if_pending and not instance.order.event.settings.ticket_download_pending
):
return []
if not instance.generate_ticket:
return []
@@ -433,11 +442,12 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled')
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
'valid_from', 'valid_until', 'blocked')
read_only_fields = (
'id', 'order', 'positionid', 'item', 'variation', 'price', 'voucher', 'tax_rate', 'tax_value', 'secret',
'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data',
'seat', 'canceled', 'discount',
'seat', 'canceled', 'discount', 'valid_from', 'valid_until', 'blocked'
)
def __init__(self, *args, **kwargs):
@@ -459,7 +469,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
class RequireAttentionField(serializers.Field):
def to_representation(self, instance: OrderPosition):
return instance.order.checkin_attention or instance.item.checkin_attention
return instance.require_checkin_attention
class AttendeeNameField(serializers.Field):
@@ -504,7 +514,7 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
'company', 'street', 'zipcode', 'city', 'country', 'state',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'require_attention',
'order__status')
'order__status', 'valid_from', 'valid_until', 'blocked')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -618,7 +628,7 @@ class OrderSerializer(I18nAwareModelSerializer):
'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer'
'url', 'customer', 'valid_if_pending'
)
read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
@@ -673,7 +683,8 @@ class OrderSerializer(I18nAwareModelSerializer):
def update(self, instance, validated_data):
# Even though all fields that shouldn't be edited are marked as read_only in the serializer
# (hopefully), we'll be extra careful here and be explicit about the model fields we update.
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'email', 'locale', 'phone']
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'email', 'locale', 'phone',
'valid_if_pending']
if 'invoice_address' in validated_data:
iadata = validated_data.pop('invoice_address')
@@ -774,12 +785,14 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
voucher = serializers.SlugRelatedField(slug_field='code', queryset=Voucher.objects.none(),
required=False, allow_null=True)
country = CompatibleCountryField(source='*')
requested_valid_from = serializers.DateTimeField(required=False, allow_null=True)
class Meta:
model = OrderPosition
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')
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until',
'requested_valid_from')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -841,6 +854,10 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
raise ValidationError(
{'attendee_name': ['Do not specify attendee_name if you specified attendee_name_parts.']}
)
if data.get('attendee_name_parts') and not isinstance(data.get('attendee_name_parts'), dict):
raise ValidationError({'attendee_name_parts': ['Invalid data type']})
if data.get('attendee_name_parts') and '_scheme' not in data.get('attendee_name_parts'):
data['attendee_name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
@@ -933,7 +950,8 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
model = Order
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts',
'force', 'send_email', 'simulate', 'customer', 'custom_followup_at', 'require_approval')
'force', 'send_email', 'simulate', 'customer', 'custom_followup_at', 'require_approval',
'valid_if_pending')
def validate_payment_provider(self, pp):
if pp is None:
@@ -1161,6 +1179,20 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
elif seated:
errs[i]['seat'] = ['The specified product requires to choose a seat.']
requested_valid_from = pos_data.pop('requested_valid_from', None)
if 'valid_from' not in pos_data and 'valid_until' not in pos_data:
valid_from, valid_until = pos_data['item'].compute_validity(
requested_start=(
max(requested_valid_from, now())
if requested_valid_from and pos_data['item'].validity_dynamic_start_choice
else now()
),
enforce_start_limit=True,
override_tz=self.context['event'].timezone,
)
pos_data['valid_from'] = valid_from
pos_data['valid_until'] = valid_until
if not force:
for i, pos_data in enumerate(positions_data):
if pos_data.get('voucher'):
@@ -1476,9 +1508,9 @@ class InvoiceSerializer(I18nAwareModelSerializer):
'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',
'introductory_text', 'additional_text', 'payment_provider_text', 'footer_text', 'lines',
'foreign_currency_display', 'foreign_currency_rate', 'foreign_currency_rate_date',
'internal_reference')
'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')
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
@@ -1524,3 +1556,10 @@ class RevokedTicketSecretSerializer(I18nAwareModelSerializer):
class Meta:
model = RevokedTicketSecret
fields = ('id', 'secret', 'created')
class BlockedTicketSecretSerializer(I18nAwareModelSerializer):
class Meta:
model = BlockedTicketSecret
fields = ('id', 'secret', 'updated', 'blocked')

View File

@@ -24,6 +24,7 @@ import os
import pycountry
from django.core.files import File
from django.core.validators import RegexValidator
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
@@ -53,7 +54,7 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
model = OrderPosition
fields = ('order', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state',
'secret', 'addon_to', 'subevent', 'answers', 'seat')
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'valid_from', 'valid_until')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -89,6 +90,8 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
addon_to=validated_data.get('addon_to'),
subevent=validated_data.get('subevent'),
seat=validated_data.get('seat'),
valid_from=validated_data.get('valid_from'),
valid_until=validated_data.get('valid_until'),
)
if self.context.get('commit', True):
ocm.commit()
@@ -158,12 +161,14 @@ class OrderPositionInfoPatchSerializer(serializers.ModelSerializer):
a.question_id: a for a in instance.answers.all()
}
for answ_data in answers_data:
if not answ_data.get('answer'):
continue
options = answ_data.pop('options', [])
if answ_data['question'].pk in qs_seen:
raise ValidationError(f'Question {answ_data["question"]} was sent twice.')
if answ_data['question'].pk in answercache:
a = answercache[answ_data['question'].pk]
if isinstance(answ_data['answer'], File):
if isinstance(answ_data.get('answer'), File):
a.file.save(answ_data['answer'].name, answ_data['answer'], save=False)
a.answer = 'file://' + a.file.name
elif a.answer.startswith('file://') and answ_data['answer'] == "file:keep":
@@ -173,7 +178,7 @@ class OrderPositionInfoPatchSerializer(serializers.ModelSerializer):
setattr(a, attr, value)
a.save()
else:
if isinstance(answ_data['answer'], File):
if isinstance(answ_data.get('answer'), File):
an = answ_data.pop('answer')
a = instance.answers.create(**answ_data, answer='')
a.file.save(os.path.basename(an.name), an, save=False)
@@ -196,7 +201,7 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
class Meta:
model = OrderPosition
fields = (
'item', 'variation', 'subevent', 'seat', 'price', 'tax_rule',
'item', 'variation', 'subevent', 'seat', 'price', 'tax_rule', 'valid_from', 'valid_until'
)
def __init__(self, *args, **kwargs):
@@ -262,6 +267,8 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
price = validated_data.get('price', instance.price)
seat = validated_data.get('seat', current_seat)
tax_rule = validated_data.get('tax_rule', instance.tax_rule)
valid_from = validated_data.get('valid_from', instance.valid_from)
valid_until = validated_data.get('valid_until', instance.valid_until)
change_item = None
if item != instance.item or variation != instance.variation:
@@ -288,6 +295,12 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
if tax_rule != instance.tax_rule:
ocm.change_tax_rule(instance, tax_rule)
if valid_from != instance.valid_from:
ocm.change_valid_from(instance, valid_from)
if valid_until != instance.valid_until:
ocm.change_valid_until(instance, valid_until)
if self.context.get('commit', True):
ocm.commit()
instance.refresh_from_db()
@@ -421,3 +434,7 @@ class OrderChangeOperationSerializer(serializers.Serializer):
seen_positions.add(d['fee'])
return data
class BlockNameSerializer(serializers.Serializer):
name = serializers.CharField(validators=[RegexValidator('^(admin|api:[a-zA-Z0-9._]+)$')])

View File

@@ -41,15 +41,21 @@ from pretix.base.models import (
from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.services.mail import SendMailException, mail
from pretix.base.settings import validate_organizer_settings
from pretix.helpers.urls import build_absolute_uri
from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger(__name__)
class OrganizerSerializer(I18nAwareModelSerializer):
public_url = serializers.SerializerMethodField('get_organizer_url', read_only=True)
def get_organizer_url(self, organizer):
return build_absolute_uri(organizer, 'presale:organizer.index')
class Meta:
model = Organizer
fields = ('name', 'slug')
fields = ('name', 'slug', 'public_url')
class SeatingPlanSerializer(I18nAwareModelSerializer):
@@ -79,6 +85,13 @@ class CustomerSerializer(I18nAwareModelSerializer):
validated_data['external_identifier'] = instance.external_identifier
return super().update(instance, validated_data)
def validate(self, data):
if data.get('name_parts') and not isinstance(data.get('name_parts'), dict):
raise ValidationError({'name_parts': ['Invalid data type']})
if data.get('name_parts') and '_scheme' not in data.get('name_parts'):
data['name_parts']['_scheme'] = self.context['request'].organizer.settings.name_scheme
return data
class CustomerCreateSerializer(CustomerSerializer):
send_email = serializers.BooleanField(default=False, required=False, allow_null=True)
@@ -220,7 +233,7 @@ class TeamInviteSerializer(serializers.ModelSerializer):
'user': self,
'organizer': self.context['organizer'].name,
'team': instance.team.name,
'url': build_absolute_uri('control:auth.invite', kwargs={
'url': build_global_uri('control:auth.invite', kwargs={
'token': instance.token
})
},

View File

@@ -55,7 +55,7 @@ class WebHookSerializer(I18nAwareModelSerializer):
class Meta:
model = WebHook
fields = ('id', 'enabled', 'target_url', 'all_events', 'limit_events', 'action_types')
fields = ('id', 'enabled', 'target_url', 'all_events', 'limit_events', 'action_types', 'comment')
def validate(self, data):
data = super().validate(data)

View File

@@ -81,6 +81,7 @@ event_router.register(r'orders', order.OrderViewSet)
event_router.register(r'orderpositions', order.OrderPositionViewSet)
event_router.register(r'invoices', order.InvoiceViewSet)
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets')
event_router.register(r'blockedsecrets', order.BlockedSecretViewSet, basename='blockedsecrets')
event_router.register(r'taxrules', event.TaxRuleViewSet)
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
event_router.register(r'checkinlists', checkin.CheckinListViewSet)

View File

@@ -24,10 +24,11 @@ from calendar import timegm
from django.db.models import Max
from django.http import HttpResponse
from django.utils.http import http_date, parse_http_date_safe
from rest_framework.filters import OrderingFilter
from pretix.api.pagination import TotalOrderingFilter
class RichOrderingFilter(OrderingFilter):
class RichOrderingFilter(TotalOrderingFilter):
def filter_queryset(self, request, queryset, view):
ordering = self.get_ordering(request, queryset, view)

View File

@@ -29,11 +29,11 @@ from django.utils.translation import gettext as _
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.filters import OrderingFilter
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
from rest_framework.response import Response
from rest_framework.serializers import as_serializer_error
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.cart import (
CartPositionCreateSerializer, CartPositionSerializer,
)
@@ -47,7 +47,7 @@ from pretix.base.services.locking import NoLockManager
class CartPositionViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = CartPositionSerializer
queryset = CartPosition.objects.none()
filter_backends = (OrderingFilter,)
filter_backends = (TotalOrderingFilter,)
ordering = ('datetime',)
ordering_fields = ('datetime', 'cart_id')
lookup_field = 'id'

View File

@@ -93,8 +93,10 @@ with scopes_disabled():
class CheckinListViewSet(viewsets.ModelViewSet):
serializer_class = CheckinListSerializer
queryset = CheckinList.objects.none()
filter_backends = (DjangoFilterBackend,)
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
filterset_class = CheckinListFilter
ordering = ('subevent__date_from', 'name', 'id')
ordering_fields = ('subevent__date_from', 'id', 'name',)
def _get_permission_name(self, request):
if request.path.endswith('/failed_checkins/'):
@@ -271,7 +273,13 @@ with scopes_disabled():
def check_rules_qs(self, queryset, name, value):
if not self.checkinlist.rules:
return queryset
return queryset.filter(SQLLogic(self.checkinlist).apply(self.checkinlist.rules))
return queryset.filter(
SQLLogic(self.checkinlist).apply(self.checkinlist.rules)
).filter(
Q(valid_from__isnull=True) | Q(valid_from__lte=now()),
Q(valid_until__isnull=True) | Q(valid_until__gte=now()),
blocked__isnull=True,
)
def _handle_file_upload(data, user, auth):
@@ -323,7 +331,13 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
if checkinlist.subevent:
list_q &= Q(subevent=checkinlist.subevent)
if not ignore_status:
list_q &= Q(order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if checkinlist.include_pending else [Order.STATUS_PAID])
if checkinlist.include_pending:
list_q &= Q(order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING])
else:
list_q &= Q(
Q(order__status=Order.STATUS_PAID) |
Q(order__status=Order.STATUS_PENDING, order__valid_if_pending=True)
)
if not checkinlist.all_products and not ignore_products:
list_q &= Q(item__in=checkinlist.limit_products.values_list('id', flat=True))
lists_qs.append(list_q)
@@ -580,7 +594,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'status': 'error',
'reason': Checkin.REASON_AMBIGUOUS,
'reason_explanation': None,
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'require_attention': op.require_checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=400)
@@ -625,7 +639,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
except RequiredQuestionsError as e:
return Response({
'status': 'incomplete',
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'require_attention': op.require_checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'questions': [
QuestionSerializer(q).data for q in e.questions
@@ -654,14 +668,14 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'status': 'error',
'reason': e.code,
'reason_explanation': e.reason,
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'require_attention': op.require_checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=400)
else:
return Response({
'status': 'ok',
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'require_attention': op.require_checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=201)
@@ -682,7 +696,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CheckinListOrderPositionSerializer
queryset = OrderPosition.all.none()
filter_backends = (ExtendedBackend, RichOrderingFilter)
ordering = (F('attendee_name_cached').asc(nulls_last=True), 'positionid')
ordering = (F('attendee_name_cached').asc(nulls_last=True), 'pk')
ordering_fields = (
'order__code', 'order__datetime', 'positionid', 'attendee_name',
'last_checked_in', 'order__email',

View File

@@ -36,8 +36,8 @@ from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets
from rest_framework.exceptions import PermissionDenied
from rest_framework.filters import OrderingFilter
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.discount import DiscountSerializer
from pretix.api.views import ConditionalListView
from pretix.base.models import CartPosition, Discount
@@ -52,7 +52,7 @@ with scopes_disabled():
class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = DiscountSerializer
queryset = Discount.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
filterset_class = DiscountFilter
ordering_fields = ('id', 'position')
ordering = ('position', 'id')

View File

@@ -39,11 +39,12 @@ from django.db.models import Prefetch, ProtectedError, Q
from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import filters, serializers, views, viewsets
from rest_framework import serializers, views, viewsets
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.response import Response
from pretix.api.auth.permission import EventCRUDPermission
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.event import (
CloneEventSerializer, DeviceEventSettingsSerializer, EventSerializer,
EventSettingsSerializer, SubEventSerializer, TaxRuleSerializer,
@@ -127,7 +128,7 @@ class EventViewSet(viewsets.ModelViewSet):
lookup_url_kwarg = 'event'
lookup_value_regex = '[^/]+'
permission_classes = (EventCRUDPermission,)
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('slug',)
ordering_fields = ('date_from', 'slug')
filterset_class = EventFilter
@@ -379,7 +380,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = SubEventSerializer
queryset = SubEvent.objects.none()
write_permission = 'can_change_event_settings'
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
filterset_class = SubEventFilter
ordering = ('date_from',)
ordering_fields = ('id', 'date_from', 'last_modified')

View File

@@ -41,9 +41,9 @@ 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.filters import OrderingFilter
from rest_framework.response import Response
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.item import (
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
ItemSerializer, ItemVariationSerializer, QuestionOptionSerializer,
@@ -75,7 +75,7 @@ with scopes_disabled():
class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = ItemSerializer
queryset = Item.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
filterset_class = ItemFilter
@@ -138,7 +138,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
class ItemVariationViewSet(viewsets.ModelViewSet):
serializer_class = ItemVariationSerializer
queryset = ItemVariation.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter,)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
ordering_fields = ('id', 'position')
ordering = ('id',)
permission = None
@@ -208,7 +208,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
class ItemBundleViewSet(viewsets.ModelViewSet):
serializer_class = ItemBundleSerializer
queryset = ItemBundle.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter,)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
ordering_fields = ('id',)
ordering = ('id',)
permission = None
@@ -260,7 +260,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
class ItemAddOnViewSet(viewsets.ModelViewSet):
serializer_class = ItemAddOnSerializer
queryset = ItemAddOn.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter,)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
ordering_fields = ('id', 'position')
ordering = ('id',)
permission = None
@@ -318,7 +318,7 @@ class ItemCategoryFilter(FilterSet):
class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = ItemCategorySerializer
queryset = ItemCategory.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
filterset_class = ItemCategoryFilter
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
@@ -373,7 +373,7 @@ with scopes_disabled():
class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = QuestionSerializer
queryset = Question.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
filterset_class = QuestionFilter
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
@@ -418,7 +418,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
class QuestionOptionViewSet(viewsets.ModelViewSet):
serializer_class = QuestionOptionSerializer
queryset = QuestionOption.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter,)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
ordering_fields = ('id', 'position')
ordering = ('position',)
permission = None
@@ -475,7 +475,7 @@ with scopes_disabled():
class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = QuotaSerializer
queryset = Quota.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter,)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
filterset_class = QuotaFilter
ordering_fields = ('id', 'size')
ordering = ('id',)

View File

@@ -34,6 +34,7 @@ from oauth2_provider.views import (
from pretix.api.models import OAuthApplication
from pretix.base.models import Organizer
from pretix.control.views.user import RecentAuthenticationRequiredMixin
logger = logging.getLogger(__name__)
@@ -54,7 +55,7 @@ class OAuthAllowForm(AllowForm):
del self.fields['organizers']
class AuthorizationView(BaseAuthorizationView):
class AuthorizationView(RecentAuthenticationRequiredMixin, BaseAuthorizationView):
template_name = "pretixcontrol/auth/oauth_authorization.html"
form_class = OAuthAllowForm

View File

@@ -43,21 +43,21 @@ from rest_framework.decorators import action
from rest_framework.exceptions import (
APIException, NotFound, PermissionDenied, ValidationError,
)
from rest_framework.filters import OrderingFilter
from rest_framework.mixins import CreateModelMixin
from rest_framework.response import Response
from pretix.api.models import OAuthAccessToken
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.order import (
InvoiceSerializer, OrderCreateSerializer, OrderPaymentCreateSerializer,
OrderPaymentSerializer, OrderPositionSerializer,
OrderRefundCreateSerializer, OrderRefundSerializer, OrderSerializer,
PriceCalcSerializer, RevokedTicketSecretSerializer,
SimulatedOrderSerializer,
BlockedTicketSecretSerializer, InvoiceSerializer, OrderCreateSerializer,
OrderPaymentCreateSerializer, OrderPaymentSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer,
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer,
RevokedTicketSecretSerializer, SimulatedOrderSerializer,
)
from pretix.api.serializers.orderchange import (
OrderChangeOperationSerializer, OrderFeeChangeSerializer,
OrderPositionChangeSerializer,
BlockNameSerializer, OrderChangeOperationSerializer,
OrderFeeChangeSerializer, OrderPositionChangeSerializer,
OrderPositionCreateForExistingOrderSerializer,
OrderPositionInfoPatchSerializer,
)
@@ -70,7 +70,9 @@ from pretix.base.models import (
OrderRefund, Quota, SubEvent, SubEventMetaValue, TaxRule, TeamAPIToken,
generate_secret,
)
from pretix.base.models.orders import QuestionAnswer, RevokedTicketSecret
from pretix.base.models.orders import (
BlockedTicketSecret, QuestionAnswer, RevokedTicketSecret,
)
from pretix.base.payment import PaymentException
from pretix.base.pdf import get_images
from pretix.base.secrets import assign_ticket_secret
@@ -179,7 +181,7 @@ with scopes_disabled():
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
queryset = Order.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('datetime',)
ordering_fields = ('datetime', 'code', 'status', 'last_modified')
filterset_class = OrderFilter
@@ -287,7 +289,7 @@ class OrderViewSet(viewsets.ModelViewSet):
if order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
raise PermissionDenied("Downloads are not available for canceled or expired orders.")
if order.status == Order.STATUS_PENDING and not request.event.settings.ticket_download_pending:
if order.status == Order.STATUS_PENDING and not (order.valid_if_pending or request.event.settings.ticket_download_pending):
raise PermissionDenied("Downloads are not available for pending orders.")
ct = CachedCombinedTicket.objects.filter(
@@ -763,6 +765,16 @@ class OrderViewSet(viewsets.ModelViewSet):
}
)
if 'valid_if_pending' in self.request.data and serializer.instance.valid_if_pending != self.request.data.get('valid_if_pending'):
serializer.instance.log_action(
'pretix.event.order.valid_if_pending',
user=self.request.user,
auth=self.request.auth,
data={
'new_value': self.request.data.get('valid_if_pending')
}
)
if 'email' in self.request.data and serializer.instance.email != self.request.data.get('email'):
serializer.instance.email_known_to_work = False
serializer.instance.log_action(
@@ -1183,7 +1195,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
if pos.order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
raise PermissionDenied("Downloads are not available for canceled or expired orders.")
if pos.order.status == Order.STATUS_PENDING and not request.event.settings.ticket_download_pending:
if pos.order.status == Order.STATUS_PENDING and not (pos.order.valid_if_pending or request.event.settings.ticket_download_pending):
raise PermissionDenied("Downloads are not available for pending orders.")
if not pos.generate_ticket:
raise PermissionDenied("Downloads are not enabled for this product.")
@@ -1225,6 +1237,54 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
raise ValidationError(str(e))
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def add_block(self, request, **kwargs):
serializer = BlockNameSerializer(
data=request.data,
context=self.get_serializer_context(),
)
serializer.is_valid(raise_exception=True)
instance = self.get_object()
try:
ocm = OrderChangeManager(
instance.order,
user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth,
notify=False,
reissue_invoice=False,
)
ocm.add_block(instance, serializer.validated_data['name'])
ocm.commit()
except OrderError as e:
raise ValidationError(str(e))
except Quota.QuotaExceededException as e:
raise ValidationError(str(e))
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def remove_block(self, request, **kwargs):
serializer = BlockNameSerializer(
data=request.data,
context=self.get_serializer_context(),
)
serializer.is_valid(raise_exception=True)
instance = self.get_object()
try:
ocm = OrderChangeManager(
instance.order,
user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth,
notify=False,
reissue_invoice=False,
)
ocm.remove_block(instance, serializer.validated_data['name'])
ocm.commit()
except OrderError as e:
raise ValidationError(str(e))
except Quota.QuotaExceededException as e:
raise ValidationError(str(e))
return self.retrieve(request, [], **kwargs)
def perform_destroy(self, instance):
try:
ocm = OrderChangeManager(
@@ -1479,21 +1539,27 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
source=OrderRefund.REFUND_SOURCE_ADMIN,
state=OrderRefund.REFUND_STATE_CREATED,
amount=amount,
provider=payment.provider
provider=payment.provider,
info='{}',
)
payment.order.log_action('pretix.event.order.refund.created', {
'local_id': r.local_id,
'provider': r.provider,
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
try:
r.payment_provider.execute_refund(r)
except PaymentException as e:
r.state = OrderRefund.REFUND_STATE_FAILED
r.save()
payment.order.log_action('pretix.event.order.refund.failed', {
'local_id': r.local_id,
'provider': r.provider,
'error': str(e)
})
return Response({'detail': 'External error: {}'.format(str(e))},
status=status.HTTP_400_BAD_REQUEST)
else:
payment.order.log_action('pretix.event.order.refund.created', {
'local_id': r.local_id,
'provider': r.provider,
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
if payment.order.pending_sum > 0:
if mark_refunded:
mark_order_refunded(payment.order,
@@ -1683,7 +1749,7 @@ class RetryException(APIException):
class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = InvoiceSerializer
queryset = Invoice.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('nr',)
ordering_fields = ('nr', 'date')
filterset_class = InvoiceFilter
@@ -1776,7 +1842,7 @@ with scopes_disabled():
class RevokedSecretViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = RevokedTicketSecretSerializer
queryset = RevokedTicketSecret.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('-created',)
ordering_fields = ('created', 'secret')
filterset_class = RevokedSecretFilter
@@ -1785,3 +1851,25 @@ class RevokedSecretViewSet(viewsets.ReadOnlyModelViewSet):
def get_queryset(self):
return RevokedTicketSecret.objects.filter(event=self.request.event)
with scopes_disabled():
class BlockedSecretFilter(FilterSet):
updated_since = django_filters.IsoDateTimeFilter(field_name='updated', lookup_expr='gte')
class Meta:
model = BlockedTicketSecret
fields = ['blocked']
class BlockedSecretViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = BlockedTicketSecretSerializer
queryset = BlockedTicketSecret.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('-updated', '-pk')
filterset_class = BlockedSecretFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
def get_queryset(self):
return BlockedTicketSecret.objects.filter(event=self.request.event)

View File

@@ -28,9 +28,7 @@ from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import (
filters, mixins, serializers, status, views, viewsets,
)
from rest_framework import mixins, serializers, status, views, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
@@ -38,6 +36,7 @@ from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from pretix.api.models import OAuthAccessToken
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.organizer import (
CustomerCreateSerializer, CustomerSerializer, DeviceSerializer,
GiftCardSerializer, GiftCardTransactionSerializer, MembershipSerializer,
@@ -51,6 +50,7 @@ from pretix.base.models import (
User,
)
from pretix.base.settings import SETTINGS_AFFECTING_CSS
from pretix.helpers import OF_SELF
from pretix.helpers.dicts import merge_dicts
from pretix.presale.style import regenerate_organizer_css
@@ -61,7 +61,7 @@ class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
lookup_field = 'slug'
lookup_url_kwarg = 'organizer'
lookup_value_regex = '[^/]+'
filter_backends = (filters.OrderingFilter,)
filter_backends = (TotalOrderingFilter,)
ordering = ('slug',)
ordering_fields = ('name', 'slug')
@@ -178,7 +178,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
def perform_update(self, serializer):
if 'include_accepted' in self.request.GET:
raise PermissionDenied("Accepted gift cards cannot be updated, use transact instead.")
GiftCard.objects.select_for_update().get(pk=self.get_object().pk)
GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
old_value = serializer.instance.value
value = serializer.validated_data.pop('value')
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
@@ -196,7 +196,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
@action(detail=True, methods=["POST"])
@transaction.atomic()
def transact(self, request, **kwargs):
gc = GiftCard.objects.select_for_update().get(pk=self.get_object().pk)
gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
value = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
request.data.get('value')
)

View File

@@ -21,6 +21,7 @@
#
import datetime
from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.timezone import now
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
from rest_framework.authentication import SessionAuthentication
@@ -33,6 +34,9 @@ from pretix.api.auth.device import DeviceTokenAuthentication
from pretix.api.auth.permission import AnyAuthenticatedClientPermission
from pretix.api.auth.token import TeamTokenAuthentication
from pretix.base.models import CachedFile
from pretix.helpers.images import (
IMAGE_TYPES, validate_uploaded_file_for_valid_image,
)
ALLOWED_TYPES = {
'image/gif': {'.gif'},
@@ -61,6 +65,13 @@ class UploadView(APIView):
name=file_obj.name,
type=content_type
))
if content_type in IMAGE_TYPES:
try:
validate_uploaded_file_for_valid_image(file_obj)
except DjangoValidationError as e:
raise ValidationError(e.message)
cf = CachedFile.objects.create(
expires=now() + datetime.timedelta(days=1),
date=now(),

View File

@@ -31,9 +31,9 @@ from django_scopes import scopes_disabled
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.voucher import VoucherSerializer
from pretix.base.models import Voucher
@@ -59,7 +59,7 @@ with scopes_disabled():
class VoucherViewSet(viewsets.ModelViewSet):
serializer_class = VoucherSerializer
queryset = Voucher.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('id',)
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
filterset_class = VoucherFilter

View File

@@ -25,9 +25,9 @@ from django_scopes import scopes_disabled
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.waitinglist import WaitingListSerializer
from pretix.base.models import WaitingListEntry
from pretix.base.models.waitinglist import WaitingListException
@@ -47,8 +47,8 @@ with scopes_disabled():
class WaitingListViewSet(viewsets.ModelViewSet):
serializer_class = WaitingListSerializer
queryset = WaitingListEntry.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('created',)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('created', 'pk',)
ordering_fields = ('id', 'created', 'email', 'item')
filterset_class = WaitingListFilter
permission = 'can_view_orders'

View File

@@ -42,6 +42,7 @@ from pretix.base.models import LogEntry
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
logger = logging.getLogger(__name__)
_ALL_EVENTS = None
@@ -95,7 +96,7 @@ def get_all_webhook_events():
return types
class ParametrizedOrderWebhookEvent(WebhookEvent):
class ParametrizedWebhookEvent(WebhookEvent):
def __init__(self, action_type, verbose_name):
self._action_type = action_type
self._verbose_name = verbose_name
@@ -109,6 +110,8 @@ class ParametrizedOrderWebhookEvent(WebhookEvent):
def verbose_name(self):
return self._verbose_name
class ParametrizedOrderWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
order = logentry.content_object
if not order:
@@ -123,19 +126,7 @@ class ParametrizedOrderWebhookEvent(WebhookEvent):
}
class ParametrizedEventWebhookEvent(WebhookEvent):
def __init__(self, action_type, verbose_name):
self._action_type = action_type
self._verbose_name = verbose_name
super().__init__()
@property
def action_type(self):
return self._action_type
@property
def verbose_name(self):
return self._verbose_name
class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
if logentry.action_type == 'pretix.event.deleted':
@@ -159,19 +150,7 @@ class ParametrizedEventWebhookEvent(WebhookEvent):
}
class ParametrizedSubEventWebhookEvent(WebhookEvent):
def __init__(self, action_type, verbose_name):
self._action_type = action_type
self._verbose_name = verbose_name
super().__init__()
@property
def action_type(self):
return self._action_type
@property
def verbose_name(self):
return self._verbose_name
class ParametrizedSubEventWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
# do not use content_object, this is also called in deletion
@@ -184,6 +163,19 @@ class ParametrizedSubEventWebhookEvent(WebhookEvent):
}
class ParametrizedItemWebhookEvent(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,
'item': logentry.object_id,
'action': logentry.action_type,
}
class ParametrizedOrderPositionWebhookEvent(ParametrizedOrderWebhookEvent):
def build_payload(self, logentry: LogEntry):
@@ -304,6 +296,11 @@ def register_default_webhook_events(sender, **kwargs):
'pretix.subevent.deleted',
pgettext_lazy('subevent', 'Event series date deleted'),
),
ParametrizedItemWebhookEvent(
'pretix.event.item.*',
_('Product changed (including product added or deleted and including changes to nested objects like '
'variations or bundles)'),
),
ParametrizedEventWebhookEvent(
'pretix.event.live.activated',
_('Shop taken live'),
@@ -502,7 +499,8 @@ def manually_retry_all_calls(webhook_id: int):
webhook = WebHook.objects.get(id=webhook_id)
with scope(organizer=webhook.organizer), transaction.atomic():
for whcr in webhook.retries.select_for_update(
skip_locked=connection.features.has_select_for_update_skip_locked
skip_locked=connection.features.has_select_for_update_skip_locked,
of=OF_SELF
):
send_webhook.apply_async(
args=(whcr.logentry_id, whcr.action_type, whcr.webhook_id, whcr.retry_count),
@@ -515,7 +513,8 @@ def manually_retry_all_calls(webhook_id: int):
def schedule_webhook_retries_on_celery(sender, **kwargs):
with transaction.atomic():
for whcr in WebHookCallRetry.objects.select_for_update(
skip_locked=connection.features.has_select_for_update_skip_locked
skip_locked=connection.features.has_select_for_update_skip_locked,
of=OF_SELF
).filter(retry_not_before__lt=now()):
send_webhook.apply_async(
args=(whcr.logentry_id, whcr.action_type, whcr.webhook_id, whcr.retry_count),

View File

@@ -42,7 +42,6 @@ from localflavor.fr.forms import FRZipCodeField
from localflavor.gb.forms import GBPostcodeField
from localflavor.gr.forms import GRPostalCodeField
from localflavor.hr.forms import HRPostalCodeField
from localflavor.id_.forms import IDPostCodeField
from localflavor.ie.forms import EircodeField
from localflavor.il.forms import ILPostalCodeField
from localflavor.in_.forms import INZipCodeField
@@ -80,8 +79,8 @@ COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED = {
# We don't presume this for countries we don't have knowledge about, there are countries in the
# world e.g. without zipcodes
'AR', 'AT', 'AU', 'BE', 'BR', 'CA', 'CH', 'CN', 'CU', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR',
'GB', 'GR', 'HR', 'ID', 'IE', 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'LT', 'LV', 'MA', 'MT', 'MX',
'NL', 'NO', 'NZ', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SG', 'SI', 'SK', 'TR', 'UA', 'US', 'ZA',
'GB', 'GR', 'HR', 'IE', 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'LT', 'LV', 'MA', 'MT', 'MX', 'NL',
'NO', 'NZ', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SG', 'SI', 'SK', 'TR', 'UA', 'US', 'ZA',
}
@@ -167,7 +166,6 @@ _zip_code_fields = {
'GB': GBPostcodeField,
'GR': GRPostalCodeField,
'HR': HRPostalCodeField,
'ID': IDPostCodeField,
'IE': EircodeField,
'IL': ILPostalCodeField,
'IN': INZipCodeField,

View File

@@ -46,7 +46,7 @@ class PretixBaseConfig(AppConfig):
from . import invoice # NOQA
from . import notifications # NOQA
from . import email # NOQA
from .services import auth, checkin, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
from .services import auth, checkin, currencies, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
from .models import _transactions # NOQA
from django.conf import settings

View File

@@ -394,6 +394,11 @@ def base_placeholders(sender, **kwargs):
lambda event_or_subevent, refund_amount: LazyCurrencyNumber(refund_amount, event_or_subevent.currency),
lambda event_or_subevent: LazyCurrencyNumber(Decimal('42.23'), event_or_subevent.currency)
),
SimpleFunctionalMailTextPlaceholder(
'pending_sum', ['event', 'pending_sum'],
lambda event, pending_sum: LazyCurrencyNumber(pending_sum, event.currency),
lambda event: LazyCurrencyNumber(Decimal('42.23'), event.currency)
),
SimpleFunctionalMailTextPlaceholder(
'total_with_currency', ['event', 'order'], lambda event, order: LazyCurrencyNumber(order.total,
event.currency),
@@ -520,20 +525,20 @@ def base_placeholders(sender, **kwargs):
lambda event: (event if not event.has_subevents or not event.subevents.exists() else event.subevents.first()).get_date_from_display()
),
SimpleFunctionalMailTextPlaceholder(
'url_remove', ['waiting_list_entry', 'event'],
lambda waiting_list_entry, event: build_absolute_uri(
'url_remove', ['waiting_list_voucher', 'event'],
lambda waiting_list_voucher, event: build_absolute_uri(
event, 'presale:event.waitinglist.remove'
) + '?voucher=' + waiting_list_entry.voucher.code,
) + '?voucher=' + waiting_list_voucher.code,
lambda event: build_absolute_uri(
event,
'presale:event.waitinglist.remove',
) + '?voucher=68CYU2H6ZTP3WLK5',
),
SimpleFunctionalMailTextPlaceholder(
'url', ['waiting_list_entry', 'event'],
lambda waiting_list_entry, event: build_absolute_uri(
'url', ['waiting_list_voucher', 'event'],
lambda waiting_list_voucher, event: build_absolute_uri(
event, 'presale:event.redeem'
) + '?voucher=' + waiting_list_entry.voucher.code,
) + '?voucher=' + waiting_list_voucher.code,
lambda event: build_absolute_uri(
event,
'presale:event.redeem',
@@ -588,7 +593,7 @@ def base_placeholders(sender, **kwargs):
_('Sample Admission Ticket')
),
SimpleFunctionalMailTextPlaceholder(
'code', ['waiting_list_entry'], lambda waiting_list_entry: waiting_list_entry.voucher.code,
'code', ['waiting_list_voucher'], lambda waiting_list_voucher: waiting_list_voucher.code,
'68CYU2H6ZTP3WLK5'
),
SimpleFunctionalMailTextPlaceholder(

View File

@@ -36,7 +36,7 @@ import io
import tempfile
from collections import OrderedDict, namedtuple
from decimal import Decimal
from typing import Tuple
from typing import Optional, Tuple
import pytz
from defusedcsv import csv
@@ -84,6 +84,27 @@ class BaseExporter:
"""
raise NotImplementedError() # NOQA
@property
def description(self) -> str:
"""
A description for this exporter.
"""
return ""
@property
def category(self) -> Optional[str]:
"""
A category name for this exporter, or ``None``.
"""
return None
@property
def featured(self) -> bool:
"""
If ``True``, this exporter will be highlighted.
"""
return False
@property
def identifier(self) -> str:
"""

View File

@@ -39,7 +39,7 @@ from zipfile import ZipFile
from django import forms
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.models import QuestionAnswer
@@ -49,7 +49,10 @@ from ..signals import register_data_exporters
class AnswerFilesExporter(BaseExporter):
identifier = 'answerfiles'
verbose_name = _('Answers to file upload questions')
verbose_name = _('Question answer file uploads')
category = pgettext_lazy('export_category', 'Order data')
description = _('Download a ZIP file including all files that have been uploaded by your customers while creating '
'an order.')
@property
def export_form_fields(self):

View File

@@ -36,7 +36,7 @@ from collections import OrderedDict
from django.dispatch import receiver
from django.utils.timezone import get_current_timezone
from django.utils.translation import gettext as _, gettext_lazy
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
from pretix.base.settings import PERSON_NAME_SCHEMES
@@ -48,6 +48,8 @@ class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'customerlist'
verbose_name = gettext_lazy('Customer accounts')
organizer_required_permission = 'can_manage_customers'
category = pgettext_lazy('export_category', 'Customer accounts')
description = gettext_lazy('Download a spreadsheet of all currently registered customer accounts.')
@property
def additional_form_fields(self):

View File

@@ -23,22 +23,24 @@ import json
from collections import OrderedDict
from decimal import Decimal
import dateutil
from django import forms
from django.core.serializers.json import DjangoJSONEncoder
from django.dispatch import receiver
from django.utils.translation import gettext, gettext_lazy
from django.utils.timezone import now
from django.utils.translation import gettext, gettext_lazy, pgettext_lazy
from pretix.base.i18n import language
from pretix.base.models import Invoice, OrderPayment
from ..exporter import BaseExporter
from ..signals import register_data_exporters
from ..timeframes import DateFrameField, resolve_timeframe_to_dates_inclusive
class DekodiNREIExporter(BaseExporter):
identifier = 'dekodi_nrei'
verbose_name = 'dekodi NREI (JSON)'
category = pgettext_lazy('export_category', 'Invoices')
description = gettext_lazy("Download invoices in a format that can be used by the dekodi NREI conversion software.")
# Specification: http://manuals.dekodi.de/nexuspub/schnittstellenbuch/
@@ -113,7 +115,7 @@ class DekodiNREIExporter(BaseExporter):
'PTNo14': p.info_data.get('reference') or '',
'PTNo15': p.full_id or '',
})
elif p.provider.startswith('stripe'):
elif p.provider and p.provider.startswith('stripe'):
src = p.info_data.get("source", p.info_data)
payments.append({
'PTID': '81',
@@ -192,17 +194,12 @@ class DekodiNREIExporter(BaseExporter):
def render(self, form_data):
qs = self.event.invoices.select_related('order').prefetch_related('lines', 'lines__subevent')
if form_data.get('date_from'):
date_value = form_data.get('date_from')
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
qs = qs.filter(date__gte=date_value)
if form_data.get('date_to'):
date_value = form_data.get('date_to')
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
qs = qs.filter(date__lte=date_value)
if form_data.get('date_range'):
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone)
if d_start:
qs = qs.filter(date__gte=d_start)
if d_end:
qs = qs.filter(date__lte=d_end)
jo = {
'Format': 'NREI',
@@ -218,22 +215,14 @@ class DekodiNREIExporter(BaseExporter):
def export_form_fields(self):
return OrderedDict(
[
('date_from',
forms.DateField(
label=gettext_lazy('Start date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
('date_range',
DateFrameField(
label=gettext_lazy('Date range'),
include_future_frames=False,
required=False,
help_text=gettext_lazy('Only include invoices issued on or after this date. Note that the invoice date does '
help_text=gettext_lazy('Only include invoices issued in this time frame. Note that the invoice date does '
'not always correspond to the order or payment date.')
)),
('date_to',
forms.DateField(
label=gettext_lazy('End date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=gettext_lazy('Only include invoices issued on or before this date. Note that the invoice date '
'does not always correspond to the order or payment date.')
)),
]
)

View File

@@ -35,7 +35,7 @@
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from ...control.forms.filter import get_all_payment_providers
from ..exporter import ListExporter
@@ -45,6 +45,8 @@ from ..signals import register_multievent_data_exporters
class EventDataExporter(ListExporter):
identifier = 'eventdata'
verbose_name = _('Event data')
category = pgettext_lazy('export_category', 'Event data')
description = _('Download a spreadsheet with information on all events in this organizer account.')
@cached_property
def providers(self):

View File

@@ -38,13 +38,15 @@ from collections import OrderedDict
from decimal import Decimal
from zipfile import ZipFile
import dateutil.parser
from django import forms
from django.db.models import CharField, Exists, F, OuterRef, Q, Subquery, Sum
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.translation import gettext, gettext_lazy as _, pgettext
from django.utils.timezone import now
from django.utils.translation import (
gettext, gettext_lazy as _, pgettext, pgettext_lazy,
)
from pretix.base.models import Invoice, InvoiceLine, OrderPayment
@@ -57,30 +59,24 @@ from ..services.invoices import invoice_pdf_task
from ..signals import (
register_data_exporters, register_multievent_data_exporters,
)
from ..timeframes import DateFrameField, resolve_timeframe_to_dates_inclusive
class InvoiceExporterMixin:
category = pgettext_lazy('export_category', 'Invoices')
@property
def invoice_exporter_form_fields(self):
return OrderedDict(
[
('date_from',
forms.DateField(
label=_('Start date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
('date_range',
DateFrameField(
label=_('Date range'),
include_future_frames=False,
required=False,
help_text=_('Only include invoices issued on or after this date. Note that the invoice date does '
help_text=_('Only include invoices issued in this time frame. Note that the invoice date does '
'not always correspond to the order or payment date.')
)),
('date_to',
forms.DateField(
label=_('End date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=_('Only include invoices issued on or before this date. Note that the invoice date '
'does not always correspond to the order or payment date.')
)),
('payment_provider',
forms.ChoiceField(
label=_('Payment provider'),
@@ -112,16 +108,12 @@ class InvoiceExporterMixin:
)
)
qs = qs.filter(has_payment_with_provider=1)
if form_data.get('date_from'):
date_value = form_data.get('date_from')
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
qs = qs.filter(date__gte=date_value)
if form_data.get('date_to'):
date_value = form_data.get('date_to')
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
qs = qs.filter(date__lte=date_value)
if form_data.get('date_range'):
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone)
if d_start:
qs = qs.filter(date__gte=d_start)
if d_end:
qs = qs.filter(date__lte=d_end)
return qs
@@ -129,6 +121,7 @@ class InvoiceExporterMixin:
class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
identifier = 'invoices'
verbose_name = _('All invoices')
description = _('Download all invoices created by the system as a ZIP file of PDF files.')
def render(self, form_data: dict, output_file=None):
qs = self.invoices_queryset(form_data).filter(shredded=False)
@@ -180,6 +173,10 @@ class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
identifier = 'invoicedata'
verbose_name = _('Invoice data')
description = _('Download a spreadsheet with the data of all invoices created by the system. The spreadsheet '
'includes two sheets, one with a line for every invoice, and one with a line for every position of '
'every invoice.')
featured = True
@property
def additional_form_fields(self):

View File

@@ -22,7 +22,7 @@
from django.db.models import Prefetch
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from openpyxl.styles import Alignment
from openpyxl.utils import get_column_letter
@@ -48,6 +48,8 @@ def _min(a1, a2):
class ItemDataExporter(ListExporter):
identifier = 'itemdata'
verbose_name = _('Product data')
category = pgettext_lazy('export_category', 'Product data')
description = _('Download a spreadsheet with details about all products and variations.')
def iterate_list(self, form_data):
locales = self.event.settings.locales
@@ -73,6 +75,7 @@ class ItemDataExporter(ListExporter):
_("Free price input"),
_("Sales tax"),
_("Is an admission ticket"),
_("Personalized ticket"),
_("Generate tickets"),
_("Waiting list"),
_("Available from"),
@@ -144,6 +147,7 @@ class ItemDataExporter(ListExporter):
_("Yes") if i.free_price else "",
str(i.tax_rule) if i.tax_rule else "",
_("Yes") if i.admission else "",
_("Yes") if i.personalized else "",
_("Yes") if i.generate_tickets else (_("Default") if i.generate_tickets is None else ""),
_("Yes") if i.allow_waitinglist else "",
date_format(_max(i.available_from, v.available_from).astimezone(self.timezone),
@@ -187,6 +191,7 @@ class ItemDataExporter(ListExporter):
_("Yes") if i.free_price else "",
str(i.tax_rule) if i.tax_rule else "",
_("Yes") if i.admission else "",
_("Yes") if i.personalized else "",
_("Yes") if i.generate_tickets else (_("Default") if i.generate_tickets is None else ""),
_("Yes") if i.allow_waitinglist else "",
date_format(i.available_from.astimezone(self.timezone),

View File

@@ -38,6 +38,8 @@ from decimal import Decimal
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Prefetch
from django.dispatch import receiver
from django.utils.functional import lazy
from django.utils.translation import gettext, gettext_lazy, pgettext_lazy
from ..exporter import BaseExporter
from ..models import ItemMetaValue, ItemVariation, ItemVariationMetaValue
@@ -46,7 +48,10 @@ from ..signals import register_data_exporters
class JSONExporter(BaseExporter):
identifier = 'json'
verbose_name = 'Order data (JSON)'
verbose_name = lazy(lambda *args: gettext('Order data') + ' (JSON)', str)()
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a structured JSON representation of all orders. This might be useful for the '
'import in third-party systems.')
def render(self, form_data):
jo = {
@@ -78,6 +83,7 @@ class JSONExporter(BaseExporter):
'tax_rate': item.tax_rule.rate if item.tax_rule else Decimal('0.00'),
'tax_name': str(item.tax_rule.name) if item.tax_rule else None,
'admission': item.admission,
'personalized': item.personalized,
'active': item.active,
'sales_channels': item.sales_channels,
'description': str(item.description),
@@ -103,6 +109,8 @@ class JSONExporter(BaseExporter):
'name': str(variation),
'description': str(variation.description),
'position': variation.position,
'checkin_attention': variation.checkin_attention,
'require_approval': variation.require_approval,
'require_membership': variation.require_membership,
'sales_channels': variation.sales_channels,
'available_from': variation.available_from,
@@ -187,6 +195,9 @@ class JSONExporter(BaseExporter):
'state': position.state,
'secret': position.secret,
'addon_to': position.addon_to_id,
'valid_from': position.valid_from,
'valid_until': position.valid_until,
'blocked': position.blocked,
'answers': [
{
'question': answer.question_id,

View File

@@ -36,7 +36,7 @@ from collections import OrderedDict
from django import forms
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.models import OrderPosition
@@ -50,6 +50,8 @@ from ..signals import (
class MailExporter(BaseExporter):
identifier = 'mailaddrs'
verbose_name = _('Email addresses (text file)')
category = pgettext_lazy('export_category', 'Order data')
description = _("Download a text file with all email addresses collected either from buyers or from ticket holders.")
def render(self, form_data: dict):
qs = Order.objects.filter(event__in=self.events, status__in=form_data['status']).prefetch_related('event')

View File

@@ -33,10 +33,8 @@
# License for the specific language governing permissions and limitations under the License.
from collections import OrderedDict
from datetime import date, datetime, time
from decimal import Decimal
import dateutil
import pytz
from django import forms
from django.db.models import (
@@ -45,9 +43,12 @@ from django.db.models import (
)
from django.db.models.functions import Coalesce
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext as _, gettext_lazy, pgettext
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import (
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
)
from pretix.base.models import (
GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order,
@@ -63,14 +64,24 @@ from ...helpers.iter import chunked_iterable
from ..exporter import (
ListExporter, MultiSheetListExporter, OrganizerLevelExportMixin,
)
from ..forms.widgets import SplitDateTimePickerWidget
from ..signals import (
register_data_exporters, register_multievent_data_exporters,
)
from ..timeframes import (
DateFrameField,
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
)
class OrderListExporter(MultiSheetListExporter):
identifier = 'orderlist'
verbose_name = gettext_lazy('Order data')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all orders. The spreadsheet will include three sheets, one '
'with a line for every order, one with a line for every order position, and one with '
'a line for every additional fee charged in an order.')
featured = True
@cached_property
def providers(self):
@@ -105,41 +116,25 @@ class OrderListExporter(MultiSheetListExporter):
initial=False,
required=False
)),
('date_from',
forms.DateField(
label=_('Start date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
('date_range',
DateFrameField(
label=_('Date range'),
include_future_frames=False,
required=False,
help_text=_('Only include orders created on or after this date.')
help_text=_('Only include orders created within this date range.')
)),
('date_to',
forms.DateField(
label=_('End date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
('event_date_range',
DateFrameField(
label=_('Event date'),
include_future_frames=True,
required=False,
help_text=_('Only include orders created on or before this date.')
)),
('event_date_from',
forms.DateField(
label=_('Start event date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=_('Only include orders including at least one ticket for a date on or after this date. '
'Will also include other dates in case of mixed orders!')
)),
('event_date_to',
forms.DateField(
label=_('End event date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=_('Only include orders including at least one ticket for a date on or before this date. '
help_text=_('Only include orders including at least one ticket for a date in this range. '
'Will also include other dates in case of mixed orders!')
)),
]
d = OrderedDict(d)
if not self.is_multievent and not self.event.has_subevents:
del d['event_date_from']
del d['event_date_to']
del d['event_date_range']
return d
def _get_all_payment_methods(self, qs):
@@ -182,45 +177,27 @@ class OrderListExporter(MultiSheetListExporter):
annotations = {}
filters = {}
if form_data.get('date_from'):
date_value = form_data.get('date_from')
if not isinstance(date_value, date):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(0, 0, 0)), self.timezone)
if form_data.get('date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['date_range'], self.timezone)
if dt_start:
filters[f'{rel}datetime__gte'] = dt_start
if dt_end:
filters[f'{rel}datetime__lt'] = dt_end
filters[f'{rel}datetime__gte'] = datetime_value
if form_data.get('date_to'):
date_value = form_data.get('date_to')
if not isinstance(date_value, date):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(23, 59, 59, 999999)), self.timezone)
filters[f'{rel}datetime__lte'] = datetime_value
if form_data.get('event_date_from'):
date_value = form_data.get('event_date_from')
if not isinstance(date_value, date):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(0, 0, 0)), self.timezone)
annotations['event_date_max'] = Case(
When(**{f'{rel}event__has_subevents': True}, then=Max(f'{rel}all_positions__subevent__date_from')),
default=F(f'{rel}event__date_from'),
)
filters['event_date_max__gte'] = datetime_value
if form_data.get('event_date_to'):
date_value = form_data.get('event_date_to')
if not isinstance(date_value, date):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(23, 59, 59, 999999)), self.timezone)
annotations['event_date_min'] = Case(
When(**{f'{rel}event__has_subevents': True}, then=Min(f'{rel}all_positions__subevent__date_from')),
default=F(f'{rel}event__date_from'),
)
filters['event_date_min__lte'] = datetime_value
if form_data.get('event_date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['event_date_range'], self.timezone)
if dt_start:
annotations['event_date_max'] = Case(
When(**{f'{rel}event__has_subevents': True}, then=Max(f'{rel}all_positions__subevent__date_from')),
default=F(f'{rel}event__date_from'),
)
filters['event_date_max__gte'] = dt_start
if dt_end:
annotations['event_date_min'] = Case(
When(**{f'{rel}event__has_subevents': True}, then=Min(f'{rel}all_positions__subevent__date_from')),
default=F(f'{rel}event__date_from'),
)
filters['event_date_min__lt'] = dt_end
if filters:
return qs.annotate(**annotations).filter(**filters)
@@ -593,6 +570,9 @@ class OrderListExporter(MultiSheetListExporter):
_('Seat zone'),
_('Seat row'),
_('Seat number'),
_('Blocked'),
_('Valid from'),
_('Valid until'),
_('Order comment'),
_('Follow-up date'),
]
@@ -706,6 +686,11 @@ class OrderListExporter(MultiSheetListExporter):
else:
row += ['', '', '', '', '']
row += [
_('Yes') if op.blocked else '',
date_format(op.valid_from, 'SHORT_DATETIME_FORMAT') if op.valid_from else '',
date_format(op.valid_until, 'SHORT_DATETIME_FORMAT') if op.valid_until else '',
]
row.append(order.comment)
row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "")
acache = {}
@@ -776,12 +761,28 @@ class OrderListExporter(MultiSheetListExporter):
class PaymentListExporter(ListExporter):
identifier = 'paymentlist'
verbose_name = gettext_lazy('Order payments and refunds')
verbose_name = gettext_lazy('Payments and refunds')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all payments or refunds of every order.')
featured = True
@property
def additional_form_fields(self):
return OrderedDict(
[
('end_date_range',
DateFrameField(
label=_('Date range (payment date)'),
include_future_frames=False,
required=False,
help_text=_('Note that using this will exclude any non-confirmed payments or non-completed refunds.'),
)),
('start_date_range',
DateFrameField(
label=_('Date range (start of transaction)'),
include_future_frames=False,
required=False
)),
('payment_states',
forms.MultipleChoiceField(
label=_('Payment states'),
@@ -808,17 +809,35 @@ class PaymentListExporter(ListExporter):
payments = OrderPayment.objects.filter(
order__event__in=self.events,
state__in=form_data.get('payment_states', [])
).order_by('created')
).select_related('order').prefetch_related('order__event').order_by('created')
refunds = OrderRefund.objects.filter(
order__event__in=self.events,
state__in=form_data.get('refund_states', [])
).order_by('created')
).select_related('order').prefetch_related('order__event').order_by('created')
if form_data.get('end_date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['end_date_range'], self.timezone)
if dt_start:
payments = payments.filter(created__gte=dt_start)
refunds = refunds .filter(created__gte=dt_start)
if dt_end:
payments = payments.filter(created__lt=dt_end)
refunds = refunds .filter(created__lt=dt_end)
if form_data.get('start_end_date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['start_date_range'], self.timezone)
if dt_start:
payments = payments.filter(payment_date__gte=dt_start)
refunds = refunds .filter(execution_date__gte=dt_start)
if dt_end:
payments = payments.filter(payment_date__lt=dt_end)
refunds = refunds.filter(execution_date__lt=dt_end)
objs = sorted(list(payments) + list(refunds), key=lambda o: o.created)
headers = [
_('Event slug'), _('Order'), _('Payment ID'), _('Creation date'), _('Completion date'), _('Status'),
_('Status code'), _('Amount'), _('Payment method'), _('Comment'),
_('Status code'), _('Amount'), _('Payment method'), _('Comment'), _('Matching ID'), _('Payment details'),
]
yield headers
@@ -831,6 +850,18 @@ class PaymentListExporter(ListExporter):
d2 = obj.execution_date.astimezone(tz).date().strftime('%Y-%m-%d')
else:
d2 = ''
matching_id = ''
payment_details = ''
try:
if isinstance(obj, OrderPayment):
matching_id = obj.payment_provider.matching_id(obj) or ''
payment_details = obj.payment_provider.payment_control_render_short(obj)
elif isinstance(obj, OrderRefund):
matching_id = obj.payment_provider.refund_matching_id(obj) or ''
payment_details = obj.payment_provider.refund_control_render_short(obj)
except Exception:
pass
row = [
obj.order.event.slug,
obj.order.code,
@@ -842,6 +873,8 @@ class PaymentListExporter(ListExporter):
obj.amount * (-1 if isinstance(obj, OrderRefund) else 1),
provider_names.get(obj.provider, obj.provider),
obj.comment if isinstance(obj, OrderRefund) else "",
matching_id,
payment_details,
]
yield row
@@ -855,6 +888,8 @@ class PaymentListExporter(ListExporter):
class QuotaListExporter(ListExporter):
identifier = 'quotalist'
verbose_name = gettext_lazy('Quota availabilities')
category = pgettext_lazy('export_category', 'Product data')
description = gettext_lazy('Download a spreadsheet of all quotas including their current availability.')
def iterate_list(self, form_data):
has_subevents = self.event.has_subevents
@@ -908,21 +943,17 @@ class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'giftcardtransactionlist'
verbose_name = gettext_lazy('Gift card transactions')
organizer_required_permission = 'can_manage_gift_cards'
category = pgettext_lazy('export_category', 'Gift cards')
description = gettext_lazy('Download a spreadsheet of all gift card transactions.')
@property
def additional_form_fields(self):
d = [
('date_from',
forms.DateField(
label=_('Start date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
)),
('date_to',
forms.DateField(
label=_('End date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
('date_range',
DateFrameField(
label=_('Date range'),
include_future_frames=False,
required=False
)),
]
d = OrderedDict(d)
@@ -933,22 +964,12 @@ class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
card__issuer=self.organizer,
).order_by('datetime').select_related('card', 'order', 'order__event')
if form_data.get('date_from'):
date_value = form_data.get('date_from')
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
qs = qs.filter(
datetime__gte=make_aware(datetime.combine(date_value, time(0, 0, 0)), self.timezone)
)
if form_data.get('date_to'):
date_value = form_data.get('date_to')
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
qs = qs.filter(
datetime__lte=make_aware(datetime.combine(date_value, time(23, 59, 59, 999999)), self.timezone)
)
if form_data.get('date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['date_range'], self.timezone)
if dt_start:
qs = qs.filter(datetime__gte=dt_start)
if dt_end:
qs = qs.filter(datetime__lt=dt_end)
headers = [
_('Gift card code'),
@@ -978,6 +999,8 @@ class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
class GiftcardRedemptionListExporter(ListExporter):
identifier = 'giftcardredemptionlist'
verbose_name = gettext_lazy('Gift card redemptions')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all payments or refunds that involve gift cards.')
def iterate_list(self, form_data):
payments = OrderPayment.objects.filter(
@@ -1023,14 +1046,18 @@ class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'giftcardlist'
verbose_name = gettext_lazy('Gift cards')
organizer_required_permission = 'can_manage_gift_cards'
category = pgettext_lazy('export_category', 'Gift cards')
description = gettext_lazy('Download a spreadsheet of all gift cards including their current value.')
@property
def additional_form_fields(self):
return OrderedDict(
[
('date', forms.DateTimeField(
('date', forms.SplitDateTimeField(
label=_('Show value at'),
initial=now(),
required=False,
widget=SplitDateTimePickerWidget(),
help_text=_('Defaults to the time of report.')
)),
('testmode', forms.ChoiceField(
label=_('Test mode'),
@@ -1058,12 +1085,13 @@ class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
)
def iterate_list(self, form_data):
d = form_data.get('date') or now()
s = GiftCardTransaction.objects.filter(
card=OuterRef('pk'),
datetime__lte=form_data['date']
datetime__lte=d
).order_by().values('card').annotate(s=Sum('value')).values('s')
qs = self.organizer.issued_gift_cards.filter(
issuance__lte=form_data['date']
issuance__lte=d
).annotate(
cached_value=Coalesce(Subquery(s), Decimal('0.00')),
).order_by('issuance').prefetch_related(
@@ -1078,11 +1106,11 @@ class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
if form_data.get('state') == 'empty':
qs = qs.filter(cached_value=0)
elif form_data.get('state') == 'valid_value':
qs = qs.exclude(cached_value=0).filter(Q(expires__isnull=True) | Q(expires__gte=form_data['date']))
qs = qs.exclude(cached_value=0).filter(Q(expires__isnull=True) | Q(expires__gte=d))
elif form_data.get('state') == 'expired_value':
qs = qs.exclude(cached_value=0).filter(expires__lt=form_data['date'])
qs = qs.exclude(cached_value=0).filter(expires__lt=d)
elif form_data.get('state') == 'expired':
qs = qs.filter(expires__lt=form_data['date'])
qs = qs.filter(expires__lt=d)
headers = [
_('Gift card code'),

Some files were not shown because too many files have changed in this diff Show More