Compare commits

...

690 Commits

Author SHA1 Message Date
Raphael Michel
c9aaa343e6 Bump to 4.6.0 2022-01-27 13:44:39 +01:00
Raphael Michel
87a196c4df Translations: Update German
Currently translated at 100.0% (4613 of 4613 strings)

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

powered by weblate
2022-01-27 12:27:58 +01:00
Raphael Michel
a220f1678b Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (4613 of 4613 strings)

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

powered by weblate
2022-01-27 12:27:58 +01:00
Raphael Michel
c8fa0852b2 Add DNS to English word list 2022-01-27 12:15:31 +01:00
Raphael Michel
fe3433106c Extend spelling wordlists 2022-01-27 12:11:34 +01:00
Raphael Michel
f8086daf34 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2022-01-27 11:02:14 +01:00
Raphael Michel
66f75a5614 Revert dnspython to 1.x 2022-01-26 16:15:12 +01:00
Raphael Michel
6f30c347c0 [SECURITY] Make redirect view dependent on referer 2022-01-26 13:41:02 +01:00
Raphael Michel
3596fa9c5a [SECURITY] Fix (non-exploitable) XSS issue 2022-01-26 13:41:02 +01:00
Raphael Michel
e3c7cd7c6d Redesign of email settings (#2426)
Co-authored-by: Felix Rindt <felix@rindt.me>
2022-01-26 12:47:58 +01:00
Raphael Michel
194042dca5 Add-on selection: Fix incorrect pre-selection across multiple base positions 2022-01-26 09:45:44 +01:00
Raphael Michel
3be6e83f33 Add missing license header 2022-01-25 21:08:28 +01:00
Raphael Michel
4262bce2b5 Limit maximum length of passwords to 4096 characters 2022-01-25 17:24:48 +01:00
Raphael Michel
73ab962e16 Respect language headers on error 400/404/500 pages 2022-01-25 16:59:30 +01:00
Raphael Michel
13a86fc6f3 Event ical feed: Do not show events more than 31 days in the past 2022-01-24 15:47:04 +01:00
Raphael Michel
9d6f11718a Work around performance issue in vobject library 2022-01-24 15:46:48 +01:00
Raphael Michel
c9d3428996 Extend check_order_transactions by number of tickets 2022-01-22 22:00:35 +01:00
Felix Schäfer
d4ef16b31a Fix #2320 - Move file upload "required" attrs manipulation from init to rendering (#2399) 2022-01-21 15:49:24 +01:00
Yuriko Matsunami
6a35e7d3cd Translated on translate.pretix.eu (Japanese)
Currently translated at 97.0% (167 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ja/

powered by weblate
2022-01-21 15:49:03 +01:00
DJG Bayern
463443d606 Translations: Update Japanese
Currently translated at 0.1% (8 of 4582 strings)

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

powered by weblate
2022-01-21 15:49:03 +01:00
Raphael Michel
6f0da5c2ca TaxRules: Add internal_name and keep_gross_if_rate_changes (#2422)
Co-authored-by: ser8phin <eva.wolkwitz@gmx.de>
2022-01-21 15:39:27 +01:00
ser8phin
c1344422a5 Remove disabled attribute on checkbox (#2423)
Co-authored-by: Raphael Michel <michel@rami.io>
2022-01-19 21:38:14 +01:00
Raphael Michel
c2bd3dde44 GitHub actions: Do not run on 3.10 yet (too many warnings) 2022-01-19 17:12:15 +01:00
Raphael Michel
9e51736232 Fix GitHub actions scripts (No YAML, Python 3.10 is not 3.1) 2022-01-19 17:01:42 +01:00
Raphael Michel
5b27ce1265 Stop testing Python 3.6 on CI 2022-01-19 17:00:24 +01:00
Raphael Michel
0757542f4f Drop Python 3.6 compatibility 2022-01-19 16:49:19 +01:00
Raphael Michel
12be98c888 Update Pillow to 9.* 2022-01-19 16:46:43 +01:00
Raphael Michel
51e6b02aa9 Docs: Remove mention of local cache backend 2022-01-19 15:24:44 +01:00
Raphael Michel
acc4a167b1 Event series calendar: Fix incorrect show_names heuristic 2022-01-19 14:58:30 +01:00
Richard Schreiber
dd9429bbfa Fix: phone being "None" or format not recognized in checkout (#2420) 2022-01-18 12:27:57 +01:00
Richard Schreiber
768bb8c106 Add phone number to customer profile (Z#178346) (#2414) 2022-01-18 11:38:32 +01:00
Raphael Michel
cbdafac999 Web check-in: Fix search 2022-01-17 14:55:16 +01:00
Raphael Michel
96f694cf61 Translations: Update German (informal) (de_Informal)
Currently translated at 99.9% (4580 of 4582 strings)

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

powered by weblate
2022-01-14 16:12:13 +01:00
Raphael Michel
5576829ebf Translations: Update German
Currently translated at 100.0% (4582 of 4582 strings)

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

powered by weblate
2022-01-14 16:12:13 +01:00
Raphael Michel
b0d67e92ac Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2022-01-14 15:33:22 +01:00
Yuriko Matsunami
63e28723d2 Translated on translate.pretix.eu (Japanese)
Currently translated at 73.2% (126 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ja/

powered by weblate
2022-01-14 14:51:58 +01:00
Mikkel Ricky
cc0656f169 Translations: Update Danish
Currently translated at 35.3% (1613 of 4565 strings)

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

powered by weblate
2022-01-14 14:51:58 +01:00
ser8phin
849c8e719a Fix #555 -- Preselect a single required add-on (#2395) 2022-01-14 14:46:04 +01:00
Raphael Michel
a3ec2a4061 Clarify help text of invoice_address_custom_field 2022-01-14 14:42:42 +01:00
Raphael Michel
00a7187a7a Duplicate line break before invoice deadline 2022-01-13 16:45:15 +01:00
Richard Schreiber
701c4f768e Improve add-to-cart checkbox for items with max. 1 per order (Z#178704) (#2413) 2022-01-12 17:10:00 +01:00
Aya Yabuki
cf751d38d2 Translated on translate.pretix.eu (Japanese)
Currently translated at 16.8% (29 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ja/

powered by weblate
2022-01-12 16:21:14 +01:00
Aya Yabuki
888402a4bf Translated on translate.pretix.eu (Japanese)
Currently translated at 16.8% (29 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ja/

powered by weblate
2022-01-12 16:21:14 +01:00
Aya Yabuki
1134f610fd Translated on translate.pretix.eu (Japanese)
Currently translated at 8.7% (15 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ja/

powered by weblate
2022-01-12 16:21:14 +01:00
Raphael Michel
8ae4304c7d Add workaround for https://github.com/getsentry/responses/issues/464 2022-01-12 10:19:02 +01:00
Raphael Michel
357092ec44 API: Add InvoiceLine.subevent (#2411) 2022-01-10 14:11:06 +01:00
Raphael Michel
70a5c76d79 Allow tax rules to trigger approval requirement (#2409) 2022-01-10 14:10:51 +01:00
ser8phin
7a4db8ea23 Add approval requirement option to product variations (#2381) 2022-01-05 18:04:12 +01:00
Raphael Michel
223b160c0c Fix booked add-ons being hidden in order change due to hide_sold_out 2022-01-05 17:58:21 +01:00
Raphael Michel
30c1771d29 Thumbnail: Support for paletted PNG files 2022-01-04 16:26:13 +01:00
Raphael Michel
b3b7b9bbab Optimize rendering of very large calendars (#2406) 2022-01-04 10:48:48 +01:00
dependabot[bot]
be040cd6ea Bump @babel/core from 7.16.0 to 7.16.7 in /src/pretix/static/npm_dir (#2401)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-04 10:33:51 +01:00
dependabot[bot]
c6665ec2e6 Bump @rollup/plugin-node-resolve from 13.0.6 to 13.1.2 in /src/pretix/static/npm_dir (#2403)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-04 10:30:57 +01:00
dependabot[bot]
fd16ef1e4d Bump rollup from 2.60.2 to 2.62.0 in /src/pretix/static/npm_dir (#2402)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-04 10:30:51 +01:00
dependabot[bot]
39557fc452 Bump @babel/preset-env from 7.16.4 to 7.16.7 in /src/pretix/static/npm_dir (#2404)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-04 10:30:44 +01:00
cpoisnel
408397a639 Translations: Update French
Currently translated at 48.8% (2229 of 4565 strings)

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

powered by weblate
2022-01-04 10:30:34 +01:00
Raphael Michel
d4a2500204 Check-in list PDF: Escape HTML tags in names 2022-01-03 12:41:37 +01:00
Raphael Michel
e74d9e56cf Waiting list: Explain that you only get one ticket 2022-01-03 10:43:13 +01:00
Raphael Michel
f3767ab4ac Gift card: Log user who triggered reversal of transaction 2022-01-03 10:39:05 +01:00
Raphael Michel
5d13f5f885 Gift cards: Fix incorrect handling of return key 2022-01-03 10:38:54 +01:00
Raphael Michel
451d3fce05 Cookie consent: Fix crash without localStorage again 2021-12-22 10:29:27 +01:00
Raphael Michel
ccb61e0f56 Docs: Fix dead external link 2021-12-21 11:45:34 +01:00
Richard Schreiber
b6273adc57 Calendar-View: add short_month_day_format for week-views (#2392) 2021-12-21 11:19:58 +01:00
Richard Schreiber
0bf7bba6ba Fix: WEEK_FORMAT fallback in calender week-views (#2391)
* switch to context-week_format for fallback-handling

* set week_format fallback to en instead of de

* add french WEEK_FORMAT and WEEK_DAY_FORMAT
2021-12-21 10:10:13 +01:00
Raphael Michel
7090e0bae2 Event settings: Do not specify fields as optional that are actually required 2021-12-20 19:20:48 +01:00
Raphael Michel
c75cb0b8e3 Cookie consent: Fail softly if localStorage is unavailable 2021-12-20 16:11:33 +01:00
Raphael Michel
3dbf22f670 Remove django-compat from settings.py 2021-12-20 12:22:13 +01:00
Raphael Michel
f26cbdc257 Bump arabic-reshaper to 2.1.3 2021-12-20 09:52:38 +01:00
Raphael Michel
6b4adccee5 Bump django-hijack to 3.1.* 2021-12-20 09:51:52 +01:00
Raphael Michel
c2a8286022 Fix celery-specific issue in 9f4b834ab 2021-12-16 19:06:16 +01:00
Martin Gross
4145887a9b Web checkin: Redirect user to login if session expired (#2383)
Co-authored-by: Raphael Michel <michel@rami.io>
2021-12-16 18:35:09 +01:00
Raphael Michel
9f4b834abc Allow to attach files to order confirmation email (#2384)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-12-16 18:34:18 +01:00
Richard Schreiber
8fcc314f09 Add fixed scroll position when navigating calendar views (Z#177488) (#2385)
* add fixed scroll position when navigating calendar views

* change from local to sessionStorage

* add check for sessionStorage
2021-12-16 13:36:10 +01:00
Felix Rindt
94a7d02ab1 Fix event settings form considered changed even if unchanged (#1739)
Co-authored-by: Raphael Michel <michel@rami.io>
2021-12-16 11:27:18 +01:00
Raphael Michel
ad2943263c Fix unnecessary override of default settings 2021-12-16 10:31:46 +01:00
Raphael Michel
5210ac3a78 Reduce confusion about customer login with event level domains (#2380) 2021-12-15 16:47:08 +01:00
Raphael Michel
0e9600a7bf Fix test isolation issue 2021-12-15 16:46:50 +01:00
ser8phin
eccba09452 Add payment search page (#2335)
Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-12-15 16:06:43 +01:00
Richard Schreiber
c8a830ecde Fix: change widget to use new date-based URLs in calendar-view (#2382) 2021-12-15 14:07:42 +01:00
Richard Schreiber
aed64d16f6 Improve calendar-navigation on organizer and events page (Z#177488) (#2373)
* hide icons for calendar-types and improve layout-breakpoints in calendar top-nav

* change month-selector to one dropdown "date"and redirect old URLs to new date-based URLs

* change week calendar to one dropdown "date“ and redirect old URLs to new date-based URLs
2021-12-14 16:38:32 +01:00
Raphael Michel
d16f6167f6 Fix rich_text crash on empty <a> element 2021-12-14 13:56:52 +01:00
Raphael Michel
77d59248e5 Translations: Update Galician
Currently translated at 1.3% (61 of 4565 strings)

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

powered by weblate
2021-12-14 13:30:11 +01:00
Raphael Michel
a0e05f8af6 Translations: Update Galician
Currently translated at 1.1% (52 of 4565 strings)

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

powered by weblate
2021-12-14 13:30:11 +01:00
Raphael Michel
9b8a47c8b8 Translations: Update Galician
Currently translated at 1.1% (52 of 4565 strings)

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

powered by weblate
2021-12-14 13:30:11 +01:00
Ismael Menéndez Fernández
b3d692276c Translations: Update Galician
Currently translated at 1.1% (52 of 4565 strings)

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

powered by weblate
2021-12-14 13:30:11 +01:00
DJG Bayern
55543e12f6 Translated on translate.pretix.eu (Japanese)
Currently translated at 7.5% (13 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ja/

powered by weblate
2021-12-14 13:30:11 +01:00
Yuriko Matsunami
1e16185c02 Translated on translate.pretix.eu (Japanese)
Currently translated at 7.5% (13 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ja/

powered by weblate
2021-12-14 13:30:11 +01:00
Raphael Michel
cd900e24bd Questions form: Do not persist values to questions hidden by dependencies 2021-12-13 15:46:58 +01:00
Raphael Michel
0dbedc07ce Fix CI dependency installation (#2376) 2021-12-13 15:24:27 +01:00
Raphael Michel
f71877b7fc Badges: Fix event copy data receiver not rewriting questions 2021-12-13 14:09:38 +01:00
Martin Gross
f69e270e4d Add filter for revoked devices (#2372)
Co-authored-by: Raphael Michel <michel@rami.io>
2021-12-13 12:47:43 +01:00
MrGamy
533939cae4 included missing adjective
fixes #2344
2021-12-10 19:29:45 +01:00
Ilona Zilgalve
91ec5fd78c Translated on translate.pretix.eu (Latvian)
Currently translated at 100.0% (172 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/lv/

powered by weblate
2021-12-09 16:54:15 +01:00
Ilona Zilgalve
0056fb447b Translations: Update Latvian
Currently translated at 31.1% (1421 of 4565 strings)

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

powered by weblate
2021-12-09 16:54:15 +01:00
Ilona Zilgalve
20c4d12e98 Translations: Update Russian
Currently translated at 25.1% (1147 of 4565 strings)

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

powered by weblate
2021-12-09 16:54:15 +01:00
Ilona Zilgalve
e13c567e84 Translations: Update Latvian
Currently translated at 28.3% (1296 of 4565 strings)

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

powered by weblate
2021-12-09 16:54:15 +01:00
Ilona Zilgalve
9fef97a7c6 Translations: Update Russian
Currently translated at 24.9% (1139 of 4565 strings)

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

powered by weblate
2021-12-09 16:54:15 +01:00
Ilona Zilgalve
e68a995376 Translations: Update Latvian
Currently translated at 27.5% (1256 of 4565 strings)

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

powered by weblate
2021-12-09 16:54:15 +01:00
Ilona Zilgalve
6abdb40ef5 Translations: Update Latvian
Currently translated at 27.4% (1252 of 4565 strings)

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

powered by weblate
2021-12-09 16:54:15 +01:00
Ilona Zilgalve
43cc06b0a1 Translations: Update Russian
Currently translated at 24.4% (1115 of 4565 strings)

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

powered by weblate
2021-12-09 16:54:15 +01:00
Ondřej Sokol
d17476cd75 Translated on translate.pretix.eu (Czech)
Currently translated at 100.0% (172 of 172 strings)

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

powered by weblate
2021-12-09 16:54:15 +01:00
Ondřej Sokol
5c3bfd2a71 Translations: Update Czech
Currently translated at 10.5% (482 of 4565 strings)

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

powered by weblate
2021-12-09 16:54:15 +01:00
Maico Timmerman
033b8d70e7 Email: Allow to override backend for custom SMTP connections (#2368) 2021-12-09 16:49:22 +01:00
Raphael Michel
bd22c2afc9 Set OrderRefund.execution_date on manual refund 2021-12-08 09:41:12 +01:00
Raphael Michel
b355733f53 Allow to link directly to voucher input form 2021-12-06 18:09:38 +01:00
Raphael Michel
e1f924c4ce Allow to reschedule a missed email 2021-12-06 17:36:49 +01:00
Raphael Michel
8038f4e173 Orders API: Allow to filter by subevent 2021-12-06 12:50:33 +01:00
Raphael Michel
5c55219d45 Allow to create new customers in backend (#2367) 2021-12-06 12:27:21 +01:00
Eva-Maria Obermann
bfd37af467 Translated on translate.pretix.eu (French)
Currently translated at 63.3% (109 of 172 strings)

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

powered by weblate
2021-12-06 12:27:12 +01:00
Eva-Maria Obermann
b2509e120c Translations: Update German
Currently translated at 100.0% (4565 of 4565 strings)

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

powered by weblate
2021-12-06 12:27:12 +01:00
ExtremeX-BB
e2339acd09 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 68.0% (117 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/zh_Hans/

powered by weblate
2021-12-06 12:27:12 +01:00
ExtremeX-BB
c15b4fa03c Translations: Update Chinese (Simplified)
Currently translated at 66.2% (3025 of 4565 strings)

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

powered by weblate
2021-12-06 12:27:12 +01:00
Ilona Zilgalve
c4aa2e0484 Translations: Update Latvian
Currently translated at 27.0% (1235 of 4565 strings)

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

powered by weblate
2021-12-06 12:27:12 +01:00
Ilona Zilgalve
361eeb7159 Translations: Update Russian
Currently translated at 24.4% (1114 of 4565 strings)

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

powered by weblate
2021-12-06 12:27:12 +01:00
Raphael Michel
0109e1806f OrderChangeManager: Move invoice reissuing after payment cancellation (#2359) 2021-12-06 12:26:53 +01:00
Raphael Michel
30aadac099 Fix isort change 2021-12-03 15:02:46 +01:00
dependabot[bot]
0458f1b2dc Bump @babel/preset-env from 7.16.0 to 7.16.4 in /src/pretix/static/npm_dir (#2360)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-03 14:40:06 +01:00
dependabot[bot]
e006ca3feb Bump @rollup/plugin-node-resolve from 11.2.1 to 13.0.6 in /src/pretix/static/npm_dir (#2361)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-03 14:39:27 +01:00
dependabot[bot]
1f31ee2ea1 Bump rollup from 2.59.0 to 2.60.2 in /src/pretix/static/npm_dir (#2362)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-03 14:39:02 +01:00
Richard Schreiber
2d37b0df77 Fix: Day calendar - scroll current .tick into view without window being scrolled (#2365) 2021-12-03 14:36:28 +01:00
Raphael Michel
4133e5ac4d Fix incorrect order change tests 2021-12-03 14:08:19 +01:00
Richard Schreiber
0fd3d0fe71 Fix #2363 – Email: change text-alignment from center to left (right for rtl) (#2364) 2021-12-03 13:44:06 +01:00
Raphael Michel
d0685e99ad Return URL: Append error/success message to query 2021-12-03 10:30:33 +01:00
Raphael Michel
c6fd5bc864 Self-service order change: Fix price constraints not actually being enforced 2021-12-03 10:04:07 +01:00
Raphael Michel
9fa935099f Email rules: Show warning when date was missed 2021-12-03 09:36:54 +01:00
Raphael Michel
83b5a325e3 Fix bug in 832235411 2021-11-30 22:52:34 +01:00
pretix translation bot
97e12c5003 Translations update from Weblate (#2356)
Co-authored-by: Raphael Michel <michel@rami.io>
2021-11-30 17:55:32 +01:00
Raphael Michel
e6db8340f2 Extend German spellcheck wordlist 2021-11-30 17:52:02 +01:00
Raphael Michel
3cf9caa5d3 Add "analytics" to wordlist 2021-11-30 17:26:28 +01:00
Ilona Zilgalve
2ffd68ace7 Translated on translate.pretix.eu (Latvian)
Currently translated at 100.0% (172 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/lv/

powered by weblate
2021-11-30 17:26:12 +01:00
Ilona Zilgalve
0231be63b4 Translations: Update Latvian
Currently translated at 27.1% (1232 of 4537 strings)

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

powered by weblate
2021-11-30 17:26:12 +01:00
Ilona Zilgalve
fae8bc254e Translations: Update Russian
Currently translated at 24.5% (1113 of 4537 strings)

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

powered by weblate
2021-11-30 17:26:12 +01:00
Tonda Pavlík
1d5c700fa2 Translations: Update Czech
Currently translated at 10.4% (474 of 4537 strings)

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

powered by weblate
2021-11-30 17:26:12 +01:00
Raphael Michel
e61775d5c1 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2021-11-30 17:13:43 +01:00
Raphael Michel
e767c6a68d Add central cookie consent mechanism (#2330)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-11-30 17:12:17 +01:00
Raphael Michel
832235411f Add subevent location to order info in emails (#2354) 2021-11-30 13:21:36 +01:00
Raphael Michel
1f0f7b752f Payment provider API: Add confirm_button_name 2021-11-29 20:54:24 +01:00
Raphael Michel
3117eceb72 Validate VAT ID when changing invoice addresses 2021-11-29 20:36:20 +01:00
Raphael Michel
c1b39782fd Bump to 4.6.0.dev0 2021-11-29 15:47:08 +01:00
Raphael Michel
860cfc3227 Bump version to 4.5.0 2021-11-29 15:46:42 +01:00
pretix translation bot
45859a07dd Translations update from Weblate (#2352)
Co-authored-by: Raphael Michel <michel@rami.io>
2021-11-29 10:35:24 +01:00
dependabot[bot]
04fb8efc0d Update flake8 requirement from ==3.7.* to >=3.7,<4.1 in /src
Updates the requirements on [flake8](https://github.com/pycqa/flake8) to permit the latest version.
- [Release notes](https://github.com/pycqa/flake8/releases)
- [Commits](https://github.com/pycqa/flake8/compare/3.7.0...4.0.1)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 09:53:14 +01:00
Raphael Michel
fdb8a3720b Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2021-11-29 09:28:15 +01:00
Raphael Michel
5638d68894 Raise some dependencies 2021-11-29 09:27:24 +01:00
Raphael Michel
f64042280a Tighten dependency ranges 2021-11-29 09:27:24 +01:00
Angel Saiz Velasco
50060cdc8d Translations: Update Spanish
Currently translated at 66.7% (2992 of 4483 strings)

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

powered by weblate
2021-11-29 09:19:44 +01:00
Ismael Menéndez Fernández
4499f58e3d Translated on translate.pretix.eu (Galician)
Currently translated at 100.0% (172 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/gl/

powered by weblate
2021-11-29 09:19:44 +01:00
Ismael Menéndez Fernández
918e4a5a89 Translations: Update Galician
Currently translated at 0.7% (33 of 4483 strings)

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

powered by weblate
2021-11-29 09:19:44 +01:00
Ismael Menéndez Fernández
15a86fd796 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (172 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/es/

powered by weblate
2021-11-29 09:19:44 +01:00
Ismael Menéndez Fernández
4126d20f1c Translations: Update Spanish
Currently translated at 66.6% (2986 of 4483 strings)

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

powered by weblate
2021-11-29 09:19:44 +01:00
Eva-Maria Obermann
ea3edf83f8 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (4483 of 4483 strings)

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

powered by weblate
2021-11-29 09:19:44 +01:00
Eva-Maria Obermann
9a42819b56 Translations: Update German
Currently translated at 100.0% (4483 of 4483 strings)

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

powered by weblate
2021-11-29 09:19:44 +01:00
Marco Giacopuzzi
3e4ba28700 Translated on translate.pretix.eu (Italian)
Currently translated at 100.0% (172 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/it/

powered by weblate
2021-11-29 09:19:44 +01:00
Marco Giacopuzzi
9014ffcc28 Translations: Update Italian
Currently translated at 17.1% (770 of 4483 strings)

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

powered by weblate
2021-11-29 09:19:44 +01:00
Raphael Michel
48f4bcf88c Fix breaking multi-event exporters 2021-11-23 17:07:39 +01:00
Raphael Michel
b7dfb3697e Widget: Fix price box not shown for free-price events with one product 2021-11-23 11:13:09 +01:00
Richard Schreiber
475a5be351 Day calendar: Fix missing current-time-bar back for all browsers (#2342) 2021-11-22 15:12:51 +01:00
Richard Schreiber
8254d8f5cc Day-Calendar: improve width of row-names (#2341) 2021-11-22 15:09:40 +01:00
Raphael Michel
6f0f4755ef Restrict day calendar JS to day calendar page 2021-11-19 19:02:46 +01:00
Richard Schreiber
910a35dedc Fix: calculate day calendar grid in JS as chrome does not support calc-division in CSS-grid (#2340)
Co-authored-by: Raphael Michel <michel@rami.io>
2021-11-19 17:42:16 +01:00
Raphael Michel
e694bd8c21 Fix next crash in day calendar if there is no start time 2021-11-19 17:08:05 +01:00
Raphael Michel
29cf384c28 Fix crash in day calendar if there is no start time 2021-11-19 16:32:07 +01:00
Raphael Michel
492288f437 Allow customers to change add-ons on existing orders (#2283) 2021-11-19 14:59:54 +01:00
Raphael Michel
34e4f7e0fc Add day calendar to organizer page (#2100)
Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-11-19 14:59:35 +01:00
Rasmus Kock Grusgaard
f6f3bbcce6 Translations: Update Danish
Currently translated at 35.9% (1613 of 4483 strings)

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

powered by weblate
2021-11-19 14:59:06 +01:00
Raphael Michel
16054893ed Avoid creation of manual payments with zero amount (#2325) 2021-11-19 12:02:36 +01:00
dependabot[bot]
f6038d2c39 Update django-statici18n requirement from ==1.9.* to >=1.9,<2.2 in /src (#2332)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-18 17:41:37 +01:00
dependabot[bot]
8d13b51271 Bump pycparser from 2.13 to 2.21 in /src (#2334)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-18 17:40:59 +01:00
Raphael Michel
83e1f365c2 Sendmail rules: Add warnings and scheduling view (#2328) 2021-11-18 12:48:27 +01:00
Raphael Michel
146e1aeb67 Upgrade mt-940 to 4.* (#2331) 2021-11-18 12:24:54 +01:00
dependabot[bot]
f9b2920984 Update libsass requirement from ==0.20.* to >=0.20,<0.22 in /src (#2315)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-18 12:14:24 +01:00
dependabot[bot]
2c01b214a7 Update pyflakes requirement from ==2.1.* to >=2.1,<2.5 in /src (#2313)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-18 12:14:01 +01:00
dependabot[bot]
fdab45e5ce Update bleach requirement from ==3.3.* to >=3.3,<4.2 in /src (#2317)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-18 12:13:17 +01:00
pretix translation bot
9d2cf18543 Translations update from Weblate (#2327)
Co-authored-by: +se <sebastiano@endsummercamp.org>
2021-11-18 12:12:42 +01:00
Martin Gross
2206ab1d35 Validate Swiss VAT ID against PROD and not TEST-env 2021-11-17 14:07:14 +01:00
Raphael Michel
ecd2c80dce Downgrade 'markdown' package (#2329) 2021-11-17 11:21:59 +01:00
Raphael Michel
3387df491a Fix error handling in Swiss VAT ID validation 2021-11-17 10:30:52 +01:00
pretix translation bot
b6974e0c77 Translations update from Weblate (#2319)
Co-authored-by: Maarten van den Berg <maartenberg1@gmail.com>
Co-authored-by: +se <sebastiano@endsummercamp.org>
2021-11-16 16:58:21 +01:00
Raphael Michel
31751cbd79 Stripe: Fix storage of failed refunds 2021-11-16 12:18:33 +01:00
Raphael Michel
993da5a392 VAT validation: Move cache to data directory 2021-11-16 10:21:08 +01:00
Richard Schreiber
72455209bb CSP: Strip keys with empty values from header (#2322) 2021-11-16 09:24:19 +01:00
Richard Schreiber
803aa0b70d Setup: Allow django-hijack v2.2 (#2321) 2021-11-16 09:24:06 +01:00
Bentrex95
954d86337c Docs: Fix typo in dev-setup-command (#2316) 2021-11-12 12:42:07 +01:00
Raphael Michel
38a58d62f3 Change default settings for background color, invoice attachmentes and name scheme (#2288) 2021-11-11 12:20:34 +01:00
Raphael Michel
e67b39a57b Increase padding if background color is set (#2301) 2021-11-11 12:20:20 +01:00
dependabot[bot]
148b67ac3f Update django-filter requirement from ==2.4.* to >=2.4,<21.2 in /src (#2311)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-11 11:14:35 +01:00
dependabot[bot]
d261cb3b6b Bump django-libsass from 0.8 to 0.9 in /src (#2312)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-11 11:14:07 +01:00
ser8phin
169a6c51b4 Add check to force users to change password (#2284) 2021-11-11 11:10:33 +01:00
Raphael Michel
245ad644ff Subevent calendar: Improve heuristic on when to show names (#2308) 2021-11-11 10:02:45 +01:00
Jaakko Rinta-Filppula
4fdce0d126 Translated on translate.pretix.eu (Finnish)
Currently translated at 50.0% (86 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/fi/

powered by weblate
2021-11-11 10:02:32 +01:00
Jaakko Rinta-Filppula
a542bc7a5a Translated on translate.pretix.eu (Finnish)
Currently translated at 19.0% (856 of 4483 strings)

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

powered by weblate
2021-11-11 10:02:32 +01:00
dependabot[bot]
3164919923 Update pytest-rerunfailures requirement from ==9.* to >=9,<11 in /src (#2303)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Raphael Michel <michel@rami.io>
2021-11-09 19:22:43 +01:00
dependabot[bot]
8085311eb6 Update django-localflavor requirement from ==3.0.* to >=3.0,<3.2 in /src (#2305)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 19:21:53 +01:00
dependabot[bot]
3887a65961 Update pytest-mock requirement from ==2.0.* to >=2.0,<3.7 in /src (#2302)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 19:20:47 +01:00
dependabot[bot]
b229c6156a Update chardet requirement from <3.1.0,>=3.0.2 to >=3.0.2,<4.1.0 in /src (#2304)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 19:20:40 +01:00
Raphael Michel
c45298544e Fix incorrect settings propagagion 2021-11-09 18:45:45 +01:00
Maarten van den Berg
7bb9d3fc3d Translated on translate.pretix.eu (Dutch)
Currently translated at 99.9% (4482 of 4483 strings)

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

powered by weblate
2021-11-09 17:25:38 +01:00
Ismael Menéndez Fernández
8607df5a9c Translated on translate.pretix.eu (Galician)
Currently translated at 31.3% (54 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/gl/

powered by weblate
2021-11-09 17:25:38 +01:00
Ismael Menéndez Fernández
c4150473fc Translated on translate.pretix.eu (Galician)
Currently translated at 0.4% (20 of 4483 strings)

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

powered by weblate
2021-11-09 17:25:38 +01:00
Martin Gross
172b2f74e0 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4483 of 4483 strings)

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

powered by weblate
2021-11-09 17:25:38 +01:00
Svyatoslav
9586f71dc2 Translated on translate.pretix.eu (Latvian)
Currently translated at 24.0% (1077 of 4483 strings)

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

powered by weblate
2021-11-09 17:25:38 +01:00
Raphael Michel
25692d180f Make weblate script more robust 2021-11-09 16:34:57 +01:00
Raphael Michel
ae047037dc Docs: Add style guide for commit messages (#2281)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-11-09 16:30:32 +01:00
dependabot[bot]
265106034b Update django-otp requirement from ==0.7.*,>=0.7.5 to >=0.7,<1.2 in /src (#2290)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 12:00:15 +01:00
Raphael Michel
dd0a4df914 Fix error 500 on non-ASCII attachment file names 2021-11-09 11:55:03 +01:00
dependabot[bot]
b0ae40c264 Bump rollup from 1.32.1 to 2.59.0 in /src/pretix/static/npm_dir (#2298)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 11:54:28 +01:00
dependabot[bot]
ad95815043 Update redis requirement from ==3.4.* to >=3.4,<3.6 in /src (#2293)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 11:54:07 +01:00
dependabot[bot]
f68522ec0d Bump @babel/core from 7.13.14 to 7.16.0 in /src/pretix/static/npm_dir (#2297)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 09:51:38 +01:00
dependabot[bot]
b831e57351 Bump @rollup/plugin-node-resolve from 11.2.0 to 11.2.1 in /src/pretix/static/npm_dir (#2299)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 09:50:08 +01:00
dependabot[bot]
51166786ee Update phonenumberslite requirement from ==8.11.* to >=8.11,<8.13 in /src (#2291)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 09:49:52 +01:00
dependabot[bot]
909e7906ff Update sentry-sdk requirement from ==1.1.* to >=1.1,<1.5 in /src (#2292)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 09:47:55 +01:00
dependabot[bot]
e185d5f0e7 Bump @babel/preset-env from 7.13.12 to 7.16.0 in /src/pretix/static/npm_dir (#2295)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 09:46:21 +01:00
dependabot[bot]
ce8edf621b Bump vue and vue-template-compiler in /src/pretix/static/npm_dir (#2296)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 09:45:24 +01:00
Raphael Michel
e58b512876 Fix ordering of questions in backend if all system questions are 0 2021-11-09 09:44:44 +01:00
Raphael Michel
d1754f6d1b GitHub: Enable dependabot (#2289) 2021-11-09 09:43:52 +01:00
Raphael Michel
ff2f1b7424 Fix incorrect check for enabled fields in QuestionList 2021-11-09 09:32:52 +01:00
Raphael Michel
fb1838a2f0 Fix incorrect help text 2021-11-09 09:32:52 +01:00
Raphael Michel
d7b05063a4 Allow to print event location on invoices (#2278)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-11-05 09:47:41 +01:00
Raphael Michel
f64a42d61a Stripe: Fix handling of charges without source 2021-11-04 18:21:29 +01:00
Raphael Michel
c1994e89a5 Stripe: Fix MultipleObjectsReturned in webhook 2021-11-04 17:58:24 +01:00
Raphael Michel
f37de1ad2f Invoice renderer: Do not show end date if same as start date 2021-11-04 17:34:44 +01:00
Raphael Michel
e1ff6f8590 Stripe: Look up charges by their source ID as well 2021-11-04 17:20:45 +01:00
Raphael Michel
a5dd22eb4d Reduce number of global locks needed for confirming payments 2021-11-04 17:18:48 +01:00
Raphael Michel
19cde63505 Fix incorrect setting if Invoice.full_invoice_no 2021-11-04 13:48:39 +01:00
Raphael Michel
754d4f4f62 Sendmail: Fix subevent-less rules in event series 2021-11-04 10:21:03 +01:00
Bentrex95
e433230573 Docs: Update dependencies for dev setup (#2282)
Co-authored-by: Raphael Michel <michel@rami.io>
2021-11-03 12:35:26 +01:00
Julia Luna
f8927396d3 API: Add endpoints for automated email rules (#2178)
Co-authored-by: Raphael Michel <michel@rami.io>
2021-11-03 11:49:01 +01:00
Raphael Michel
60be99fbb2 Another attempt at correct sanitization of HTML in invoice content (#2279) 2021-11-03 11:13:43 +01:00
Raphael Michel
0c508c5ba4 Fix remaining DST error in auto check-out 2021-11-03 09:34:50 +01:00
Richard Schreiber
ea6067ab3f Fix Outlook >= 2010 trimming header image (#2277)
* fix image cutoff with mso-line-height: at-least
* align text to the left; fully centered text is hard to read
* remove mso cellpadding-tables as they double up the spacing
* additionally add background-color to a table with width=100% for broader support (e.g. Yahoo and AOL)
2021-11-02 12:59:09 +01:00
Raphael Michel
9d0fa84277 Add nodejs to update notes 2021-10-31 18:32:16 +01:00
Raphael Michel
a6835d3b14 Fix bug in 03de0d5d2 2021-10-31 18:26:45 +01:00
Raphael Michel
9ff565f772 Fix unreadable active tab 2021-10-31 17:28:35 +01:00
Raphael Michel
5d41b20bae Fix crash in waiting list 2021-10-31 17:28:29 +01:00
Raphael Michel
03de0d5d2e Do not ask authenticated customers to re-type their email address 2021-10-29 17:23:26 +02:00
Raphael Michel
2937acdc66 Bump to 4.5.0.dev0 2021-10-29 15:38:52 +02:00
Raphael Michel
6fd09e99e2 Bump version to 4.4.0 2021-10-29 15:38:52 +02:00
Raphael Michel
290e14689d Fix check_order_transactions on SQLite 2021-10-29 15:38:52 +02:00
Raphael Michel
89c937089b Translated on translate.pretix.eu (Galician)
Currently translated at 0.0% (0 of 4483 strings)

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

powered by weblate
2021-10-29 14:17:23 +02:00
Raphael Michel
0e02febe76 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (4483 of 4483 strings)

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

powered by weblate
2021-10-29 14:17:23 +02:00
Raphael Michel
771f822e5f Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4483 of 4483 strings)

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

powered by weblate
2021-10-29 14:17:23 +02:00
Raphael Michel
e8936551c0 Extend spellcheck word list 2021-10-29 13:58:29 +02:00
Raphael Michel
ea0f6dfc54 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2021-10-29 12:10:08 +02:00
Raphael Michel
abeddd360e Invoices: Change expected behaviour for switches in numbering scheme 2021-10-29 12:09:09 +02:00
Maarten van den Berg
c209d195bf Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (172 of 172 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/nl/

powered by weblate
2021-10-29 10:24:22 +02:00
Maarten van den Berg
35c46d320c Translated on translate.pretix.eu (Dutch)
Currently translated at 99.9% (4470 of 4474 strings)

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

powered by weblate
2021-10-29 10:24:22 +02:00
Raphael Michel
30621568ab Added translation on translate.pretix.eu (Galician) 2021-10-29 10:24:22 +02:00
Raphael Michel
403c4f4499 Add Galician as an incubating language 2021-10-29 10:23:57 +02:00
Raphael Michel
884bba0088 Fix transaction creation during split order creation 2021-10-29 10:21:37 +02:00
Raphael Michel
2b52edd5b7 Remove wrong optimization 2021-10-28 11:12:16 +02:00
Richard Schreiber
a4aed96784 Fix: add support for rtl-languages to checkout-step-bars 2021-10-27 16:16:04 +02:00
Raphael Michel
4bdfd56264 E-mail layout with logo: Make image display:block for outlook 2021-10-27 11:31:33 +02:00
Raphael Michel
31f0b07325 FIx typo causing test failure 2021-10-27 11:09:00 +02:00
pretix translation bot
3f08f3a7f4 Translations update from Weblate (#2266)
Co-authored-by: Tony Pavlik <kontakt@playton.cz>
Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Maarten van den Berg <maartenberg1@gmail.com>
2021-10-27 09:22:02 +02:00
Raphael Michel
93263e7567 money template filter: coerce None to 0.00 2021-10-26 18:07:37 +02:00
Raphael Michel
69cf62d2ca Fix missing or wrong create_transactions calls 2021-10-26 18:07:23 +02:00
Raphael Michel
bb353e5fde Improve detection of missing transactions 2021-10-26 18:06:49 +02:00
Raphael Michel
2dceff1218 Fix transaction creation issues and improve debugging 2021-10-26 11:33:44 +02:00
Raphael Michel
5ea8a8ef82 Ask and validate VAT IDs for Switzerland (#2259)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-10-26 11:20:45 +02:00
pretix translation bot
03a7a3303c Translations update from Weblate (#2264)
Co-authored-by: Tony Pavlik <kontakt@playton.cz>
Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Jacek Wielemborek <github@d33.pl>
Co-authored-by: Maarten van den Berg <maartenberg1@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
2021-10-26 11:19:45 +02:00
Raphael Michel
2beb0b20ca Check-in API: Work around libpretixsync issue with space encoding 2021-10-26 10:46:28 +02:00
Richard Schreiber
24eea02e0d API: sort ordered items’ answers by questions’ position (#2182) 2021-10-26 09:42:01 +02:00
Raphael Michel
15ab9c72d3 Invoice renderer: Reduce a few spacings 2021-10-22 13:10:48 +02:00
Raphael Michel
c957d77fe0 Fix linter issues 2021-10-22 12:58:45 +02:00
Raphael Michel
7697018ca4 Order JSON export: Add a lot more fields 2021-10-22 12:43:41 +02:00
Raphael Michel
3980a7b2a7 Docs: Fix missing files 2021-10-22 11:06:23 +02:00
pretix translation bot
035bb56386 Translations update from Weblate (#2254)
Co-authored-by: Tony Pavlik <kontakt@playton.cz>
Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Jacek Wielemborek <github@d33.pl>
Co-authored-by: Maarten van den Berg <maartenberg1@gmail.com>
2021-10-22 11:04:42 +02:00
Raphael Michel
837b03fff3 Add ugprade note to docs 2021-10-22 11:01:35 +02:00
Raphael Michel
d3dec72831 Add missing import 2021-10-22 10:26:10 +02:00
Raphael Michel
3d78f68d94 Docs: Add page on errors 2021-10-22 10:25:58 +02:00
Raphael Michel
faa43d4df8 Remove duplicate form field 2021-10-21 13:25:52 +02:00
Raphael Michel
78917afa1a Event settings API: Expose mail_days_order_expire_warning 2021-10-19 17:12:13 +02:00
Raphael Michel
4b53d39e3e Add debug command check_order_transactions 2021-10-19 17:10:08 +02:00
Raphael Michel
02db07cd25 Work around potential caching issue 2021-10-19 17:04:28 +02:00
Raphael Michel
19fb6c8c34 create_order_transactions: Make suitable for large datasets 2021-10-19 15:25:34 +02:00
Raphael Michel
0c25b2df92 Docs: Fix typo in index name 2021-10-19 15:25:15 +02:00
Raphael Michel
6a543e4557 Fix missing log message 2021-10-18 18:50:53 +02:00
Raphael Michel
846527546a Improve visual transaction table 2021-10-18 18:35:02 +02:00
Raphael Michel
c8cdb2b311 Log silent DirtyTransactionsForOrderException to sentry 2021-10-18 17:57:36 +02:00
Raphael Michel
96ff3d532d Fix logic error 2021-10-18 17:55:32 +02:00
Raphael Michel
8ebba9de86 Data model for transactional history (#2147) 2021-10-18 17:28:58 +02:00
Raphael Michel
c4e71011ee Update English wordlist 2021-10-18 13:24:38 +02:00
Raphael Michel
e71ad4bfba CSS: Always clear floats before drawing footer 2021-10-18 10:37:23 +02:00
Raphael Michel
05a5a69128 Lightbox: Remove .min.js and make dependency on gettext optional 2021-10-18 09:23:12 +02:00
Raphael Michel
bb83cd2f39 Fix duplicate margin 2021-10-17 19:16:19 +02:00
Raphael Michel
df26171ff1 Fix/Improve responsiveness of calendar pages 2021-10-17 18:55:01 +02:00
Raphael Michel
da937dc4e3 [a11y] Small fixes and improvements 2021-10-17 18:35:55 +02:00
Raphael Michel
bb9508ad96 Fix typo 2021-10-17 17:38:34 +02:00
Raphael Michel
41fed7d6a2 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 98.8% (170 of 172 strings)

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

powered by weblate
2021-10-17 17:37:17 +02:00
Raphael Michel
f441e9984d Translated on translate.pretix.eu (German)
Currently translated at 98.8% (170 of 172 strings)

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

powered by weblate
2021-10-17 17:37:17 +02:00
Raphael Michel
05c6155f37 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4474 of 4474 strings)

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

powered by weblate
2021-10-17 17:37:17 +02:00
Raphael Michel
3c096325bd Translated on translate.pretix.eu (German)
Currently translated at 100.0% (4474 of 4474 strings)

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

powered by weblate
2021-10-17 17:37:17 +02:00
Raphael Michel
d06a352df5 Update wordlist 2021-10-17 17:36:58 +02:00
Raphael Michel
ba7b1bb89e Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2021-10-17 16:57:30 +02:00
Richard Schreiber
3dcfa57b70 A11y improvements (#2081)
Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2021-10-17 16:56:16 +02:00
Raphael Michel
cc13ca1c3f Fix #2165 -- Idempotency key errors from Stripe 2021-10-15 12:01:58 +02:00
Raphael Michel
aac67ebf83 Refs #2165 -- Lock payment object while processing Stripe response 2021-10-15 11:57:40 +02:00
Raphael Michel
b51e1cfc6f Fix #2241 -- Display timezone for sale start 2021-10-15 11:46:45 +02:00
Raphael Michel
f0508cdcc3 Fix #2228 -- Date filter behavior in order data export 2021-10-15 11:46:45 +02:00
Raphael Michel
9ed2dc7b46 Add exporter for gift card transactions 2021-10-15 11:46:45 +02:00
Raphael Michel
0e568a3fca Translated on translate.pretix.eu (Spanish)
Currently translated at 67.8% (2995 of 4413 strings)

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

powered by weblate
2021-10-15 11:18:45 +02:00
ityd
7f3606ee81 Translated on translate.pretix.eu (Spanish)
Currently translated at 67.8% (2996 of 4413 strings)

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

powered by weblate
2021-10-15 11:18:45 +02:00
DJG Bayern
b22d43860a Translated on translate.pretix.eu (Japanese)
Currently translated at 2.9% (5 of 171 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ja/

powered by weblate
2021-10-15 11:18:45 +02:00
DJG Bayern
0f9b339f01 Translated on translate.pretix.eu (Japanese)
Currently translated at 0.1% (4 of 4413 strings)

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

powered by weblate
2021-10-15 11:18:45 +02:00
DJG Bayern
cd1e9c1740 Translated on translate.pretix.eu (Japanese)
Currently translated at 0.1% (2 of 4413 strings)

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

powered by weblate
2021-10-15 11:18:45 +02:00
Raphael Michel
aec1ce53fc Added translation on translate.pretix.eu (Japanese) 2021-10-15 11:18:45 +02:00
Raphael Michel
aae129be6a Added translation on translate.pretix.eu (Japanese) 2021-10-15 11:18:45 +02:00
Tony Pavlik
b906fe0fc3 Translated on translate.pretix.eu (Czech)
Currently translated at 8.8% (390 of 4413 strings)

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

powered by weblate
2021-10-15 11:18:45 +02:00
Adri
f0f1537e9c Translated on translate.pretix.eu (French)
Currently translated at 50.7% (2238 of 4413 strings)

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

powered by weblate
2021-10-15 11:18:45 +02:00
Raphael Michel
7b7e77d497 Subevent editor: Fix Quota.ignore_for_event_availability not being copied 2021-10-15 11:12:36 +02:00
Raphael Michel
9ac705cd88 Web check-in: Show subevent with check result 2021-10-14 18:48:19 +02:00
Richard Schreiber
01d9574ddf Fix #2244 -- Show products without category first on product-list (#2249) 2021-10-13 09:33:43 +02:00
Richard Schreiber
8121167d5e Control: Add drag and drop to sort categories and products (#2242)
* add drag and drop to categories

* add drag and drop to products

* add light grey background to dragged element

* add missing th, add sr-only desc of columns

* group up/down/move elements

* improve visualizing drag-area by dimming others

* change up/down-links to buttons in form-post

* limit sorting to POST requests

Co-authored-by: Raphael Michel <michel@rami.io>
2021-10-12 14:46:56 +02:00
Raphael Michel
dde4e12ce1 Fix bug in 6cd32400a 2021-10-11 17:36:57 +02:00
Raphael Michel
6cd32400ae Mails: Add elaborate retry logic for MS Exchange 2021-10-11 12:41:26 +02:00
Raphael Michel
8fa71ccad4 Show remaining quota on voucher redemption page 2021-10-08 18:08:28 +02:00
Raphael Michel
0f47bff5cd Allow to hide products that require membership (#2240)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-10-07 10:11:31 +02:00
Raphael Michel
f459f1f12d Fix logging error for automated emails 2021-10-07 10:08:30 +02:00
Richard Schreiber
65167cc290 Add new alert icons (#2226) 2021-10-06 12:31:08 +02:00
Raphael Michel
bc7300c393 Track if invoices have been sent via email (#2231) 2021-10-05 13:47:55 +02:00
Jaakko Rinta-Filppula
d8450202fe Translated on translate.pretix.eu (Finnish)
Currently translated at 50.2% (86 of 171 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/fi/

powered by weblate
2021-10-05 12:47:08 +02:00
Jaakko Rinta-Filppula
41d2bcc34f Translated on translate.pretix.eu (Finnish)
Currently translated at 19.3% (852 of 4413 strings)

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

powered by weblate
2021-10-05 12:47:08 +02:00
Fabian Rodriguez
0e1589013a Translated on translate.pretix.eu (French)
Currently translated at 63.7% (109 of 171 strings)

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

powered by weblate
2021-10-04 17:34:05 +02:00
cpoisnel
39f81617e1 Translated on translate.pretix.eu (French)
Currently translated at 63.7% (109 of 171 strings)

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

powered by weblate
2021-10-04 17:34:05 +02:00
cpoisnel
b394ef6de1 Translated on translate.pretix.eu (French)
Currently translated at 50.5% (2231 of 4413 strings)

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

powered by weblate
2021-10-04 17:34:05 +02:00
Raphael Michel
177906e2ac Custom order emails: Allow to attach tickets and invoices 2021-09-30 12:15:55 +02:00
Raphael Michel
59f6b20129 Add email placeholder {voucher_url_list} 2021-09-30 11:54:41 +02:00
Raphael Michel
51998e820d Orders API: Add item and variation filters 2021-09-30 11:48:23 +02:00
Raphael Michel
e803b56716 Bump to 4.4.0.dev0 2021-09-29 11:17:50 +02:00
Raphael Michel
fa8b1c176b Bump to 4.3.0 2021-09-29 11:17:12 +02:00
Richard Schreiber
2598787602 Customer profiles: add minor improvements around disabled fields and margins (#2195) 2021-09-29 10:34:45 +02:00
Raphael Michel
003fa62996 Fix help text 2021-09-28 18:07:34 +02:00
Raphael Michel
798c21955e Translated on translate.pretix.eu (German)
Currently translated at 100.0% (4413 of 4413 strings)

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

powered by weblate
2021-09-27 21:52:58 +02:00
Raphael Michel
fe6185af4b Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4413 of 4413 strings)

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

powered by weblate
2021-09-27 21:52:58 +02:00
Raphael Michel
7bacefa442 Update spelling wordlist 2021-09-27 21:47:05 +02:00
Raphael Michel
04e187c297 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2021-09-27 20:49:18 +02:00
Raphael Michel
9f2ffc3276 Improvements around the waiting list (#2219)
* Waiting list: Support for seated events, pre-fill customer email address

* Allow people to remove themselves

* Update src/pretix/base/settings.py

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Update src/pretix/control/views/waitinglist.py

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Update src/pretix/control/views/waitinglist.py

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Update src/pretix/control/views/waitinglist.py

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Update src/pretix/presale/views/waiting.py

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Resolve a review note

* Review notes

* Fix linter issues

* Fix import

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-09-27 20:48:02 +02:00
Diego Rodrigo
a9a4cf6fca Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 14.5% (641 of 4403 strings)

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

powered by weblate
2021-09-27 20:26:20 +02:00
ofirtro
a563316e22 Translated on translate.pretix.eu (Hebrew)
Currently translated at 20.4% (35 of 171 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/he/

powered by weblate
2021-09-27 20:26:20 +02:00
ofirtro
4b6f55c31d Added translation on translate.pretix.eu (Hebrew) 2021-09-27 20:26:20 +02:00
Klevagruva
21a8fad17a Translated on translate.pretix.eu (Swedish)
Currently translated at 17.0% (749 of 4403 strings)

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

powered by weblate
2021-09-27 20:26:20 +02:00
Raphael Michel
7586df9d3f Docs: Fix note on widget on mobile 2021-09-27 11:34:55 +02:00
Raphael Michel
1d4afa5d27 Sendmail: Fix invalid state if attachment is adde then removed 2021-09-23 17:36:33 +02:00
Richard Schreiber
720d9b924e [Fix] on rtl-languages float productpicture to the right (#2224) 2021-09-23 17:22:50 +02:00
Raphael Michel
9f56669f2a Truelink filter: Allow dots with spaces 2021-09-23 09:50:39 +02:00
Richard Schreiber
fc541016c6 fix typo in logo-image-settings 2021-09-23 08:28:39 +02:00
Raphael Michel
5eefe9ad1e Fix linter issues 2021-09-20 16:51:48 +02:00
Raphael Michel
1d065a7672 Add setting organizer_logo_image_inherit 2021-09-17 13:33:34 +02:00
Raphael Michel
101f5f7781 Rewrite default ticket PDF to make sure caches affected by previous bug are cleaned 2021-09-17 11:55:53 +02:00
Raphael Michel
af7c6d360f Partially revert migration command monkeypatching 2021-09-17 11:07:46 +02:00
Raphael Michel
8751e6e5ba Product list: Show "sold out" before expanding variations 2021-09-17 10:20:43 +02:00
Raphael Michel
93004a8125 Customer detailv iew: Do not show names as "None" 2021-09-17 10:20:43 +02:00
Raphael Michel
adf40e1d56 Refactor our migrate command monkeypatching 2021-09-17 10:20:43 +02:00
Raphael Michel
364cfe0131 Update nginx config for static files 2021-09-17 10:20:43 +02:00
Raphael Michel
1514527ef3 Consistent naming 2021-09-17 10:20:43 +02:00
Tim Neumann
680024234d Dockerfile: Move nginx client_max_body_size to seperate file (#2207) 2021-09-16 12:36:25 +02:00
Richard Schreiber
2a3660f2d1 Fix -- copy answers even when matching customer profiles exist (#2209) 2021-09-16 10:07:43 +02:00
Richard Schreiber
2041d1213a fix address expand button submit-bug 2021-09-16 09:10:20 +02:00
Raphael Michel
42a1fe9bd1 Event settings API: Fix setting confirm_texts 2021-09-15 16:28:57 +02:00
Raphael Michel
002469d523 Fix .po file 2021-09-15 14:47:58 +02:00
pretix translation bot
5be4af1305 Translations update from Weblate (#2205)
* Translated on translate.pretix.eu (German)

Currently translated at 100.0% (4403 of 4403 strings)

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

powered by weblate

* Translated on translate.pretix.eu (German (informal) (de_Informal))

Currently translated at 100.0% (4403 of 4403 strings)

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

powered by weblate

Co-authored-by: Raphael Michel <michel@rami.io>
2021-09-15 13:54:39 +02:00
Raphael Michel
0b241438e1 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2021-09-15 13:44:42 +02:00
Raphael Michel
61649ab2b8 Self-service cancellation: Allow to disable auto-refunds 2021-09-15 13:43:55 +02:00
Mohamed Tawfiq
848ea999c5 Translated on translate.pretix.eu (Arabic)
Currently translated at 96.4% (165 of 171 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ar/

powered by weblate
2021-09-15 13:43:52 +02:00
Mohamed Tawfiq
dfa82870fb Translated on translate.pretix.eu (Arabic)
Currently translated at 88.6% (3891 of 4391 strings)

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

powered by weblate
2021-09-15 13:43:52 +02:00
Mohamed Tawfiq
e05ac7ef34 Translated on translate.pretix.eu (Arabic)
Currently translated at 88.6% (3891 of 4391 strings)

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

powered by weblate
2021-09-15 13:43:52 +02:00
Raphael Michel
ad2334bffc WebHookCall: Increase max URL size 2021-09-15 13:06:25 +02:00
Raphael Michel
17adde99fa Allow to restrict availability of variations by date, sales channel, and voucher (#2202) 2021-09-15 12:04:17 +02:00
Raphael Michel
4789d82c4e Shredder: Fix crash in AttendeeInfoShredder 2021-09-14 15:28:39 +02:00
Raphael Michel
0567e2d22b API: Fix crash on missing require_membership_types property 2021-09-14 15:28:02 +02:00
Raphael Michel
2e0592b0a6 API: Fix crash on invalid input (PRETIXEU-5A9) 2021-09-14 15:02:06 +02:00
Mie Frydensbjerg
7f6d234b4c Translated on translate.pretix.eu (Danish)
Currently translated at 59.0% (101 of 171 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/da/

powered by weblate
2021-09-13 12:00:03 +02:00
Mie Frydensbjerg
0436de316b Translated on translate.pretix.eu (Danish)
Currently translated at 36.4% (1600 of 4391 strings)

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

powered by weblate
2021-09-13 12:00:03 +02:00
Klevagruva
e16d643d2a Translated on translate.pretix.eu (Swedish)
Currently translated at 16.6% (730 of 4391 strings)

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

powered by weblate
2021-09-13 12:00:03 +02:00
Raphael Michel
bdec22cf3b Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4391 of 4391 strings)

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

powered by weblate
2021-09-13 12:00:03 +02:00
Raphael Michel
b38df27dce Order import: Fix handling of seat IDs 2021-09-09 18:14:43 +02:00
Tim Neumann
b95f556d8f Add config options for max file upload sizes (#2199)
* feat(config): Add config options for max file upload sizes

Closes #2198

* Apply suggestions from code review

Fix docs and comment in settings.py

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

* Fix import order using isort

Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
2021-09-09 15:55:06 +02:00
Raphael Michel
851a4c977c Fix inconsistent handling of all_optional 2021-09-08 20:43:56 +02:00
Raphael Michel
7bffd461d1 Allow sales channels to opt out of customer accounts 2021-09-08 20:33:18 +02:00
Richard Schreiber
9a3b4f7863 Subevent: fix overflow for long lines in location 2021-09-08 13:22:57 +02:00
Raphael Michel
673a38ddc8 Cart: Display subevent location and end time in cart (#2191) 2021-09-08 11:24:39 +02:00
Richard Schreiber
a27b8bf213 Subevent: add missing verbose_name for seating plan (#2194) 2021-09-07 09:09:16 +02:00
Raphael Michel
36e6f10b37 Check-in list rule visualization: Fix broken height calculation 2021-09-06 22:35:14 +02:00
Raphael Michel
fde10d7f55 Fix missing license header 2021-09-06 21:14:25 +02:00
Raphael Michel
6b44b2f429 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (4391 of 4391 strings)

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

powered by weblate
2021-09-06 21:14:11 +02:00
Raphael Michel
5e9018e0fd Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4391 of 4391 strings)

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

powered by weblate
2021-09-06 21:14:11 +02:00
Raphael Michel
185f8066ae Fix incorrect part of previous commit 2021-09-06 20:58:40 +02:00
Raphael Michel
6388f7b29c Fix #2192 -- Invoice address name-field always gets overwritten with customer profile 2021-09-06 20:57:45 +02:00
Raphael Michel
4aa2c9d51d Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2021-09-06 20:51:34 +02:00
Raphael Michel
ef9256f0b0 Fix typo in cache key 2021-09-06 20:50:47 +02:00
Raphael Michel
28d78e40f9 Allow to save invoice addresses and attendee profiles to customer account (#2084)
Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2021-09-06 20:50:25 +02:00
Klevagruva
89554a82eb Translated on translate.pretix.eu (Swedish)
Currently translated at 16.2% (708 of 4364 strings)

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

powered by weblate
2021-09-06 20:18:55 +02:00
Klevagruva
ae99e82ad1 Translated on translate.pretix.eu (Swedish)
Currently translated at 15.1% (663 of 4364 strings)

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

powered by weblate
2021-09-06 20:18:55 +02:00
rauxenz
5ea3d01b8d Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (171 of 171 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/es/

powered by weblate
2021-09-06 20:18:55 +02:00
rauxenz
aa2bd79b99 Translated on translate.pretix.eu (Spanish)
Currently translated at 68.2% (2977 of 4364 strings)

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

powered by weblate
2021-09-06 20:18:55 +02:00
Niklas Forsström
44ee35b885 Translated on translate.pretix.eu (Swedish)
Currently translated at 14.9% (654 of 4364 strings)

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

powered by weblate
2021-09-06 20:18:55 +02:00
Niklas Forsström
22b79a8c22 Translated on translate.pretix.eu (Swedish)
Currently translated at 14.7% (643 of 4364 strings)

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

powered by weblate
2021-09-06 20:18:55 +02:00
Klevagruva
65bbd537e6 Translated on translate.pretix.eu (Swedish)
Currently translated at 14.7% (643 of 4364 strings)

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

powered by weblate
2021-09-06 20:18:55 +02:00
Klevagruva
34387d7bc0 Translated on translate.pretix.eu (Swedish)
Currently translated at 11.1% (486 of 4364 strings)

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

powered by weblate
2021-09-06 20:18:55 +02:00
ityd
ca38204313 Translated on translate.pretix.eu (Spanish)
Currently translated at 68.0% (2969 of 4364 strings)

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

powered by weblate
2021-09-06 20:18:55 +02:00
Niklas Forsström
b7083eca2e Translated on translate.pretix.eu (Swedish)
Currently translated at 10.3% (452 of 4364 strings)

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

powered by weblate
2021-09-06 20:18:55 +02:00
Raphael Michel
6bb8b428dc Stripe: API keys consistently are prefered over connect keys 2021-09-06 20:18:15 +02:00
Raphael Michel
677142d0c9 API: Fix storage of Item.picture 2021-09-06 19:56:57 +02:00
Raphael Michel
d1b66e365a API: Add test case for unsetting settings 2021-09-06 19:33:17 +02:00
Raphael Michel
50154c02ce Voucher: Add error message to form_invalid 2021-09-06 19:32:40 +02:00
Raphael Michel
04375d4fcf Fix voucher form validation (Z#2384192) 2021-09-06 19:32:15 +02:00
Raphael Michel
9c1ff296bb Add missing template 2021-09-06 16:33:41 +02:00
Raphael Michel
0b3acb06b5 Invoice: Fix incorrect reference to original invoice number 2021-09-06 16:33:27 +02:00
Raphael Michel
b2cdccedd6 Docker: Specify distribution of base image, upgrade to Python 3.9 2021-09-05 12:40:21 +02:00
Raphael Michel
7ebefa7b85 Allow to manually bump carts blocking a voucher 2021-08-30 15:57:28 +02:00
Raphael Michel
c7b5baa185 Widget: Only show new tab button on connection error 2021-08-30 15:49:22 +02:00
Raphael Michel
6d08e7a8b0 Docs: libmariadbclient-dev has been replaced by libmariadb-dev 2021-08-30 12:58:15 +02:00
Raphael Michel
0da2b12646 Check-in log exporter: Expose upload time 2021-08-30 12:44:25 +02:00
Raphael Michel
a0693483dc Bump to 4.3.0.dev0 2021-08-27 16:44:36 +02:00
Raphael Michel
29826a9f08 Bump to 4.2.0 2021-08-27 16:44:03 +02:00
Raphael Michel
36a045020f Fix typo check 2021-08-27 16:05:40 +02:00
Raphael Michel
40c2b774aa Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4364 of 4364 strings)

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

powered by weblate
2021-08-27 16:04:05 +02:00
Raphael Michel
8422b2b4aa Translated on translate.pretix.eu (German)
Currently translated at 100.0% (4364 of 4364 strings)

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

powered by weblate
2021-08-27 16:04:05 +02:00
Raphael Michel
ae334c4860 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2021-08-27 15:05:38 +02:00
Raphael Michel
722f36121d Docs: Fix typo 2021-08-27 15:04:37 +02:00
Richard Schreiber
529092a4ed Order import: warn when char-replacement happens due to unknown encoding (#2184)
Co-authored-by: Raphael Michel <michel@rami.io>
2021-08-27 15:03:35 +02:00
Bruno
e7068020d5 Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 14.1% (616 of 4357 strings)

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

powered by weblate
2021-08-27 15:03:19 +02:00
Raphael Michel
08acecf37b Docs: Add algorithm diagrams for checkin 2021-08-27 12:29:21 +02:00
Raphael Michel
b200ca5ad5 Set verbose name of Event.seating_plan 2021-08-24 12:25:42 +02:00
Raphael Michel
e564952148 Web check-in: Show ticket secret on result card 2021-08-24 12:05:45 +02:00
Maarten van den Berg
f4ad2a2293 Translated on translate.pretix.eu (Dutch (informal) (nl_Informal))
Currently translated at 93.6% (4079 of 4357 strings)

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

powered by weblate
2021-08-24 09:00:42 +02:00
Maarten van den Berg
854bbf26c2 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (4357 of 4357 strings)

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

powered by weblate
2021-08-24 09:00:42 +02:00
Raphael Michel
ec5a670ea6 PDF: Fix inheritance of answers to add-on products 2021-08-23 18:42:21 +02:00
Raphael Michel
276add9163 Refactor bulk-generation of voucher codes into utility function 2021-08-23 12:50:02 +02:00
Raphael Michel
de977f4818 event_copy_data Signal: Add quota_map argument 2021-08-23 12:03:03 +02:00
Raphael Michel
a4827fc992 Fix sorting of devices by descending name 2021-08-20 14:23:01 +02:00
Fabian
9a002bf172 Docs: no diff in mysql / pstgres in upgrade (#2180) 2021-08-19 12:37:48 +02:00
Raphael Michel
9a7f3e2d8a Stripe: Fix crash in shredder 2021-08-19 12:09:15 +02:00
Raphael Michel
e7546a7575 Manual refunds: Do not attempt to cancel if already canceled 2021-08-19 11:03:45 +02:00
Raphael Michel
434719285b Widget: Make "voucher required" clickable 2021-08-19 10:39:36 +02:00
Raphael Michel
5bc9ba4641 Deleting products: Catch ProtectedError 2021-08-19 10:20:01 +02:00
Raphael Michel
74dd13abd5 Fix unlimited quota issues 2021-08-18 18:40:33 +02:00
Raphael Michel
ead755aa86 Report canceled orders as underpaid if necessary 2021-08-18 17:36:36 +02:00
Raphael Michel
1f46a8b91b Remove print statement 2021-08-18 16:50:03 +02:00
Raphael Michel
eb77c2f6f6 API: Allow bulk-cration of cart positions 2021-08-18 15:28:41 +02:00
Raphael Michel
c5fe615be5 Sendmail: Force preview and show number of orders (#2099) 2021-08-18 15:28:33 +02:00
Raphael Michel
f5504e11ac Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (171 of 171 strings)

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

powered by weblate
2021-08-18 15:25:38 +02:00
Raphael Michel
e88a1a52f9 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (171 of 171 strings)

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

powered by weblate
2021-08-18 15:25:38 +02:00
Raphael Michel
b86d54ea9f Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4357 of 4357 strings)

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

powered by weblate
2021-08-18 15:25:38 +02:00
Raphael Michel
b6e2ed14db Translated on translate.pretix.eu (German)
Currently translated at 100.0% (4357 of 4357 strings)

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

powered by weblate
2021-08-18 15:25:38 +02:00
nd
c513868afa Fix TypeError in Order.ticket_download_available on "empty" orders (#2176)
Co-authored-by: alice <tickets-dev@kulturkosmos.de>
Co-authored-by: nd <git@notandy.de>

Co-authored-by: alice <tickets-dev@kulturkosmos.de>
2021-08-17 23:42:36 +02:00
Raphael Michel
3f7664f743 Subevent bulk-creation: Fix flaky test (#2177) 2021-08-17 08:11:11 +02:00
Raphael Michel
e654b951ed Fix typo in docs 2021-08-16 17:55:33 +02:00
Raphael Michel
b5c7556abe Add FEE_TYPE_INSURANCE 2021-08-16 17:03:57 +02:00
Raphael Michel
53e3619140 Self-service cancellation: Never suggest cancellation fee higher than order total 2021-08-16 14:43:08 +02:00
Raphael Michel
e191988b81 Self-service cancellation: Do not allow to adjust fee on free orders 2021-08-16 14:40:47 +02:00
Raphael Michel
bb7fd9423b Invoice: Auto-resize font of meta data line 2021-08-16 13:07:07 +02:00
Raphael Michel
c10c6ee28d Subevent bulk creation: Resolve AmbiguousTimeError by preferring non-dst time 2021-08-16 13:07:07 +02:00
Raphael Michel
3c64733e93 Fix crash trying to thumbnail invalid image 2021-08-16 13:07:07 +02:00
dependabot[bot]
08cb045f2e Bump path-parse from 1.0.6 to 1.0.7 in /src/pretix/static/npm_dir
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-16 11:35:45 +02:00
Martin Gross
7bf854fe0b Add uninstall-handler for plugins 2021-08-16 11:35:27 +02:00
René Georgi
f2a1e11b85 Translated on translate.pretix.eu (German)
Currently translated at 99.8% (4349 of 4357 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
amandajurno
9b07912b7f Translated on translate.pretix.eu (Portuguese)
Currently translated at 4.2% (186 of 4357 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
amandajurno
e1cec9882a Translated on translate.pretix.eu (Portuguese)
Currently translated at 4.2% (185 of 4357 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
Alberto Leoncio
1ff9c1a84b Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 12.7% (556 of 4357 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
Michael
0035825f33 Translated on translate.pretix.eu (Czech)
Currently translated at 99.4% (170 of 171 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
Michael
1ec73b1b33 Translated on translate.pretix.eu (Czech)
Currently translated at 4.0% (177 of 4357 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
Svyatoslav
ed83f4558e Translated on translate.pretix.eu (Russian)
Currently translated at 58.4% (100 of 171 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ru/

powered by weblate
2021-08-15 19:11:03 +02:00
Svyatoslav
b18ec7605a Translated on translate.pretix.eu (Russian)
Currently translated at 25.2% (1102 of 4357 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
Maarten van den Berg
f96bc0776d Translated on translate.pretix.eu (Dutch (informal) (nl_Informal))
Currently translated at 100.0% (171 of 171 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/nl_Informal/

powered by weblate
2021-08-15 19:11:03 +02:00
Maarten van den Berg
629bdcd55d Translated on translate.pretix.eu (Dutch (informal) (nl_Informal))
Currently translated at 91.5% (3989 of 4357 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
Maarten van den Berg
829fd907a1 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (171 of 171 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/nl/

powered by weblate
2021-08-15 19:11:03 +02:00
Abdullah
d7fe321f36 Translated on translate.pretix.eu (Arabic)
Currently translated at 89.3% (3891 of 4357 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
Maarten van den Berg
517432319e Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (4357 of 4357 strings)

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

powered by weblate
2021-08-15 19:11:03 +02:00
Richard Schreiber
edef9f1b23 Control: fix typo to correctly initialize basket’s reservation_time input 2021-08-12 12:39:41 +02:00
Martin Gross
617730ab76 Update Smallscale Docker Docs: bind exposed ports to 127.0.0.1 explicitly 2021-08-10 10:47:14 -04:00
Martin Gross
7c17d041f4 Fix wrong pretix.event.deleted webhook-label 2021-08-10 09:29:58 -04:00
Richard Schreiber
9295abb80e Fix item’s panel-title in checkout questions: move variation to name (#2172) 2021-08-09 15:13:19 +02:00
Richard Schreiber
4c3192f116 Payment availability-check: ignore country if no invoice address given (#2171) 2021-08-09 15:11:49 +02:00
Richard Schreiber
9c6a2eb85a Subevent bulk edit: check if list formset has changed only if not None (#2173) 2021-08-09 15:10:55 +02:00
Martin Gross
bcbc8a542f Update po files
[CI skip]

Signed-off-by: Martin Gross <gross@rami.io>
2021-08-04 13:18:45 -04:00
pretix translation bot
a915442efc Translations update from Weblate (#2167)
* Translated on translate.pretix.eu (Dutch)

Currently translated at 100.0% (4351 of 4351 strings)

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

powered by weblate

* Translated on translate.pretix.eu (Dutch)

Currently translated at 100.0% (170 of 170 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/nl/

powered by weblate

* Translated on translate.pretix.eu (Dutch (informal) (nl_Informal))

Currently translated at 91.7% (3991 of 4351 strings)

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

powered by weblate

* Translated on translate.pretix.eu (Dutch (informal) (nl_Informal))

Currently translated at 78.8% (134 of 170 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/nl_Informal/

powered by weblate

Co-authored-by: Maarten van den Berg <maartenberg1@gmail.com>
2021-08-04 13:17:07 -04:00
Richard Schreiber
103631a14b Bundled items: improve label for quantity (#2166)
The label was „Number“, which is rather ambiguous. Changed it to „Quantity“.
2021-08-03 11:24:38 +02:00
Maarten van den Berg
e9d7a24cbf Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (4351 of 4351 strings)

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

powered by weblate
2021-08-02 19:00:31 +02:00
Maarten van den Berg
cc977e441a Translated on translate.pretix.eu (Dutch)
Currently translated at 99.9% (4347 of 4351 strings)

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

powered by weblate
2021-08-02 19:00:31 +02:00
Björn Out
add9bae018 Translated on translate.pretix.eu (Dutch)
Currently translated at 99.9% (4347 of 4351 strings)

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

powered by weblate
2021-08-02 19:00:31 +02:00
Hans Fraiponts
77d157ab8e Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (170 of 170 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/nl/

powered by weblate
2021-08-02 19:00:31 +02:00
Raphael Michel
e42bc94329 Remove print statement 2021-08-01 17:39:17 +02:00
Raphael Michel
b4bf5f998e Check-in API: Recover from IntegrityError when saving questions 2021-07-31 11:14:36 +02:00
Raphael Michel
dc785e9dac Fix failing tests 2021-07-30 17:34:07 +02:00
Raphael Michel
8f5f95b04e nginx: Do not attempt to parse and rewrite our URLs, please 2021-07-30 16:46:03 +02:00
Raphael Michel
c86839ed41 Translated on translate.pretix.eu (Russian)
Currently translated at 25.3% (1102 of 4351 strings)

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

powered by weblate
2021-07-26 17:23:38 +02:00
Björn Out
8b6e0f0de7 Translated on translate.pretix.eu (Dutch)
Currently translated at 98.2% (167 of 170 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/nl/

powered by weblate
2021-07-26 17:23:38 +02:00
Björn Out
a65243e4bb Translated on translate.pretix.eu (Dutch)
Currently translated at 99.8% (4344 of 4351 strings)

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

powered by weblate
2021-07-26 17:23:38 +02:00
Svyatoslav
ac028be84e Translated on translate.pretix.eu (Russian)
Currently translated at 25.3% (1103 of 4351 strings)

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

powered by weblate
2021-07-26 17:23:38 +02:00
Raphael Michel
efd5b5b1da Thumbnailer: Do not screw with image modes 2021-07-26 17:08:26 +02:00
Raphael Michel
4be618bc93 Thumbnailing: Redefine min-size as exact-size 2021-07-26 15:53:21 +02:00
Raphael Michel
7b6d5a0cc9 Exporters: Allow extended XSLX formatting 2021-07-24 17:35:33 +02:00
Raphael Michel
f367d5e675 Multi-event exports: Pass organizer, fix in staff mode 2021-07-24 16:46:37 +02:00
Raphael Michel
f9b7894c4d Add tab-content stylesheet to frontend 2021-07-24 12:51:21 +02:00
Raphael Michel
354bbb485b Pagination: Add intcomma to number of results 2021-07-24 12:51:21 +02:00
Raphael Michel
8dc5dbd547 PDF: Add placehodler for order code + positionid 2021-07-23 19:29:27 +02:00
Raphael Michel
e04793d2eb pretixSCAN: Add new security profile PretixScanNoSyncSecurityProfile 2021-07-23 18:48:40 +02:00
Raphael Michel
db65c14733 Fix style check failures 2021-07-23 18:14:52 +02:00
Raphael Michel
f10c8b229f Check-in: Ticket should not count as redeemed if only exits have been scanned so far 2021-07-23 11:15:35 +02:00
Raphael Michel
4655d8237f Check-in API: Improve handling of unknown ticket codes 2021-07-23 10:49:33 +02:00
Raphael Michel
78f4f35ca3 Add documentation on certificates plugin 2021-07-21 18:00:33 +02:00
Raphael Michel
3a01a05a08 Check-in history: Fix incorrectly linked product 2021-07-21 10:17:18 +02:00
dedecosta
1738c710cb Translated on translate.pretix.eu (Italian)
Currently translated at 16.5% (720 of 4351 strings)

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

powered by weblate
2021-07-21 09:47:27 +02:00
dedecosta
d07783a453 Translated on translate.pretix.eu (Italian)
Currently translated at 100.0% (170 of 170 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/it/

powered by weblate
2021-07-21 09:47:27 +02:00
dedecosta
1ce331f163 Translated on translate.pretix.eu (Italian)
Currently translated at 16.5% (720 of 4351 strings)

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

powered by weblate
2021-07-21 09:47:27 +02:00
Abdullah
586f95bc6d Translated on translate.pretix.eu (Arabic)
Currently translated at 89.4% (3892 of 4351 strings)

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

powered by weblate
2021-07-21 09:47:27 +02:00
Frank
5620aec5f2 Translated on translate.pretix.eu (Italian)
Currently translated at 14.4% (629 of 4351 strings)

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

powered by weblate
2021-07-21 09:47:27 +02:00
Martin Gross
c1dfec20f6 Always register ApplePay domains in livemode (#2154) 2021-07-21 09:41:57 +02:00
Raphael Michel
7fef81bdef Add fix and text for 0f2e90567 2021-07-20 18:14:59 +02:00
Raphael Michel
0f2e905672 Allow to remove seats that have canceled tickets 2021-07-20 17:17:26 +02:00
Raphael Michel
a57a4e7350 Subevent filter: Filter by start time 2021-07-20 09:28:15 +02:00
Raphael Michel
b57a6e982a Order import: Ignore completely empty lines 2021-07-20 08:55:07 +02:00
Raphael Michel
39736ef0d4 Check-in list: Allow to filter by date 2021-07-19 14:27:23 +02:00
Raphael Michel
f7e5f0b567 Order overview: Adjust to new filter style 2021-07-19 13:58:45 +02:00
Raphael Michel
b6078d5272 Allow to filter and sort the list of devices 2021-07-19 13:57:17 +02:00
Raphael Michel
1ed1cd33e8 Consistent styling for filter forms 2021-07-19 13:40:44 +02:00
Raphael Michel
a4a2500725 Subevent list: Allow to filter by time of day or multiple week days 2021-07-19 13:10:21 +02:00
Raphael Michel
3fb44ec9dd Widget: Fix crash if availability is unknown 2021-07-16 13:49:53 +02:00
Raphael Michel
2a96575b4d Event-level domains: Fix customer logout 2021-07-16 13:30:21 +02:00
Raphael Michel
dcf29ec63e Add workaround for issue with "back" button in Safari 2021-07-15 18:38:16 +02:00
Raphael Michel
a743605bd3 Stats: Allow queryset of subevents 2021-07-15 13:21:36 +02:00
Raphael Michel
75dc80eb09 API: Allow to set a custom pagination size 2021-07-13 18:20:12 +02:00
Richard Schreiber
ac16d9d900 Event calendar: Fix duplicate hidden inputs in week select form (#2152) 2021-07-12 16:12:07 +02:00
Raphael Michel
736d26c232 Order import: Fix redirect after import 2021-07-12 16:03:12 +02:00
Richard Schreiber
8985dfc5eb Fix #2150 -- Do not expose internal product names in modify view (#2151) 2021-07-12 15:55:32 +02:00
Martin Gross
bb80ef067a Fix nodejs doku link 2021-07-12 15:09:16 +02:00
Richard Schreiber
bdd9751f0e Widget: remove limit of 50 events in list-view by adding a load-more-button (#2144) 2021-07-10 21:37:27 +02:00
Raphael Michel
965aac6ad5 Add postfix to spellcheck list 2021-07-09 12:01:18 +02:00
Raphael Michel
e3858373d1 Fix missing Event.save() call after setting default plugins 2021-07-09 12:01:18 +02:00
Raphael Michel
fcdfae88d7 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4351 of 4351 strings)

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

powered by weblate
2021-07-09 11:54:40 +02:00
Raphael Michel
7d5a85e26f Translated on translate.pretix.eu (German)
Currently translated at 100.0% (4351 of 4351 strings)

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

powered by weblate
2021-07-09 11:54:40 +02:00
Raphael Michel
b8b2c2eba3 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2021-07-09 11:37:19 +02:00
Raphael Michel
c6a3280d69 Improve order confirmation message for free orders requiring approval 2021-07-09 11:34:36 +02:00
Raphael Michel
7f9368c415 QuestionStep: Correctly apply order of "initial" values 2021-07-08 09:28:36 +02:00
Raphael Michel
add764e3f0 Update django-i18nfield to 1.9.3 2021-07-06 11:28:45 +02:00
Raphael Michel
a3431cd51e Update django-i18nfield to 1.9.2 2021-07-06 10:31:04 +02:00
Martin Gross
9772d43235 Move PayPal postfix to end of settings list 2021-07-05 15:33:33 +02:00
Richard Schreiber
2e29e369f5 Fix celery progress: assign state/info once to avoid race-conditions (#2142) 2021-07-05 15:09:47 +02:00
Raphael Michel
9f6ce81229 Order import: Support phone number 2021-07-05 10:12:06 +02:00
Martin Gross
d67954de3f Extend WebHook URL to 255 chars (was: 200) 2021-07-05 09:56:50 +02:00
Martin Gross
d04f93d45c Revert the revertion + Fix "PayPal: Add postfix-field and pass information to custom-field" 2021-07-02 13:58:03 +02:00
Raphael Michel
ef70209ba8 Revert "PayPal: Add postfix-field and pass information to custom-field (#2137)"
This reverts commit 32f690e9d0.
2021-07-02 13:50:01 +02:00
Raphael Michel
f127cfc46a Dockerfile: Fix pretix version not known to pip 2021-07-02 10:07:49 +02:00
Raphael Michel
ec444e5bf3 Invoice: Show preview in browser during rebugging 2021-07-02 10:04:30 +02:00
Martin Gross
32f690e9d0 PayPal: Add postfix-field and pass information to custom-field (#2137) 2021-07-02 09:44:38 +02:00
Raphael Michel
9089b630ed Add new settings invoice_regenerate_allowed (#2071) 2021-07-01 14:51:08 +02:00
Richard Schreiber
0c6971ff5f Email: make responsive and show header image in MS Outlook (#2138) 2021-07-01 11:49:30 +02:00
Raphael Michel
59e92245de Merge branch 'release/4.1.x' 2021-06-30 16:39:42 +02:00
Raphael Michel
9894954233 Add static files to check-manifest ignore list 2021-06-30 16:14:27 +02:00
Raphael Michel
6e7505abd5 Add tests/plugins/sendmail to check-manifest ignore 2021-06-30 15:38:31 +02:00
Raphael Michel
9df381ec4c Do not require requirements.txt in GitLab CI 2021-06-30 15:26:53 +02:00
Raphael Michel
be726183cb Bump to 4.2.0.dev0 2021-06-30 15:15:27 +02:00
Raphael Michel
be5e2d8c33 Bump to 4.1.0 2021-06-30 15:14:46 +02:00
Raphael Michel
9da7321a19 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 97.6% (166 of 170 strings)

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

powered by weblate
2021-06-30 15:14:03 +02:00
Raphael Michel
cc7d95b805 Translated on translate.pretix.eu (German)
Currently translated at 97.6% (166 of 170 strings)

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

powered by weblate
2021-06-30 15:14:03 +02:00
Raphael Michel
5376dbffc0 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (4342 of 4342 strings)

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

powered by weblate
2021-06-30 15:14:03 +02:00
Raphael Michel
cddefd98d3 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (4342 of 4342 strings)

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

powered by weblate
2021-06-30 15:14:03 +02:00
Frank
5ae0e55f7e Translated on translate.pretix.eu (Italian)
Currently translated at 14.5% (629 of 4328 strings)

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

powered by weblate
2021-06-30 15:14:03 +02:00
Raphael Michel
856c36b85a Add "TODO" to German wordlist as well 2021-06-30 15:09:39 +02:00
Raphael Michel
a3bc717a5b Spellcheck: Add "TODO" to word list 2021-06-30 14:55:01 +02:00
Raphael Michel
6fa198a175 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2021-06-30 14:23:50 +02:00
Raphael Michel
9596f48fed Sendmail: Copy note from rule_update to rule_create 2021-06-30 12:41:34 +02:00
Raphael Michel
11b1c81633 Sendmail: Improve performance of creating rules 2021-06-30 12:41:03 +02:00
Maico Timmerman
212f33afee Widget: Use absolute URL for poweredby URL (#2131)
When the <pretix-widget> was hosted on a separate domain, the
source code URL was pointing to the wrong page. This updates the URL to
always point to the Pretix installation.
2021-06-30 12:40:10 +02:00
Raphael Michel
3fab15d086 Team list: Rewrite search query 2021-06-29 18:06:10 +02:00
Marc-Pascal Clement
2f1dd79162 Docs: Recommend upgrade strategy "eager" (#2135)
Pip's default strategy is to keep the version of all packages which do not explicitly require an upgrade. This caused issues for me for the second time now in the migration step, because some dependencies were not compatible with the new pretix version, but not explicitly listed as a such. Also I think the "eager" strategy better resembles what happens in the docker container build, as it always installs the newest versions.
2021-06-29 14:36:35 +02:00
Raphael Michel
e00ab01235 Teams list: Fix missing pagination 2021-06-29 11:23:39 +02:00
Raphael Michel
60f0e297e3 Order import: Improve subevent handling 2021-06-28 12:13:45 +02:00
Raphael Michel
93c791e16f Thumbnails: Do not convert JPG to PNG images 2021-06-28 09:18:28 +02:00
Raphael Michel
6da8caaa2b Calendar: Do not show next week if a same-day slot is still running 2021-06-27 18:19:03 +02:00
Raphael Michel
38ffd7d6ba OrderListExporter: Fix incorrect order of columns 2021-06-27 10:25:29 +02:00
Raphael Michel
ff4f56392d Fix isort issue 2021-06-25 11:34:44 +02:00
Raphael Michel
618b67ca2f Add meta_noindex setting on organizer level 2021-06-25 11:27:12 +02:00
Raphael Michel
a856a3ef6f Stripe: Fix a rare crash with malformed settings 2021-06-25 11:27:12 +02:00
Raphael Michel
573284c480 PayPal refunds: Actively fetch sale ID if not known (#2134)
Co-authored-by: Martin Gross <gross@rami.io>
2021-06-25 11:12:52 +02:00
Martin Gross
d4712266ff Properly slice the first 10 characters for QR-secret popover hint 2021-06-25 09:32:05 +02:00
Raphael Michel
e4f542b060 Check-in: Return "revoked" code even in online mode 2021-06-22 17:56:50 +02:00
Martin Gross
1b68e8bf0e Fix global invite-url (PRETIXEU-4N7) 2021-06-22 17:52:51 +02:00
Raphael Michel
c8d464ded7 Check-in: Change text represenation of "product" 2021-06-22 17:19:42 +02:00
Raphael Michel
12ab5ace9c Check-in errors: Change text of "result.product" 2021-06-22 17:19:05 +02:00
Raphael Michel
a2126c7b15 Check-in history: Allow to filter for error reasons 2021-06-22 17:18:42 +02:00
Raphael Michel
cba2ad5333 Add Invoice.fee_type, Invoice.fee_internal_type 2021-06-22 16:58:54 +02:00
Raphael Michel
8700c41f5e Event calendar: Forcefully break very long words 2021-06-22 16:48:14 +02:00
Raphael Michel
a88fed283a Customers: Additional filter form fields 2021-06-22 16:30:26 +02:00
Raphael Michel
130ffddf48 Customers: Allow admin to trigger password reset 2021-06-22 16:23:05 +02:00
Raphael Michel
f84b612d7b OrderChange: Fix invalid quota query 2021-06-22 15:51:26 +02:00
Richard Schreiber
e1ac22067a mail: strip image_src before converting to cid 2021-06-22 13:29:25 +02:00
Raphael Michel
60c3b76ee9 Order creation API: Fix possible crash (PRETIXEU-4MS) 2021-06-22 11:00:21 +02:00
Raphael Michel
fa8552e86f Event cancellation: Fix DivisionByZero when cancelling empty events 2021-06-22 10:58:56 +02:00
Raphael Michel
ecf1a40a5e Fix rare crash in async task handling (PRETIXEU-4MB) 2021-06-22 10:57:34 +02:00
Raphael Michel
ecfeae6ad9 OrderPosition.send_mail: Support for attach_ical argument 2021-06-22 10:56:43 +02:00
Raphael Michel
3544c3f5b8 Order change page: Fix pathological performance in event series 2021-06-22 10:50:09 +02:00
Raphael Michel
d8f3a3f5be Waiting list: Warn about functional limits 2021-06-22 10:49:27 +02:00
Raphael Michel
d6849c45fe Fix handling of cancellation requests with free and unpaid orders 2021-06-22 09:41:18 +02:00
Raphael Michel
eaf663794e Translated on translate.pretix.eu (Italian)
Currently translated at 14.5% (629 of 4328 strings)

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

powered by weblate
2021-06-22 09:40:34 +02:00
Frank
dbbd4fe47f Translated on translate.pretix.eu (Italian)
Currently translated at 14.5% (629 of 4328 strings)

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

powered by weblate
2021-06-22 09:40:34 +02:00
Raphael Michel
abab7dc874 Translated on translate.pretix.eu (Finnish)
Currently translated at 17.6% (766 of 4328 strings)

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

powered by weblate
2021-06-22 09:40:34 +02:00
Jaakko Rinta-Filppula
11ddfc511b Translated on translate.pretix.eu (Finnish)
Currently translated at 50.8% (85 of 167 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/fi/

powered by weblate
2021-06-22 09:40:34 +02:00
Jaakko Rinta-Filppula
3e50f3dd33 Translated on translate.pretix.eu (Finnish)
Currently translated at 17.6% (766 of 4328 strings)

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

powered by weblate
2021-06-22 09:40:34 +02:00
Abdullah
bf5becad82 Translated on translate.pretix.eu (Arabic)
Currently translated at 89.9% (3892 of 4328 strings)

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

powered by weblate
2021-06-22 09:40:34 +02:00
Abdullah
f191ce823a Translated on translate.pretix.eu (Arabic)
Currently translated at 99.4% (166 of 167 strings)

Translation: pretix/pretix (frontend)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ar/

powered by weblate
2021-06-22 09:40:34 +02:00
Abdullah
b03fed979f Translated on translate.pretix.eu (Arabic)
Currently translated at 89.9% (3892 of 4328 strings)

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

powered by weblate
2021-06-22 09:40:34 +02:00
Abdullah
91de41b782 Translated on translate.pretix.eu (Arabic)
Currently translated at 89.8% (3888 of 4328 strings)

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

powered by weblate
2021-06-22 09:40:34 +02:00
digitalesIch
ff1cfe269f Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 99.4% (166 of 167 strings)

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

powered by weblate
2021-06-22 09:40:34 +02:00
digitalesIch
2641a40142 Translated on translate.pretix.eu (German)
Currently translated at 99.4% (166 of 167 strings)

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

powered by weblate
2021-06-22 09:40:34 +02:00
Maico Timmerman
584d869729 Event creation: call plugin installed() after persisting event (#2133)
With #2089, PluginConfig.installed() is also called for newly created events.
However, the passed event argument is still not committed. The plugins
cannot use the event to insert or select objects, as it does not have an
ID yet.
2021-06-22 09:26:21 +02:00
Maico Timmerman
8b9b86a68d Event setup: do not create new team when staff session active (#2132)
With b260cca412, team provisioning has
been disabled for users that are staff. However, this leads to strange
UX, as a new event created by a staff member, not currently in a staff
session resulted in a 404 directly after creation.

The PR updates this requirement to not need to select a team, only when
a staff session is active.
2021-06-22 09:26:09 +02:00
Raphael Michel
b7f5631ad0 _merge_csp: Avoid duplicate values 2021-06-20 10:12:47 +02:00
Raphael Michel
038413be88 Remove pretix logo from error 500 pages
It triggers to many calls to our support if a self-hosted system is down
where we can do nothing at all...
2021-06-20 09:21:42 +02:00
Raphael Michel
4508745feb Widget: Support for rate-limited servers returning error 429 2021-06-19 18:03:14 +02:00
Raphael Michel
f9fa1733b0 OrganizerIndex cache: ignore cookies starting with __ 2021-06-18 19:28:43 +02:00
Raphael Michel
d50dff4a6e Add missing license header 2021-06-18 19:28:43 +02:00
Tim Neumann
2852722b50 Add redis sentinel support (#1909)
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2021-06-18 19:25:08 +02:00
Raphael Michel
1ef076bb9b Deal with cancelling memberships (#2130) 2021-06-17 18:10:45 +02:00
Raphael Michel
8ad53256c2 Show frontpage_text even in iframes 2021-06-17 11:06:26 +02:00
Raphael Michel
f51155a5df Waiting list emails: Add subevent, subevent_date_from placeholders 2021-06-16 16:54:04 +02:00
Raphael Michel
75f9824095 Run rjsmin over widget.js 2021-06-16 11:51:41 +02:00
Raphael Michel
9678ef3dd4 Fix missing show_names variable 2021-06-15 17:04:30 +02:00
Raphael Michel
4d945cf1e3 Proper fix for 8f05de700 2021-06-15 13:55:40 +02:00
Raphael Michel
8f05de7004 Fix incorrect pre-selection of month select box 2021-06-15 12:25:53 +02:00
Raphael Michel
72388abd57 Performance: Cache complete organizer index page, cache subevent list template fragment (#2125) 2021-06-15 00:02:47 +02:00
Raphael Michel
5801c8602e Revert "Performance: Cache complete organizer index page, cache subevent list template fragment (#2125)"
This reverts commit ba895270fa.
2021-06-14 23:16:19 +02:00
Raphael Michel
eb77f67d28 Fix AttributeError usage when creating external refund matching pending sum 2021-06-14 20:45:15 +02:00
Raphael Michel
ba895270fa Performance: Cache complete organizer index page, cache subevent list template fragment (#2125) 2021-06-14 17:12:11 +02:00
Martin Gross
cd88659351 Show secret in backend ticket barcode popover 2021-06-14 09:40:36 +02:00
Raphael Michel
eabead4768 Email renderers: Improve line height 2021-06-13 21:47:25 +02:00
Raphael Michel
9cb0cf210a Email renderer preview: Do not wrap placeholders in title tags 2021-06-13 21:45:45 +02:00
Raphael Michel
d181241a63 Email renderers: Adjust CSS for compatibility with css-inline 2021-06-13 21:45:28 +02:00
Raphael Michel
b3edb82ffd Performance: Only load ArabicReshaper once 2021-06-13 21:20:35 +02:00
Raphael Michel
eb5ed2bdf9 Performance: Do not load the same TTF font multiple times 2021-06-13 21:20:35 +02:00
Raphael Michel
c132ccd141 Performance: Replace inlinestyler with css_inline; Add drf-ujson (#2123) 2021-06-13 21:20:18 +02:00
Raphael Michel
fb2827e9ab AsyncMixin: Fix occasional crash (PRETIXEU-49P) 2021-06-11 18:09:12 +02:00
Raphael Michel
bb89bf68ef Do not show waiting list if presale is over 2021-06-11 17:58:23 +02:00
Raphael Michel
97d67d58d5 Add Order.custom_followup_at (#2124) 2021-06-11 17:08:13 +02:00
Raphael Michel
3235f90876 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (167 of 167 strings)

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

powered by weblate
2021-06-11 13:12:18 +02:00
Raphael Michel
227b2513b4 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 98.5% (4265 of 4328 strings)

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

powered by weblate
2021-06-11 13:12:18 +02:00
Raphael Michel
5952fdccb8 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (4328 of 4328 strings)

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

powered by weblate
2021-06-11 13:12:18 +02:00
dependabot[bot]
4874748aa2 Bump glob-parent from 5.1.1 to 5.1.2 in /src/pretix/static/npm_dir (#2122)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-11 12:56:24 +02:00
Raphael Michel
030ea269b0 Typocheck: Add "OK" to german wordlist 2021-06-11 12:55:50 +02:00
Raphael Michel
d187a497f9 Restructure dependencies (#2121) 2021-06-11 12:51:20 +02:00
Richard Schreiber
efc2efac84 Exporters: Fix crash in error url determination (#2114) 2021-06-09 18:40:21 +02:00
Raphael Michel
3378744a5c Translated on translate.pretix.eu (German)
Currently translated at 100.0% (167 of 167 strings)

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

powered by weblate
2021-06-09 18:39:59 +02:00
Raphael Michel
71ac461929 Translated on translate.pretix.eu (German)
Currently translated at 98.7% (4273 of 4328 strings)

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

powered by weblate
2021-06-09 18:39:59 +02:00
Raphael Michel
039da531c4 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 100.0% (167 of 167 strings)

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

powered by weblate
2021-06-09 18:39:59 +02:00
Raphael Michel
91e080d962 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (167 of 167 strings)

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

powered by weblate
2021-06-09 18:39:59 +02:00
Raphael Michel
b78cf9f2c5 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 98.4% (4261 of 4328 strings)

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

powered by weblate
2021-06-09 18:39:59 +02:00
Raphael Michel
af68053195 Translated on translate.pretix.eu (German)
Currently translated at 98.4% (4263 of 4328 strings)

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

powered by weblate
2021-06-09 18:39:59 +02:00
Maico Timmerman
2dd1e567cf Event creation: set the organizer locales as default (#2109) 2021-06-09 18:39:44 +02:00
Raphael Michel
33400ed7cc Sendmail: Fix issue during scheduling (PRETIXEU-4FY) 2021-06-09 18:15:41 +02:00
Raphael Michel
fe2e01938a Orders API: Silently fail if an answer to a question is submitted twice 2021-06-09 17:42:39 +02:00
Richard Schreiber
fccd119a1f Waitinglist: fix batch selection, add event.slug to download-filename (#2116)
* fix missing id in inputs for batch delete

* add event.slug to filename
2021-06-09 13:21:02 +02:00
Raphael Michel
b1dee5ae7c AsyncFormView: Pass query string 2021-06-07 16:55:57 +02:00
Raphael Michel
3e178a7293 Calendar: Fix hide/show of selected day contents 2021-06-07 12:52:25 +02:00
Raphael Michel
193407d819 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2021-06-07 12:48:20 +02:00
lapor-kris
25419dc8e8 Translated on translate.pretix.eu (Slovenian)
Currently translated at 30.0% (1281 of 4261 strings)

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

powered by weblate
2021-06-07 12:47:33 +02:00
Raphael Michel
cec27d7a44 Docs: Fix wrong parameter documentation 2021-06-07 12:42:01 +02:00
Raphael Michel
881f0e04a0 Do not offer to create a new customer account if a membership is required 2021-06-07 12:41:03 +02:00
Raphael Michel
5ee51c8f9a Sendmail: Fix missing gettext() call 2021-06-07 12:40:53 +02:00
Julia Luna
050f3990c3 Sendmail: Allow to disable rules (#2111) 2021-06-07 12:40:01 +02:00
Raphael Michel
b11ae9e5dd Multiple choice fields: Make search optional 2021-06-05 13:01:31 +02:00
Raphael Michel
c7ef79be90 Store all check-in attempts, not only successful ones (#2074) 2021-06-05 13:00:58 +02:00
Raphael Michel
9c3fc69176 Quotas API: Allow to bulk-query availability 2021-06-04 17:16:42 +02:00
Maico Timmerman
18df9d66bb i18n: add config for enabling a set of languages
Adds the `enabled` setting to the `[languages]` table in the pretix.cfg.
If left empty, or  omitted all languages are enabled.
2021-06-04 15:35:09 +02:00
Richard Schreiber
a43625c5e8 fix ignore_for_event_availability for batch edit of subevents (#2107) 2021-06-04 11:02:35 +02:00
Raphael Michel
a4d9d7041c Customer login: Normalize email addresses 2021-06-03 16:46:56 +02:00
Raphael Michel
96eabebc15 Stripe: Improve refund error handling 2021-06-03 16:37:02 +02:00
Raphael Michel
3819df57d8 Fix incorrect session handling in checkout flow 2021-06-03 14:22:50 +02:00
Raphael Michel
0fee7b0613 Sendmail rules: Fix typo 2021-06-03 14:22:42 +02:00
Raphael Michel
1a17f54354 Sendmail rules: Add "missed" state 2021-06-03 13:57:18 +02:00
Raphael Michel
750231eb3c Sendmail rules: Use same labels as manual mail form 2021-06-03 13:55:05 +02:00
Raphael Michel
1bb84b7296 Sendmail rules: Allow 0 days 2021-06-03 13:54:52 +02:00
Raphael Michel
6d9ef397ee Fix bug introduced in PR refactoring 2021-06-03 13:28:45 +02:00
Julia Luna
64d07a2811 Auto-scheduled emails
Co-authored-by: Raphael Michel <michel@rami.io>
2021-06-03 12:47:07 +02:00
Mohamed Tawfiq
e4949b6491 Translated on translate.pretix.eu (Arabic)
Currently translated at 91.2% (3888 of 4261 strings)

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

powered by weblate
2021-06-03 09:30:45 +02:00
Maico Timmerman
71e7df3038 Event API: Add search parameter (#2088)
The events API is paginated with 50 events. When searching for a
specific event within a large Pretix installations UX becomes very bad.
Using this filter users are able to quickly find their event by slug or
their name/location in any language.
2021-06-03 09:25:45 +02:00
Raphael Michel
47df6fe2bc Rich text: Build regex lazily (slight import time improvement) 2021-06-03 09:09:40 +02:00
Raphael Michel
ec08faf205 Docs: Fix a missing colon 2021-06-03 09:08:03 +02:00
Raphael Michel
76e86cbdd1 Fix ignore_for_event_availability in SubEvent bulk editor 2021-06-02 18:24:01 +02:00
Raphael Michel
f1b072b9a4 Fix verification of used seats in is_still_available 2021-06-02 12:35:10 +02:00
Raphael Michel
e792e8fd1e Fix sender address for organizer-level emails 2021-06-02 12:30:50 +02:00
Raphael Michel
bc8b3f504c Event API: Allow to query availability 2021-06-01 19:14:41 +02:00
Raphael Michel
0bcbfda276 Do not propose "-2-2" as a slug when cloning events 2021-06-01 16:07:19 +02:00
Raphael Michel
db029882ec Fix case-sensitive email handling in password recovery 2021-06-01 15:30:36 +02:00
Fabian
a1cc17094d Docs: Note to use UTF8 in PostgreSQL #2096 (#2103)
* Docs: Note to use UTF8 in PostgreSQL #2096

* Docs:  Same like 288f85b but for Docker docs
2021-06-01 13:57:30 +02:00
Raphael Michel
1c763ccce3 Docs: Fix missing typo 2021-06-01 12:28:51 +02:00
Raphael Michel
36fd5e7d01 Require django-countries 7.2 2021-06-01 12:20:13 +02:00
Raphael Michel
66cf9c1ac7 Docs: We do no longer require the [postgres] item 2021-06-01 12:20:13 +02:00
Fabian
0b9b67d603 Docs: Update Python Version in Nginx Config (#2101)
Debian 10 is shipping with Python 3.7 so the Debian 10 based and tested guide should use Python 3.7 in the nginx config.
2021-06-01 08:16:42 +02:00
Raphael Michel
520fb62088 Bump to 4.1.0.dev0 2021-05-31 15:27:31 +02:00
526 changed files with 299898 additions and 130678 deletions

15
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pip"
directory: "/src"
schedule:
interval: "daily"
- package-ecosystem: "npm"
directory: "/src/pretix/static/npm_dir"
schedule:
interval: "monthly"

View File

@@ -33,7 +33,8 @@ jobs:
- name: Install system packages
run: sudo apt update && sudo apt install enchant hunspell aspell-en
- name: Install Dependencies
run: pip3 install -Ur doc/requirements.txt
run: pip3 install -Ur requirements.txt
working-directory: ./doc
- name: Spellcheck docs
run: make spelling
working-directory: ./doc

View File

@@ -31,7 +31,8 @@ jobs:
- name: Install system packages
run: sudo apt update && sudo apt install gettext
- name: Install Dependencies
run: pip3 install -Ur src/requirements.txt
run: pip3 install -e ".[dev]"
working-directory: ./src
- name: Compile messages
run: python manage.py compilemessages
working-directory: ./src
@@ -56,7 +57,8 @@ jobs:
- name: Install system packages
run: sudo apt update && sudo apt install enchant hunspell hunspell-de-de aspell-en aspell-de
- name: Install Dependencies
run: pip3 install -Ur src/requirements/dev.txt
run: pip3 install -e ".[dev]"
working-directory: ./src
- name: Spellcheck translations
run: potypo
working-directory: ./src

View File

@@ -29,7 +29,8 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install Dependencies
run: pip3 install -Ur src/requirements/dev.txt
run: pip3 install -e ".[dev]" mysqlclient psycopg2-binary
working-directory: ./src
- name: Run isort
run: isort -c .
working-directory: ./src
@@ -49,7 +50,8 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install Dependencies
run: pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt
run: pip3 install -e ".[dev]" mysqlclient psycopg2-binary
working-directory: ./src
- name: Run flake8
run: flake8 .
working-directory: ./src

View File

@@ -18,17 +18,17 @@ jobs:
name: Tests
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
python-version: ["3.7", "3.8", "3.9"]
database: [sqlite, postgres, mysql]
exclude:
- database: mysql
python-version: 3.7
- database: sqlite
python-version: 3.7
python-version: "3.8"
- database: mysql
python-version: 3.6
python-version: "3.9"
- database: sqlite
python-version: 3.6
python-version: "3.7"
- database: sqlite
python-version: "3.8"
steps:
- uses: actions/checkout@v2
- uses: getong/mariadb-action@v1.1
@@ -55,9 +55,10 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install system dependencies
run: sudo apt update && sudo apt install gettext mysql-client
run: sudo apt update && sudo apt install gettext mariadb-client
- name: Install Python dependencies
run: pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt mysqlclient psycopg2-binary
run: pip3 install -e ".[dev]" mysqlclient psycopg2-binary
working-directory: ./src
- name: Run checks
run: python manage.py check
working-directory: ./src

View File

@@ -5,8 +5,8 @@ tests:
- virtualenv env
- source env/bin/activate
- pip install -U pip wheel setuptools
- XDG_CACHE_HOME=/cache pip3 install -r src/requirements.txt --no-use-pep517 -Ur src/requirements/dev.txt
- cd src
- XDG_CACHE_HOME=/cache pip3 install -e ".[dev]"
- python manage.py check
- make all compress
- py.test --reruns 3 -n 3 tests
@@ -21,8 +21,8 @@ pypi:
- virtualenv env
- source env/bin/activate
- pip install -U pip wheel setuptools check-manifest twine
- XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt
- cd src
- XDG_CACHE_HOME=/cache pip3 install -e ".[dev]"
- python setup.py sdist
- pip install dist/pretix-*.tar.gz
- python -m pretix migrate

View File

@@ -1,9 +1,9 @@
FROM python:3.8
FROM python:3.9-bullseye
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
default-libmysqlclient-dev \
libmariadb-dev \
gettext \
git \
libffi-dev \
@@ -15,8 +15,7 @@ RUN apt-get update && \
libxslt1-dev \
locales \
nginx \
python-dev \
python-virtualenv \
python3-virtualenv \
python3-dev \
sudo \
supervisor \
@@ -41,17 +40,14 @@ ENV LC_ALL=C.UTF-8 \
DJANGO_SETTINGS_MODULE=production_settings
# To copy only the requirements files needed to install from PIP
COPY src/requirements /pretix/src/requirements
COPY src/requirements.txt /pretix/src
COPY src/setup.py /pretix/src/setup.py
RUN pip3 install -U \
pip \
setuptools \
wheel && \
cd /pretix/src && \
pip3 install \
-r requirements.txt \
-r requirements/memcached.txt \
-r requirements/mysql.txt \
PRETIX_DOCKER_BUILD=TRUE pip3 install \
-e ".[memcached,mysql]" \
gunicorn django-extensions ipython && \
rm -rf ~/.cache/pip
@@ -60,10 +56,11 @@ COPY deployment/docker/supervisord /etc/supervisord
COPY deployment/docker/supervisord.all.conf /etc/supervisord.all.conf
COPY deployment/docker/supervisord.web.conf /etc/supervisord.web.conf
COPY deployment/docker/nginx.conf /etc/nginx/nginx.conf
COPY deployment/docker/nginx-max-body-size.conf /etc/nginx/conf.d/nginx-max-body-size.conf
COPY deployment/docker/production_settings.py /pretix/src/production_settings.py
COPY src /pretix/src
RUN cd /pretix/src && pip3 install .
RUN cd /pretix/src && python setup.py install
RUN chmod +x /usr/local/bin/pretix && \
rm /etc/nginx/sites-enabled/default && \

View File

@@ -0,0 +1 @@
client_max_body_size 100M;

View File

@@ -16,7 +16,6 @@ http {
charset utf-8;
tcp_nopush on;
tcp_nodelay on;
client_max_body_size 100M;
log_format private '[$time_local] $host "$request" $status $body_bytes_sent';
@@ -66,9 +65,18 @@ http {
access_log off;
expires 365d;
add_header Cache-Control "public";
add_header Access-Control-Allow-Origin "*";
gzip on;
}
location / {
proxy_pass http://unix:/tmp/pretix.sock:/;
# Very important:
# proxy_pass http://unix:/tmp/pretix.sock:;
# is not the same as
# proxy_pass http://unix:/tmp/pretix.sock:/;
# In the latter case, nginx will apply its URL parsing, in the former it doesn't.
# There are situations in which pretix' API will deal with "file names" containing %2F%2F, which
# nginx will normalize to %2F, which can break ticket validation.
proxy_pass http://unix:/tmp/pretix.sock:;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}

View File

@@ -220,12 +220,30 @@ Example::
``user``, ``password``
The SMTP user data to use for the connection. Empty by default.
``tls``, ``ssl``
Use STARTTLS or SSL for the SMTP connection. Off by default.
``from``
The email address to set as ``From`` header in outgoing emails by the system.
Default: ``pretix@localhost``
``tls``, ``ssl``
Use STARTTLS or SSL for the SMTP connection. Off by default.
``from_notifications``
The email address to set as ``From`` header in admin notification emails by the system.
Defaults to the value of ``from``.
``from_organizers``
The email address to set as ``From`` header in outgoing emails by the system sent on behalf of organizers.
Defaults to the value of ``from``.
``custom_sender_verification_required``
If this is on (the default), organizers need to verify email addresses they want to use as senders in their event.
``custom_sender_spf_string``
If this is set to a valid SPF string, pretix will show a warning if organizers use a sender address from a domain
that does not include this value.
``custom_smtp_allow_private_networks``
If this is off (the default), custom SMTP servers cannot be private network addresses.
``admins``
Comma-separated list of email addresses that should receive a report about every error code 500 thrown by pretix.
@@ -282,7 +300,7 @@ You can use an existing memcached server as pretix's caching backend::
``location``
The location of memcached, either a host:port combination or a socket file.
If no memcached is configured, pretix will use Django's built-in local-memory caching method.
If no memcached is configured, pretix will use redis for caching. If neither is configured, pretix will not use any caching.
.. note:: If you use memcached and you deploy pretix across multiple servers, you should use *one*
shared memcached instance, not multiple ones, because cache invalidations would not be
@@ -297,6 +315,12 @@ to speed up various operations::
[redis]
location=redis://127.0.0.1:6379/1
sessions=false
sentinels=[
["sentinel_host_1", 26379],
["sentinel_host_2", 26379],
["sentinel_host_3", 26379]
]
password=password
``location``
The location of redis, as a URL of the form ``redis://[:password]@localhost:6379/0``
@@ -305,13 +329,34 @@ to speed up various operations::
``session``
When this is set to ``True``, redis will be used as the session storage.
``sentinels``
Configures redis sentinels to use.
If you don't want to use redis sentinels, you should omit this option.
If this is set, redis via sentinels will be used instead of plain redis.
In this case the location should be of the form ``redis://my_master/0``.
The ``sentinels`` variable should be a json serialized list of sentinels,
each being a list with the two elements hostname and port.
You cannot provide a password within the location when using sentinels.
Note that the configuration format requires you to either place the entire
value on one line or make sure all values are indented by at least one space.
``password``
If your redis setup doesn't require a password or you already specified it in the location you can omit this option.
If this is set it will be passed to redis as the connection option PASSWORD.
If redis is not configured, pretix will store sessions and locks in the database. If memcached
is configured, memcached will be used for caching instead of redis.
Translations
------------
pretix comes with a number of translations. Some of them are marked as "incubating", which means
pretix comes with a number of translations. All languages are enabled by default. If you want to limit
the languages available in your installation, you can enable a set of languages like this::
[languages]
enabled=en,de
Some of the languages them are marked as "incubating", which means
they can usually only be selected in development mode. If you want to use them nevertheless, you
can activate them like this::
@@ -337,11 +382,22 @@ an AMQP server (e.g. RabbitMQ) as a broker and redis or your database as a resul
[celery]
broker=amqp://guest:guest@localhost:5672//
backend=redis://localhost/0
broker_transport_options="{}"
backend_transport_options="{}"
RabbitMQ might be the better choice if you have a complex, multi-server, high-performance setup,
but as you already should have a redis instance ready for session and lock storage, we recommend
redis for convenience. See the `Celery documentation`_ for more details.
The two ``transport_options`` entries can be omitted in most cases.
If they are present they need to be a valid JSON dictionary.
For possible entries in that dictionary see the `Celery documentation`_.
To use redis with sentinels set the broker or backend to ``sentinel://sentinel_host_1:26379;sentinal_host_2:26379/0``
and the respective transport_options to ``{"master_name":"mymaster"}``.
If your redis instances behind the sentinel have a password use ``sentinel://:my_password@sentinel_host_1:26379;sentinal_host_2:26379/0``.
If your redis sentinels themselves have a password set the transport_options to ``{"master_name":"mymaster","sentinel_kwargs":{"password":"my_password"}}``.
Sentry
------
@@ -396,3 +452,21 @@ pretix can make use of some external tools if they are installed. Currently, the
.. _Python documentation: https://docs.python.org/3/library/configparser.html?highlight=configparser#supported-ini-file-structure
.. _Celery documentation: http://docs.celeryproject.org/en/latest/userguide/configuration.html
Maximum upload file sizes
-------------------------
You can configure the maximum file size for uploading various files::
[pretix_file_upload]
; Max upload size for images in MiB, defaults to 10 MiB
max_size_image = 12
; Max upload size for favicons in MiB, defaults to 1 MiB
max_size_favicon = 2
; Max upload size for email attachments of manually sent emails in MiB, defaults to 10 MiB
max_size_email_attachment = 15
; Max upload size for email attachments of automatically sent emails in MiB, defaults to 1 MiB
max_size_email_auto_attachment = 2
; Max upload size for other files in MiB, defaults to 10 MiB
; This includes all file upload type order questions
max_size_other = 100

40
doc/admin/errors.rst Normal file
View File

@@ -0,0 +1,40 @@
.. _`admin-errors`:
Dealing with errors
===================
If you encounter an error in pretix, please follow the following steps to debug it:
* If the error message is shown on a **white page** and the last line of the error includes "nginx", the error is not with pretix
directly but with your nginx webserver. This might mean that pretix is not running, but it could also be something else.
Please first check your nginx error log. The default location is ``/var/log/nginx/error.log``.
* If it turns out pretix is not running, check the output of ``docker logs pretix`` for a docker installation and
``journalctl -u pretix-web.service`` for a manual installation.
* If the error message is an "**Internal Server Error**" in purple pretix design, please check pretix' log file which by default is at
``/var/pretix-data/logs/pretix.log`` if you installed with docker and ``/var/pretix/data/logs/pretix.log`` otherwise. If you don't
know how to interpret it, open a discussion on GitHub with the relevant parts of the log file.
* If the error message includes ``/usr/bin/env: node: No such file or directory``, you forgot to install ``node.js``
* If the error message includes ``OfflineGenerationError``, you might have forgot to run the ``rebuild`` step after a pretix update
or plugin installation.
* If the error message mentions your database server or redis server, make sure these are running and accessible.
* If pretix loads fine but certain actions (creating carts, orders, or exports, downloading tickets, sending emails) **take forever**,
``pretix-worker`` is not running. Check the output of ``docker logs pretix`` for a docker installation and
``journalctl -u pretix-worker.service`` for a manual installation.
* If the page loads but all **styles are missing**, you probably forgot to update your nginx configuration file after an upgrade of your
operating system's python version.
If you are unable to debug the issue any further, please open a **discussion** on GitHub in our `Q&A Forum`_. Do **not** open an issue
right away, since most things turn out not to be a bug in pretix but a mistake in your server configuration. Make sure to include
relevant log excerpts in your question.
If you're a pretix Enterprise customer, you can also reach out to support@pretix.eu with your issue right away.
.. _Q&A Forum: https://github.com/pretix/pretix/discussions/categories/q-a

View File

@@ -9,7 +9,9 @@ This documentation is for everyone who wants to install pretix on a server.
:maxdepth: 2
installation/index
updates
config
maintainance
scaling
errors
indexes

View File

@@ -50,7 +50,7 @@ Here is the currently recommended set of commands::
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_name
ON pretixbase_orderposition
USING gin (upper("attendee_name_cached") gin_trgm_ops);
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_scret
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_secret
ON pretixbase_orderposition
USING gin (upper("secret") gin_trgm_ops);
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_email

View File

@@ -39,6 +39,10 @@ Linux and firewalls, we recommend that you start with `ufw`_.
.. warning:: We recommend **PostgreSQL**. If you go for MySQL, make sure you run **MySQL 5.7 or newer** or
**MariaDB 10.2.7 or newer**.
.. warning:: By default, using `ufw` in conjunction will not have any effect. Please make sure to either bind the exposed
ports of your docker container explicitly to 127.0.0.1 or configure docker to respect any set up firewall
rules.
On this guide
-------------
@@ -58,7 +62,12 @@ Database
--------
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. For PostgreSQL, we would do::
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'
For PostgreSQL database creation, we would do::
# sudo -u postgres createuser -P pretix
# sudo -u postgres createdb -O pretix pretix
@@ -178,7 +187,7 @@ named ``/etc/systemd/system/pretix.service`` with the following content::
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill %n
ExecStartPre=-/usr/bin/docker rm %n
ExecStart=/usr/bin/docker run --name %n -p 8345:80 \
ExecStart=/usr/bin/docker run --name %n -p 127.0.0.1:8345:80 \
-v /var/pretix-data:/data \
-v /etc/pretix:/etc/pretix \
-v /var/run/redis:/var/run/redis \
@@ -228,7 +237,7 @@ The following snippet is an example on how to configure a nginx proxy for pretix
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8345/;
proxy_pass http://localhost:8345;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
@@ -247,6 +256,8 @@ create an event and start selling tickets!
You should probably read :ref:`maintainance` next.
.. _`docker_updates`:
Updates
-------
@@ -262,6 +273,8 @@ Restarting the service can take a few seconds, especially if the update requires
Replace ``stable`` above with a specific version number like ``1.0`` or with ``latest`` for the development
version, if you want to.
Make sure to also read :ref:`update_notes` and the release notes of the version you are updating to.
.. _`docker_plugininstall`:
Install a plugin

View File

@@ -25,7 +25,7 @@ installation guides):
* 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 `redis`_ server
* A `nodejs_` installation
* A `nodejs`_ installation
We also recommend that you use a firewall, although this is not a pretix-specific recommendation. If you're new to
Linux and firewalls, we recommend that you start with `ufw`_.
@@ -51,7 +51,12 @@ Database
--------
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. For PostgreSQL, we would do::
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'
For PostgreSQL database creation, we would do::
# sudo -u postgres createuser pretix
# sudo -u postgres createdb -O pretix pretix
@@ -67,7 +72,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 libmariadbclient-dev libjpeg-dev libopenjp2-7-dev
gettext libpq-dev libmariadb-dev libjpeg-dev libopenjp2-7-dev
Config file
-----------
@@ -129,12 +134,15 @@ python installation::
$ source /var/pretix/venv/bin/activate
(venv)$ pip3 install -U pip setuptools wheel
We now install pretix, its direct dependencies and gunicorn. Replace ``postgres`` with ``mysql`` in the following
command if you're running MySQL::
We now install pretix, its direct dependencies and gunicorn::
(venv)$ pip3 install "pretix[postgres]" gunicorn
(venv)$ pip3 install pretix gunicorn
Note that you need Python 3.6 or newer. You can find out your Python version using ``python -V``.
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``.
We also need to create a data directory::
@@ -229,7 +237,7 @@ The following snippet is an example on how to configure a nginx proxy for pretix
add_header X-Content-Type-Options nosniff;
location / {
proxy_pass http://localhost:8345/;
proxy_pass http://localhost:8345;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
@@ -251,14 +259,14 @@ The following snippet is an example on how to configure a nginx proxy for pretix
}
location /static/ {
alias /var/pretix/venv/lib/python3.5/site-packages/pretix/static.dist/;
alias /var/pretix/venv/lib/python3.10/site-packages/pretix/static.dist/;
access_log off;
expires 365d;
add_header Cache-Control "public";
}
}
.. note:: Remember to replace the ``python3.5`` in the ``/static/`` path in the config
.. note:: Remember to replace the ``python3.10`` in the ``/static/`` path in the config
above with your python version.
We recommend reading about setting `strong encryption settings`_ for your web server.
@@ -272,21 +280,23 @@ create an event and start selling tickets!
You should probably read :ref:`maintainance` next.
.. _`manual_updates`:
Updates
-------
.. warning:: While we try hard not to break things, **please perform a backup before every upgrade**.
To upgrade to a new pretix release, pull the latest code changes and run the following commands (again, replace
``postgres`` with ``mysql`` if necessary)::
To upgrade to a new pretix release, pull the latest code changes and run the following commands::
$ source /var/pretix/venv/bin/activate
(venv)$ pip3 install -U pretix[postgres] gunicorn
(venv)$ pip3 install -U --upgrade-strategy eager pretix gunicorn
(venv)$ python -m pretix migrate
(venv)$ python -m pretix rebuild
(venv)$ python -m pretix updatestyles
# systemctl restart pretix-web pretix-worker
Make sure to also read :ref:`update_notes` and the release notes of the version you are updating to.
.. _`manual_plugininstall`:

View File

@@ -9,6 +9,8 @@ If you host your own pretix instance, you also need to care about the availabili
of your service and the safety of your data yourself. This page gives you some
information that you might need to do so properly.
.. _`backups`:
Backups
-------

51
doc/admin/updates.rst Normal file
View File

@@ -0,0 +1,51 @@
.. _`update_notes`:
Update notes
============
pretix receives regular feature and bugfix updates and we highly encourage you to always update to
the latest version for maximum quality and security. Updates are announces on our `blog`_. There are
usually 10 feature updates in a year, so you can expect a new release almost every month.
Pure bugfix releases are only issued in case of very critical bugs or security vulnerabilities. In these
case, we'll publish bugfix releases for the last three stable release branches.
Compatibility to plugins and in very rare cases API clients may break. For in-depth details on the
API changes of every version, please refer to the release notes published on our blog.
Upgrade steps
-------------
For the actual upgrade, you can usually just follow the steps from the installation guide for :ref:`manual installations <manual_updates>`
or :ref:`docker installations <docker_updates>` respectively.
Generally, it is always strongly recommended to perform a :ref:`backup <backups>` first.
It is possible to skip versions during updates, although we recommend not skipping over major version numbers
(i.e. if you want to go from 2.4 to 4.4, first upgrade to 3.0, then upgrade to 4.0, then to 4.4).
In addition to these standard update steps, the following list issues steps that should be taken when you upgrade
to specific versions for pretix. If you're skipping versions, please read the instructions for every version in
between as well.
Upgrade to 3.17.0 or newer
""""""""""""""""""""""""""
pretix 3.17 introduces a dependency on ``nodejs``, so you should install it on your system::
# apt install nodejs npm
Upgrade to 4.4.0 or newer
"""""""""""""""""""""""""
pretix 4.4 introduces a new data structure to store historical financial data. If you already have existing
data in your database, you will need to back-fill this data or you might get incorrect reports! This is not
done automatically as part of the usual update steps since it can take a while on large databases and you might
want to do it in parallel while the system is already running again. Please execute the following command::
(venv)$ python -m pretix create_order_transactions
Or, with a docker installation::
$ docker exec -it pretix.service pretix create_order_transactions
.. _blog: https://pretix.eu/about/en/blog/

View File

@@ -87,7 +87,8 @@ respectively, or ``null`` if there is no such page. You can use those URLs to re
respective page.
The field ``results`` contains a list of objects representing the first results. For most
objects, every page contains 50 results.
objects, every page contains 50 results. You can specify a lower pagination size using the
``page_size`` query parameter, but no more than 50.
Conditional fetching
--------------------

View File

@@ -97,7 +97,8 @@ For example, if you want users to be redirected to ``https://example.org/order/r
either enter ``https://example.org`` or ``https://example.org/order/``.
The user will be redirected back to your page instead of pretix' order confirmation page after the payment,
**regardless of whether it was successful or not**. Make sure you use our API to check if the payment actually
**regardless of whether it was successful or not**. We will append an ``error=…`` query parameter with an error
message, but you should not rely on that and instead make sure you use our API to check if the payment actually
worked! Your final URL could look like this::
https://test.pretix.eu/democon/3vjrh/order/NSLEZ/ujbrnsjzbq4dzhck/pay/123/?return_url=https%3A%2F%2Fexample.org%2Forder%2Freturn%3Ftx_id%3D1234

View File

@@ -243,6 +243,99 @@ Cart position endpoints
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this
order.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/cartpositions/bulk_create/
Creates multiple new cart position. This operation is deliberately not atomic, so each cart position can succeed
or fail individually, so the response code of the response is not the only thing to look at!
.. warning:: This endpoint is considered **experimental**. It might change at any time without prior notice.
.. warning:: The same limitations as with the regular creation endpoint apply.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/cartpositions/bulk_create/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
[
{
"item": 1,
"variation": null,
"price": "23.00",
"attendee_name_parts": {
"given_name": "Peter",
"family_name": "Miller"
},
"attendee_email": null,
"answers": [
{
"question": 1,
"answer": "23",
"options": []
}
],
"subevent": null
},
{
"item": 1,
"variation": null,
"price": "23.00",
"attendee_name_parts": {
"given_name": "Maria",
"family_name": "Miller"
},
"attendee_email": null,
"answers": [
{
"question": 1,
"answer": "23",
"options": []
}
],
"subevent": null
}
]
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"results": [
{
"success": true,
"errors": null,
"data": {
"id": 1,
...
},
},
{
"success": "false",
"errors": {
"non_field_errors": ["There is not enough quota available on quota \"Tickets\" to perform the operation."]
},
"data": null
}
]
}
:param organizer: The ``slug`` field of the organizer of the event to create positions for
:param event: The ``slug`` field of the event to create positions for
:statuscode 200: See response for success
:statuscode 400: Your input could not be parsed
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this
order.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/cartpositions/(id)/
Deletes a cart position, identified by its internal ID.

View File

@@ -362,6 +362,42 @@ Endpoints
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/failed_checkins/
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.
:<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"``.
:<json position: Internal ID of an order position you matched. Optional.
:<json raw_item: Internal ID of an item you matched. Optional.
:<json raw_variation: Internal ID of an item variation you matched. Optional.
:<json raw_subevent: Internal ID of an event series date you matched. Optional.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/checkinlists/1/failed_checkins/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
{
"raw_barcode": "Pvrk50vUzQd0DhdpNRL4I4OcXsvg70uA",
"error_reason": "canceled"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param list: The ID of the check-in list to save for
:statuscode 201: no error
:statuscode 400: Invalid request
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested order position or check-in list does not exist.
Order position endpoints
------------------------
@@ -424,6 +460,9 @@ Order position endpoints
"checkins": [
{
"list": 1,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": true
}
@@ -535,6 +574,9 @@ Order position endpoints
{
"list": 1,
"datetime": "2017-12-25T12:45:23Z",
"type": "entry",
"gate": null,
"device": 2,
"auto_checked_in": true
}
],
@@ -562,6 +604,8 @@ Order position endpoints
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested order position or check-in list does not exist.
.. _`rest-checkin-redeem`:
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/(id)/redeem/
Tries to redeem an order position, identified by its internal ID, i.e. checks the attendee in. This endpoint
@@ -576,8 +620,9 @@ Order position endpoints
:<json boolean canceled_supported: When this parameter is set to ``true``, the response code ``canceled`` may be
returned. Otherwise, canceled orders will return ``unpaid``.
:<json datetime datetime: Specifies the datetime of the check-in. If not supplied, the current time will be used.
:<json boolean force: Specifies that the check-in should succeed regardless of previous check-ins or required
questions that have not been filled. Defaults to ``false``.
:<json boolean force: Specifies that the check-in should succeed regardless of revoked barcode, previous check-ins or required
questions that have not been filled. This is usually used to upload offline scans that already happened,
because there's no point in validating them since they happened whether they are valid or not. Defaults to ``false``.
:<json string type: Send ``"exit"`` for an exit and ``"entry"`` (default) for an entry.
:<json boolean ignore_unpaid: Specifies that the check-in should succeed even if the order is in pending state.
Defaults to ``false`` and only works when ``include_pending`` is set on the check-in

View File

@@ -84,6 +84,12 @@ Endpoints
The ``clone_from`` parameter has been added to the event creation endpoint.
.. versionchanged:: 4.1
The ``with_availability_for`` parameter has been added.
The ``search`` query parameter has been added to filter events by their slug, name, or location in any language.
.. http:get:: /api/v1/organizers/(organizer)/events/
Returns a list of all events within a given organizer the authenticated user/token has access to.
@@ -162,6 +168,11 @@ Endpoints
events having set their ``Format`` meta data to ``Seminar``, ``?attr[Format]=`` only those, that have no value
set. Please note that this filter will respect default values set on organizer level.
:query sales_channel: If set to a sales channel identifier, only events allowed to be sold on the specified sales channel are returned.
:query with_availability_for: If set to a sales channel identifier, the response will contain a special ``best_availability_state``
attribute with values of 100 for "tickets available", values less than 100 for "tickets sold out or reserved",
and ``null`` for "status unknown". These values might be served from a cache. This parameter can make the response
slow.
:query search: Only return events matching a given search query.
:param organizer: The ``slug`` field of a valid organizer
:statuscode 200: no error
:statuscode 401: Authentication failure

View File

@@ -31,5 +31,6 @@ Resources and endpoints
webhooks
seatingplans
exporters
sendmail_rules
billing_invoices
billing_var

View File

@@ -58,6 +58,21 @@ lines list of objects The actual invo
created before this field was introduced as well as for
all lines not created by a product (e.g. a shipping or
cancellation fee).
├ subevent integer Event series date ID used to create this line. Note that everything
about the subevent might have changed since the creation
of the invoice. Can be ``null`` for all invoice lines
created before this field was introduced as well as for
all lines not created by a product (e.g. a shipping or
cancellation fee) as well as for all events that are not a series.
├ fee_type string Fee type, e.g. ``shipping``, ``service``, ``payment``,
``cancellation``, ``giftcard``, or ``other. Can be ``null`` for
all invoice lines
created before this field was introduced as well as for
all lines not created by a fee (e.g. a product).
├ fee_internal_type string Additional fee type, e.g. type of payment provider. Can be ``null``
for all invoice lines
created before this field was introduced as well as for
all lines not created by a fee (e.g. a product).
├ event_date_from datetime Start date of the (sub)event this line was created for as it
was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in
@@ -69,6 +84,12 @@ lines list of objects The actual invo
an event series not created by a product (e.g. shipping or
cancellation fees) as well as whenever the respective (sub)event
has no end date set.
├ event_location string Location of the (sub)event this line was created for as it
was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in
an event series not created by a product (e.g. shipping or
cancellation fees) as well as whenever the respective (sub)event
has no location set.
├ attendee_name string Attendee name at time of invoice creation. Can be ``null`` if no
name was set or if names are configured to not be added to invoices.
├ gross_value money (string) Price including taxes
@@ -97,6 +118,18 @@ internal_reference string Customer's refe
``lines.event_date_to``, and ``lines.attendee_name`` have been added.
``refers`` now returns an invoice number including the prefix.
.. versionchanged:: 4.1
The attributes ``fee_type`` and ``fee_internal_type`` have been added.
.. versionchanged:: 4.1
The attribute ``lines.event_location`` has been added.
.. versionchanged:: 4.6
The attribute ``lines.subevent`` has been added.
Endpoints
---------
@@ -162,8 +195,12 @@ Endpoints
"description": "Budget Ticket",
"item": 1234,
"variation": 245,
"subevent": null,
"fee_type": null,
"fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null,
"event_location": "Heidelberg",
"attendee_name": null,
"gross_value": "23.00",
"tax_value": "0.00",
@@ -248,8 +285,12 @@ Endpoints
"description": "Budget Ticket",
"item": 1234,
"variation": 245,
"subevent": null,
"fee_type": null,
"fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null,
"event_location": "Heidelberg",
"attendee_name": null,
"gross_value": "23.00",
"tax_value": "0.00",

View File

@@ -24,8 +24,25 @@ 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
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``
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.
===================================== ========================== =======================================================
Endpoints
@@ -62,8 +79,14 @@ Endpoints
"en": "S"
},
"active": true,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": {
"en": "Test2"
},
@@ -78,7 +101,9 @@ Endpoints
"en": "L"
},
"active": true,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"description": {},
"position": 1,
@@ -127,8 +152,14 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": true,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 0
}
@@ -158,8 +189,14 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"active": true,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 0
}
@@ -179,8 +216,14 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": true,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 0
}
@@ -231,8 +274,14 @@ Endpoints
"price": "10.00",
"original_price": null,
"active": false,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 1
}

View File

@@ -70,6 +70,8 @@ require_approval boolean If ``true``, or
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.
@@ -105,8 +107,22 @@ variations list of objects A list with one
├ 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.
└ 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,
@@ -143,6 +159,10 @@ meta_data object Values set for
The attributes ``require_membership``, ``require_membership_types``, ``grant_membership_type``, ``grant_membership_duration_like_event``,
``grant_membership_duration_days`` and ``grant_membership_duration_months`` have been added.
.. versionchanged:: 4.4
The attributes ``require_membership_hidden`` attribute has been added.
Notes
-----
@@ -230,6 +250,10 @@ Endpoints
"active": true,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 0
},
@@ -241,6 +265,10 @@ Endpoints
"active": true,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 1
}
@@ -337,6 +365,10 @@ Endpoints
"require_membership": false,
"require_membership_types": [],
"description": null,
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"position": 0
},
{
@@ -347,6 +379,10 @@ Endpoints
"active": true,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 1
}
@@ -422,6 +458,10 @@ Endpoints
"active": true,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 0
},
@@ -433,6 +473,10 @@ Endpoints
"active": true,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 1
}
@@ -497,6 +541,10 @@ Endpoints
"active": true,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 0
},
@@ -508,6 +556,10 @@ Endpoints
"active": true,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 1
}
@@ -603,6 +655,10 @@ Endpoints
"active": true,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 0
},
@@ -614,6 +670,10 @@ Endpoints
"active": true,
"require_membership": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": null,
"position": 1
}

View File

@@ -41,6 +41,7 @@ payment_date date **DEPRECATED AN
payment_provider string **DEPRECATED AND INACCURATE** Payment provider used for this order
total money (string) Total value of this order
comment string Internal comment on this order
custom_followup_at date Internal date for a custom follow-up action
checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if a ticket
of this order is scanned.
@@ -123,6 +124,18 @@ last_modified datetime Last modificati
The ``customer`` attribute has been added.
.. versionchanged:: 4.1
The ``custom_followup_at`` attribute has been added.
.. versionchanged:: 4.4
The ``item`` and ``variation`` query parameters have been added.
.. versionchanged:: 4.6
The ``subevent`` query parameters has been added.
.. _order-position-resource:
@@ -160,11 +173,13 @@ 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``).
pseudonymization_id string A random ID, e.g. for use in lead scanning apps
checkins list of objects List of check-ins with this ticket
checkins list of objects List of **successful** check-ins with this ticket
├ id integer Internal ID of the check-in event
├ list integer Internal ID of the check-in list
├ datetime datetime Time of check-in
├ type string Type of scan (defaults to ``entry``)
├ gate integer Internal ID of the gate. Can be ``null``.
├ device integer Internal ID of the device. Can be ``null``.
└ auto_checked_in boolean Indicates if this check-in been performed automatically by the system
downloads list of objects List of ticket download options
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
@@ -305,6 +320,7 @@ List of all orders
"fees": [],
"total": "23.00",
"comment": "",
"custom_followup_at": null,
"checkin_attention": false,
"require_approval": false,
"invoice_address": {
@@ -355,6 +371,8 @@ List of all orders
{
"list": 44,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
@@ -405,6 +423,8 @@ List of all orders
:query string code: Only return orders that match the given order code
:query string status: Only return orders in the given order status (see above)
:query string search: Only return orders matching a given search query
:query integer item: Only return orders with a position that contains this item ID. *Warning:* Result will also include orders if they contain mixed items, and it will even return orders where the item is only contained in a canceled position.
:query integer variation: Only return orders with a position that contains this variation ID. *Warning:* Result will also include orders if they contain mixed items and variations, and it will even return orders where the variation is only contained in a canceled position.
:query boolean testmode: Only return orders with ``testmode`` set to ``true`` or ``false``
:query boolean require_approval: If set to ``true`` or ``false``, only categories with this value for the field
``require_approval`` will be returned.
@@ -417,6 +437,7 @@ List of all orders
recommend using this in combination with ``testmode=false``, since test mode orders can vanish at any time and
you will not notice it using this method.
:query datetime created_since: Only return orders that have been created since the given date.
:query integer subevent: Only return orders with a position that contains this subevent ID. *Warning:* Result will also include orders if they contain mixed subevents, and it will even return orders where the subevent is only contained in a canceled position.
:query datetime subevent_after: Only return orders that contain a ticket for a subevent taking place after the given date. This is an exclusive after, and it considers the **end** of the subevent (or its start, if the end is not set).
:query datetime subevent_before: Only return orders that contain a ticket for a subevent taking place after the given date. This is an exclusive before, and it considers the **start** of the subevent.
:query string exclude: Exclude a field from the output, e.g. ``fees`` or ``positions.downloads``. Can be used as a performance optimization. Can be passed multiple times.
@@ -474,6 +495,7 @@ Fetching individual orders
"fees": [],
"total": "23.00",
"comment": "",
"custom_followup_at": null,
"checkin_attention": false,
"require_approval": false,
"invoice_address": {
@@ -524,6 +546,8 @@ Fetching individual orders
{
"list": 44,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
@@ -638,6 +662,8 @@ Updating order fields
* ``comment``
* ``custom_followup_at``
* ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address)
**Example request**:
@@ -811,6 +837,7 @@ Creating orders
charge will be created), this is just informative in case you *handled the payment already*.
* ``payment_date`` (optional) Date and time of the completion of the payment.
* ``comment`` (optional)
* ``custom_followup_at`` (optional)
* ``checkin_attention`` (optional)
* ``invoice_address`` (optional)
@@ -1421,6 +1448,8 @@ List of all order positions
{
"list": 44,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
@@ -1527,6 +1556,8 @@ Fetching individual positions
{
"list": 44,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}

View File

@@ -28,12 +28,22 @@ closed boolean Whether the quo
field).
release_after_exit boolean Whether the quota regains capacity as soon as some tickets
have been scanned at an exit.
available boolean Whether this quota is available. Only returned if ``with_availability=true``
is set on the request. Do not rely on this value for critical operations, it may be
slightly out of date.
available_number integer Number of available tickets. Only returned if ``with_availability=true``
is set on the request. Do not rely on this value for critical operations, it may be
slightly out of date. ``null`` means unlimited.
===================================== ========================== =======================================================
.. versionchanged:: 3.10
The attribute ``release_after_exit`` has been added.
.. versionchanged:: 4.1
The ``with_availability`` query parameter has been added.
Endpoints
---------
@@ -80,6 +90,7 @@ Endpoints
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
Default: ``position``
:query integer subevent: Only return quotas of the sub-event with the given ID
:query string with_availability: Set to ``true`` to get availability information. Can lead to increased answer times.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:statuscode 200: no error
@@ -120,6 +131,7 @@ Endpoints
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the quota to fetch
:query string with_availability: Set to ``true`` to get availability information. Can lead to increased answer times.
: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

@@ -0,0 +1,281 @@
Automated email rules
=====================
Resource description
--------------------
Automated email rules that specify emails that the system will send automatically at a specific point in time, e.g.
the day of the event.
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the rule
enabled boolean If ``false``, the rule is ignored
subject multi-lingual string The subject of the email
template multi-lingual string The body of the email
all_products boolean If ``true``, the email is sent to buyers of all products
limit_products list of integers List of product IDs, if ``all_products`` is not set
include_pending boolean If ``true``, the email is sent to pending orders. If ``false``,
only paid orders are considered.
date_is_absolute boolean If ``true``, the email is set at a specific point in time.
send_date datetime If ``date_is_absolute`` is set: Date and time to send the email.
send_offset_days integer If ``date_is_absolute`` is not set, this is the number of days
before/after the email is sent.
send_offset_time time If ``date_is_absolute`` is not set, this is the time of day the
email is sent on the day specified by ``send_offset_days``.
offset_to_event_end boolean If ``true``, ``send_offset_days`` is relative to the event end
date. Otherwise it is relative to the event start date.
offset_is_after boolean If ``true``, ``send_offset_days`` is the number of days **after**
the event start or end date. Otherwise it is the number of days
**before**.
send_to string Can be ``"orders"`` if the email should be sent to customers
(one email per order),
``"attendees"`` if the email should be sent to every attendee,
or ``"both"``.
date. Otherwise it is relative to the event start date.
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/
Returns a list of all rules configured for an event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/sendmail_rules/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"enabled": true,
"subject": {"en": "See you tomorrow!"},
"template": {"en": "Don't forget your tickets, download them at {url}"},
"all_products": true,
"limit_products": [],
"include_pending": false,
"send_date": null,
"send_offset_days": 1,
"send_offset_time": "18:00",
"date_is_absolute": false,
"offset_to_event_end": false,
"offset_is_after": false,
"send_to": "orders"
}
]
}
:query page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of the event to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/(id)/
Returns information on one rule, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/sendmail_rules/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"enabled": true,
"subject": {"en": "See you tomorrow!"},
"template": {"en": "Don't forget your tickets, download them at {url}"},
"all_products": true,
"limit_products": [],
"include_pending": false,
"send_date": null,
"send_offset_days": 1,
"send_offset_time": "18:00",
"date_is_absolute": false,
"offset_to_event_end": false,
"offset_is_after": false,
"send_to": "orders"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the rule to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to view it.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/
Create a new rule.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/sendmail_rules/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 166
{
"enabled": true,
"subject": {"en": "See you tomorrow!"},
"template": {"en": "Don't forget your tickets, download them at {url}"},
"all_products": true,
"limit_products": [],
"include_pending": false,
"send_date": null,
"send_offset_days": 1,
"send_offset_time": "18:00",
"date_is_absolute": false,
"offset_to_event_end": false,
"offset_is_after": false,
"send_to": "orders"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"enabled": true,
"subject": {"en": "See you tomorrow!"},
"template": {"en": "Don't forget your tickets, download them at {url}"},
"all_products": true,
"limit_products": [],
"include_pending": false,
"send_date": null,
"send_offset_days": 1,
"send_offset_time": "18:00",
"date_is_absolute": false,
"offset_to_event_end": false,
"offset_is_after": false,
"send_to": "orders"
}
:param organizer: The ``slug`` field of the organizer to create a rule for
:param event: The ``slug`` field of the event to create a rule for
:statuscode 201: no error
:statuscode 400: The rule 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 rules.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/(id)/
Update a rule. 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/sendmail_rules/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 34
{
"enabled": false,
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"id": 1,
"enabled": false,
"subject": {"en": "See you tomorrow!"},
"template": {"en": "Don't forget your tickets, download them at {url}"},
"all_products": true,
"limit_products": [],
"include_pending": false,
"send_date": null,
"send_offset_days": 1,
"send_offset_time": "18:00",
"date_is_absolute": false,
"offset_to_event_end": false,
"offset_is_after": false,
"send_to": "orders"
}
: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 rule to modify
:statuscode 200: no error
:statuscode 400: The rule could not be modified due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to change it.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/(id)/
Delete a rule.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/sendmail_rules/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 rule to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to change it **or** this rule cannot be deleted since it is currently in use.

View File

@@ -82,6 +82,10 @@ Endpoints
The sub-events resource can now be filtered by meta data attributes.
.. versionchanged:: 4.1
The ``with_availability_for`` parameter has been added.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/subevents/
Returns a list of all sub-events of an event.
@@ -152,6 +156,10 @@ Endpoints
only those sub-events having set their ``Format`` meta data to ``Seminar``, ``?attr[Format]=`` only those, that
have no value set. Please note that this filter will respect default values set on
organizer or event level.
:query with_availability_for: If set to a sales channel identifier, the response will contain a special ``best_availability_state``
attribute with values of 100 for "tickets available", values less than 100 for "tickets sold out or reserved",
and ``null`` for "status unknown". These values might be served from a cache. This parameter can make the response
slow.
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.

View File

@@ -16,15 +16,22 @@ Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the tax rule
name multi-lingual string The tax rules' name
internal_name string An optional name that is only used in the backend
rate decimal (string) Tax rate in percent
price_includes_tax boolean If ``true`` (default), tax is assumed to be included in
the specified product price
eu_reverse_charge boolean If ``true``, EU reverse charge rules are applied
home_country string Merchant country (required for reverse charge), can be
``null`` or empty string
keep_gross_if_rate_changes boolean If ``true``, changes of the tax rate based on custom
rules keep the gross price constant (default is ``false``)
===================================== ========================== =======================================================
.. versionchanged:: 4.6
The ``internal_name`` and ``keep_gross_if_rate_changes`` attributes have been added.
Endpoints
---------
@@ -56,9 +63,11 @@ Endpoints
{
"id": 1,
"name": {"en": "VAT"},
"internal_name": "VAT",
"rate": "19.00",
"price_includes_tax": true,
"eu_reverse_charge": false,
"keep_gross_if_rate_changes": false,
"home_country": "DE"
}
]
@@ -94,9 +103,11 @@ Endpoints
{
"id": 1,
"name": {"en": "VAT"},
"internal_name": "VAT",
"rate": "19.00",
"price_includes_tax": true,
"eu_reverse_charge": false,
"keep_gross_if_rate_changes": false,
"home_country": "DE"
}
@@ -140,9 +151,11 @@ Endpoints
{
"id": 1,
"name": {"en": "VAT"},
"internal_name": "VAT",
"rate": "19.00",
"price_includes_tax": true,
"eu_reverse_charge": false,
"keep_gross_if_rate_changes": false,
"home_country": "DE"
}
@@ -185,9 +198,11 @@ Endpoints
{
"id": 1,
"name": {"en": "VAT"},
"internal_name": "VAT",
"rate": "20.00",
"price_includes_tax": true,
"eu_reverse_charge": false,
"keep_gross_if_rate_changes": false,
"home_country": "DE"
}

View File

@@ -0,0 +1,28 @@
.. spelling: libpretixsync
Check-in algorithms
===================
When a ticket is scanned at the entrance or exit of an event, we follow a series of steps to determine whether
the check-in is allowed or not. To understand some of the terms in the following diagrams, you should also check
out the documentation of the :ref:`ticket redemption API endpoint <rest-checkin-redeem>`.
Server-side
-----------
The following diagram shows the series of checks executed on the server when a ticket is redeemed through the API.
Some simplifications have been made, for example the deduplication mechanism based on the ``nonce`` parameter
to prevent re-uploads of the same scan is not shown.
.. image:: /images/checkin_online.png
Client-side
-----------
The process of verifying tickets offline is a little different. There are two different approaches,
depending on whether we have information about all tickets in the local database. The following diagram shows
the algorithm as currently implemented in recent versions of `libpretixsync`_.
.. image:: /images/checkin_offline.png
.. _libpretixsync: https://github.com/pretix/libpretixsync

View File

@@ -0,0 +1,13 @@
Algorithms
==========
The business logic inside pretix is full of complex algorithms making decisions based on all the hundreds of settings
and input parameters available. Some of them are documented here as graphs, either because fully understanding them is very important
when working on features close to them, or because they also need to be re-implemented by client-side components like our
ticket scanning apps and we want to ensure the implementations are as similar as possible to avoid confusion.
.. toctree::
:maxdepth: 2
checkin
layouts

View File

@@ -0,0 +1,15 @@
.. spelling: pretixPOS
Ticket layout
=============
When a ticket is exported to PDF, the system needs to decide which of multiple PDF layouts to use. The
following diagram shows the steps of the decision, showing both the implementation in pretix itself as
well as the implementation in `pretixPOS`_.
The process can be influenced by plugins, which is demonstrated with the example of the shipping plugin.
.. image:: /images/ticket_layouts.png
.. _pretixPOS: https://pretix.eu/about/en/pos

View File

@@ -0,0 +1,119 @@
.. highlight:: python
:linenothreshold: 5
.. _`cookieconsent`:
Handling cookie consent
=======================
pretix includes an optional feature to handle cookie consent explicitly to comply with EU regulations.
If your plugin sets non-essential cookies or includes a third-party service that does so, you should
integrate with this feature.
Server-side integration
-----------------------
First, you need to declare that you are using non-essential cookies by responding to the following
signal:
.. automodule:: pretix.presale.signals
:members: register_cookie_providers
You are expected to return a list of ``CookieProvider`` objects instantiated from the following class:
.. class:: pretix.presale.cookies.CookieProvider
.. py:attribute:: CookieProvider.identifier
A short and unique identifier used to distinguish this cookie provider form others (required).
.. py:attribute:: CookieProvider.provider_name
A human-readable name of the entity of feature responsible for setting the cookie (required).
.. py:attribute:: CookieProvider.usage_classes
A list of enum values from the ``pretix.presale.cookies.UsageClass`` enumeration class, such as
``UsageClass.ANALYTICS``, ``UsageClass.MARKETING``, or ``UsageClass.SOCIAL`` (required).
.. py:attribute:: CookieProvider.privacy_url
A link to a privacy policy (optional).
Here is an example of such a receiver:
.. code-block:: python
@receiver(register_cookie_providers)
def recv_cookie_providers(sender, request, **kwargs):
return [
CookieProvider(
identifier='google_analytics',
provider_name='Google Analytics',
usage_classes=[UsageClass.ANALYTICS],
)
]
JavaScript-side integration
---------------------------
The server-side integration only causes the cookie provider to show up in the cookie dialog. You still
need to care about actually enforcing the consent state.
You can access the consent state through the ``window.pretix.cookie_consent`` variable. Whenever the
value changes, a ``pretix:cookie-consent:change`` event is fired on the ``document`` object.
The variable will generally have one of the following states:
.. rst-class:: rest-resource-table
================================================================ =====================================================
State Interpretation
================================================================ =====================================================
``pretix === undefined || pretix.cookie_consent === undefined`` Your JavaScript has loaded before the cookie consent
script. Wait for the event to be fired, then try again,
do not yet set a cookie.
``pretix.cookie_consent === null`` The cookie consent mechanism has not been enabled. This
usually means that you can set cookies however you like.
``pretix.cookie_consent[identifier] === undefined`` The cookie consent mechanism is loaded, but has no data
on your cookie yet, wait for the event to be fired, do not
yet set a cookie.
``pretix.cookie_consent[identifier] === true`` The user has consented to your cookie.
``pretix.cookie_consent[identifier] === false`` The user has actively rejected your cookie.
================================================================ =====================================================
If you are integrating e.g. a tracking provider with native cookie consent support such
as Facebook's Pixel, you can integrate it like this:
.. code-block:: javascript
var consent = (window.pretix || {}).cookie_consent;
if (consent !== null && !(consent || {}).facebook) {
fbq('consent', 'revoke');
}
fbq('init', ...);
document.addEventListener('pretix:cookie-consent:change', function (e) {
fbq('consent', (e.detail || {}).facebook ? 'grant' : 'revoke');
})
If you have a JavaScript function that you only want to load if consent for a specific ``identifier``
is given, you can wrap it like this:
.. code-block:: javascript
var consent_identifier = "youridentifier";
var consent = (window.pretix || {}).cookie_consent;
if (consent === null || (consent || {})[consent_identifier] === true) {
// Cookie consent tool is either disabled or consent is given
addScriptElement(src);
return;
}
// Either cookie consent tool has not loaded yet or consent is not given
document.addEventListener('pretix:cookie-consent:change', function onChange(e) {
var consent = e.detail || {};
if (consent === null || consent[consent_identifier] === true) {
addScriptElement(src);
document.removeEventListener('pretix:cookie-consent:change', onChange);
}
})

View File

@@ -17,6 +17,7 @@ Contents:
shredder
import
customview
cookieconsent
auth
general
quality

View File

@@ -62,6 +62,8 @@ The provider class
.. autoattribute:: public_name
.. autoattribute:: confirm_button_name
.. autoattribute:: is_enabled
.. autoattribute:: priority

View File

@@ -1,6 +1,11 @@
.. spelling:: Rebase rebasing
Coding style and quality
========================
Code
----
* Basically, we want all python code to follow the `PEP 8`_ standard. There are a few exceptions where
we see things differently or just aren't that strict. The ``setup.cfg`` file in the project's source
folder contains definitions that allow `flake8`_ to check for violations automatically. See :ref:`checksandtests`
@@ -20,8 +25,62 @@ Coding style and quality
test suite are in the style of Python's unit test module. If you extend those files, you might continue in this style,
but please use ``pytest`` style for any new test files.
* Please keep the first line of your commit messages short. When referencing an issue, please phrase it like
``Fix #123 -- Problems with order creation`` or ``Refs #123 -- Fix this part of that bug``.
Commits and Pull Requests
-------------------------
Most commits should start as pull requests, therefore this applies to the titles of pull requests as well since
the pull request title will become the commit message on merge. We prefer merging with GitHub's "Squash and merge"
feature if the PR contains multiple commits that do not carry value to keep. If there is value in keeping the
individual commits, we use "Rebase and merge" instead. Merge commits should be avoided.
* The commit message should start with a single subject line and can optionally be followed by a commit message body.
* The subject line should be the shortest possible representation of what the commit changes. Someone who reviewed
the commit should able to immediately remember the commit in a couple of weeks based on the subject line and tell
it apart from other commits.
* If there's additional useful information that we should keep, such as reasoning behind the commit, you can
add a longer body, separated from the first line by a blank line.
* The body should explain **what** you changed and more importantly **why** you changed it. There's no need to iterate
**how** you changed something.
* The subject line should be capitalized ("Add new feature" instead of "add new feature") and should not end with a period
("Add new feature" instead of "Add new feature.")
* The subject line should be written in imperative mood, as if you were giving a command what the computer should do if the
commit is applied. This is how generated commit messages by git itself are already written ("Merge branch …", "Revert …")
and makes for short and consistent messages.
* Good: "Fix typo in template"
* Good: "Add Chinese translation"
* Good: "Remove deprecated method"
* Good: "Bump version to 4.4.0"
* Bad: "Fixed bug with …"
* Bad: "Fixes bug with …"
* Bad: "Fixing bug …"
* If all changes in your commit are in context of a single feature or e.g. a bundled plugin, it makes sense to prefix the
subject line with the name of that feature. Examples:
* "API: Add support for PATCH on customers"
* "Docs: Add chapter on alpaca feeding"
* "Stripe: Fix duplicate payments"
* "Order change form: Fix incorrect validation"
* If your commit references a GitHub issue that is fully resolved by your commit, start your subject line with the issue
ID in the form of "Fix #1234 -- Crash in order list". In this case, you can omit the verb "Fix" at the beginning of the
second part of the message to avoid repetition of the word "fix". If your commit only partially resolves the issue, use
"Refs #1234 -- Crash in order list" instead.
* Applies to pretix employees only: If your commit references a sentry issue, please put it in parentheses at the end
of the subject line or inside the body ("Fix crash in order list (PRETIXEU-ABC)"). If your commit references a support
ticket, please put it in parentheses at the end of the subject line with a "Z#" prefix ("Fix crash in order list (Z#12345)").
* If your PR was open for a while and might cause conflicts on merge, please prefer rebasing it (``git rebase -i master``)
over merging ``master`` into your branch unless it is prohibitively complicated.
.. _PEP 8: https://legacy.python.org/dev/peps/pep-0008/

View File

@@ -92,6 +92,9 @@ Carts and Orders
.. autoclass:: pretix.base.models.OrderRefund
:members:
.. autoclass:: pretix.base.models.Transaction
:members:
.. autoclass:: pretix.base.models.CartPosition
:members:

View File

@@ -8,6 +8,7 @@ Developer documentation
setup
contribution/index
implementation/index
translation/index
algorithms/index
api/index
structure
translation/index

View File

@@ -26,7 +26,7 @@ Your should install the following on your system:
* ``libssl`` (Debian package: ``libssl-dev``)
* ``libxml2`` (Debian package ``libxml2-dev``)
* ``libxslt`` (Debian package ``libxslt1-dev``)
* ``libenchant1c2a`` (Debian package ``libenchant1c2a``)
* ``libenchant-2-2`` (Debian package ``libenchant-2-2``)
* ``msgfmt`` (Debian package ``gettext``)
* ``git``
@@ -51,10 +51,15 @@ the dependencies might fail::
Working with the code
---------------------
The first thing you need are all the main application's dependencies::
If you do not have a recent installation of ``nodejs``, install it now::
curl -sL https://deb.nodesource.com/setup_17.x | sudo -E bash -
sudo apt install nodejs
To make sure it is on your path variable, close and reopen your terminal. Now, install the Python-level dependencies of pretix::
cd src/
pip3 install -r requirements.txt -r requirements/dev.txt
pip3 install -e ".[dev]"
Next, you need to copy the SCSS files from the source folder to the STATIC_ROOT directory::

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

View File

@@ -0,0 +1,146 @@
@startuml
partition "data-based check" {
"Check based on local database" --> "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?"
--> 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
-down->[yes] "Is the order in status PAID?"
--> if "" then
-right->[no] "Does the check-in list include pending orders?"
--> if "" then
-right->[no] "Return error UNPAID "
else
-down->[yes] "Is ignore_unpaid set?\n(Has the operator confirmed\nthe checkin?)"
--> if "" then
-right->[no] "Return error UNPAID "
else
-down->[yes] "Is this an entry or exit?"
endif
endif
else
-down->[yes] "Is this an entry or exit?"
endif
endif
endif
endif
"Is this an entry or exit?" --> if "" then
-right->[entry] Evaluate custom logic (rules)
--> if "" then
-right->[error] "Return error RULES"
else
-down->[ok] "Are all required questions answered?"
--> if "" then
-right->[no] "Return error INCOMPLETE"
else
-down->[yes] "Does the check-in list allow multi-entry?"
endif
endif
else
-->[exit] "Return OK "
endif
"Does the check-in list allow multi-entry?" --> if "" then
-right->[yes] "Return OK"
else
-down->[no] "Is this the first checkin\nfor this ticket on this list?"
--> if "" then
-right->[yes] "Return OK"
else
-down->[no] "Are all previous checkins\nfor this ticket on this list exits?"
--> if "" then
-right->[yes] "Return OK"
else
-down->[no] "Does the check-in list\n allow entry after exit\nand is the last checkin\nan exit?"
--> if "" then
-right->[yes] "Return OK"
else
-down->[no] "Return error ALREADY_REDEEMED"
endif
endif
endif
endif
}
partition "dataless check" {
"Check based on secret content" --> "Does the secret decode with\nany supported scheme\nand has a valid signature?"
--> if "" then
-down->[yes] "Is the ticket secret on the revocation list?"
--> if "" then
-right->[yes] "Return error REVOKED"
else
-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
else
-right>[no] "Return error INVALID "
endif
"Is this an entry or exit? " --> if "" then
-right->[entry] "Evaluate custom logic (rules) "
--> if "" then
-right->[error] "Return error RULES "
else
-down->[ok] "Are all required questions answered? "
--> if "" then
-right->[no] "Return error INCOMPLETE "
else
-down->[yes] "Does the check-in list allow multi-entry? "
endif
endif
else
-->[exit] " Return OK "
endif
"Does the check-in list allow multi-entry? " --> if "" then
-right->[yes] " Return OK "
else
-down->[no] "Are any locally queued checkins for\nthis ticket of this list known?"
--> if "" then
-right->[no] " Return OK "
else
-down->[yes] "Are all locally queued checkins\nfor this ticket on this list exits? "
--> if "" then
-right->[yes] " Return OK "
else
-down->[no] "Does the check-in list\n allow entry after exit\nand is the last locally\nqueued checkin\nan exit? "
--> if "" then
-right->[yes] " Return OK "
else
-down->[no] "Return error ALREADY_REDEEMED "
endif
endif
endif
endif
}
(*) --> "Check if order position with\nscanned ticket secret exists"
--> if "" then
-down->[yes] "Check based on local database"
else
-->[no] "Check based on secret content"
endif
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View File

@@ -0,0 +1,92 @@
@startuml
(*) --> "Check if order position with\nscanned ticket secret exists"
--> if "" then
-down->[yes] ===CHECK===
else
-->[no] "Check if secret exists\nin revocation list"
--> if "" then
--> "Is this a forced upload?"
--> if "" then
-->[yes] ===CHECK===
else
-right->[no] "Return error REVOKED"
endif
else
-right->[no] "Return error INVALID"
endif
endif
===CHECK=== -down-> "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?"
--> 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 PRODUCT "
else
-down->[yes] "Is the order in status PAID\nor is this a forced upload?"
--> if "" then
-right->[no] "Does the check-in list include pending orders?"
--> if "" then
-right->[no] "Return error UNPAID "
else
-down->[yes] "Is ignore_unpaid set?\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
else
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
endif
endif
endif
endif
"Is this an entry or exit?\nIs the upload forced?" --> if "" then
-right->[entry && not force] Evaluate custom logic (rules)
--> if "" then
-right->[error] "Return error RULES"
else
-down->[ok] "Are all required questions answered?"
--> if "" then
-right->[no && questions_supported] "Return error INCOMPLETE"
else
-down->[yes || not questions_supported] "Does the check-in list allow multi-entry?"
endif
endif
else
-->[exit || force=true] "Return OK "
endif
"Does the check-in list allow multi-entry?" --> if "" then
-right->[yes] "Return OK"
else
-down->[no] "Is this the first checkin\nfor this ticket on this list?"
--> if "" then
-right->[yes] "Return OK"
else
-down->[no] "Are all previous checkins\nfor this ticket on this list exits?"
--> if "" then
-right->[yes] "Return OK"
else
-down->[no] "Does the check-in list\n allow entry after exit\nand is the last checkin\nan exit?"
--> if "" then
-right->[yes] "Return OK"
else
-down->[no] "Return error ALREADY_REDEEMED"
endif
endif
endif
endif
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -0,0 +1,52 @@
@startuml
(*) --> "Which implementation?"
--> if "" then
-down->[pretixPOS] "Check for TicketLayoutItem with\nsales_channel=pretixpos [libpretixsync]"
--> if "" then
--> (*)
else
-->[not found] "Check for TicketLayoutItem with\nsales_channel=web [libpretixsync]"
--> if "" then
--> (*)
else
-->[not found] "Use event default [libpretixsync]"
--> (*)
endif
endif
else
-right->[pretix] "Check for TicketLayoutItem with\nsales_channel=order.sales_channel"
--> if "" then
-right-> "Run override_layout plugin signal on result"
else
-down->[not found] "Check for TicketLayoutItem with\nsales_channel=web"
--> if "" then
--> "Run override_layout plugin signal on result"
else
-->[not found] "Use event default"
--> "Run override_layout plugin signal on result"
endif
endif
endif
"Run override_layout plugin signal on result" -> (*)
partition pretix_shipping {
"Run override_layout plugin signal on result" --> "Check for ShippingLayoutItem with\nmethod=order.shipping_method"
--> if "" then
--> (*)
else
-down->[not found] "Check for ShippingMethod.layout"
--> if "" then
--> (*)
else
-down->[not found] "Keep original layout"
--> (*)
endif
endif
}
@enduml

View File

@@ -0,0 +1,64 @@
Certificates of attendance
==========================
The certificates plugin provides a HTTP API that allows you to download the certificate for a specific attendee.
Certificate download
--------------------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/certificate/
Downloads the certificate for one order position, identified by its internal ID. Download is a two-step
process. You will always get a :http:statuscode:`303` response with a ``Location`` header to a different
URL. In the background, our server starts preparing the PDF file.
If you then do a ``GET`` to the URL you were given, you will either receive a :http:statuscode:`409` response
indicating to retry after a few seconds, or a :http:statuscode:`200` response with the PDF file.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/certificate/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 303 See Other
Location: /api/v1/organizers/democon/events/3vjrh/orderpositions/426/certificate/?result=1f550651-ae7b-4911-a76c-2be8f348aaa5
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/certificate/?result=1f550651-ae7b-4911-a76c-2be8f348aaa5 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/pdf
...
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the order position to fetch
:statuscode 200: File ready for download
:statuscode 303: Processing started
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource
**or** downloads are not available for this order position at this time. The response content will
contain more details.
:statuscode 404: The requested order position or download provider does not exist.
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting for a few
seconds.

View File

@@ -15,5 +15,6 @@ If you want to **create** a plugin, please go to the
ticketoutputpdf
badges
campaigns
certificates
digital
webinar

View File

@@ -1,4 +1,4 @@
-r ../src/requirements.txt
-e ../src/
sphinx==2.3.*
sphinx-rtd-theme
sphinxcontrib-httpdomain

View File

@@ -17,10 +17,12 @@ bic
BIC
boolean
booleans
bugfix
cancelled
casted
Ceph
checkbox
checkins
checksum
config
contenttypes
@@ -76,6 +78,7 @@ mixin
mixins
multi
multidomain
multiplicator
namespace
namespaced
namespaces

View File

@@ -203,4 +203,4 @@ Then, please contact support@pretix.eu and we will enable DKIM for your domain o
.. _Sender Policy Framework: https://en.wikipedia.org/wiki/Sender_Policy_Framework
.. _SPF specification: http://www.openspf.org/SPF_Record_Syntax
.. _SPF specification: http://www.open-spf.org/SPF_Record_Syntax

View File

@@ -4,8 +4,7 @@ Embeddable Widget
=================
If you want to show your ticket shop on your event website or blog, you can use our JavaScript widget. This way,
users will not need to leave your site to buy their ticket in most cases. The widget will still open a new tab
for the checkout if the user is on a mobile device.
users will not need to leave your site to buy their ticket in most cases.
To obtain the correct HTML code for embedding your event into your website, we recommend that you go to the "Widget"
tab of your event's settings. You can specify some optional settings there (for example the language of the widget)
@@ -310,6 +309,10 @@ Currently, the following attributes are understood by pretix itself:
always be modified. Note that this is not a security feature and can easily be overridden by users, so do not rely
on this for authentication.
* If ``data-consent="…"`` is given, the cookie consent mechanism will be initialized with consent for the given cookie
providers. All other providers will be disabled, no consent dialog will be shown. This is useful if you already
asked the user for consent and don't want them to be asked again. Example: ``data-consent="facebook,google_analytics"``
Any configured pretix plugins might understand more data fields. For example, if the appropriate plugins on pretix
Hosted or pretix Enterprise are active, you can pass the following fields:

2
src/.gitignore vendored
View File

@@ -9,4 +9,4 @@ dist/
*.bak
pretix/static/jsi18n/
node_modules/
.eggs/

View File

@@ -34,5 +34,7 @@ git push
# Unlock Weblate
for c in $COMPONENTS; do
wlc unlock $c;
done
for c in $COMPONENTS; do
wlc pull $c;
done

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.0.0"
__version__ = "4.6.0"

View File

@@ -59,6 +59,7 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:badgeitem-list'),
('GET', 'api-v1:checkinlist-list'),
('GET', 'api-v1:checkinlist-status'),
('POST', 'api-v1:checkinlist-failed_checkins'),
('GET', 'api-v1:checkinlistpos-list'),
('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'),
@@ -69,9 +70,9 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
)
class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
class PretixScanNoSyncNoSearchSecurityProfile(AllowListSecurityProfile):
identifier = 'pretixscan_online_kiosk'
verbose_name = _('pretixSCAN (kiosk mode, online only)')
verbose_name = _('pretixSCAN (kiosk mode, no order sync, no search)')
allowlist = (
('GET', 'api-v1:version'),
('GET', 'api-v1:device.eventselection'),
@@ -89,6 +90,37 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:badgeitem-list'),
('GET', 'api-v1:checkinlist-list'),
('GET', 'api-v1:checkinlist-status'),
('POST', 'api-v1:checkinlist-failed_checkins'),
('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
)
class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
identifier = 'pretixscan_online_noorders'
verbose_name = _('pretixSCAN (online only, no order sync)')
allowlist = (
('GET', 'api-v1:version'),
('GET', 'api-v1:device.eventselection'),
('POST', 'api-v1:device.update'),
('POST', 'api-v1:device.revoke'),
('POST', 'api-v1:device.roll'),
('GET', 'api-v1:event-list'),
('GET', 'api-v1:event-detail'),
('GET', 'api-v1:subevent-list'),
('GET', 'api-v1:subevent-detail'),
('GET', 'api-v1:itemcategory-list'),
('GET', 'api-v1:item-list'),
('GET', 'api-v1:question-list'),
('GET', 'api-v1:badgelayout-list'),
('GET', 'api-v1:badgeitem-list'),
('GET', 'api-v1:checkinlist-list'),
('GET', 'api-v1:checkinlist-status'),
('POST', 'api-v1:checkinlist-failed_checkins'),
('GET', 'api-v1:checkinlistpos-list'),
('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'),
@@ -131,6 +163,7 @@ class PretixPosSecurityProfile(AllowListSecurityProfile):
('POST', 'api-v1:orderrefund-list'),
('POST', 'api-v1:orderrefund-done'),
('POST', 'api-v1:cartposition-list'),
('POST', 'api-v1:cartposition-bulk-create'),
('GET', 'api-v1:checkinlist-list'),
('POST', 'api-v1:checkinlistpos-redeem'),
('POST', 'plugins:pretix_posbackend:order.posprintlog'),
@@ -158,6 +191,7 @@ DEVICE_SECURITY_PROFILES = {
FullAccessSecurityProfile,
PretixScanSecurityProfile,
PretixScanNoSyncSecurityProfile,
PretixScanNoSyncNoSearchSecurityProfile,
PretixPosSecurityProfile,
)
}

View File

@@ -49,7 +49,9 @@ class EventPermission(BasePermission):
if not request.user.is_authenticated and not isinstance(request.auth, (Device, TeamAPIToken)):
return False
if request.method not in SAFE_METHODS and hasattr(view, 'write_permission'):
if hasattr(view, '_get_permission_name'):
required_permission = getattr(view, '_get_permission_name')(request)
elif request.method not in SAFE_METHODS and hasattr(view, 'write_permission'):
required_permission = getattr(view, 'write_permission')
elif hasattr(view, 'permission'):
required_permission = getattr(view, 'permission')

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.2 on 2021-07-05 07:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixapi', '0005_auto_20191028_1541'),
]
operations = [
migrations.AlterField(
model_name='webhook',
name='target_url',
field=models.URLField(max_length=255),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-09-15 11:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixapi', '0006_alter_webhook_target_url'),
]
operations = [
migrations.AlterField(
model_name='webhookcall',
name='target_url',
field=models.URLField(max_length=255),
),
]

View File

@@ -95,7 +95,7 @@ class OAuthRefreshToken(AbstractRefreshToken):
class WebHook(models.Model):
organizer = models.ForeignKey('pretixbase.Organizer', on_delete=models.CASCADE, related_name='webhooks')
enabled = models.BooleanField(default=True, verbose_name=_("Enable webhook"))
target_url = models.URLField(verbose_name=_("Target URL"))
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)
@@ -120,7 +120,7 @@ class WebHookEventListener(models.Model):
class WebHookCall(models.Model):
webhook = models.ForeignKey('WebHook', on_delete=models.CASCADE, related_name='calls')
datetime = models.DateTimeField(auto_now_add=True)
target_url = models.URLField()
target_url = models.URLField(max_length=255)
action_type = models.CharField(max_length=255)
is_retry = models.BooleanField(default=False)
execution_time = models.FloatField(null=True)

View File

@@ -0,0 +1,27 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from rest_framework.pagination import PageNumberPagination
class Pagination(PageNumberPagination):
page_size_query_param = 'page_size'
max_page_size = 50

View File

@@ -73,53 +73,61 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
minutes=self.context['event'].settings.get('reservation_time', as_type=int)
)
with self.context['event'].lock():
new_quotas = (validated_data.get('variation').quotas.filter(subevent=validated_data.get('subevent'))
if validated_data.get('variation')
else validated_data.get('item').quotas.filter(subevent=validated_data.get('subevent')))
if len(new_quotas) == 0:
new_quotas = (validated_data.get('variation').quotas.filter(subevent=validated_data.get('subevent'))
if validated_data.get('variation')
else validated_data.get('item').quotas.filter(subevent=validated_data.get('subevent')))
if len(new_quotas) == 0:
raise ValidationError(
gettext_lazy('The product "{}" is not assigned to a quota.').format(
str(validated_data.get('item'))
)
)
for quota in new_quotas:
avail = quota.availability(_cache=self.context['quota_cache'])
if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < 1):
raise ValidationError(
gettext_lazy('The product "{}" is not assigned to a quota.').format(
str(validated_data.get('item'))
gettext_lazy('There is not enough quota available on quota "{}" to perform '
'the operation.').format(
quota.name
)
)
for quota in new_quotas:
avail = quota.availability()
if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < 1):
raise ValidationError(
gettext_lazy('There is not enough quota available on quota "{}" to perform '
'the operation.').format(
quota.name
)
)
attendee_name = validated_data.pop('attendee_name', '')
if attendee_name and not validated_data.get('attendee_name_parts'):
validated_data['attendee_name_parts'] = {
'_legacy': attendee_name
}
seated = validated_data.get('item').seat_category_mappings.filter(subevent=validated_data.get('subevent')).exists()
if validated_data.get('seat'):
if not seated:
raise ValidationError('The specified product does not allow to choose a seat.')
try:
seat = self.context['event'].seats.get(seat_guid=validated_data['seat'], subevent=validated_data.get('subevent'))
except Seat.DoesNotExist:
raise ValidationError('The specified seat does not exist.')
except Seat.MultipleObjectsReturned:
raise ValidationError('The specified seat ID is not unique.')
else:
validated_data['seat'] = seat
if not seat.is_available(
sales_channel=validated_data.get('sales_channel', 'web'),
distance_ignore_cart_id=validated_data['cart_id'],
):
raise ValidationError(gettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name))
elif seated:
raise ValidationError('The specified product requires to choose a seat.')
for quota in new_quotas:
oldsize = self.context['quota_cache'][quota.pk][1]
newsize = oldsize - 1 if oldsize is not None else None
self.context['quota_cache'][quota.pk] = (
Quota.AVAILABILITY_OK if newsize is None or newsize > 0 else Quota.AVAILABILITY_GONE,
newsize
)
validated_data.pop('sales_channel')
cp = CartPosition.objects.create(event=self.context['event'], **validated_data)
attendee_name = validated_data.pop('attendee_name', '')
if attendee_name and not validated_data.get('attendee_name_parts'):
validated_data['attendee_name_parts'] = {
'_legacy': attendee_name
}
seated = validated_data.get('item').seat_category_mappings.filter(subevent=validated_data.get('subevent')).exists()
if validated_data.get('seat'):
if not seated:
raise ValidationError('The specified product does not allow to choose a seat.')
try:
seat = self.context['event'].seats.get(seat_guid=validated_data['seat'], subevent=validated_data.get('subevent'))
except Seat.DoesNotExist:
raise ValidationError('The specified seat does not exist.')
except Seat.MultipleObjectsReturned:
raise ValidationError('The specified seat ID is not unique.')
else:
validated_data['seat'] = seat
if not seat.is_available(
sales_channel=validated_data.get('sales_channel', 'web'),
distance_ignore_cart_id=validated_data['cart_id'],
):
raise ValidationError(gettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name))
elif seated:
raise ValidationError('The specified product requires to choose a seat.')
validated_data.pop('sales_channel')
cp = CartPosition.objects.create(event=self.context['event'], **validated_data)
for answ_data in answers_data:
options = answ_data.pop('options')

View File

@@ -42,6 +42,7 @@ from django.utils.functional import cached_property
from django.utils.translation import gettext as _
from django_countries.serializers import CountryFieldMixin
from pytz import common_timezones
from rest_framework import serializers
from rest_framework.fields import ChoiceField, Field
from rest_framework.relations import SlugRelatedField
@@ -53,7 +54,7 @@ from pretix.base.models.items import SubEventItem, SubEventItemVariation
from pretix.base.services.seating import (
SeatProtected, generate_seats, validate_plan_change,
)
from pretix.base.settings import validate_event_settings
from pretix.base.settings import LazyI18nStringList, validate_event_settings
from pretix.base.signals import api_event_settings_fields
logger = logging.getLogger(__name__)
@@ -93,9 +94,12 @@ class MetaPropertyField(Field):
class SeatCategoryMappingField(Field):
def to_representation(self, value):
qs = value.seat_category_mappings.all()
if isinstance(value, Event):
qs = qs.filter(subevent=None)
if hasattr(value, '_seat_category_mappings'):
qs = value._seat_category_mappings
else:
qs = value.seat_category_mappings.all()
if isinstance(value, Event):
qs = qs.filter(subevent=None)
return {
v.layout_category: v.product_id for v in qs
}
@@ -156,6 +160,7 @@ class EventSerializer(I18nAwareModelSerializer):
seat_category_mapping = SeatCategoryMappingField(source='*', required=False)
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)
class Meta:
model = Event
@@ -163,12 +168,14 @@ 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')
'sales_channels', 'best_availability_state')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not hasattr(self.context['request'], 'event'):
self.fields.pop('valid_keys')
if not self.context.get('request') or 'with_availability_for' not in self.context['request'].GET:
self.fields.pop('best_availability_state')
def validate(self, data):
data = super().validate(data)
@@ -441,13 +448,19 @@ class SubEventSerializer(I18nAwareModelSerializer):
seat_category_mapping = SeatCategoryMappingField(source='*', required=False)
event = SlugRelatedField(slug_field='slug', read_only=True)
meta_data = MetaDataField(source='*')
best_availability_state = serializers.IntegerField(allow_null=True, read_only=True)
class Meta:
model = SubEvent
fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission',
'presale_start', 'presale_end', 'location', 'geo_lat', 'geo_lon', 'event', 'is_public',
'frontpage_text', 'seating_plan', 'item_price_overrides', 'variation_price_overrides',
'meta_data', 'seat_category_mapping', 'last_modified')
'meta_data', 'seat_category_mapping', 'last_modified', 'best_availability_state')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.context.get('request') or 'with_availability_for' not in self.context['request'].GET:
self.fields.pop('best_availability_state')
def validate(self, data):
data = super().validate(data)
@@ -624,7 +637,7 @@ class SubEventSerializer(I18nAwareModelSerializer):
class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
class Meta:
model = TaxRule
fields = ('id', 'name', 'rate', 'price_includes_tax', 'eu_reverse_charge', 'home_country')
fields = ('id', 'name', 'rate', 'price_includes_tax', 'eu_reverse_charge', 'home_country', 'internal_name', 'keep_gross_if_rate_changes')
class EventSettingsSerializer(SettingsSerializer):
@@ -691,6 +704,7 @@ class EventSettingsSerializer(SettingsSerializer):
'payment_term_accept_late',
'payment_explanation',
'payment_pending_hidden',
'mail_days_order_expire_warning',
'ticket_download',
'ticket_download_date',
'ticket_download_addons',
@@ -699,7 +713,6 @@ class EventSettingsSerializer(SettingsSerializer):
'ticket_download_require_validated_email',
'ticket_secret_length',
'mail_prefix',
'mail_from',
'mail_from_name',
'mail_attach_ical',
'mail_attach_tickets',
@@ -720,6 +733,7 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_numbers_prefix_cancellations',
'invoice_numbers_counter_length',
'invoice_attendee_name',
'invoice_event_location',
'invoice_include_expire_date',
'invoice_address_explanation_text',
'invoice_email_attachment',
@@ -749,6 +763,7 @@ class EventSettingsSerializer(SettingsSerializer):
'cancel_allow_user_paid_refund_as_giftcard',
'cancel_allow_user_paid_require_approval',
'change_allow_user_variation',
'change_allow_user_addons',
'change_allow_user_until',
'change_allow_user_price',
'primary_color',
@@ -776,6 +791,10 @@ class EventSettingsSerializer(SettingsSerializer):
data = super().validate(data)
settings_dict = self.instance.freeze()
settings_dict.update(data)
if data.get('confirm_texts') is not None:
data['confirm_texts'] = LazyI18nStringList(data['confirm_texts'])
validate_event_settings(self.event, settings_dict)
return data

View File

@@ -31,9 +31,10 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import os.path
from decimal import Decimal
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import QuerySet
@@ -57,8 +58,10 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
class Meta:
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price',
'require_membership', 'require_membership_types',)
'position', 'default_price', 'price', 'original_price', 'require_approval',
'require_membership', 'require_membership_types',
'require_membership_hidden', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -72,8 +75,10 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
class Meta:
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price',
'require_membership', 'require_membership_types',)
'position', 'default_price', 'price', 'original_price', 'require_approval',
'require_membership', 'require_membership_types',
'require_membership_hidden', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -161,7 +166,7 @@ class ItemSerializer(I18nAwareModelSerializer):
meta_data = MetaDataField(required=False, source='*')
picture = UploadedFileField(required=False, allow_null=True, allowed_types=(
'image/png', 'image/jpeg', 'image/gif'
), max_size=10 * 1024 * 1024)
), max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE)
class Meta:
model = Item
@@ -172,7 +177,7 @@ class ItemSerializer(I18nAwareModelSerializer):
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data',
'require_membership', 'require_membership_types', 'grant_membership_type',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
'grant_membership_duration_like_event', 'grant_membership_duration_days',
'grant_membership_duration_months')
read_only_fields = ('has_variations',)
@@ -245,10 +250,13 @@ class ItemSerializer(I18nAwareModelSerializer):
addons_data = validated_data.pop('addons') if 'addons' in validated_data else {}
bundles_data = validated_data.pop('bundles') if 'bundles' in validated_data else {}
meta_data = validated_data.pop('meta_data', None)
picture = validated_data.pop('picture', None)
item = Item.objects.create(**validated_data)
if picture:
item.picture.save(os.path.basename(picture.name), picture)
for variation_data in variations_data:
require_membership_types = variation_data.pop('require_membership_types')
require_membership_types = variation_data.pop('require_membership_types', [])
v = ItemVariation.objects.create(item=item, **variation_data)
if require_membership_types:
v.require_membership_types.add(*require_membership_types)
@@ -269,7 +277,10 @@ class ItemSerializer(I18nAwareModelSerializer):
def update(self, instance, validated_data):
meta_data = validated_data.pop('meta_data', None)
picture = validated_data.pop('picture', None)
item = super().update(instance, validated_data)
if picture:
item.picture.save(os.path.basename(picture.name), picture)
# Meta data
if meta_data is not None:
@@ -408,10 +419,19 @@ class QuestionSerializer(I18nAwareModelSerializer):
class QuotaSerializer(I18nAwareModelSerializer):
available = serializers.BooleanField(read_only=True)
available_number = serializers.IntegerField(read_only=True)
class Meta:
model = Quota
fields = ('id', 'name', 'size', 'items', 'variations', 'subevent', 'closed', 'close_when_sold_out', 'release_after_exit')
fields = ('id', 'name', 'size', 'items', 'variations', 'subevent', 'closed', 'close_when_sold_out',
'release_after_exit', 'available', 'available_number')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'request' not in self.context or self.context['request'].GET.get('with_availability') != 'true':
del self.fields['available']
del self.fields['available_number']
def validate(self, data):
data = super().validate(data)

View File

@@ -26,6 +26,7 @@ from collections import Counter, defaultdict
from decimal import Decimal
import pycountry
from django.conf import settings
from django.core.files import File
from django.db.models import F, Q
from django.utils.timezone import now
@@ -191,7 +192,7 @@ class AnswerSerializer(I18nAwareModelSerializer):
)
if cf.type not in allowed_types:
raise ValidationError('The submitted file "{fid}" has a file type that is not allowed in this field.'.format(fid=data))
if cf.file.size > 10 * 1024 * 1024:
if cf.file.size > settings.FILE_UPLOAD_MAX_SIZE_OTHER:
raise ValidationError('The submitted file "{fid}" is too large to be used in this field.'.format(fid=data))
data['options'] = []
@@ -199,7 +200,9 @@ class AnswerSerializer(I18nAwareModelSerializer):
return data
def validate(self, data):
if data.get('question').type == Question.TYPE_FILE:
if not data.get('question'):
raise ValidationError('Question not specified.')
elif data.get('question').type == Question.TYPE_FILE:
return self._handle_file_upload(data)
elif data.get('question').type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE):
if not data.get('options'):
@@ -250,7 +253,30 @@ class AnswerSerializer(I18nAwareModelSerializer):
class CheckinSerializer(I18nAwareModelSerializer):
class Meta:
model = Checkin
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'type')
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'type')
class FailedCheckinSerializer(I18nAwareModelSerializer):
error_reason = serializers.ChoiceField(choices=Checkin.REASONS, required=True, allow_null=False)
raw_barcode = serializers.CharField(required=True, allow_null=False)
position = serializers.PrimaryKeyRelatedField(queryset=OrderPosition.all.none(), required=False, allow_null=True)
raw_item = serializers.PrimaryKeyRelatedField(queryset=Item.objects.none(), required=False, allow_null=True)
raw_variation = serializers.PrimaryKeyRelatedField(queryset=ItemVariation.objects.none(), required=False, allow_null=True)
raw_subevent = serializers.PrimaryKeyRelatedField(queryset=SubEvent.objects.none(), required=False, allow_null=True)
class Meta:
model = Checkin
fields = ('error_reason', 'error_explanation', 'raw_barcode', 'raw_item', 'raw_variation',
'raw_subevent', 'datetime', 'type', 'position')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
event = self.context['event']
self.fields['raw_item'].queryset = event.items.all()
self.fields['raw_variation'].queryset = ItemVariation.objects.filter(item__event=event)
self.fields['position'].queryset = OrderPosition.all.filter(order__event=event)
if event.has_subevents:
self.fields['raw_subevent'].queryset = event.subevents.all()
class OrderDownloadsField(serializers.Field):
@@ -631,7 +657,7 @@ class OrderSerializer(I18nAwareModelSerializer):
model = Order
fields = (
'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer'
)
@@ -662,7 +688,7 @@ 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', 'checkin_attention', 'email', 'locale', 'phone']
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'email', 'locale', 'phone']
if 'invoice_address' in validated_data:
iadata = validated_data.pop('invoice_address')
@@ -902,6 +928,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
min_length=5
)
comment = serializers.CharField(required=False, allow_blank=True)
custom_followup_at = serializers.DateField(required=False, allow_null=True)
payment_provider = serializers.CharField(required=False, allow_null=True)
payment_info = CompatibleJSONField(required=False)
consume_carts = serializers.ListField(child=serializers.CharField(), required=False)
@@ -920,7 +947,7 @@ 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')
'force', 'send_email', 'simulate', 'customer', 'custom_followup_at')
def validate_payment_provider(self, pp):
if pp is None:
@@ -1269,7 +1296,13 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
if pos.voucher:
Voucher.objects.filter(pk=pos.voucher.pk).update(redeemed=F('redeemed') + 1)
pos.save()
seen_answers = set()
for answ_data in answers_data:
# Workaround for a pretixPOS bug :-(
if answ_data.get('question') in seen_answers:
continue
seen_answers.add(answ_data.get('question'))
options = answ_data.pop('options', [])
if isinstance(answ_data['answer'], File):
@@ -1371,6 +1404,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
state=OrderPayment.PAYMENT_STATE_CREATED
)
order.create_transactions(is_new=True, fees=fees, positions=pos_map.values())
return order
@@ -1392,8 +1426,9 @@ class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
class Meta:
model = InvoiceLine
fields = ('position', 'description', 'item', 'variation', 'attendee_name', 'event_date_from',
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_name')
fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from',
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_name', 'fee_type',
'fee_internal_type', 'event_location')
class InvoiceSerializer(I18nAwareModelSerializer):

View File

@@ -275,6 +275,7 @@ class OrganizerSettingsSerializer(SettingsSerializer):
default_fields = [
'customer_accounts',
'customer_accounts_link_by_email',
'invoice_regenerate_allowed',
'contact_mail',
'imprint_url',
'organizer_info_text',
@@ -294,7 +295,15 @@ class OrganizerSettingsSerializer(SettingsSerializer):
'theme_color_background',
'theme_round_borders',
'primary_font',
'organizer_logo_image'
'organizer_logo_image_inherit',
'organizer_logo_image',
'privacy_url',
'cookie_consent',
'cookie_consent_dialog_title',
'cookie_consent_dialog_text',
'cookie_consent_dialog_text_secondary',
'cookie_consent_dialog_button_yes',
'cookie_consent_dialog_button_no',
]
def __init__(self, *args, **kwargs):

View File

@@ -54,8 +54,8 @@ class SettingsSerializer(serializers.Serializer):
f = DEFAULTS[fname]['serializer_class'](
**kwargs
)
f._label = form_kwargs.get('label', fname)
f._help_text = form_kwargs.get('help_text')
f._label = str(form_kwargs.get('label', fname))
f._help_text = str(form_kwargs.get('help_text'))
f.parent = self
self.fields[fname] = f

View File

@@ -21,14 +21,18 @@
#
from django.db import transaction
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.settings import api_settings
from pretix.api.serializers.cart import (
CartPositionCreateSerializer, CartPositionSerializer,
)
from pretix.base.models import CartPosition
from pretix.base.services.locking import NoLockManager
class CartPositionViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
@@ -50,18 +54,61 @@ class CartPositionViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnly
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['quota_cache'] = {}
return ctx
def create(self, request, *args, **kwargs):
serializer = CartPositionCreateSerializer(data=request.data, context=self.get_serializer_context())
serializer.is_valid(raise_exception=True)
with transaction.atomic():
with transaction.atomic(), self.request.event.lock():
self.perform_create(serializer)
cp = serializer.instance
serializer = CartPositionSerializer(cp, context=serializer.context)
cp = serializer.instance
serializer = CartPositionSerializer(cp, context=serializer.context)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
@action(detail=False, methods=['POST'])
def bulk_create(self, request, *args, **kwargs):
if not isinstance(request.data, list): # noqa
return Response({"error": "Please supply a list"}, status=status.HTTP_400_BAD_REQUEST)
ctx = self.get_serializer_context()
with transaction.atomic():
serializers = [
CartPositionCreateSerializer(data=d, context=ctx)
for d in request.data
]
lockfn = self.request.event.lock
if not any(s.is_valid(raise_exception=False) for s in serializers):
lockfn = NoLockManager
results = []
with lockfn():
for s in serializers:
if s.is_valid(raise_exception=False):
try:
cp = s.save()
except ValidationError as e:
results.append({
'success': False,
'data': None,
'errors': {api_settings.NON_FIELD_ERRORS_KEY: e.detail},
})
else:
results.append({
'success': True,
'data': CartPositionSerializer(cp, context=ctx).data,
'errors': None,
})
else:
results.append({
'success': False,
'data': None,
'errors': s.errors,
})
return Response({'results': results}, status=status.HTTP_200_OK)
def perform_create(self, serializer):
serializer.save()

View File

@@ -20,7 +20,9 @@
# <https://www.gnu.org/licenses/>.
#
import django_filters
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import (
Count, Exists, F, Max, OrderBy, OuterRef, Prefetch, Q, Subquery,
)
@@ -31,19 +33,24 @@ from django.utils.functional import cached_property
from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from packaging.version import parse
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.fields import DateTimeField
from rest_framework.permissions import SAFE_METHODS
from rest_framework.response import Response
from pretix.api.serializers.checkin import CheckinListSerializer
from pretix.api.serializers.item import QuestionSerializer
from pretix.api.serializers.order import CheckinListOrderPositionSerializer
from pretix.api.serializers.order import (
CheckinListOrderPositionSerializer, FailedCheckinSerializer,
)
from pretix.api.views import RichOrderingFilter
from pretix.api.views.order import OrderPositionFilter
from pretix.base.i18n import language
from pretix.base.models import (
CachedFile, Checkin, CheckinList, Event, Order, OrderPosition, Question,
CachedFile, Checkin, CheckinList, Device, Event, Order, OrderPosition,
Question,
)
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
@@ -79,8 +86,14 @@ class CheckinListViewSet(viewsets.ModelViewSet):
queryset = CheckinList.objects.none()
filter_backends = (DjangoFilterBackend,)
filterset_class = CheckinListFilter
permission = ('can_view_orders', 'can_checkin_orders',)
write_permission = 'can_change_event_settings'
def _get_permission_name(self, request):
if request.path.endswith('/failed_checkins/'):
return 'can_checkin_orders', 'can_change_orders'
elif request.method in SAFE_METHODS:
return 'can_view_orders', 'can_checkin_orders',
else:
return 'can_change_event_settings'
def get_queryset(self):
qs = self.request.event.checkin_lists.prefetch_related(
@@ -125,6 +138,49 @@ class CheckinListViewSet(viewsets.ModelViewSet):
)
super().perform_destroy(instance)
@action(detail=True, methods=['POST'], url_name='failed_checkins')
@transaction.atomic()
def failed_checkins(self, *args, **kwargs):
serializer = FailedCheckinSerializer(
data=self.request.data,
context={'event': self.request.event}
)
serializer.is_valid(raise_exception=True)
kwargs = {}
if not serializer.validated_data.get('position'):
kwargs['position'] = OrderPosition.all.filter(
secret=serializer.validated_data['raw_barcode']
).first()
c = serializer.save(
list=self.get_object(),
successful=False,
forced=True,
device=self.request.auth if isinstance(self.request.auth, Device) else None,
gate=self.request.auth.gate if isinstance(self.request.auth, Device) else None,
**kwargs,
)
if c.position:
c.position.order.log_action('pretix.event.checkin.denied', data={
'position': c.position.id,
'positionid': c.position.positionid,
'errorcode': c.error_reason,
'reason_explanation': c.error_explanation,
'datetime': c.datetime,
'type': c.type,
'list': c.list.pk
}, user=self.request.user, auth=self.request.auth)
else:
self.request.event.log_action('pretix.event.checkin.unknown', data={
'datetime': c.datetime,
'type': c.type,
'list': c.list.pk,
'barcode': c.raw_barcode
}, user=self.request.user, auth=self.request.auth)
return Response(serializer.data, status=201)
@action(detail=True, methods=['GET'])
def status(self, *args, **kwargs):
with language(self.request.event.settings.locale):
@@ -294,7 +350,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
lookup='checkins',
queryset=Checkin.objects.filter(list_id=self.checkinlist.pk)
),
'checkins', 'answers', 'answers__options', 'answers__question',
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
@@ -304,7 +360,8 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
Prefetch(
'positions',
OrderPosition.objects.prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
))
@@ -356,30 +413,113 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
else:
dt = now()
common_checkin_args = dict(
raw_barcode=self.kwargs['pk'],
type=type,
list=self.checkinlist,
datetime=dt,
device=self.request.auth if isinstance(self.request.auth, Device) else None,
gate=self.request.auth.gate if isinstance(self.request.auth, Device) else None,
nonce=nonce,
forced=force,
)
raw_barcode_for_checkin = None
try:
queryset = self.get_queryset(ignore_status=True, ignore_products=True)
if self.kwargs['pk'].isnumeric():
op = queryset.get(Q(pk=self.kwargs['pk']) | Q(secret=self.kwargs['pk']))
else:
op = queryset.get(secret=self.kwargs['pk'])
# In application/x-www-form-urlencoded, you can encodes space ' ' with '+' instead of '%20'.
# `id`, however, is part of a path where this technically is not allowed. Old versions of our
# scan apps still do it, so we try work around it!
try:
op = queryset.get(secret=self.kwargs['pk'])
except OrderPosition.DoesNotExist:
op = queryset.get(secret=self.kwargs['pk'].replace('+', ' '))
except OrderPosition.DoesNotExist:
revoked_matches = list(self.request.event.revoked_secrets.filter(secret=self.kwargs['pk']))
if len(revoked_matches) == 0 or not force:
if len(revoked_matches) == 0:
self.request.event.log_action('pretix.event.checkin.unknown', data={
'datetime': dt,
'type': type,
'list': self.checkinlist.pk,
'barcode': self.kwargs['pk']
}, user=self.request.user, auth=self.request.auth)
raise Http404()
op = revoked_matches[0].position
op.order.log_action('pretix.event.checkin.revoked', data={
'datetime': dt,
'type': type,
'list': self.checkinlist.pk,
'barcode': self.kwargs['pk']
}, user=self.request.user, auth=self.request.auth)
for k, s in self.request.event.ticket_secret_generators.items():
try:
parsed = s.parse_secret(self.kwargs['pk'])
common_checkin_args.update({
'raw_item': parsed.item,
'raw_variation': parsed.variation,
'raw_subevent': parsed.subevent,
})
except:
pass
Checkin.objects.create(
position=None,
successful=False,
error_reason=Checkin.REASON_INVALID,
**common_checkin_args,
)
if force and isinstance(self.request.auth, Device):
# There was a bug in libpretixsync: If you scanned a ticket in offline mode that was
# valid at the time but no longer exists at time of upload, the device would retry to
# upload the same scan over and over again. Since we can't update all devices quickly,
# here's a dirty workaround to make it stop.
try:
brand = self.request.auth.software_brand
ver = parse(self.request.auth.software_version)
legacy_mode = (
(brand == 'pretixSCANPROXY' and ver < parse('0.0.3')) or
(brand == 'pretixSCAN Android' and ver < parse('1.11.2')) or
(brand == 'pretixSCAN' and ver < parse('1.11.2'))
)
if legacy_mode:
return Response({
'status': 'error',
'reason': Checkin.REASON_ALREADY_REDEEMED,
'reason_explanation': None,
'require_attention': False,
'__warning': 'Compatibility hack active due to detected old pretixSCAN version',
}, status=400)
except: # we don't care e.g. about invalid version numbers
pass
return Response({
'detail': 'Not found.', # for backwards compatibility
'status': 'error',
'reason': Checkin.REASON_INVALID,
'reason_explanation': None,
'require_attention': False,
}, status=404)
elif revoked_matches and force:
op = revoked_matches[0].position
raw_barcode_for_checkin = self.kwargs['pk']
else:
op = revoked_matches[0].position
op.order.log_action('pretix.event.checkin.revoked', data={
'datetime': dt,
'type': type,
'list': self.checkinlist.pk,
'barcode': self.kwargs['pk']
}, user=self.request.user, auth=self.request.auth)
Checkin.objects.create(
position=op,
successful=False,
error_reason=Checkin.REASON_REVOKED,
**common_checkin_args
)
return Response({
'status': 'error',
'reason': Checkin.REASON_REVOKED,
'reason_explanation': None,
'require_attention': False,
'position': CheckinListOrderPositionSerializer(op, context=self.get_serializer_context()).data
}, status=400)
given_answers = {}
if 'answers' in self.request.data:
@@ -409,6 +549,8 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
user=self.request.user,
auth=self.request.auth,
type=type,
raw_barcode=raw_barcode_for_checkin,
from_revoked_secret=True,
)
except RequiredQuestionsError as e:
return Response({
@@ -424,11 +566,19 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
'position': op.id,
'positionid': op.positionid,
'errorcode': e.code,
'reason_explanation': e.reason,
'force': force,
'datetime': dt,
'type': type,
'list': self.checkinlist.pk
}, user=self.request.user, auth=self.request.auth)
Checkin.objects.create(
position=op,
successful=False,
error_reason=e.code,
error_explanation=e.reason,
**common_checkin_args,
)
return Response({
'status': 'error',
'reason': e.code,
@@ -460,7 +610,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
)
if cf.type not in allowed_types:
raise ValidationError('The submitted file "{fid}" has a file type that is not allowed in this field.'.format(fid=data))
if cf.file.size > 10 * 1024 * 1024:
if cf.file.size > settings.FILE_UPLOAD_MAX_SIZE_OTHER:
raise ValidationError('The submitted file "{fid}" is too large to be used in this field.'.format(fid=data))
return cf.file

View File

@@ -34,7 +34,7 @@
import django_filters
from django.db import transaction
from django.db.models import ProtectedError, Q
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
@@ -49,20 +49,24 @@ from pretix.api.serializers.event import (
)
from pretix.api.views import ConditionalListView
from pretix.base.models import (
CartPosition, Device, Event, TaxRule, TeamAPIToken,
CartPosition, Device, Event, SeatCategoryMapping, TaxRule, TeamAPIToken,
)
from pretix.base.models.event import SubEvent
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.settings import SETTINGS_AFFECTING_CSS
from pretix.helpers.dicts import merge_dicts
from pretix.helpers.i18n import i18ncomp
from pretix.presale.style import regenerate_css
from pretix.presale.views.organizer import filter_qs_by_attr
with scopes_disabled():
class EventFilter(FilterSet):
is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs')
is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs')
ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs')
sales_channel = django_filters.rest_framework.CharFilter(method='sales_channel_qs')
search = django_filters.rest_framework.CharFilter(method='search_qs')
class Meta:
model = Event
@@ -107,6 +111,13 @@ with scopes_disabled():
def sales_channel_qs(self, queryset, name, value):
return queryset.filter(sales_channels__contains=value)
def search_qs(self, queryset, name, value):
return queryset.filter(
Q(name__icontains=i18ncomp(value))
| Q(slug__icontains=value)
| Q(location__icontains=i18ncomp(value))
)
class EventViewSet(viewsets.ModelViewSet):
serializer_class = EventSerializer
@@ -136,10 +147,43 @@ class EventViewSet(viewsets.ModelViewSet):
)
qs = filter_qs_by_attr(qs, self.request)
if 'with_availability_for' in self.request.GET:
qs = Event.annotated(qs, channel=self.request.GET.get('with_availability_for'))
return qs.prefetch_related(
'meta_values', 'meta_values__property', 'seat_category_mappings'
'organizer',
'meta_values',
'meta_values__property',
'item_meta_properties',
Prefetch(
'seat_category_mappings',
to_attr='_seat_category_mappings',
queryset=SeatCategoryMapping.objects.filter(subevent=None)
),
)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if 'with_availability_for' in self.request.GET:
quotas_to_compute = []
qcache = {}
for se in page:
se._quota_cache = qcache
quotas_to_compute += se.active_quotas
if quotas_to_compute:
qa = QuotaAvailability()
qa.queue(*quotas_to_compute)
qa.compute(allow_cache=True)
qcache.update(qa.results)
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
def perform_update(self, serializer):
current_live_value = serializer.instance.live
updated_live_value = serializer.validated_data.get('live', None)
@@ -197,7 +241,6 @@ class EventViewSet(viewsets.ModelViewSet):
except Event.DoesNotExist:
raise ValidationError('Event to copy from was not found')
print(copy_from, self.request.GET)
new_event = serializer.save(organizer=self.request.organizer)
if copy_from:
@@ -336,8 +379,18 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
qs = filter_qs_by_attr(qs, self.request)
if 'with_availability_for' in self.request.GET:
qs = SubEvent.annotated(qs, channel=self.request.GET.get('with_availability_for'))
return qs.prefetch_related(
'subeventitem_set', 'subeventitemvariation_set', 'seat_category_mappings', 'meta_values'
'event',
'subeventitem_set',
'subeventitemvariation_set',
'meta_values',
Prefetch(
'seat_category_mappings',
to_attr='_seat_category_mappings',
),
)
def list(self, request, **kwargs):
@@ -345,14 +398,24 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
resp = self.get_paginated_response(serializer.data)
resp['X-Page-Generated'] = date
return resp
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, headers={'X-Page-Generated': date})
if 'with_availability_for' in self.request.GET:
quotas_to_compute = []
qcache = {}
for se in page:
se._quota_cache = qcache
quotas_to_compute += se.active_quotas
if quotas_to_compute:
qa = QuotaAvailability()
qa.queue(*quotas_to_compute)
qa.compute(allow_cache=True)
qcache.update(qa.results)
serializer = self.get_serializer(page, many=True)
resp = self.get_paginated_response(serializer.data)
resp['X-Page-Generated'] = date
return resp
def perform_update(self, serializer):
original_data = self.get_serializer(instance=serializer.instance).data

View File

@@ -69,7 +69,7 @@ class ExportersMixin:
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
if cf.file:
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename)
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
return resp
elif not settings.HAS_CELERY:
return Response(
@@ -132,7 +132,7 @@ class EventExportersViewSet(ExportersMixin, viewsets.ViewSet):
def exporters(self):
exporters = []
responses = register_data_exporters.send(self.request.event)
for ex in sorted([response(self.request.event) for r, response in responses], key=lambda ex: str(ex.verbose_name)):
for ex in sorted([response(self.request.event, self.request.organizer) for r, response in responses], key=lambda ex: str(ex.verbose_name)):
ex._serializer = JobRunSerializer(exporter=ex)
exporters.append(ex)
return exporters
@@ -151,7 +151,7 @@ class OrganizerExportersViewSet(ExportersMixin, viewsets.ViewSet):
organizer=self.request.organizer
)
responses = register_multievent_data_exporters.send(self.request.organizer)
for ex in sorted([response(events) for r, response in responses if response], key=lambda ex: str(ex.verbose_name)):
for ex in sorted([response(events, self.request.organizer) for r, response in responses if response], key=lambda ex: str(ex.verbose_name)):
ex._serializer = JobRunSerializer(exporter=ex, events=events)
exporters.append(ex)
return exporters

View File

@@ -477,6 +477,23 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
def get_queryset(self):
return self.request.event.quotas.all()
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if self.request.GET.get('with_availability') == 'true':
if page:
qa = QuotaAvailability()
qa.queue(*page)
qa.compute(allow_cache=False)
for q in page:
q.available = qa.results[q][0] == Quota.AVAILABILITY_OK
q.available_number = qa.results[q][1]
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -496,6 +513,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['request'] = self.request
return ctx
def perform_update(self, serializer):

View File

@@ -55,9 +55,9 @@ from pretix.api.serializers.order import (
)
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Device, Event, Invoice, InvoiceAddress,
Order, OrderFee, OrderPayment, OrderPosition, OrderRefund, Quota, SubEvent,
TaxRule, TeamAPIToken, generate_secret,
CachedCombinedTicket, CachedTicket, Checkin, Device, Event, Invoice,
InvoiceAddress, Order, OrderFee, OrderPayment, OrderPosition, OrderRefund,
Quota, SubEvent, TaxRule, TeamAPIToken, generate_secret,
)
from pretix.base.models.orders import QuestionAnswer, RevokedTicketSecret
from pretix.base.payment import PaymentException
@@ -92,6 +92,9 @@ with scopes_disabled():
subevent_after = django_filters.IsoDateTimeFilter(method='subevent_after_qs')
subevent_before = django_filters.IsoDateTimeFilter(method='subevent_before_qs')
search = django_filters.CharFilter(method='search_qs')
item = django_filters.CharFilter(field_name='all_positions', lookup_expr='item_id')
variation = django_filters.CharFilter(field_name='all_positions', lookup_expr='variation_id')
subevent = django_filters.CharFilter(field_name='all_positions', lookup_expr='subevent_id')
class Meta:
model = Order
@@ -201,7 +204,8 @@ class OrderViewSet(viewsets.ModelViewSet):
Prefetch(
'positions',
opq.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
'item', 'variation', 'answers', 'answers__options', 'answers__question',
'item__category', 'addon_to', 'seat',
Prefetch('addons', opq.select_related('item', 'variation', 'seat'))
)
@@ -212,7 +216,10 @@ class OrderViewSet(viewsets.ModelViewSet):
Prefetch(
'positions',
opq.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question', 'seat',
Prefetch('checkins', queryset=Checkin.objects.all()),
'item', 'variation',
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options', 'question').order_by('question__position')),
'seat',
)
)
)
@@ -690,6 +697,16 @@ class OrderViewSet(viewsets.ModelViewSet):
}
)
if 'custom_followup_at' in self.request.data and serializer.instance.custom_followup_at != self.request.data.get('custom_followup_at'):
serializer.instance.log_action(
'pretix.event.order.custom_followup_at',
user=self.request.user,
auth=self.request.auth,
data={
'new_custom_followup_at': self.request.data.get('custom_followup_at')
}
)
if 'checkin_attention' in self.request.data and serializer.instance.checkin_attention != self.request.data.get('checkin_attention'):
serializer.instance.log_action(
'pretix.event.order.checkin_attention',
@@ -781,7 +798,7 @@ with scopes_disabled():
)
def has_checkin_qs(self, queryset, name, value):
return queryset.filter(checkins__isnull=not value)
return queryset.alias(ce=Exists(Checkin.objects.filter(position=OuterRef('pk')))).filter(ce=value)
def attendee_name_qs(self, queryset, name, value):
return queryset.filter(Q(attendee_name_cached__iexact=value) | Q(addon_to__attendee_name_cached__iexact=value))
@@ -835,7 +852,8 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, vi
qs = qs.filter(order__event=self.request.event)
if self.request.query_params.get('pdf_data', 'false') == 'true':
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', qs.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
@@ -845,7 +863,8 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, vi
Prefetch(
'positions',
qs.prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
'item', 'variation', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
)
)
))
@@ -854,7 +873,8 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, vi
)
else:
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
'answers', 'answers__options', 'answers__question',
).select_related(
'item', 'order', 'order__event', 'order__event__organizer', 'seat'
)
@@ -1436,8 +1456,14 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
inv = self.get_object()
if inv.canceled:
raise ValidationError('The invoice has already been canceled.')
if not inv.event.settings.invoice_regenerate_allowed:
raise PermissionDenied('Invoices may not be changed after they are created.')
elif inv.shredded:
raise PermissionDenied('The invoice file is no longer stored on the server.')
elif inv.sent_to_organizer:
raise PermissionDenied('The invoice file has already been exported.')
elif now().astimezone(self.request.event.timezone).date() - inv.date > datetime.timedelta(days=1):
raise PermissionDenied('The invoice file is too old to be regenerated.')
else:
inv = regenerate_invoice(inv)
inv.order.log_action(

View File

@@ -261,7 +261,7 @@ def register_default_webhook_events(sender, **kwargs):
),
ParametrizedEventWebhookEvent(
'pretix.event.deleted',
_('Event details changed'),
_('Event deleted'),
),
ParametrizedSubEventWebhookEvent(
'pretix.subevent.added',

View File

@@ -47,6 +47,7 @@ class PretixBaseConfig(AppConfig):
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 .models import _transactions # NOQA
from django.conf import settings
try:

View File

@@ -146,7 +146,8 @@ class NativeAuthBackend(BaseAuthBackend):
d = OrderedDict([
('email', forms.EmailField(label=_("E-mail"), max_length=254,
widget=forms.EmailInput(attrs={'autofocus': 'autofocus'}))),
('password', forms.CharField(label=_("Password"), widget=forms.PasswordInput)),
('password', forms.CharField(label=_("Password"), widget=forms.PasswordInput,
max_length=4096)),
])
return d

View File

@@ -82,6 +82,13 @@ class SalesChannel:
"""
return False
@property
def customer_accounts_supported(self) -> bool:
"""
If this property is ``True``, checkout will show the customer login step.
"""
return True
def get_all_sales_channels():
global _ALL_CHANNELS

View File

@@ -30,7 +30,7 @@ from pretix.base.settings import GlobalSettingsObject
from pretix.base.templatetags.safelink import safelink as sl
def get_powered_by(safelink=True):
def get_powered_by(request, safelink=True):
gs = GlobalSettingsObject()
d = gs.settings.license_check_input
if d.get('poweredby_name'):
@@ -57,7 +57,7 @@ def get_powered_by(safelink=True):
if d.get('base_license') == 'agpl':
msg += ' (<a href="{}" target="_blank" rel="noopener">{}</a>)'.format(
reverse('source'),
request.build_absolute_uri(reverse('source')),
gettext('source code')
)
@@ -69,7 +69,7 @@ def contextprocessor(request):
'rtl': getattr(request, 'LANGUAGE_CODE', 'en') in settings.LANGUAGES_RTL,
}
try:
ctx['poweredby'] = get_powered_by(safelink=True)
ctx['poweredby'] = get_powered_by(request, safelink=True)
except Exception:
ctx['poweredby'] = 'powered by <a href="https://pretix.eu/" target="_blank" rel="noopener">pretix</a>'
if settings.DEBUG and 'runserver' not in sys.argv:

View File

@@ -25,7 +25,9 @@ from datetime import timedelta
from decimal import Decimal
from itertools import groupby
from smtplib import SMTPResponseException
from typing import TypeVar
import css_inline
from django.conf import settings
from django.core.mail.backends.smtp import EmailBackend
from django.db.models import Count
@@ -35,7 +37,6 @@ from django.utils.timezone import now
from django.utils.translation import (
get_language, gettext_lazy as _, pgettext_lazy,
)
from inlinestyler.utils import inline_css
from pretix.base.i18n import (
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
@@ -49,23 +50,23 @@ from pretix.base.templatetags.rich_text import markdown_compile_email
logger = logging.getLogger('pretix.base.email')
T = TypeVar("T", bound=EmailBackend)
class CustomSMTPBackend(EmailBackend):
def test(self, from_addr):
try:
self.open()
self.connection.ehlo_or_helo_if_needed()
(code, resp) = self.connection.mail(from_addr, [])
if code != 250:
logger.warn('Error testing mail settings, code %d, resp: %s' % (code, resp))
raise SMTPResponseException(code, resp)
(code, resp) = self.connection.rcpt('testdummy@pretix.eu')
if (code != 250) and (code != 251):
logger.warn('Error testing mail settings, code %d, resp: %s' % (code, resp))
raise SMTPResponseException(code, resp)
finally:
self.close()
def test_custom_smtp_backend(backend: T, from_addr: str) -> None:
try:
backend.open()
backend.connection.ehlo_or_helo_if_needed()
(code, resp) = backend.connection.mail(from_addr, [])
if code != 250:
logger.warning('Error testing mail settings, code %d, resp: %s' % (code, resp))
raise SMTPResponseException(code, resp)
(code, resp) = backend.connection.rcpt('testdummy@pretix.eu')
if (code != 250) and (code != 251):
logger.warning('Error testing mail settings, code %d, resp: %s' % (code, resp))
raise SMTPResponseException(code, resp)
finally:
backend.close()
class BaseHTMLMailRenderer:
@@ -174,7 +175,11 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
htmlctx['ev'] = position.subevent or self.event
tpl = get_template(self.template_name)
body_html = inline_css(tpl.render(htmlctx))
body_html = tpl.render(htmlctx)
inliner = css_inline.CSSInliner(remove_style_tags=True)
body_html = inliner.inline(body_html)
return body_html
@@ -448,6 +453,26 @@ def base_placeholders(sender, **kwargs):
}
),
),
SimpleFunctionalMailTextPlaceholder(
'subevent', ['waiting_list_entry', 'event'],
lambda waiting_list_entry, event: str(waiting_list_entry.subevent or event),
lambda event: str(event if not event.has_subevents or not event.subevents.exists() else event.subevents.first())
),
SimpleFunctionalMailTextPlaceholder(
'subevent_date_from', ['waiting_list_entry', 'event'],
lambda waiting_list_entry, event: (waiting_list_entry.subevent or event).get_date_from_display(),
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(
event, 'presale:event.waitinglist.remove'
) + '?voucher=' + waiting_list_entry.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(
@@ -515,6 +540,22 @@ def base_placeholders(sender, **kwargs):
'voucher_list', ['voucher_list'], lambda voucher_list: ' \n'.join(voucher_list),
' 68CYU2H6ZTP3WLK5\n 7MB94KKPVEPSMVF2'
),
SimpleFunctionalMailTextPlaceholder(
# join vouchers with two spaces at end of line so markdown-parser inserts a <br>
'voucher_url_list', ['event', 'voucher_list'],
lambda event, voucher_list: ' \n'.join([
build_absolute_uri(
event, 'presale:event.redeem'
) + '?voucher=' + c
for c in voucher_list
]),
lambda event: ' \n'.join([
build_absolute_uri(
event, 'presale:event.redeem'
) + '?voucher=' + c
for c in ['68CYU2H6ZTP3WLK5', '7MB94KKPVEPSMVF2']
]),
),
SimpleFunctionalMailTextPlaceholder(
'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={
'event': event.slug,

View File

@@ -47,12 +47,15 @@ from django.db.models import QuerySet
from django.utils.formats import localize
from django.utils.translation import gettext, gettext_lazy as _
from openpyxl import Workbook
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE, KNOWN_TYPES
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE, KNOWN_TYPES, Cell
from pretix.base.models import Event
def excel_safe(val):
if isinstance(val, Cell):
return val
if not isinstance(val, KNOWN_TYPES):
val = str(val)
@@ -70,8 +73,9 @@ class BaseExporter:
This is the base class for all data exporters
"""
def __init__(self, event, progress_callback=lambda v: None):
def __init__(self, event, organizer, progress_callback=lambda v: None):
self.event = event
self.organizer = organizer
self.progress_callback = progress_callback
self.is_multievent = isinstance(event, QuerySet)
if isinstance(event, QuerySet):
@@ -220,9 +224,13 @@ class ListExporter(BaseExporter):
writer.writerow(line)
return self.get_filename() + '.csv', 'text/csv', output.getvalue().encode("utf-8")
def prepare_xlsx_sheet(self, ws):
pass
def _render_xlsx(self, form_data, output_file=None):
wb = Workbook(write_only=True)
ws = wb.create_sheet()
self.prepare_xlsx_sheet(ws)
try:
ws.title = str(self.verbose_name)
except:

View File

@@ -324,7 +324,6 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
_('Tax rate'),
_('Tax name'),
_('Event start date'),
_('Date'),
_('Order code'),
_('E-mail address'),
@@ -348,6 +347,8 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
_('Invoice recipient:') + ' ' + _('Beneficiary'),
_('Invoice recipient:') + ' ' + _('Internal reference'),
_('Payment providers'),
_('Event end date'),
_('Location'),
]
p_providers = OrderPayment.objects.filter(
@@ -406,7 +407,9 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
', '.join([
str(self.providers.get(p, p)) for p in sorted(set((l.payment_providers or '').split(',')))
if p and p != 'free'
])
]),
date_format(l.event_date_to, "SHORT_DATE_FORMAT") if l.event_date_to else "",
l.event_location or "",
]
@cached_property

View File

@@ -55,16 +55,20 @@ class JSONExporter(BaseExporter):
'name': str(self.event.organizer.name),
'slug': self.event.organizer.slug
},
'meta_data': self.event.meta_data,
'categories': [
{
'id': category.id,
'name': str(category.name),
'description': str(category.description),
'position': category.position,
'internal_name': category.internal_name
} for category in self.event.categories.all()
],
'items': [
{
'id': item.id,
'position': item.position,
'name': str(item.name),
'internal_name': str(item.internal_name),
'category': item.category_id,
@@ -73,13 +77,35 @@ class JSONExporter(BaseExporter):
'tax_name': str(item.tax_rule.name) if item.tax_rule else None,
'admission': item.admission,
'active': item.active,
'sales_channels': item.sales_channels,
'description': str(item.description),
'available_from': item.available_from,
'available_until': item.available_until,
'require_voucher': item.require_voucher,
'hide_without_voucher': item.hide_without_voucher,
'allow_cancel': item.allow_cancel,
'require_bundling': item.require_bundling,
'min_per_order': item.min_per_order,
'max_per_order': item.max_per_order,
'checkin_attention': item.checkin_attention,
'original_price': item.original_price,
'issue_giftcard': item.issue_giftcard,
'meta_data': item.meta_data,
'require_membership': item.require_membership,
'variations': [
{
'id': variation.id,
'active': variation.active,
'price': variation.default_price if variation.default_price is not None else
item.default_price,
'name': str(variation)
'name': str(variation),
'description': str(variation.description),
'position': variation.position,
'require_membership': variation.require_membership,
'sales_channels': variation.sales_channels,
'available_from': variation.available_from,
'available_until': variation.available_until,
'hide_without_voucher': variation.hide_without_voucher,
} for variation in item.variations.all()
]
} for item in self.event.items.select_related('tax_rule').prefetch_related('variations')
@@ -87,7 +113,13 @@ class JSONExporter(BaseExporter):
'questions': [
{
'id': question.id,
'identifier': question.identifier,
'required': question.required,
'question': str(question.question),
'position': question.position,
'hidden': question.hidden,
'ask_during_checkin': question.ask_during_checkin,
'help_text': str(question.help_text),
'type': question.type
} for question in self.event.questions.all()
],
@@ -95,7 +127,18 @@ class JSONExporter(BaseExporter):
{
'code': order.code,
'status': order.status,
'customer': order.customer.identifier if order.customer else None,
'testmode': order.testmode,
'user': order.email,
'email': order.email,
'phone': str(order.phone),
'locale': order.locale,
'comment': order.comment,
'custom_followup_at': order.custom_followup_at,
'require_approval': order.require_approval,
'checkin_attention': order.checkin_attention,
'sales_channel': order.sales_channel,
'expires': order.expires,
'datetime': order.datetime,
'fees': [
{
@@ -108,11 +151,21 @@ class JSONExporter(BaseExporter):
'positions': [
{
'id': position.id,
'positionid': position.positionid,
'item': position.item_id,
'variation': position.variation_id,
'subevent': position.subevent_id,
'seat': position.seat.seat_guid if position.seat else None,
'price': position.price,
'tax_rate': position.tax_rate,
'tax_value': position.tax_value,
'attendee_name': position.attendee_name,
'attendee_email': position.attendee_email,
'company': position.company,
'street': position.street,
'zipcode': position.zipcode,
'country': str(position.country) if position.country else None,
'state': position.state,
'secret': position.secret,
'addon_to': position.addon_to_id,
'answers': [
@@ -124,15 +177,30 @@ class JSONExporter(BaseExporter):
} for position in order.positions.all()
]
} for order in
self.event.orders.all().prefetch_related('positions', 'positions__answers', 'fees')
self.event.orders.all().prefetch_related('positions', 'positions__answers', 'positions__seat', 'customer', 'fees')
],
'quotas': [
{
'id': quota.id,
'size': quota.size,
'subevent': quota.subevent_id,
'items': [item.id for item in quota.items.all()],
'variations': [variation.id for variation in quota.variations.all()],
} for quota in self.event.quotas.all().prefetch_related('items', 'variations')
],
'subevents': [
{
'id': se.id,
'name': str(se.name),
'location': str(se.location),
'date_from': se.date_from,
'date_to': se.date_to,
'date_admission': se.date_admission,
'geo_lat': se.geo_lat,
'geo_lon': se.geo_lon,
'is_public': se.is_public,
'meta_data': se.meta_data,
} for se in self.event.subevents.all()
]
}
}

View File

@@ -33,6 +33,7 @@
# 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
@@ -42,10 +43,10 @@ from django.db.models import (
Case, CharField, Count, DateTimeField, F, IntegerField, Max, Min, OuterRef,
Q, Subquery, Sum, When,
)
from django.db.models.functions import Coalesce, TruncDate
from django.db.models.functions import Coalesce
from django.dispatch import receiver
from django.utils.functional import cached_property
from django.utils.timezone import get_current_timezone, now
from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext as _, gettext_lazy, pgettext
from pretix.base.models import (
@@ -129,7 +130,7 @@ class OrderListExporter(MultiSheetListExporter):
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 after this date. '
help_text=_('Only include orders including at least one ticket for a date on or before this date. '
'Will also include other dates in case of mixed orders!')
)),
]
@@ -181,41 +182,43 @@ class OrderListExporter(MultiSheetListExporter):
if form_data.get('date_from'):
date_value = form_data.get('date_from')
if isinstance(date_value, str):
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['date'] = TruncDate(f'{rel}datetime')
filters['date__gte'] = date_value
filters[f'{rel}datetime__gte'] = datetime_value
if form_data.get('date_to'):
date_value = form_data.get('date_to')
if isinstance(date_value, str):
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['date'] = TruncDate(f'{rel}datetime')
filters['date__lte'] = date_value
filters[f'{rel}datetime__lte'] = datetime_value
if form_data.get('event_date_from'):
date_value = form_data.get('event_date_from')
if isinstance(date_value, str):
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'] = date_value
filters['event_date_max__gte'] = datetime_value
if form_data.get('event_date_to'):
date_value = form_data.get('event_date_to')
if isinstance(date_value, str):
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'] = date_value
filters['event_date_min__lte'] = datetime_value
if filters:
return qs.annotate(**annotations).filter(**filters)
@@ -288,6 +291,7 @@ class OrderListExporter(MultiSheetListExporter):
headers.append(_('Sales channel'))
headers.append(_('Requires special attention'))
headers.append(_('Comment'))
headers.append(_('Follow-up date'))
headers.append(_('Positions'))
headers.append(_('E-mail address verified'))
headers.append(_('Payment providers'))
@@ -393,6 +397,7 @@ class OrderListExporter(MultiSheetListExporter):
row.append(order.sales_channel)
row.append(_('Yes') if order.checkin_attention else _('No'))
row.append(order.comment or "")
row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "")
row.append(order.pcnt)
row.append(_('Yes') if order.email_known_to_work else _('No'))
row.append(', '.join([
@@ -574,6 +579,7 @@ class OrderListExporter(MultiSheetListExporter):
_('Seat row'),
_('Seat number'),
_('Order comment'),
_('Follow-up date'),
]
questions = list(Question.objects.filter(event__in=self.events))
@@ -677,6 +683,7 @@ class OrderListExporter(MultiSheetListExporter):
row += ['', '', '', '', '']
row.append(order.comment)
row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "")
acache = {}
for a in op.answers.all():
# We do not want to localize Date, Time and Datetime question answers, as those can lead
@@ -721,7 +728,7 @@ class OrderListExporter(MultiSheetListExporter):
row += [
order.sales_channel,
order.locale,
row.append(_('Yes') if order.email_known_to_work else _('No'))
_('Yes') if order.email_known_to_work else _('No')
]
row.append(', '.join([
str(self.providers.get(p, p)) for p in sorted(set((op.payment_providers or '').split(',')))
@@ -780,7 +787,7 @@ class PaymentListExporter(ListExporter):
headers = [
_('Event slug'), _('Order'), _('Payment ID'), _('Creation date'), _('Completion date'), _('Status'),
_('Status code'), _('Amount'), _('Payment method'), _('Comment')
_('Status code'), _('Amount'), _('Payment method'), _('Comment'),
]
yield headers
@@ -866,6 +873,78 @@ class QuotaListExporter(ListExporter):
return '{}_quotas'.format(self.event.slug)
def generate_GiftCardTransactionListExporter(organizer): # hackhack
class GiftcardTransactionListExporter(ListExporter):
identifier = 'giftcardtransactionlist'
verbose_name = gettext_lazy('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,
)),
]
d = OrderedDict(d)
return d
def iterate_list(self, form_data):
qs = GiftCardTransaction.objects.filter(
card__issuer=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)
)
headers = [
_('Gift card code'),
_('Test mode'),
_('Date'),
_('Amount'),
_('Currency'),
_('Order'),
]
yield headers
for obj in qs:
row = [
obj.card.secret,
_('TEST MODE') if obj.card.testmode else '',
obj.datetime.astimezone(self.timezone).strftime('%Y-%m-%d %H:%M:%S'),
obj.value,
obj.card.currency,
obj.order.full_code if obj.order else None,
]
yield row
def get_filename(self):
return '{}_giftcardtransactions'.format(organizer.slug)
return GiftcardTransactionListExporter
class GiftcardRedemptionListExporter(ListExporter):
identifier = 'giftcardredemptionlist'
verbose_name = gettext_lazy('Gift card redemptions')
@@ -1058,3 +1137,8 @@ def register_multievent_i_giftcardredemptionlist_exporter(sender, **kwargs):
@receiver(register_multievent_data_exporters, dispatch_uid="multiexporter_giftcardlist")
def register_multievent_i_giftcardlist_exporter(sender, **kwargs):
return generate_GiftCardListExporter(sender)
@receiver(register_multievent_data_exporters, dispatch_uid="multiexporter_giftcardtransactionlist")
def register_multievent_i_giftcardtransactionlist_exporter(sender, **kwargs):
return generate_GiftCardTransactionListExporter(sender)

View File

@@ -118,6 +118,27 @@ class SettingsForm(i18nfield.forms.I18nFormMixin, HierarkeyForm):
self.cleaned_data[k] = self.initial[k]
return super().save()
def clean(self):
d = super().clean()
# There is logic in HierarkeyForm.save() to only persist fields that changed. HierarkeyForm determines if
# something changed by comparing `self._s.get(name)` to `value`. This leaves an edge case open for multi-lingual
# text fields. On the very first load, the initial value in `self._s.get(name)` will be a LazyGettextProxy-based
# string. However, only some of the languages are usually visible, so even if the user does not change anything
# at all, it will be considered a changed value and stored. We do not want that, as it makes it very hard to add
# languages to an organizer/event later on. So we trick it and make sure nothing gets changed in that situation.
for name, field in self.fields.items():
if isinstance(field, i18nfield.forms.I18nFormField):
value = d.get(name)
if not value:
continue
current = self._s.get(name, as_type=type(value))
if name not in self.changed_data:
d[name] = current
return d
def get_new_filename(self, name: str) -> str:
from pretix.base.models import Event

View File

@@ -154,6 +154,7 @@ class RegistrationForm(forms.Form):
widget=forms.PasswordInput(attrs={
'autocomplete': 'new-password' # see https://bugs.chromium.org/p/chromium/issues/detail?id=370363#c7
}),
max_length=4096,
required=True
)
password_repeat = forms.CharField(
@@ -161,6 +162,7 @@ class RegistrationForm(forms.Form):
widget=forms.PasswordInput(attrs={
'autocomplete': 'new-password' # see https://bugs.chromium.org/p/chromium/issues/detail?id=370363#c7
}),
max_length=4096,
required=True
)
keep_logged_in = forms.BooleanField(label=_("Keep me logged in"), required=False)
@@ -204,11 +206,13 @@ class PasswordRecoverForm(forms.Form):
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput,
max_length=4096,
required=True
)
password_repeat = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput
widget=forms.PasswordInput,
max_length=4096,
)
def __init__(self, user_id=None, *args, **kwargs):

View File

@@ -37,21 +37,19 @@ import json
import logging
from decimal import Decimal
from io import BytesIO
from urllib.error import HTTPError
import dateutil.parser
import pycountry
import pytz
import vat_moss.errors
import vat_moss.id
from babel import Locale
from django import forms
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db.models import QuerySet
from django.forms import Select
from django.forms import Select, widgets
from django.utils import translation
from django.utils.formats import date_format
from django.utils.html import escape
@@ -75,8 +73,9 @@ from pretix.base.i18n import (
get_babel_locale, get_language_without_region, language,
)
from pretix.base.models import InvoiceAddress, Question, QuestionOption
from pretix.base.models.tax import (
EU_COUNTRIES, cc_to_vat_prefix, is_eu_country,
from pretix.base.models.tax import VAT_ID_COUNTRIES, ask_for_vat_id
from pretix.base.services.tax import (
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
)
from pretix.base.settings import (
COUNTRIES_WITH_STATE_IN_ADDRESS, PERSON_NAME_SALUTATIONS,
@@ -153,8 +152,9 @@ class NamePartsWidget(forms.MultiWidget):
final_attrs,
id='%s_%s' % (id_, i),
title=self.scheme['fields'][i][1],
placeholder=self.scheme['fields'][i][1],
)
if not isinstance(widget, widgets.Select):
these_attrs['placeholder'] = self.scheme['fields'][i][1]
if self.scheme['fields'][i][0] in REQUIRED_NAME_PARTS:
if self.field.required:
these_attrs['required'] = 'required'
@@ -333,23 +333,41 @@ class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
def guess_country(event):
# Try to guess the initial country from either the country of the merchant
# or the locale. This will hopefully save at least some users some scrolling :)
locale = get_language_without_region()
country = event.settings.region or event.settings.invoice_address_from_country
if not country:
valid_countries = countries.countries
if '-' in locale:
parts = locale.split('-')
# TODO: does this actually work?
if parts[1].upper() in valid_countries:
country = Country(parts[1].upper())
elif parts[0].upper() in valid_countries:
country = Country(parts[0].upper())
else:
if locale.upper() in valid_countries:
country = Country(locale.upper())
country = get_country_by_locale(get_language_without_region())
return country
def get_country_by_locale(locale):
country = None
valid_countries = countries.countries
if '-' in locale:
parts = locale.split('-')
# TODO: does this actually work?
if parts[1].upper() in valid_countries:
country = Country(parts[1].upper())
elif parts[0].upper() in valid_countries:
country = Country(parts[0].upper())
else:
if locale.upper() in valid_countries:
country = Country(locale.upper())
return country
def guess_phone_prefix(event):
with language(get_babel_locale()):
country = str(guess_country(event))
return get_phone_prefix(country)
def get_phone_prefix(country):
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
if country in values:
return prefix
return None
class QuestionCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
option_template_name = 'pretixbase/forms/widgets/checkbox_option_with_links.html'
@@ -507,7 +525,7 @@ class PortraitImageField(SizeValidationMixin, ExtValidationMixin, forms.FileFiel
def __init__(self, *args, **kwargs):
kwargs.setdefault('ext_whitelist', (".png", ".jpg", ".jpeg", ".jfif", ".tif", ".tiff", ".bmp"))
kwargs.setdefault('max_size', 10 * 1024 * 1024)
kwargs.setdefault('max_size', settings.FILE_UPLOAD_MAX_SIZE_IMAGE)
super().__init__(*args, **kwargs)
@@ -739,7 +757,7 @@ class BaseQuestionsForm(forms.Form):
".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
".bmp", ".tif", ".tiff"
),
max_size=10 * 1024 * 1024,
max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER,
)
elif q.type == Question.TYPE_DATE:
attrs = {}
@@ -780,25 +798,26 @@ class BaseQuestionsForm(forms.Form):
if q.valid_datetime_max:
field.validators.append(MaxDateTimeValidator(q.valid_datetime_max))
elif q.type == Question.TYPE_PHONENUMBER:
with language(get_babel_locale()):
default_country = guess_country(event)
default_prefix = None
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
if str(default_country) in values:
default_prefix = prefix
if initial:
try:
initial = PhoneNumber().from_string(initial.answer) if initial else "+{}.".format(default_prefix)
initial = PhoneNumber().from_string(initial.answer)
except NumberParseException:
initial = None
field = PhoneNumberField(
label=label, required=required,
help_text=help_text,
# We now exploit an implementation detail in PhoneNumberPrefixWidget to allow us to pass just
# a country code but no number as an initial value. It's a bit hacky, but should be stable for
# the future.
initial=initial,
widget=WrappedPhoneNumberPrefixWidget()
)
if not initial:
phone_prefix = guess_phone_prefix(event)
if phone_prefix:
initial = "+{}.".format(phone_prefix)
field = PhoneNumberField(
label=label, required=required,
help_text=help_text,
# We now exploit an implementation detail in PhoneNumberPrefixWidget to allow us to pass just
# a country code but no number as an initial value. It's a bit hacky, but should be stable for
# the future.
initial=initial,
widget=WrappedPhoneNumberPrefixWidget()
)
field.question = q
if answers:
# Cache the answer object for later use
@@ -869,6 +888,12 @@ class BaseQuestionsForm(forms.Form):
if question_is_required(q) and not answer and answer != 0 and not field.errors:
raise ValidationError({'question_%d' % q.pk: [_('This field is required.')]})
# Strip invisible question from cleaned_data so they don't end up in the database
for q in question_cache.values():
answer = d.get('question_%d' % q.pk)
if q.dependency_question_id and not question_is_visible(q.dependency_question_id, q.dependency_values) and answer is not None:
d['question_%d' % q.pk] = None
return d
@@ -900,7 +925,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
'data-display-dependency': '#id_is_business_1',
'autocomplete': 'organization',
}),
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-in-eu': ','.join(EU_COUNTRIES)}),
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-with-vat-id': ','.join(VAT_ID_COUNTRIES)}),
'internal_reference': forms.TextInput,
}
labels = {
@@ -920,6 +945,18 @@ class BaseInvoiceAddressForm(forms.ModelForm):
super().__init__(*args, **kwargs)
if not event.settings.invoice_address_vatid:
del self.fields['vat_id']
elif self.validate_vat_id:
self.fields['vat_id'].help_text = '<br/>'.join([
str(_('Optional, but depending on the country you reside in we might need to charge you '
'additional taxes if you do not enter it.')),
str(_('If you are registered in Switzerland, you can enter your UID instead.')),
])
else:
self.fields['vat_id'].help_text = '<br/>'.join([
str(_('Optional, but it might be required for you to claim tax benefits on your invoice '
'depending on your and the sellers country of residence.')),
str(_('If you are registered in Switzerland, you can enter your UID instead.')),
])
self.fields['country'].choices = CachedCountries()
@@ -951,7 +988,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
self.fields['state'].widget.is_required = True
# Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected.
if cc and not is_eu_country(cc) and fprefix + 'vat_id' in self.data:
if cc and not ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data:
self.data = self.data.copy()
del self.data[fprefix + 'vat_id']
@@ -976,7 +1013,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
scheme=event.settings.name_scheme,
titles=event.settings.name_scheme_titles,
label=_('Name'),
initial=(self.instance.name_parts if self.instance else self.instance.name_parts),
initial=self.instance.name_parts,
)
if event.settings.invoice_address_required and not event.settings.invoice_address_company_required and not self.all_optional:
if not event.settings.invoice_name_required:
@@ -1001,7 +1038,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if not data.get('is_business'):
data['company'] = ''
data['vat_id'] = ''
if data.get('is_business') and not is_eu_country(data.get('country')):
if data.get('is_business') and not ask_for_vat_id(data.get('country')):
data['vat_id'] = ''
if self.event.settings.invoice_address_required:
if data.get('is_business') and not data.get('company'):
@@ -1024,36 +1061,23 @@ class BaseInvoiceAddressForm(forms.ModelForm):
# Do not save the country if it is the only field set -- we don't know the user even checked it!
self.cleaned_data['country'] = ''
if data.get('vat_id') and is_eu_country(data.get('country')) and data.get('vat_id')[:2] != cc_to_vat_prefix(str(data.get('country'))):
raise ValidationError(_('Your VAT ID does not match the selected country.'))
if self.validate_vat_id and self.instance.vat_id_validated and 'vat_id' not in self.changed_data:
pass
elif self.validate_vat_id and data.get('is_business') and is_eu_country(data.get('country')) and data.get('vat_id'):
elif self.validate_vat_id and data.get('is_business') and ask_for_vat_id(data.get('country')) and data.get('vat_id'):
try:
result = vat_moss.id.validate(data.get('vat_id'))
if result:
country_code, normalized_id, company_name = result
self.instance.vat_id_validated = True
self.instance.vat_id = normalized_id
except (vat_moss.errors.InvalidError, ValueError):
raise ValidationError(_('This VAT ID is not valid. Please re-check your input.'))
except vat_moss.errors.WebServiceUnavailableError:
logger.exception('VAT ID checking failed for country {}'.format(data.get('country')))
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')))
self.instance.vat_id_validated = True
self.instance.vat_id = normalized_id
except VATIDFinalError as e:
if self.all_optional:
self.instance.vat_id_validated = False
messages.warning(self.request, e.message)
else:
raise ValidationError(e.message)
except VATIDTemporaryError as e:
self.instance.vat_id_validated = False
if self.request and self.vat_warning:
messages.warning(self.request, _('Your VAT ID could not be checked, as the VAT checking service of '
'your country is currently not available. We will therefore '
'need to charge VAT on your invoice. You can get the tax amount '
'back via the VAT reimbursement process.'))
except (vat_moss.errors.WebServiceError, HTTPError):
logger.exception('VAT ID checking failed for country {}'.format(data.get('country')))
self.instance.vat_id_validated = False
if self.request and self.vat_warning:
messages.warning(self.request, _('Your VAT ID could not be checked, as the VAT checking service of '
'your country returned an incorrect result. We will therefore '
'need to charge VAT on your invoice. Please contact support to '
'resolve this manually.'))
messages.warning(self.request, e.message)
else:
self.instance.vat_id_validated = False

View File

@@ -55,6 +55,7 @@ class UserSettingsForm(forms.ModelForm):
'pw_current_wrong': _("The current password you entered was not correct."),
'pw_mismatch': _("Please enter the same password twice"),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
'pw_equal': _("Please choose a password different to your current one.")
}
old_pw = forms.CharField(max_length=255,
@@ -158,6 +159,12 @@ class UserSettingsForm(forms.ModelForm):
code='pw_current'
)
if password1 and password1 == old_pw:
raise forms.ValidationError(
self.error_messages['pw_equal'],
code='pw_equal'
)
if password1:
self.instance.set_password(password1)

View File

@@ -86,14 +86,6 @@ class TimePickerWidget(forms.TimeInput):
class UploadedFileWidget(forms.ClearableFileInput):
def __init__(self, *args, **kwargs):
# Browsers can't recognize that the server already has a file uploaded
# Don't mark this input as being required if we already have an answer
# (this needs to be done via the attrs, otherwise we wouldn't get the "required" star on the field label)
attrs = kwargs.get('attrs', {})
if kwargs.get('required') and kwargs.get('initial'):
attrs.update({'required': None})
kwargs.update({'attrs': attrs})
self.position = kwargs.pop('position')
self.event = kwargs.pop('event')
self.answer = kwargs.pop('answer')
@@ -125,6 +117,15 @@ class UploadedFileWidget(forms.ClearableFileInput):
'answer': self.answer.pk,
})
def get_context(self, name, value, attrs):
# Browsers can't recognize that the server already has a file uploaded
# Don't mark this input as being required if we already have an answer
# (this needs to be done via the attrs, otherwise we wouldn't get the "required" star on the field label)
ctx = super().get_context(name, value, attrs)
if ctx['widget']['is_initial']:
ctx['widget']['attrs']['required'] = False
return ctx
def format_value(self, value):
if self.is_initial(value):
return self.FakeFile(value, self.position, self.event, self.answer)
@@ -184,7 +185,7 @@ class BusinessBooleanRadio(forms.RadioSelect):
self.require_business = require_business
if self.require_business:
choices = (
('business', _('Business customer')),
('business', _('Business or institutional customer')),
)
else:
choices = (

View File

@@ -395,7 +395,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
return txt
if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage:
if self.invoice.event.settings.show_date_to and self.invoice.event.date_to:
tz = self.invoice.event.timezone
show_end_date = (
self.invoice.event.settings.show_date_to and
self.invoice.event.date_to and
self.invoice.event.date_to.astimezone(tz).date() != self.invoice.event.date_from.astimezone(tz).date()
)
if show_end_date:
p_str = (
shorten(self.invoice.event.name) + '\n' +
pgettext('invoice', '{from_date}\nuntil {to_date}').format(
@@ -550,7 +556,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
for line in self.invoice.lines.all():
if has_taxes:
tdata.append((
Paragraph(line.description, self.stylesheet['Normal']),
Paragraph(
bleach.clean(line.description, tags=['br']).strip().replace('<br>', '<br/>').replace('\n', '<br />\n'),
self.stylesheet['Normal']
),
"1",
localize(line.tax_rate) + " %",
money_filter(line.net_value, self.invoice.event.currency),
@@ -558,7 +567,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
))
else:
tdata.append((
Paragraph(line.description, self.stylesheet['Normal']),
Paragraph(
bleach.clean(line.description, tags=['br']).strip().replace('<br>', '<br/>').replace('\n', '<br />\n'),
self.stylesheet['Normal']
),
"1",
money_filter(line.gross_value, self.invoice.event.currency),
))
@@ -595,7 +607,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
table.setStyle(TableStyle(tstyledata))
story.append(table)
story.append(Spacer(1, 15 * mm))
story.append(Spacer(1, 10 * mm))
if self.invoice.payment_provider_text:
story.append(Paragraph(
@@ -611,12 +623,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
self.invoice.additional_text,
self.stylesheet['Normal']
))
story.append(Spacer(1, 15 * mm))
story.append(Spacer(1, 5 * mm))
tstyledata = [
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
('LEFTPADDING', (0, 0), (0, -1), 0),
('RIGHTPADDING', (-1, 0), (-1, -1), 0),
('TOPPADDING', (0, 0), (-1, -1), 1),
('BOTTOMPADDING', (0, 0), (-1, -1), 1),
('FONTSIZE', (0, 0), (-1, -1), 8),
('FONTNAME', (0, 0), (-1, -1), self.font_regular),
]
@@ -769,44 +783,55 @@ class Modern1Renderer(ClassicInvoiceRenderer):
]
def _draw_metadata(self, canvas):
# Draws the "invoice number -- date" line. This has gotten a little more complicated since we
# encountered some events with very long invoice numbers. In this case, we automatically reduce
# the font size until it fits.
begin_top = 100 * mm
textobject = canvas.beginText(self.left_margin, self.pagesize[1] - begin_top)
textobject.setFont(self.font_regular, 8)
textobject.textLine(pgettext('invoice', 'Order code'))
textobject.moveCursor(0, 5)
textobject.setFont(self.font_regular, 10)
textobject.textLine(self.invoice.order.full_code)
canvas.drawText(textobject)
if self.invoice.is_cancellation:
textobject = canvas.beginText(self.left_margin + 50 * mm, self.pagesize[1] - begin_top)
def _draw(label, value, value_size, x, width):
if canvas.stringWidth(value, self.font_regular, value_size) > width and value_size > 6:
return False
textobject = canvas.beginText(x, self.pagesize[1] - begin_top)
textobject.setFont(self.font_regular, 8)
textobject.textLine(pgettext('invoice', 'Cancellation number'))
textobject.textLine(label)
textobject.moveCursor(0, 5)
textobject.setFont(self.font_regular, 10)
textobject.textLine(self.invoice.number)
canvas.drawText(textobject)
textobject.setFont(self.font_regular, value_size)
textobject.textLine(value)
return textobject
textobject = canvas.beginText(self.left_margin + 100 * mm, self.pagesize[1] - begin_top)
textobject.setFont(self.font_regular, 8)
textobject.textLine(pgettext('invoice', 'Original invoice'))
textobject.moveCursor(0, 5)
textobject.setFont(self.font_regular, 10)
textobject.textLine(self.invoice.refers.number)
canvas.drawText(textobject)
else:
textobject = canvas.beginText(self.left_margin + 70 * mm, self.pagesize[1] - begin_top)
textobject.textLine(pgettext('invoice', 'Invoice number'))
textobject.moveCursor(0, 5)
textobject.setFont(self.font_regular, 10)
textobject.textLine(self.invoice.number)
canvas.drawText(textobject)
value_size = 10
while value_size >= 5:
objects = [
_draw(pgettext('invoice', 'Order code'), self.invoice.order.full_code, value_size, self.left_margin, 45 * mm)
]
p = Paragraph(
date_format(self.invoice.date, "DATE_FORMAT"),
style=ParagraphStyle(name=f'Normal{value_size}', fontName=self.font_regular, fontSize=value_size, leading=value_size * 1.2)
)
w = stringWidth(p.text, p.frags[0].fontName, p.frags[0].fontSize)
p.wrapOn(canvas, w, 15 * mm)
date_x = self.pagesize[0] - w - self.right_margin
if self.invoice.is_cancellation:
objects += [
_draw(pgettext('invoice', 'Cancellation number'), self.invoice.number,
value_size, self.left_margin + 50 * mm, 45 * mm),
_draw(pgettext('invoice', 'Original invoice'), self.invoice.refers.number,
value_size, self.left_margin + 100 * mm, date_x - self.left_margin - 100 * mm - 5 * mm),
]
else:
objects += [
_draw(pgettext('invoice', 'Invoice number'), self.invoice.number,
value_size, self.left_margin + 70 * mm, date_x - self.left_margin - 70 * mm - 5 * mm),
]
if all(objects):
for o in objects:
canvas.drawText(o)
break
value_size -= 1
p = Paragraph(date_format(self.invoice.date, "DATE_FORMAT"), style=self.stylesheet['Normal'])
w = stringWidth(p.text, p.frags[0].fontName, p.frags[0].fontSize)
p.wrapOn(canvas, w, 15 * mm)
date_x = self.pagesize[0] - w - self.right_margin
p.drawOn(canvas, date_x, self.pagesize[1] - begin_top - 10 - 6)
textobject = canvas.beginText(date_x, self.pagesize[1] - begin_top)

View File

@@ -0,0 +1,67 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
"""
Django, for theoretically very valid reasons, creates migrations for *every single thing*
we change on a model. Even the `help_text`! This makes sense, as we don't know if any
database backend unknown to us might actually use this information for its database schema.
However, pretix only supports PostgreSQL, MySQL, MariaDB and SQLite and we can be pretty
certain that some changes to models will never require a change to the database. In this case,
not creating a migration for certain changes will save us some performance while applying them
*and* allow for a cleaner git history. Win-win!
Only caveat is that we need to do some dirty monkeypatching to achieve it...
"""
from django.db import models
from django.db.migrations.operations import models as modelops
from django_countries.fields import CountryField
def monkeypatch_migrations():
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("verbose_name")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("verbose_name_plural")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("ordering")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("get_latest_by")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("default_manager_name")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("permissions")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("default_permissions")
IGNORED_ATTRS = [
# (field type, attribute name, banlist of field sub-types)
(models.Field, 'verbose_name', []),
(models.Field, 'help_text', []),
(models.Field, 'validators', []),
(models.Field, 'editable', [models.DateField, models.DateTimeField, models.DateField, models.BinaryField]),
(models.Field, 'blank', [models.DateField, models.DateTimeField, models.AutoField, models.NullBooleanField,
models.TimeField]),
(models.CharField, 'choices', [CountryField])
]
original_deconstruct = models.Field.deconstruct
def new_deconstruct(self):
name, path, args, kwargs = original_deconstruct(self)
for ftype, attr, banlist in IGNORED_ATTRS:
if isinstance(self, ftype) and not any(isinstance(self, ft) for ft in banlist):
kwargs.pop(attr, None)
return name, path, args, kwargs
models.Field.deconstruct = new_deconstruct

View File

@@ -0,0 +1,107 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from decimal import Decimal
from django.core.management.base import BaseCommand
from django.db import models
from django.db.models import (
Case, Count, F, OuterRef, Q, Subquery, Sum, Value, When,
)
from django.db.models.functions import Coalesce
from django_scopes import scopes_disabled
from pretix.base.models import Order, OrderFee, OrderPosition
from pretix.base.models.orders import Transaction
class Command(BaseCommand):
help = "Check order for consistency with their transactions"
@scopes_disabled()
def handle(self, *args, **options):
qs = Order.objects.annotate(
position_total=Coalesce(
Subquery(
OrderPosition.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum('price')).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
position_cnt=Case(
When(Q(status__in=('e', 'c')) | Q(require_approval=True), then=Value(0)),
default=Coalesce(
Subquery(
OrderPosition.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Count('*')).values('p'),
output_field=models.IntegerField()
), Value(0), output_field=models.IntegerField()
),
output_field=models.IntegerField()
),
fee_total=Coalesce(
Subquery(
OrderFee.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum('value')).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
tx_total=Coalesce(
Subquery(
Transaction.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum(F('price') * F('count'))).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
tx_cnt=Coalesce(
Subquery(
Transaction.objects.filter(
order=OuterRef('pk'),
item__isnull=False,
).order_by().values('order').annotate(p=Sum(F('count'))).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
).annotate(
correct_total=Case(
When(Q(status=Order.STATUS_CANCELED) | Q(status=Order.STATUS_EXPIRED) | Q(require_approval=True),
then=Value(0)),
default=F('position_total') + F('fee_total'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
).exclude(
total=F('position_total') + F('fee_total'),
tx_total=F('correct_total'),
tx_cnt=F('position_cnt')
).select_related('event')
for o in qs:
if abs(o.tx_total - o.correct_total) < Decimal('0.00001') and abs(o.position_total + o.fee_total - o.total) < Decimal('0.00001') \
and o.tx_cnt == o.position_cnt:
# Ignore SQLite which treats Decimals like floats…
continue
print(f"Error in order {o.full_code}: status={o.status}, sum(positions)+sum(fees)={o.position_total + o.fee_total}, "
f"order.total={o.total}, sum(transactions)={o.tx_total}, expected={o.correct_total}, pos_cnt={o.position_cnt}, tx_pos_cnt={o.tx_cnt}")
self.stderr.write(self.style.SUCCESS(f'Check completed.'))

View File

@@ -0,0 +1,95 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import time
from django.core.management.base import BaseCommand
from django.db.models import F, Max, Q
from django.utils.timezone import now
from django_scopes import scopes_disabled
from tqdm import tqdm
from pretix.base.models import Order
class Command(BaseCommand):
help = "Create missing order transactions"
def add_arguments(self, parser):
parser.add_argument(
"--slowdown",
dest="interval",
type=int,
default=0,
help="Interval for staggered execution. If set to a value different then zero, we will "
"wait this many milliseconds between every order we process.",
)
@scopes_disabled()
def handle(self, *args, **options):
t = 0
qs = Order.objects.annotate(
last_transaction=Max('transactions__created')
).filter(
Q(last_transaction__isnull=True) | Q(last_modified__gt=F('last_transaction')),
require_approval=False,
).prefetch_related(
'all_positions', 'all_fees'
).order_by(
'pk'
)
last_pk = 0
with tqdm(total=qs.count()) as pbar:
while True:
batch = list(qs.filter(pk__gt=last_pk)[:5000])
if not batch:
break
for o in batch:
if o.last_transaction is None:
tn = o.create_transactions(
positions=o.all_positions.all(),
fees=o.all_fees.all(),
dt_now=o.datetime,
migrated=True,
is_new=True,
_backfill_before_cancellation=True,
)
o.create_transactions(
positions=o.all_positions.all(),
fees=o.all_fees.all(),
dt_now=o.cancellation_date or (o.expires if o.status == Order.STATUS_EXPIRED else o.datetime),
migrated=True,
)
else:
tn = o.create_transactions(
positions=o.all_positions.all(),
fees=o.all_fees.all(),
dt_now=now(),
migrated=True,
)
if tn:
t += 1
time.sleep(0)
pbar.update(1)
last_pk = batch[-1].pk
self.stderr.write(self.style.SUCCESS(f'Created transactions for {t} orders.'))

View File

@@ -103,7 +103,7 @@ class Command(BaseCommand):
with language(locale), override(timezone):
for receiver, response in signal_result:
ex = response(e, report_status)
ex = response(e, o, report_status)
if ex.identifier == options['export_provider']:
params = json.loads(options.get('parameters') or '{}')
with open(options['output_file'], 'wb') as f:

View File

@@ -32,53 +32,11 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
"""
Django, for theoretically very valid reasons, creates migrations for *every single thing*
we change on a model. Even the `help_text`! This makes sense, as we don't know if any
database backend unknown to us might actually use this information for its database schema.
However, pretix only supports PostgreSQL, MySQL, MariaDB and SQLite and we can be pretty
certain that some changes to models will never require a change to the database. In this case,
not creating a migration for certain changes will save us some performance while applying them
*and* allow for a cleaner git history. Win-win!
Only caveat is that we need to do some dirty monkeypatching to achieve it...
"""
from django.core.management.commands.makemigrations import Command as Parent
from django.db import models
from django.db.migrations.operations import models as modelops
from django_countries.fields import CountryField
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("verbose_name")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("verbose_name_plural")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("ordering")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("get_latest_by")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("default_manager_name")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("permissions")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("default_permissions")
IGNORED_ATTRS = [
# (field type, attribute name, banlist of field sub-types)
(models.Field, 'verbose_name', []),
(models.Field, 'help_text', []),
(models.Field, 'validators', []),
(models.Field, 'editable', [models.DateField, models.DateTimeField, models.DateField, models.BinaryField]),
(models.Field, 'blank', [models.DateField, models.DateTimeField, models.AutoField, models.NullBooleanField,
models.TimeField]),
(models.CharField, 'choices', [CountryField])
]
from ._migrations import monkeypatch_migrations
original_deconstruct = models.Field.deconstruct
def new_deconstruct(self):
name, path, args, kwargs = original_deconstruct(self)
for ftype, attr, banlist in IGNORED_ATTRS:
if isinstance(self, ftype) and not any(isinstance(self, ft) for ft in banlist):
kwargs.pop(attr, None)
return name, path, args, kwargs
models.Field.deconstruct = new_deconstruct
monkeypatch_migrations()
class Command(Parent):

View File

@@ -32,12 +32,6 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
"""
Django tries to be helpful by suggesting to run "makemigrations" in red font on every "migrate"
run when there are things we have no migrations for. Usually, this is intended, and running
"makemigrations" can really screw up the environment of a user, so we want to prevent novice
users from doing that by going really dirty and filtering it from the output.
"""
import sys
from django.core.management.base import OutputWrapper
@@ -45,9 +39,15 @@ from django.core.management.commands.migrate import Command as Parent
class OutputFilter(OutputWrapper):
"""
Django tries to be helpful by suggesting to run "makemigrations" in red font on every "migrate"
run when there are things we have no migrations for. Usually, this is intended, and running
"makemigrations" can really screw up the environment of a user, so we want to prevent novice
users from doing that by going really dirty and filtering it from the output.
"""
banlist = (
"Your models have changes that are not yet reflected",
"Run 'manage.py makemigrations' to make new "
"have changes that are not yet reflected",
"re-run 'manage.py migrate'"
)
def write(self, msg, style_func=None, ending=None):

View File

@@ -208,13 +208,13 @@ def _parse_csp(header):
def _render_csp(h):
return "; ".join(k + ' ' + ' '.join(v) for k, v in h.items())
return "; ".join(k + ' ' + ' '.join(v) for k, v in h.items() if v)
def _merge_csp(a, b):
for k, v in a.items():
if k in b:
a[k] += b[k]
a[k] += [i for i in b[k] if i not in a[k]]
for k, v in b.items():
if k not in a:

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.2 on 2021-05-24 12:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0190_quota_ignore_for_event_availability'),
]
operations = [
migrations.AddField(
model_name='event',
name='last_modified',
field=models.DateTimeField(auto_now=True, db_index=True),
),
]

View File

@@ -0,0 +1,60 @@
# Generated by Django 3.2.2 on 2021-05-11 16:13
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0191_event_last_modified'),
]
operations = [
migrations.AddField(
model_name='checkin',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AddField(
model_name='checkin',
name='error_explanation',
field=models.TextField(null=True),
),
migrations.AddField(
model_name='checkin',
name='error_reason',
field=models.CharField(max_length=100, null=True),
),
migrations.AddField(
model_name='checkin',
name='raw_barcode',
field=models.TextField(null=True),
),
migrations.AddField(
model_name='checkin',
name='raw_item',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checkins', to='pretixbase.item'),
),
migrations.AddField(
model_name='checkin',
name='raw_subevent',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checkins', to='pretixbase.subevent'),
),
migrations.AddField(
model_name='checkin',
name='raw_variation',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checkins', to='pretixbase.itemvariation'),
),
migrations.AddField(
model_name='checkin',
name='successful',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='checkin',
name='position',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='all_checkins', to='pretixbase.orderposition'),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 3.2.3 on 2021-06-11 13:55
import django.db.models.manager
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0192_checkin_more_fields'),
]
operations = [
migrations.AlterModelManagers(
name='checkin',
managers=[
('all', django.db.models.manager.Manager()),
],
),
migrations.AddField(
model_name='order',
name='custom_followup_at',
field=models.DateField(blank=True, null=True),
),
]

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