Compare commits

...

535 Commits

Author SHA1 Message Date
Mira
c285ee2d8c Implement hidden_if_item_available_mode option (Z#23177008) (#4776) 2025-01-24 14:08:41 +01:00
Mira Weller
1736efbdc3 Preliminary migration 2025-01-24 14:08:13 +01:00
Mira Weller
5cd7959e86 Revert "Implement hidden_if_item_available_mode option (Z#23177008) (#4776)"
This reverts commit b847612e1a.
2025-01-24 14:07:55 +01:00
Mira
b847612e1a Implement hidden_if_item_available_mode option (Z#23177008) (#4776) 2025-01-24 11:24:50 +01:00
Mira
832f4e4d68 Define LogEntryTypes for all actions in pretix core, improve content_object handling (#4768)
Create LogEntryType definitions for all missing action_types (order changes, check-in events, settings changes of PaymentProviders and TicketOutputs).

Check whether the stored content_object is of the expected model type, preventing incorrect links.

Refactoring:
-    Move the base LogEntryType definitions for our models to their own file
-    Move HTML escaping into make_link to make it less likely to oversee in the LogEntryType definitions
-    Log pretix.event.order.deleted with the deleted Order model as content_object, matching the other *.deleted action_types
2025-01-24 10:05:19 +01:00
Mira
0a23aeece4 Allow 0% tax rate on event creation (#4756)
(but still warn if tax rate is not filled at all)
2025-01-23 12:59:39 +01:00
Martin Gross
9622bf41a1 InvoiceForm: Display vat_id especially if company_required (Z#23180046) (#4775) 2025-01-23 12:09:45 +01:00
Hijiri Umemoto
dd4bac70be Translations: Update Japanese
Currently translated at 99.8% (5837 of 5844 strings)

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

powered by weblate
2025-01-22 18:36:36 +01:00
Johanna Ketola
1187757b56 Translations: Update Finnish
Currently translated at 25.3% (1484 of 5844 strings)

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

powered by weblate
2025-01-22 18:36:36 +01:00
CVZ-es
abe5b4ef53 Translations: Update Spanish
Currently translated at 100.0% (5844 of 5844 strings)

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

powered by weblate
2025-01-22 18:36:36 +01:00
CVZ-es
b1fb391d08 Translations: Update French
Currently translated at 100.0% (5844 of 5844 strings)

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

powered by weblate
2025-01-22 18:36:36 +01:00
Raphael Michel
a95c6d94ee Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (235 of 235 strings)

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

powered by weblate
2025-01-22 18:36:36 +01:00
Raphael Michel
4809558343 Translations: Update German
Currently translated at 100.0% (235 of 235 strings)

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

powered by weblate
2025-01-22 18:36:36 +01:00
Raphael Michel
a6d1af01d2 Fix missing language info for nb-NO (default is just nb) (Z#23177041) 2025-01-22 15:57:47 +01:00
Raphael Michel
23e58996bc Invoice preview: Fix missing delivery date 2025-01-22 11:49:20 +01:00
Raphael Michel
15e05dae2f Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5844 of 5844 strings)

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

powered by weblate
2025-01-21 18:25:40 +01:00
Raphael Michel
ce40524ae8 Translations: Update German
Currently translated at 100.0% (5844 of 5844 strings)

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

powered by weblate
2025-01-21 18:25:40 +01:00
Raphael Michel
46aefc10f3 Order change form: No default fee type, use most generic fee type first (Z#23179634) (#4771) 2025-01-21 17:48:31 +01:00
Raphael Michel
1f49b577f0 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2025-01-21 17:43:41 +01:00
Hijiri Umemoto
b3aa405bcc Translations: Update Japanese
Currently translated at 100.0% (5840 of 5840 strings)

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

powered by weblate
2025-01-21 17:42:33 +01:00
Hector
f29b60b3db Translations: Update Spanish
Currently translated at 100.0% (235 of 235 strings)

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

powered by weblate
2025-01-21 17:42:33 +01:00
Hector
603e7821cc Translations: Update Spanish
Currently translated at 100.0% (5844 of 5844 strings)

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

powered by weblate
2025-01-21 17:42:33 +01:00
Raphael Michel
ffdc73e0a3 Show percentage of discount in cart (Z#23176955) (#4719)
* Show percentage of discount in cart (Z#23176955)

* Fix computation
2025-01-21 16:35:15 +01:00
Raphael Michel
00b4622afa Address form logic: Respect dependency of VAT ID on is_business (#4713) 2025-01-21 15:05:16 +01:00
Raphael Michel
045edc7cec Invoice rendering: Remove transparency from logos (Z#23179391) (#4762)
* Invoice rendering: Remove logos from transparency

* Add comment
2025-01-21 15:03:43 +01:00
Raphael Michel
1635118772 Copy ItemVariation.limit_sales_channels when copying items (#4752) 2025-01-21 15:01:28 +01:00
Mira
87c987fee5 Only display tax rates with non-zero gross amount (#4760) 2025-01-20 14:49:04 +01:00
dependabot[bot]
1267bf8ba8 Update webauthn requirement from ==2.4.* to ==2.5.* (#4766)
Updates the requirements on [webauthn](https://github.com/duo-labs/py_webauthn) to permit the latest version.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Changelog](https://github.com/duo-labs/py_webauthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v2.4.0...v2.5.0)

---
updated-dependencies:
- dependency-name: webauthn
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:49:03 +01:00
Serge Bazanski
a8d1ed8ee1 Translations: Update Polish (informal) (pl_Informal)
Currently translated at 13.3% (782 of 5840 strings)

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

powered by weblate
2025-01-20 12:45:01 +01:00
Serge Bazanski
b7736d5e82 Translations: Update Polish (informal) (pl_Informal)
Currently translated at 12.0% (701 of 5840 strings)

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

powered by weblate
2025-01-20 12:45:01 +01:00
Serge Bazanski
cfefe5bfc3 Translations: Update Polish (informal) (pl_Informal)
Currently translated at 100.0% (235 of 235 strings)

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

powered by weblate
2025-01-20 12:45:01 +01:00
Serge Bazanski
f0f272b304 Translations: Update Polish (informal) (pl_Informal)
Currently translated at 6.8% (398 of 5840 strings)

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

powered by weblate
2025-01-20 12:45:01 +01:00
Wiktor Przybylski
e8b159e6d4 Translations: Update Polish
Currently translated at 98.7% (5769 of 5840 strings)

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

powered by weblate
2025-01-20 12:45:01 +01:00
Hijiri Umemoto
b0de6815db Translations: Update Japanese
Currently translated at 100.0% (235 of 235 strings)

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

powered by weblate
2025-01-20 12:45:01 +01:00
Hijiri Umemoto
92ceea2680 Translations: Update Japanese
Currently translated at 100.0% (5840 of 5840 strings)

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

powered by weblate
2025-01-20 12:45:01 +01:00
Wiktor Przybylski
c2a9f9f76a Translations: Update Polish
Currently translated at 98.4% (5751 of 5840 strings)

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

powered by weblate
2025-01-20 12:45:01 +01:00
Wiktor Przybylski
1e1f0e5d86 Translations: Update Polish
Currently translated at 98.3% (5746 of 5840 strings)

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

powered by weblate
2025-01-20 12:45:01 +01:00
Richard Schreiber
9634907539 [a11y] Remove h1 aria-label in presale 2025-01-20 08:57:21 +01:00
Raphael Michel
70dd688ec1 Translations: Add Flemish (West) 2025-01-17 17:49:14 +01:00
Raphael Michel
5ad0213195 Translations: Add Albanian 2025-01-17 17:49:14 +01:00
Raphael Michel
c40cf45179 Translations: Add Faroese 2025-01-17 17:49:14 +01:00
CVZ-es
a72839fd0e Translations: Update Spanish
Currently translated at 100.0% (5844 of 5844 strings)

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

powered by weblate
2025-01-17 17:49:14 +01:00
CVZ-es
5071db0a8b Translations: Update French
Currently translated at 100.0% (5840 of 5840 strings)

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

powered by weblate
2025-01-17 17:49:14 +01:00
Hijiri Umemoto
0dce6464ad Translations: Update Japanese
Currently translated at 99.5% (5815 of 5840 strings)

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

powered by weblate
2025-01-17 09:53:12 +01:00
Mira
e1d3f16819 Fix rendering of pretix.event.order.deleted log entries (#4757) 2025-01-16 16:03:41 +01:00
deborahfoell
6c0b266260 Translations: Update Korean
Currently translated at 0.4% (29 of 5840 strings)

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

powered by weblate
2025-01-16 13:47:36 +01:00
CVZ-es
e4692ed746 Translations: Update Spanish
Currently translated at 100.0% (235 of 235 strings)

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

powered by weblate
2025-01-16 13:47:36 +01:00
CVZ-es
a99bb283e2 Translations: Update Spanish
Currently translated at 99.9% (5843 of 5844 strings)

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

powered by weblate
2025-01-16 13:47:36 +01:00
CVZ-es
06f09dda49 Translations: Update French
Currently translated at 100.0% (235 of 235 strings)

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

powered by weblate
2025-01-16 13:47:36 +01:00
CVZ-es
da1de7d646 Translations: Update French
Currently translated at 99.9% (5839 of 5840 strings)

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

powered by weblate
2025-01-16 13:47:36 +01:00
Raphael Michel
63a2e2e058 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5840 of 5840 strings)

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

powered by weblate
2025-01-16 13:47:36 +01:00
Raphael Michel
5d83f70f75 Translations: Update German
Currently translated at 100.0% (5840 of 5840 strings)

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

powered by weblate
2025-01-16 13:47:36 +01:00
Hector
f8badea1d3 Translations: Update Spanish
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-16 13:47:36 +01:00
Raphael Michel
d727d58bc9 Allow to attach .ics files to manual emails (Z#23179129) (#4753) 2025-01-16 13:30:47 +01:00
Mira
c8d4815c9e LogEntryType registry (#4235)
Move display of LogEntry details from the `logentry_display` and 
`logentry_object_link` signals to a class hierarchy based approach. 
For each action_type, an instance of a subclass of `LogEntryType` 
is registered in the `log_entry_types` registry.

Analogous to EventPluginSignal, this registry is an `EventPluginRegistry`, 
so it keeps track of the plugin the LogEntryType is defined in.

---------

Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2025-01-16 13:05:57 +01:00
Richard Schreiber
c25d6988a7 Widget: add option to always show event info 2025-01-16 11:54:24 +01:00
Mira
89f1f61b73 Refactor RelativeDate(Time)Field and -Widget (#4746)
* refactor to use namedtuples for the sub-fields and sub-widgets
* fix RelativeDateTimeField.set_event: apply relative_to filter not only to minutes, but to days as well
* fix bug in RelativeDateTimeField.clean: validate days relation_to instead of minutes relation_to when "Relative date" is selected

---------

Co-authored-by: Raphael Michel <michel@rami.io>
2025-01-16 11:32:02 +01:00
Raphael Michel
ecd0cbe00d Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2025-01-15 17:46:10 +01:00
dependabot[bot]
6cb9dc5b35 Update sentry-sdk requirement from ==2.18.* to ==2.20.* (#4749)
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.18.0...2.20.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-15 09:33:34 +01:00
Nicolas Thumann
6846348a7f Fix typo (#4748) 2025-01-14 19:49:23 +01:00
Raphael Michel
df785f8c6d Set locale properly if locale is already in query string (#4733) 2025-01-14 18:52:02 +01:00
Mira
bc32fe91a6 Make "Show in backend" button on ticket pages available on custom domains (#4743) 2025-01-14 18:37:38 +01:00
Raphael Michel
0664127e70 Invoices: Show sum of 0%-tax amount if there are any taxes (Z#23178464) (#4737) 2025-01-14 15:09:38 +01:00
Raphael Michel
5cf2f1beef New invoice generation setting: User request but paid only (#4740) 2025-01-14 13:54:25 +01:00
Raphael Michel
ff6b4ae7bf Translations: Allow to change field order for relative date form field (#4738) 2025-01-14 13:48:37 +01:00
Raphael Michel
6b5fd07535 Questions: Increase field size for min/max number (#4741) 2025-01-14 11:18:28 +01:00
Nikolai
7a423ebead Translations: Update Danish
Currently translated at 47.9% (2799 of 5836 strings)

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

powered by weblate
2025-01-14 10:57:01 +01:00
Raphael Michel
62c028d9ac Fix order availability with blocked seeat (Z#23178286) (#4734) 2025-01-13 12:49:49 +01:00
Hijiri
57f5eeff82 Update name scheme examples, adapt addressing system for Japan 2025-01-13 10:51:24 +01:00
Hijiri Umemoto
d92e82a133 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-13 10:47:57 +01:00
Hijiri Umemoto
5704142fe1 Translations: Update Korean
Currently translated at 0.4% (26 of 5836 strings)

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

powered by weblate
2025-01-13 10:47:57 +01:00
Hijiri Umemoto
8c5768912c Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-13 10:47:57 +01:00
Hijiri Umemoto
934567c811 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-13 10:47:57 +01:00
David Vaz
b16f54fad9 Translations: Update Portuguese (Portugal)
Currently translated at 85.5% (4991 of 5836 strings)

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

powered by weblate
2025-01-13 10:47:57 +01:00
Martin Gross
5dcd1f229b Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-10 19:21:20 +01:00
Martin Gross
40e4f8a05a Translations: Update German
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-10 19:21:20 +01:00
Hijiri Umemoto
8d9295a628 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-10 19:21:20 +01:00
Bernd Mohring
063e1e16a3 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-10 19:21:20 +01:00
Hijiri Umemoto
b5733a433d Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-10 19:21:20 +01:00
Bernd Mohring
454354f148 Translations: Update German
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-10 19:21:20 +01:00
Hijiri Umemoto
8480f45997 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-10 19:21:20 +01:00
Hijiri Umemoto
b909b0b4bd Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-10 19:21:20 +01:00
Hijiri Umemoto
c4b45b7d95 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-10 19:21:20 +01:00
Raphael Michel
3ce6030122 PDF n-up generation: Fix ordering of badges 2025-01-07 19:32:51 +01:00
Martin Gross
28e26284a6 API: Return order-information on availability-endpoint, even if quota is unlimited 2025-01-07 17:25:39 +01:00
Raphael Michel
aaf1b7776e Set up flag for japanese fields 2025-01-07 16:09:28 +01:00
Raphael Michel
d545b15d0d Fix incorrect language code for Japanese 2025-01-07 15:12:26 +01:00
Raphael Michel
0a334d1c67 Explicitly set none algorithm 2025-01-07 12:01:52 +01:00
dependabot[bot]
1a2fb688de Update pyjwt requirement from ==2.9.* to ==2.10.*
Updates the requirements on [pyjwt](https://github.com/jpadilla/pyjwt) to permit the latest version.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jpadilla/pyjwt/compare/2.9.0...2.10.1)

---
updated-dependencies:
- dependency-name: pyjwt
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-07 12:01:52 +01:00
Raphael Michel
c1333d6edb Add Japanese to supported languages 2025-01-07 11:37:42 +01:00
Hijiri Umemoto
e3e432a9e4 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-07 11:19:37 +01:00
Hijiri Umemoto
36ea18970b Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-07 11:19:37 +01:00
Hector
bde2d50828 Translations: Update Spanish
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-07 11:19:37 +01:00
Hijiri Umemoto
e8334df908 Translations: Update Chinese (Traditional Han script)
Currently translated at 91.6% (5351 of 5836 strings)

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

powered by weblate
2025-01-07 11:19:37 +01:00
Hector
c8943785d7 Translations: Update Spanish
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-07 11:19:37 +01:00
Hijiri Umemoto
41b878b667 Translations: Update Turkish
Currently translated at 43.1% (2516 of 5836 strings)

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

powered by weblate
2025-01-07 11:19:37 +01:00
Hijiri Umemoto
4f63ba967b Translations: Update Chinese (Traditional Han script)
Currently translated at 91.3% (5331 of 5836 strings)

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

powered by weblate
2025-01-07 11:19:37 +01:00
Raphael Michel
a56c6ae1e0 Fix secret regeneration 2025-01-03 17:21:12 +01:00
Raphael Michel
1efd952a19 Revert "Update pyjwt requirement from ==2.9.* to ==2.10.* (#4639)"
This reverts commit 71caa17879.
2025-01-03 17:10:59 +01:00
Hector
ddd0db3d98 Translations: Update Spanish
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hector
dde724d0be Translations: Update Spanish
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
9f55187690 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
e572bfb752 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
beccdf8dad Translations: Update Korean
Currently translated at 0.2% (17 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
b141ea4ed5 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
32dd125b65 Translations: Update Turkish
Currently translated at 43.0% (2515 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
b5794780da Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
835c08f1ca Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Wiktor Przybylski
a79d5fddda Translations: Update Polish
Currently translated at 98.5% (5750 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
beb03e07e0 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
dd2e5d09f9 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
5c2456e92e Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
3579a7f298 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
5dd20de745 Translations: Update Japanese
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
5708099fc9 Translations: Update Japanese
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri Umemoto
2c2e8e7d21 Translations: Update Japanese
Currently translated at 90.9% (211 of 232 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri
5993482f6c Translations: Update Japanese
Currently translated at 98.6% (5755 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri
c905659dfb Translations: Update Japanese
Currently translated at 78.8% (183 of 232 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri
196c131ac9 Translations: Update Japanese
Currently translated at 98.5% (5749 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Aarni Heinonen
a554433fad Translations: Update Chinese (Simplified Han script)
Currently translated at 50.1% (2924 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Aarni Heinonen
c552dd876c Translations: Update Finnish
Currently translated at 24.4% (1429 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Aarni Heinonen
105ae8592d Translations: Update Finnish
Currently translated at 20.1% (1175 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri
ca4540eeb7 Translations: Update Japanese
Currently translated at 5.6% (331 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Dimitris Tsimpidis
381366a248 Translations: Update Greek
Currently translated at 41.8% (97 of 232 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Dimitris Tsimpidis
816a3ec994 Translations: Update Greek
Currently translated at 47.7% (2785 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
Hijiri
88d3d12dbc Translations: Update Japanese
Currently translated at 4.0% (238 of 5836 strings)

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

powered by weblate
2025-01-03 15:31:39 +01:00
dependabot[bot]
71caa17879 Update pyjwt requirement from ==2.9.* to ==2.10.* (#4639)
Updates the requirements on [pyjwt](https://github.com/jpadilla/pyjwt) to permit the latest version.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jpadilla/pyjwt/compare/2.9.0...2.10.0)

---
updated-dependencies:
- dependency-name: pyjwt
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 15:23:36 +01:00
dependabot[bot]
2ecdfde756 Bump @rollup/plugin-node-resolve in /src/pretix/static/npm_dir (#4728)
Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/HEAD/packages/node-resolve) from 15.3.0 to 16.0.0.
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/node-resolve/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/commonjs-v16.0.0/packages/node-resolve)

---
updated-dependencies:
- dependency-name: "@rollup/plugin-node-resolve"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 15:23:19 +01:00
dependabot[bot]
1f753a57c5 Update pillow requirement from ==11.0.* to ==11.1.* (#4729)
Updates the requirements on [pillow](https://github.com/python-pillow/Pillow) to permit the latest version.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/11.0.0...11.1.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 15:22:58 +01:00
Raphael Michel
595c042624 Regenerate OrderPosition.web_secret when chaning other secrets 2025-01-03 15:21:45 +01:00
Raphael Michel
5a5a551c21 Fix tests failing after 2025-01-01 2025-01-03 15:21:32 +01:00
Raphael Michel
e74793994a Fix invalid usage of re.split() on Python 3.13 2025-01-03 15:21:15 +01:00
Raphael Michel
f1bdd3b7af Remove debug print 2024-12-25 18:58:00 +01:00
Raphael Michel
13c40f9bb7 API: Fix sales channels when copying events (Z#23177154, Z#23169537) 2024-12-20 14:09:10 +01:00
Raphael Michel
3e15e2a887 Raise minimum version of cryptography 2024-12-20 14:09:10 +01:00
Raphael Michel
7525ee853b Ticket preview: Fix incorrect type of sample price 2024-12-19 13:37:38 +01:00
Richard Schreiber
f02b1be659 [A11y] Improve grouping and labels in widget’s product list 2024-12-18 15:24:28 +01:00
Raphael Michel
20f171b790 Order import: Respect sales channel when validating seats (#4707) 2024-12-18 13:57:29 +01:00
CVZ-es
22906dfa77 Translations: Update Spanish
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2024-12-18 13:49:18 +01:00
CVZ-es
5f74e661b3 Translations: Update French
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2024-12-18 13:49:18 +01:00
Richard Schreiber
5b99788354 Unify event list view on organizer and event-series (#4702) 2024-12-18 13:32:26 +01:00
Raphael Michel
b45d58b60e Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2024-12-16 17:32:16 +01:00
Raphael Michel
2a58e958b0 Translations: Update German
Currently translated at 100.0% (5836 of 5836 strings)

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

powered by weblate
2024-12-16 17:32:16 +01:00
Raphael Michel
e44695dfcf Extend spelling word lists 2024-12-16 17:30:25 +01:00
Raphael Michel
1d289088f4 Extend spelling word lists 2024-12-16 17:27:53 +01:00
Raphael Michel
a9be6337bc Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-12-16 15:20:40 +01:00
Raphael Michel
df29d4e8c4 Bump django-i18nfield to 1.10.* (#4709) 2024-12-16 15:20:09 +01:00
Richard Schreiber
bf2cabf7b6 Fix color differences for panel-heading in accordion-panel 2024-12-16 08:35:30 +01:00
Raphael Michel
db614d36e6 Block access to manifests in default nginx configurations 2024-12-15 11:59:11 +01:00
Raphael Michel
13452b5d8c Make BaseInvoiceForm more reusable (#4669)
* Make BaseInvoiceForm more reusable

* Remove code
2024-12-13 16:28:21 +01:00
Raphael Michel
6422cd7858 API: Fix format of price calculation 2024-12-13 16:17:24 +01:00
dependabot[bot]
2e87cb5691 Bump nanoid from 3.3.7 to 3.3.8 in /src/pretix/static/npm_dir (#4706)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-13 16:01:01 +01:00
Vasco Baleia
b53ee938bf Translations: Update Portuguese (Portugal)
Currently translated at 86.5% (5003 of 5782 strings)

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

powered by weblate
2024-12-13 14:19:08 +01:00
Wikinaut
ec3bdd4a57 Translations: Update German
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-12-13 14:19:08 +01:00
dependabot[bot]
7b607594d8 Update webauthn requirement from ==2.3.* to ==2.4.* (#4689)
Updates the requirements on [webauthn](https://github.com/duo-labs/py_webauthn) to permit the latest version.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Changelog](https://github.com/duo-labs/py_webauthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: webauthn
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-13 14:16:26 +01:00
Raphael Michel
53f129d5d3 Add codification of tax rates (#4372)
* draft

* .

* Rebase migration

* Update src/pretix/base/models/tax.py

Co-authored-by: Mira <weller@rami.io>

* Test, isort, flake, migration rebase

* carry data & API

* Fix failing tests

* docs fixes

* Improve validation

* Tests

* More fixes

---------

Co-authored-by: Mira <weller@rami.io>
2024-12-13 12:04:38 +01:00
Mira
a4385c8b6e Use new fieldset panels throughout checkout (#4688)
Use <fieldset> accordion-panels in checkout_customer, checkout_payment and 
order_pay_change. Unify markup in checkout_payment and order_pay_change. 
Adapt Javascript in the dynamic PayPal and Stripe payment forms.

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-12-12 15:48:27 +01:00
Neriman Memmedli
3acae96021 Translations: Update Azerbaijani
Currently translated at 0.3% (21 of 5782 strings)

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

powered by weblate
2024-12-11 15:48:37 +01:00
Neriman Memmedli
b9add5ff6f Translations: Add Azerbaijani 2024-12-11 15:48:37 +01:00
Patrick Chilton
4ca9813a1d Translations: Update Hungarian
Currently translated at 10.8% (630 of 5782 strings)

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

powered by weblate
2024-12-11 15:48:37 +01:00
Christiaan de Die le Clercq
347748896d Translations: Update Dutch
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-12-11 15:48:37 +01:00
Richard Schreiber
0f590caa18 [UI] Fix organizer event-list status-bubble overflow 2024-12-11 11:36:15 +01:00
Richard Schreiber
18801f2d1c Widget: add unique html_id to use in labels, etc. 2024-12-11 08:30:44 +01:00
Richard Schreiber
e5f29bd592 Widget: add span around currency for easier styling 2024-12-11 08:29:55 +01:00
Martin Gross
1f904d482b Stripe: Add missing identifier for revolut_pay 2024-12-10 15:15:43 +01:00
Raphael Michel
b8ad276f53 Fix duplicate field (Z#23168530) 2024-12-09 21:58:11 +01:00
Raphael Michel
e109c37738 API: use correct validation exception 2024-12-09 14:17:27 +01:00
Raphael Michel
4d597d5be3 Stripe: Remove option to enable method_sofort (#4638) 2024-12-09 13:51:32 +01:00
Mira
ae8ec42905 Fix backend validation if name is required as part of a required non-business invoice address (#4674)
---------

Co-authored-by: Raphael Michel <michel@rami.io>
2024-12-09 12:27:59 +01:00
Richard Schreiber
e5b89e9b08 [A11y] fix color-contrast for event-list status-bubbles 2024-12-06 15:51:40 +01:00
Mira
da91f5f117 Animation in <fieldset> accordion (#4682) 2024-12-06 12:46:40 +01:00
Richard Schreiber
ae29240e58 [A11y] Improve number inputs for screen-readers 2024-12-06 11:11:36 +01:00
Richard Schreiber
74edf10b04 Move cursor fix for disabled fieldsets from bootstrap to presale 2024-12-06 11:07:11 +01:00
Richard Schreiber
e2e0eca872 Fix accordion-radio fieldset css 2024-12-06 10:13:32 +01:00
Raphael Michel
6132e4a2c4 Remove re-implementations if i18nfield API integration 2024-12-05 17:56:30 +01:00
Raphael Michel
7df7d28518 Bump django-i18nfield to 1.9.5 2024-12-05 17:56:30 +01:00
Raphael Michel
11ab5c5eeb Event dashboard: Use intcomma in numbers (Z#23175343) (#4687) 2024-12-05 17:11:57 +01:00
CVZ-es
20211d2097 Translations: Update Spanish
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-12-05 13:46:11 +01:00
CVZ-es
d760ad38bf Translations: Update French
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-12-05 13:46:11 +01:00
CVZ-es
69af2cee93 Translations: Update French
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-12-05 13:46:11 +01:00
Raphael Michel
6b199a2b9c Allow to enter gift cards into the voucher input (Z#23171961) (#4670) 2024-12-05 13:43:46 +01:00
Richard Schreiber
94a64ba53a Hide empty panels (#4684) 2024-12-05 12:08:33 +01:00
Raphael Michel
70f06a8f40 Fix an incorrect exception handling 2024-12-04 16:59:58 +01:00
Mira Weller
a747ab154a Fix cursor on elements in disabled fieldsets' legends 2024-12-04 14:54:49 +01:00
Mira
6317233150 New accordion panels using <fieldset> (#4681) 2024-12-04 14:34:42 +01:00
Richard Schreiber
4d94158ff0 Improve organizer/event-series calendar UI on mobile 2024-12-04 08:17:52 +01:00
Raphael Michel
8f92eb2d2d remove debug statement 2024-12-03 12:40:29 +01:00
Richard Schreiber
f29896b267 [A11y] Fix missing aria-hidden and translation 2024-12-03 12:10:16 +01:00
Raphael Michel
2dc625cf31 Add the option to introduce rich-text placeholders (#4657)
* Add the option to introduce rich-text placeholders

* Add tests in test_format

* Add some css

* Block vs inline

* Some fixed css

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

Co-authored-by: Mira <weller@rami.io>

* Add missing docstring prat

---------

Co-authored-by: Mira <weller@rami.io>
2024-12-03 11:38:15 +01:00
dependabot[bot]
855226d37c Update ua-parser requirement from ==0.18.* to ==1.0.* (#4665)
Updates the requirements on [ua-parser](https://github.com/ua-parser/uap-python) to permit the latest version.
- [Release notes](https://github.com/ua-parser/uap-python/releases)
- [Commits](https://github.com/ua-parser/uap-python/compare/0.18.0...1.0.0)

---
updated-dependencies:
- dependency-name: ua-parser
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 11:18:37 +01:00
dependabot[bot]
648c0da9fe Update webauthn requirement from ==2.2.* to ==2.3.* (#4655)
Updates the requirements on [webauthn](https://github.com/duo-labs/py_webauthn) to permit the latest version.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Changelog](https://github.com/duo-labs/py_webauthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v2.2.0...v2.3.0)

---
updated-dependencies:
- dependency-name: webauthn
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 11:01:05 +01:00
Raphael Michel
59e3494fa2 Add fee type for late fees (#4656) 2024-12-03 11:00:11 +01:00
Mira
c4ff57c07a Change error message for unavailable addon products (#4673)
This can not only happen in case of sold-out addons, but also if they are e.g. not available via the current sales channel.
2024-12-03 10:59:15 +01:00
Raphael Michel
cc4fbfe4c7 API: Allow to block/unblock seats in bulk (#4668)
* API: Allow to block/unblock seats in bulk

* Update doc/api/resources/seats.rst

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

* Update doc/api/resources/seats.rst

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

* Update doc/api/resources/seats.rst

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

* Update src/pretix/api/views/event.py

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

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-12-02 16:03:11 +01:00
Raphael Michel
e99ee91573 Allow to use custom domains for some but not all events (Z#23153875) (#4627)
* Allow to use custom domains for some but not all events

* Update src/pretix/multidomain/urlreverse.py

* Apply suggestions from code review

Co-authored-by: Mira <weller@rami.io>

* Logging for domain config changes

---------

Co-authored-by: Mira <weller@rami.io>
2024-12-02 15:58:50 +01:00
Patrick Chilton
e2753686ee Translations: Update Hungarian
Currently translated at 10.8% (629 of 5782 strings)

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

powered by weblate
2024-12-02 15:58:41 +01:00
CVZ-es
33f8b9851e Translations: Update Spanish
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-12-02 15:58:41 +01:00
CVZ-es
e3d8cf07af Translations: Update French
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-12-02 15:58:41 +01:00
Mira Weller
0279ca7d94 Add missing error handling to addressform.js 2024-12-02 10:15:16 +01:00
Richard Schreiber
d1989c3cd3 Fix all-optional in address-form for resellers (#4672) 2024-12-02 09:46:33 +01:00
Raphael Michel
61cb2e15cf Fix validation crash of InvoiceNameForm 2024-11-29 20:08:36 +01:00
Mira Weller
f2ee1d00b3 Don't use animation for address information load indicator 2024-11-29 17:09:14 +01:00
Mira
e8e9698a31 Update address field logic (Z#23163120) (#4659)
* Move country-dependent JS logic to separate file (avoids code duplication for presale and control)
* Correctly apply "required" attribute to address state field
* Load address format information when selecting country
* Fix some other bugs and inconsistencies
2024-11-29 14:56:56 +01:00
Richard Schreiber
a1bf7be244 [A11y] Improve customer account pages (#4654) 2024-11-29 14:16:40 +01:00
Patrick Chilton
f4ca9a5681 Translations: Update Hungarian
Currently translated at 45.6% (106 of 232 strings)

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

powered by weblate
2024-11-29 09:22:29 +01:00
dependabot[bot]
e6d984538f Update django-statici18n requirement from ==2.5.* to ==2.6.* (#4664)
Updates the requirements on [django-statici18n](https://github.com/zyegfryed/django-statici18n) to permit the latest version.
- [Changelog](https://github.com/zyegfryed/django-statici18n/blob/main/docs/changelog.rst)
- [Commits](https://github.com/zyegfryed/django-statici18n/compare/v2.5.0...v2.6.0)

---
updated-dependencies:
- dependency-name: django-statici18n
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 09:22:04 +01:00
dependabot[bot]
9f1ee9157f Update protobuf requirement from ==5.28.* to ==5.29.* (#4666)
Updates the requirements on [protobuf](https://github.com/protocolbuffers/protobuf) to permit the latest version.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v5.28.0-rc1...v5.29.0)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 09:21:54 +01:00
Raphael Michel
242e5af4b5 Bump version to 2024.12.0.dev0 2024-11-27 13:57:03 +01:00
Raphael Michel
7d6e98e6da Bump version to 2024.11.0 2024-11-27 13:56:37 +01:00
Mira
27f964f3ae Checkout flow: Observe direction when skipping AddOnsStep (#4658) 2024-11-27 11:13:07 +01:00
Patrick Chilton
84b3060c0f Translations: Update Hungarian
Currently translated at 10.7% (623 of 5782 strings)

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

powered by weblate
2024-11-27 11:10:55 +01:00
CVZ-es
25dcb72f92 Translations: Update Spanish
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-11-27 11:10:55 +01:00
CVZ-es
4b078867c6 Translations: Update French
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-11-27 11:10:55 +01:00
Jakub Stribrny
c595a59d4a Translations: Update Czech
Currently translated at 73.5% (4250 of 5782 strings)

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

powered by weblate
2024-11-26 18:48:58 +01:00
Patrick Chilton
f164daeaee Translations: Update Hungarian
Currently translated at 10.7% (621 of 5782 strings)

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

powered by weblate
2024-11-26 18:48:58 +01:00
gabriblas
c6b6dd8d49 Translations: Update Italian
Currently translated at 24.3% (1409 of 5782 strings)

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

powered by weblate
2024-11-26 18:48:58 +01:00
CVZ-es
8038c87963 Translations: Update French
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-11-26 18:48:58 +01:00
Ryo
c45a970d32 Translations: Update Japanese
Currently translated at 3.8% (220 of 5782 strings)

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

powered by weblate
2024-11-26 18:48:58 +01:00
kei ogane
a34517233d Translations: Update Japanese
Currently translated at 3.8% (220 of 5782 strings)

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

powered by weblate
2024-11-26 18:48:58 +01:00
Yasunobu YesNo Kawaguchi
8fb2e5383c Translations: Update Japanese
Currently translated at 3.4% (200 of 5782 strings)

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

powered by weblate
2024-11-26 18:48:58 +01:00
CVZ-es
86a00f3338 Translations: Update Spanish
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-11-26 18:48:58 +01:00
CVZ-es
c8c0d3e7f5 Translations: Update French
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-11-26 18:48:58 +01:00
Raphael Michel
7dd455ce15 Fix #4641 -- Make usage of argon2id optional (#4643) 2024-11-26 17:31:27 +01:00
Richard Schreiber
391eda25da [A11y] Improve color combinations for alerts 2024-11-21 13:58:19 +01:00
Raphael Michel
fcff5a522d Fix inconsistent labels 2024-11-19 16:36:09 +01:00
Raphael Michel
7e93d38a01 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-11-19 16:33:52 +01:00
Raphael Michel
6469381899 Translations: Update German
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-11-19 16:33:52 +01:00
Raphael Michel
761706c60c Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-11-19 16:16:36 +01:00
CVZ-es
f91315c88e Translations: Update Spanish
Currently translated at 100.0% (5809 of 5809 strings)

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

powered by weblate
2024-11-19 16:16:13 +01:00
CVZ-es
bc05afeab9 Translations: Update French
Currently translated at 100.0% (5779 of 5779 strings)

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

powered by weblate
2024-11-19 16:16:13 +01:00
Raphael Michel
02d495d287 Revert "Update po files"
This reverts commit 894878d9da.
2024-11-19 16:15:55 +01:00
Raphael Michel
894878d9da Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-11-19 16:15:26 +01:00
Raphael Michel
5896ca0197 Event creation: Prevent accidentally creating events without tax rate (#4623)
* Event creation: Prevent accidentally creating events without tax rate

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

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

* Fix tests

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-11-19 16:14:56 +01:00
Raphael Michel
fe6fc8df32 Fix placehodler sample in empty series (PRETIXEU-ATN) 2024-11-19 16:14:13 +01:00
dependabot[bot]
9de8f3a775 Update aiohttp requirement from ==3.10.* to ==3.11.*
Updates the requirements on [aiohttp](https://github.com/aio-libs/aiohttp) to permit the latest version.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.0b0...v3.11.0)

---
updated-dependencies:
- dependency-name: aiohttp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-19 15:08:00 +01:00
Martin Gross
c92bb9cb8b Stripe: (FIX) Make MobilePay optional 2024-11-19 13:17:52 +01:00
Raphael Michel
76ecec8b98 Scheduled mails: Allow subevent-dependent placeholders (Z#23171818) (#4629) 2024-11-19 10:50:10 +01:00
Mira
4b8416df8f docs: update nginx config example (#4640) 2024-11-19 09:28:01 +01:00
Martin Gross
a601c75923 CheckIns: Display a source_type icon (barcode/nfc) where known (#4628)
Co-authored-by: Raphael Michel <michel@rami.io>
2024-11-18 17:50:43 +01:00
Raphael Michel
f94227f00f Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5779 of 5779 strings)

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

powered by weblate
2024-11-18 17:49:24 +01:00
Raphael Michel
a0c1e5369c Translations: Update German
Currently translated at 100.0% (5779 of 5779 strings)

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

powered by weblate
2024-11-18 17:49:24 +01:00
Jakub Stribrny
633bfcf73a Translations: Update Czech
Currently translated at 73.5% (4253 of 5781 strings)

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

powered by weblate
2024-11-18 17:49:24 +01:00
CVZ-es
0d3b5b82c1 Translations: Update Spanish
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-11-18 17:49:24 +01:00
CVZ-es
ab95f33546 Translations: Update Spanish
Currently translated at 100.0% (5784 of 5784 strings)

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

powered by weblate
2024-11-18 17:49:24 +01:00
Raphael Michel
5034b366c5 Stripe: Disable SOFORT (#4616)
* Stripe: Disable SOFORT

* Update src/pretix/plugins/stripe/payment.py

Co-authored-by: Raphael Michel <michel@rami.io>

---------

Co-authored-by: Martin Gross <gross@rami.io>
2024-11-18 17:24:01 +01:00
Raphael Michel
03d3c389da Fix #1674 -- Change spelling of e-mail to email (#4636)
* Fix #1674 -- Change spelling of e-mail to email

* Conflicts and word list

* Add MobilePay to wordlist

* fix usage in tests
2024-11-18 17:21:29 +01:00
Raphael Michel
3e934acfa0 Allow "open in new tab" for event typeahead (#4631)
* Allow "open in new tab" for event typeahead

* use default link-behaviour for e.g. open in new ...

* navigate in typeahead with tab, add esc to close

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-11-18 17:16:02 +01:00
Raphael Michel
d2a364e848 Docs: Fix documentation of invoice API 2024-11-18 17:08:51 +01:00
Martin Gross
2824b40299 Stripe: Add MobilePay as a supported payment method (Z#23172523) (#4635)
Co-authored-by: robbi5 <richt@rami.io>
2024-11-18 15:03:32 +01:00
Damiano
c6c2c90908 Translations: Update Italian
Currently translated at 24.3% (1405 of 5781 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
Pavle Ergović
d4ae7df2ec Translations: Update Croatian
Currently translated at 1.7% (4 of 232 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
Pavle Ergović
79dd7fb596 Translations: Update Croatian
Currently translated at 0.3% (23 of 5781 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
CVZ-es
5ed87cd019 Translations: Update Spanish
Currently translated at 100.0% (5784 of 5784 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
CVZ-es
ccdcbe0cc5 Translations: Update French
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
CVZ-es
4f8607a9db Translations: Update French
Currently translated at 100.0% (5781 of 5781 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
Patrick Chilton
57ecaa2676 Translations: Update Hungarian
Currently translated at 10.7% (622 of 5781 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
CVZ-es
96fd2b1a95 Translations: Update Spanish
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
CVZ-es
5cf24fb6a6 Translations: Update Spanish
Currently translated at 100.0% (5784 of 5784 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
Eva-Maria Obermann
1d2ea35a39 Translations: Update French
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
CVZ-es
ac98ae7941 Translations: Update French
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
CVZ-es
a0d055e202 Translations: Update French
Currently translated at 99.3% (5742 of 5781 strings)

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

powered by weblate
2024-11-18 10:48:18 +01:00
Raphael Michel
27ec5ca006 Change order of menu items in backend (#4633) 2024-11-18 10:45:27 +01:00
Eva-Maria Obermann
9d2edc405d Translations: Update French
Currently translated at 98.7% (229 of 232 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
CVZ-es
fb95fe7cf6 Translations: Update French
Currently translated at 94.6% (5474 of 5781 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
CVZ-es
5b5360ef8b Translations: Update French
Currently translated at 98.7% (229 of 232 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
Gravity Fox
129d10ca35 Translations: Update Portuguese (Brazil)
Currently translated at 13.5% (784 of 5781 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
CVZ-es
093a705ff9 Translations: Update French
Currently translated at 94.6% (5474 of 5781 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
CVZ-es
6130ae4630 Translations: Update Spanish
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
CVZ-es
11a8ed6c7a Translations: Update Spanish
Currently translated at 100.0% (5784 of 5784 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
arjan-s
f6392592c5 Translations: Update Dutch
Currently translated at 100.0% (5784 of 5784 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
CVZ-es
ecb9ad28ea Translations: Update Spanish
Currently translated at 100.0% (5784 of 5784 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
Raphael Michel
45a506fd37 Translations: Update German
Currently translated at 100.0% (5781 of 5781 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
Raphael Michel
3b16e6356b Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5781 of 5781 strings)

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

powered by weblate
2024-11-15 17:21:50 +01:00
Raphael Michel
9583a50c4e Voucher import: Allow empty subevent (Z#23171356) (#4622) 2024-11-13 12:00:48 +01:00
Martin Gross
6e6d6b2746 PayPal2: Skip webhook order capture if no OrderPayment exists yet 2024-11-13 10:13:50 +01:00
arjan-s
7266d90c6b Translations: Update Dutch
Currently translated at 99.0% (5731 of 5784 strings)

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

powered by weblate
2024-11-12 09:36:00 +01:00
Richard Schreiber
5e4e88c91d Fix creating to many ScheduledMails on subevent creation (#4620) 2024-11-12 09:22:25 +01:00
Raphael Michel
e74d12e8b8 API: Don't require sales channel in input (PRETIXEU-AST) 2024-11-11 17:22:06 +01:00
Raphael Michel
a5c39271dd Make API security profiles pluggable (#4597)
* Make API security profiles pluggable

* Update src/pretix/api/signals.py

Co-authored-by: robbi5 <richt@rami.io>

* REmove dead class

---------

Co-authored-by: robbi5 <richt@rami.io>
2024-11-11 17:13:53 +01:00
Mira
3170744c56 Bleach 6 update (#4610)
* Update bleach requirement from ==5.0.* to ==6.2.*

Updates the requirements on [bleach](https://github.com/mozilla/bleach) to permit the latest version.
- [Changelog](https://github.com/mozilla/bleach/blob/main/CHANGES)
- [Commits](https://github.com/mozilla/bleach/compare/v5.0.0...v6.2.0)

---
updated-dependencies:
- dependency-name: bleach
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update bleach parameter types

* Fix tests

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-11 15:34:40 +01:00
Damiano
9ec161561b Translations: Update Italian
Currently translated at 24.2% (1402 of 5781 strings)

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

powered by weblate
2024-11-11 13:49:23 +01:00
CVZ-es
aff4f4b8f8 Translations: Update Spanish
Currently translated at 100.0% (5784 of 5784 strings)

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

powered by weblate
2024-11-11 13:49:23 +01:00
Damiano
75addfe9f4 Translations: Update Italian
Currently translated at 24.0% (1393 of 5781 strings)

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

powered by weblate
2024-11-11 13:49:23 +01:00
CVZ-es
4b05ce5835 Translations: Update Spanish
Currently translated at 100.0% (5784 of 5784 strings)

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

powered by weblate
2024-11-11 13:49:23 +01:00
CVZ-es
34c247f423 Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-11 13:49:23 +01:00
arjan-s
3aad6852cb Translations: Update Dutch
Currently translated at 96.8% (5592 of 5774 strings)

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

powered by weblate
2024-11-11 13:49:23 +01:00
CVZ-es
5cdb07bce6 Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-11 13:49:23 +01:00
arjan-s
6cb2d68948 Translations: Update Dutch
Currently translated at 76.7% (178 of 232 strings)

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

powered by weblate
2024-11-11 13:49:23 +01:00
arjan-s
4a7a6273c6 Translations: Update Dutch
Currently translated at 95.8% (5535 of 5774 strings)

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

powered by weblate
2024-11-11 13:49:23 +01:00
Raphael Michel
ebe343458a Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-11-08 14:45:14 +01:00
Mira
f9a93b765c Use correct LogEntry type for changes to product categories (#4617) 2024-11-08 10:45:46 +01:00
Raphael Michel
5aba1f9a23 Do not try to delete cart positions twice (PRETIXEU-ARN) 2024-11-07 14:28:35 +01:00
Damiano
a4eed87396 Translations: Update Italian
Currently translated at 23.7% (1370 of 5774 strings)

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

powered by weblate
2024-11-07 11:27:00 +01:00
Damiano
08879d0d55 Translations: Update Italian
Currently translated at 23.5% (1361 of 5774 strings)

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

powered by weblate
2024-11-07 11:27:00 +01:00
Christiaan de Die le Clercq
c276a19bcc Translations: Update Dutch
Currently translated at 87.7% (5067 of 5774 strings)

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

powered by weblate
2024-11-07 11:27:00 +01:00
Raphael Michel
1e3c6e0b68 Order detail: Do not show expire date for pending approval (Z#23171168) (#4613) 2024-11-07 09:26:10 +01:00
CVZ-es
4e283eb560 Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-06 12:32:10 +01:00
CVZ-es
52a1983630 Translations: Update French
Currently translated at 94.8% (5475 of 5774 strings)

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

powered by weblate
2024-11-06 12:32:10 +01:00
Raphael Michel
3d85d9d865 Remove deprecated auto_checkin_sales_channels (#4587)
* Remove deprecated auto_checkin_sales_channels

* Fix Query count
2024-11-06 12:30:41 +01:00
Raphael Michel
4ca9a43890 Fix performance issue in filtering checkin list (Z#23170917) (#4607)
* Fix performance issue in filtering checkin list

* remove test
2024-11-06 12:30:37 +01:00
mscherer
d8bac7db65 Remove slimit dependencies (#4609)
It doesn't work anymore on python 3.13, since
2to3 is deprecated and removed (PEP 594).

Trying to build on Fedora 41 result in:

	Downloading slimit-0.8.1.zip (88 kB)
	Installing build dependencies: started
	Installing build dependencies: finished with status 'done'
	Getting requirements to build wheel: started
	Getting requirements to build wheel: finished with status 'error'
	error: subprocess-exited-with-error
	× Getting requirements to build wheel did not run successfully.
	│ exit code: 1
	╰─> [1 lines of output]
	Python 3.X support requires the 2to3 tool.
	[end of output]

And slimit is not used in the code base nor anywhere in git (no single
match)
2024-11-06 12:28:43 +01:00
Mira
91de0f93e6 Allow organizers to manually add fees to an existing order (#4590) 2024-11-05 14:37:50 +01:00
Richard Schreiber
901565203b Hide input dependencies recursively if input will be disabled (#4599)
---------

Co-authored-by: Mira <weller@rami.io>
2024-11-05 14:34:42 +01:00
dependabot[bot]
14c6c9c0d7 Update sentry-sdk requirement from ==2.17.* to ==2.18.* (#4608)
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.17.0...2.18.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-05 09:55:13 +01:00
CVZ-es
6de6cf6c08 Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es
29306b3a4d Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Katrine Tella
ca69996611 Translations: Update Danish
Currently translated at 48.6% (2809 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Casper Schack
16419b6ae4 Translations: Update Danish
Currently translated at 48.6% (2809 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Damiano
d6258b9b54 Translations: Update Italian
Currently translated at 23.3% (1351 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Christiaan de Die le Clercq
6f75608196 Translations: Update Dutch
Currently translated at 87.7% (5064 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Mika Andrianarijaona
6ef88e009b Translations: Update French
Currently translated at 94.8% (5475 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es
957100a195 Translations: Update Spanish
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es
112ef0908f Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es
91aaff7359 Translations: Update Spanish
Currently translated at 96.5% (5575 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es
8ab61e2c38 Translations: Update French
Currently translated at 91.8% (213 of 232 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es
c8ba5cc427 Translations: Update French
Currently translated at 94.8% (5474 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es
5ebad31b7d Translations: Update Spanish
Currently translated at 96.4% (5567 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es
0429377f7d Translations: Update French
Currently translated at 91.3% (212 of 232 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Mossroy
76e4b797a1 Translations: Update French
Currently translated at 94.6% (5466 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Jonathan Berger
5f0009c996 Translations: Update French
Currently translated at 94.6% (5466 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
cpoisnel
de63a4be01 Translations: Update French
Currently translated at 94.6% (5466 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es
f3432139cb Translations: Update French
Currently translated at 94.6% (5466 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
dependabot[bot]
0b82ac9115 Bump @babel/preset-env in /src/pretix/static/npm_dir (#4604)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.25.4 to 7.26.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.0/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-04 20:55:31 +01:00
Martin Gross
eb685b5141 API: Expose cancellation_date on order endpoint (Z#23170733) (#4606)
Co-authored-by: robbi5 <richt@rami.io>
2024-11-04 16:41:44 +01:00
dependabot[bot]
5f7f0bd8f1 Bump @babel/core from 7.25.2 to 7.26.0 in /src/pretix/static/npm_dir
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.25.2 to 7.26.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.0/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 14:44:32 +01:00
Raphael Michel
9fcef2dcaa API: Fix deleting checkin-lists (Z#23170525) (PRETIXEU-AS5) (#4600) 2024-10-31 17:44:04 +01:00
Raphael Michel
fc3b186b93 Badge export: Allow to filter and sort by order date (Z#23168742) (#4588) 2024-10-31 14:42:44 +01:00
Tinna Sandström
a406884575 Translations: Update Swedish
Currently translated at 98.3% (5679 of 5774 strings)

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

powered by weblate
2024-10-31 13:02:34 +01:00
Anarion Dunedain
57ccd5f289 Translations: Update Polish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-10-31 13:02:34 +01:00
Mira
f4ac7e7f65 Add warning that cross-selling can't prevent unwanted combinations (#4596)
* Add warning that cross-selling can't prevent unwanted combinations

* Update src/pretix/control/templates/pretixcontrol/items/category.html

Co-authored-by: Raphael Michel <michel@rami.io>

---------

Co-authored-by: Raphael Michel <michel@rami.io>
2024-10-31 12:59:28 +01:00
Raphael Michel
81d7045b31 Public event filter: Make "all" option clearer (Z#23169843) (#4585)
* Public event filter: Make "all" option clearer

* Fix widget tests

* Update src/tests/presale/test_widget.py

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

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-10-29 17:31:51 +01:00
Richard Schreiber
f9502a3212 Fix testutils redis mock (#4586) 2024-10-29 16:37:06 +01:00
Richard Schreiber
a31f624417 Dev: add support for asyncio_default_fixture_loop_scope in pytest-asyncio (#4589) 2024-10-29 16:36:43 +01:00
Raphael Michel
3f99e0bece Bump version to 2024.11.0.dev0 2024-10-29 11:47:44 +01:00
Raphael Michel
7e64f2b38a Bump version to 2024.10.0 2024-10-29 11:47:10 +01:00
Raphael Michel
ee2bc93608 Fix handling of device_info_updated signal 2024-10-29 11:30:35 +01:00
Raphael Michel
fb4bed9d0d Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-10-29 10:08:27 +01:00
Raphael Michel
aec75e4d0c Translations: Update German
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-10-29 10:08:27 +01:00
Raphael Michel
e7e41470fb Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-10-29 09:54:05 +01:00
Raphael Michel
0aa9dda90a Cross-selling: Use different label if there are no add-on products (#4577)
* Cross-selling: Use different label if there are no add-on products

* Update src/pretix/presale/templates/pretixpresale/event/checkout_addons.html

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

* Fix a11y problem

* Fix headline order

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-10-29 09:53:36 +01:00
Mira
d97c983b6f Fix API serializer for auto_checkin_rules to accept sales channel identifiers (Z#23170095) (#4579)
* Fix API serializer for auto_checkin_rules to accept sales channel identifiers instead of PKs

* False -> false in json
2024-10-29 09:14:22 +01:00
dependabot[bot]
6c957f31ca Update django-hijack requirement from ==3.6.* to ==3.7.* (#4581)
Updates the requirements on [django-hijack](https://github.com/django-hijack/django-hijack) to permit the latest version.
- [Release notes](https://github.com/django-hijack/django-hijack/releases)
- [Changelog](https://github.com/django-hijack/django-hijack/blob/master/docs/release-button.png)
- [Commits](https://github.com/django-hijack/django-hijack/compare/3.6.0...3.7.0)

---
updated-dependencies:
- dependency-name: django-hijack
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 09:14:04 +01:00
dependabot[bot]
8e6b4b3ec7 Update pypdf requirement from ==5.0.* to ==5.1.* (#4582)
Updates the requirements on [pypdf](https://github.com/py-pdf/pypdf) to permit the latest version.
- [Release notes](https://github.com/py-pdf/pypdf/releases)
- [Changelog](https://github.com/py-pdf/pypdf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/py-pdf/pypdf/compare/5.0.0...5.1.0)

---
updated-dependencies:
- dependency-name: pypdf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 09:13:58 +01:00
arjan-s
b24de62f73 Translations: Update Dutch
Currently translated at 85.5% (4938 of 5773 strings)

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

powered by weblate
2024-10-29 09:13:45 +01:00
Raphael Michel
cdbd220a12 Fix time zone of parsed print log 2024-10-28 17:49:47 +01:00
Raphael Michel
2f11aee512 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5773 of 5773 strings)

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

powered by weblate
2024-10-28 13:04:02 +01:00
Raphael Michel
8ea475ce39 Translations: Update German
Currently translated at 100.0% (5773 of 5773 strings)

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

powered by weblate
2024-10-28 13:04:02 +01:00
Raphael Michel
b29bc9db96 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5773 of 5773 strings)

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

powered by weblate
2024-10-28 13:04:02 +01:00
Raphael Michel
6bd6694132 Translations: Update German
Currently translated at 100.0% (5773 of 5773 strings)

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

powered by weblate
2024-10-28 13:04:02 +01:00
Raphael Michel
110e6e248e Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-10-28 12:31:20 +01:00
Davide Manzella
985f4d969d Translations: Update Italian
Currently translated at 23.1% (1333 of 5762 strings)

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

powered by weblate
2024-10-28 12:30:56 +01:00
Mira
826bd07b01 Add action buttons to "missing quota" warnings (#4533)
* Add action buttons to "missing quota" warnings

* Update item.py
2024-10-28 12:25:03 +01:00
Raphael Michel
3e4e86742a Add print logs (#4475)
* Add print logs

* Add attribute successful

* Rebase migration

* Fix tests on postgres
2024-10-28 12:24:23 +01:00
Raphael Michel
ef5fcde5d9 Seating plans: Validate duplicate seat IDs (#4564) 2024-10-28 11:27:27 +01:00
Raphael Michel
8f1d53d016 Tests: Add separation of database lock indices by pytest worker (#4553)
* Add separation of database lock indices by pytest worker

* Fix ID derivation

* Update conftest.py
2024-10-25 22:05:02 +02:00
Raphael Michel
9ca1573fcf Tests: Remove pytest-rerunfailures (#4572) 2024-10-25 22:00:14 +02:00
Raphael Michel
5795aa6492 Docs: Fix various sphinx warnings 2024-10-25 21:58:14 +02:00
Raphael Michel
6e0613a2af CI: Remove warnings from PostgreSQL about health check (#4571)
* CI: Remove warnings from PostgreSQL about health check

* Update tests.yml

* Update tests.yml
2024-10-25 21:45:34 +02:00
Raphael Michel
b43ed38483 CI: Do not fail if codecov upload fails (#4570)
This happens all the time with external PRs until https://github.com/codecov/engineering-team/issues/1574 is solved and is doing more harm than good. We still see if there is no coverage attached.
2024-10-25 21:40:03 +02:00
Raphael Michel
f0fedf0001 Tests: Resolve deprecation warning for pytest-asyncio (#4569) 2024-10-25 21:27:38 +02:00
Raphael Michel
19373b8f91 CI: Update codecov-action (#4568) 2024-10-25 21:19:48 +02:00
dependabot[bot]
45fd13786a Update pillow requirement from ==10.4.* to ==11.0.* (#4566)
Updates the requirements on [pillow](https://github.com/python-pillow/Pillow) to permit the latest version.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.4.0...11.0.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 21:14:30 +02:00
dependabot[bot]
ae5111ee7e Update zeep requirement from ==4.2.* to ==4.3.* (#4567)
Updates the requirements on [zeep](https://github.com/mvantellingen/python-zeep) to permit the latest version.
- [Release notes](https://github.com/mvantellingen/python-zeep/releases)
- [Changelog](https://github.com/mvantellingen/python-zeep/blob/main/CHANGES)
- [Commits](https://github.com/mvantellingen/python-zeep/compare/4.2.0...4.3.1)

---
updated-dependencies:
- dependency-name: zeep
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 20:53:11 +02:00
dependabot[bot]
d8bf3065d0 Update redis requirement from ==5.1.* to ==5.2.* (#4565)
Updates the requirements on [redis](https://github.com/redis/redis-py) to permit the latest version.
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](https://github.com/redis/redis-py/compare/v5.1.0a1...v5.2.0)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 20:51:30 +02:00
Raphael Michel
54f077665c CI: Remove mentions of travis CI, we have migrated away years ago (#4562) 2024-10-25 17:47:04 +02:00
Raphael Michel
482a66c546 CI: Run PostgreSQL as a native service (#4560)
* GH Actions: Fix PostgreSQL issues

* Map PostgreSQL port
2024-10-25 17:18:42 +02:00
Raphael Michel
e4cef6e46b CI: Make sure apt is non-interactive (to not break act) (#4559) 2024-10-25 17:06:11 +02:00
Raphael Michel
cbee1b71fe CI: Install dependencies with uv for speedup (#4558) 2024-10-25 17:05:58 +02:00
Raphael Michel
0cd1290624 Tests: Fix two flaky tests (#4557)
* Test: Fix test that relies on unstable database result order

* Tests: Fix test that fails between 0am and 1am
2024-10-25 17:04:16 +02:00
Raphael Michel
565f5e2ea7 Tests: Run fakeredis on different virtual ports per pytest worker (#4555) 2024-10-25 16:44:22 +02:00
Raphael Michel
b46c0eba0c pytest: Fix failure with fakeserver and xdist 2024-10-25 14:32:28 +02:00
Raphael Michel
39c3aef7bc Fix fakeredis usage 2024-10-25 12:54:25 +02:00
dependabot[bot]
cf3087453c Update fakeredis requirement from ==2.24.* to ==2.26.* (#4551)
Updates the requirements on [fakeredis](https://github.com/cunla/fakeredis-py) to permit the latest version.
- [Release notes](https://github.com/cunla/fakeredis-py/releases)
- [Commits](https://github.com/cunla/fakeredis-py/compare/v2.24.0...v2.26.0)

---
updated-dependencies:
- dependency-name: fakeredis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 12:28:22 +02:00
Raphael Michel
7a870ee521 Fix test that does not work on postgresql 2024-10-25 00:02:52 +02:00
dependabot[bot]
3922290633 Update dnspython requirement from ==2.6.* to ==2.7.* (#4511)
Updates the requirements on [dnspython](https://github.com/rthalley/dnspython) to permit the latest version.
- [Release notes](https://github.com/rthalley/dnspython/releases)
- [Changelog](https://github.com/rthalley/dnspython/blob/main/doc/whatsnew.rst)
- [Commits](https://github.com/rthalley/dnspython/compare/v2.6.0rc1...v2.7.0)

---
updated-dependencies:
- dependency-name: dnspython
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 22:54:28 +02:00
dependabot[bot]
8aa13d7e3e Update sentry-sdk requirement from ==2.15.* to ==2.17.* (#4536)
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.15.0...2.17.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 22:53:58 +02:00
Raphael Michel
22e9a6eb92 Add warning to backend if cronjob is not running (#4550) 2024-10-24 22:50:34 +02:00
arjan-s
2b6f82502e Translations: Update Dutch
Currently translated at 85.6% (4936 of 5762 strings)

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

powered by weblate
2024-10-24 13:06:26 +02:00
Mira
a10bf2a939 Include "Cross-selling condition products" option when cloning category (#4534)
* Include "Cross-selling condition products" option when cloning category

* Add test case

* Remove print
2024-10-23 17:12:03 +02:00
arjan-s
a80b7087d9 Translations: Update Dutch
Currently translated at 75.8% (176 of 232 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
arjan-s
4b143e98eb Translations: Update Dutch
Currently translated at 85.6% (4935 of 5762 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
Yasunobu YesNo Kawaguchi
bdb8b597d0 Translations: Update Japanese
Currently translated at 3.4% (198 of 5762 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
Ryo Tagami
b1c9f40bc8 Translations: Update Japanese
Currently translated at 3.4% (198 of 5762 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
CVZ-es
a3b6a008b5 Translations: Update Spanish
Currently translated at 99.5% (231 of 232 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
CVZ-es
9ce05e5cb9 Translations: Update Spanish
Currently translated at 96.4% (5560 of 5762 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
CVZ-es
f306527981 Translations: Update Spanish
Currently translated at 95.1% (5485 of 5762 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
CVZ-es
3e17ff9faa Translations: Update Spanish
Currently translated at 91.7% (5288 of 5762 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
CVZ-es
2a16cd4655 Translations: Update Spanish
Currently translated at 90.7% (5228 of 5762 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
Ryo Tagami
d1078da5bf Translations: Update Japanese
Currently translated at 3.3% (195 of 5762 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
CVZ-es
483e7bc4ad Translations: Update Spanish
Currently translated at 89.8% (5175 of 5762 strings)

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

powered by weblate
2024-10-23 17:10:32 +02:00
Mira
401218b0a3 API: Allow filtering subevents by is_public (#4544) 2024-10-23 16:42:22 +02:00
Raphael Michel
19175258fd API: Fix event cloning, limit_sales_channels should never be set when all_sales_channels is set (Z#23169537) 2024-10-23 16:30:00 +02:00
Mira
22c36b89da Show proper error message in case of add-ons that are required, sold-out and hidden (#4545) 2024-10-21 16:46:32 +02:00
Mira
2697ed0c5d QuestionView: Carry over subevent filter to order list (#4543) 2024-10-21 14:45:04 +02:00
pretix translation bot
f81d820a02 Update translations (#4537)
Co-authored-by: Patrick Chilton <chpatrick@gmail.com>
Co-authored-by: Lovro <lovrogrilc@gmail.com>
Co-authored-by: CVZ-es <damien.bremont@casadevelazquez.org>
Co-authored-by: Ryo Tagami <rtagami@airstrip.jp>
Co-authored-by: Ryo <saremba@rami.io>
2024-10-21 13:28:34 +02:00
Mira
f8df66e621 Redirect to next step if AddOnsStep would show up empty (Z#23167007) (#4530) 2024-10-21 13:27:43 +02:00
Mira
2d9bfc80dc Fix "Your order qualifies for a discount" notice for discounted variation products (#4541) 2024-10-21 13:26:20 +02:00
Martin Gross
17b2e95569 Questions: Add SubEvent-Filter; Renew Filter-Form Layout (Z#23168996) (#4538) 2024-10-21 13:25:18 +02:00
Martin Gross
e49f938eb3 BulkVouchers: Append mail-recipient to comment instead of overwriting it (Z#23168852) 2024-10-16 10:37:06 +02:00
Martin Gross
8d63906341 Remove nl_BE from setup.cfg 2024-10-15 16:22:50 +02:00
Martin Gross
cfefa1aad0 Remove be_NL again, again, again 2024-10-15 16:17:20 +02:00
Martin Gross
1d16049dc5 add "Cross" and "Selling" (as in: "Cross-Selling") to de/de_Informal wordlist 2024-10-15 14:48:25 +02:00
Martin Gross
8452899edd add "Cross-Selling" to de/de_Informal wordlist 2024-10-15 14:43:31 +02:00
pretix translation bot
d67ebc0f80 Update translations (#4529) 2024-10-15 14:03:10 +02:00
Mira Weller
0e87f03e1e Update po files
[CI skip]

Signed-off-by: Mira Weller <weller@rami.io>
2024-10-15 13:14:42 +02:00
Mira Weller
868408ea55 Fix typo 2024-10-15 13:13:43 +02:00
Mira Weller
fc75cd35f8 Update po files
[CI skip]

Signed-off-by: Mira Weller <weller@rami.io>
2024-10-15 13:06:53 +02:00
Mira
a3e2540331 Display "Normal category" instead of "None" in category list (#4526) 2024-10-15 12:14:15 +02:00
Raphael Michel
99ce7effde Add ticket renderer RPC API (Z#23165429) (#4525)
---------

Co-authored-by: Mira Weller <weller@rami.io>
2024-10-15 12:11:09 +02:00
Raphael Michel
0d645fc4c5 Hotfix broken query away 2024-10-14 18:17:30 +02:00
Mira
359df1f51e Cross selling (#4185)
Product categories can now be marked as "cross-selling categories", causing them to 
appear in the add-on checkout step as additional recommendations, depending on 
their cross-selling visibility (always, only if certain products are already in the cart, or 
only if they qualify for a discount according to discount rules).

---------

Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-10-14 14:39:49 +02:00
Raphael Michel
7607cc5d2f Fix indentation 2024-10-14 09:26:56 +02:00
Raphael Michel
40c8d014df Waiting list: Redirect to shop if no products can be awaited (Z#23168172) (#4517)
* Waiting list: Redirect to shop if no products can be awaited (Z#23168172)

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

Co-authored-by: Mira <weller@rami.io>

---------

Co-authored-by: Mira <weller@rami.io>
2024-10-14 09:26:06 +02:00
Mira
c10efc692d Let organizers specify a helptext for the custom invoice field (Z#23167497) (#4520) 2024-10-14 09:22:58 +02:00
Raphael Michel
8f0a277c7b Fix tax rule calculation of negative amounts (PRETIXEU-ANN) 2024-10-11 15:28:07 +02:00
George Hickman
9dc38e42d8 Add device_changed signal (#4412)
This provides both the original and updated version of the Device so
subscribers can see the changes.
2024-10-11 11:08:23 +02:00
Raphael Michel
bfd88d1496 Docs: Fix wrong field name in example 2024-10-10 13:51:33 +02:00
Patrick Chilton
be6bd501bd Translations: Update Hungarian
Currently translated at 10.5% (608 of 5745 strings)

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

powered by weblate
2024-10-10 09:16:51 +02:00
Raphael Michel
d160c9fd67 Fix crash in checkin list action view (PRETIXEU-AN8) 2024-10-09 17:11:10 +02:00
Raphael Michel
221f14cc21 API: Fix crash PRETIXEU-AN5 2024-10-09 12:25:36 +02:00
Felix Schäfer
1dda2eb4fb Fix reauth loops with redirect style authentication plugins (#4512)
* Test reauth with redirect style auth #4498

* Fix reauth loops with redirect style auth #4498
2024-10-09 09:24:49 +02:00
Davide Manzella
30f2e99020 Translations: Update Italian
Currently translated at 22.2% (1279 of 5745 strings)

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

powered by weblate
2024-10-09 09:16:52 +02:00
Raphael Michel
8efe276ed0 Fix negative prices in bundles when tax rate is 0 (#4513) 2024-10-09 08:16:01 +02:00
Raphael Michel
61b25acdd2 Fix email confirm hash in templates 2024-10-07 17:54:40 +02:00
Raphael Michel
6cc9529d9a Authentication: Support for fallback secret keys in get_session_auth_hash (#4481)
* Authentication: Support for fallback secret keys in get_session_auth_hash

* Update src/pretix/presale/utils.py

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

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-10-07 16:58:37 +02:00
Raphael Michel
cdc5401dc2 Allow to set fallback secret keys (#4482) 2024-10-07 16:31:24 +02:00
Raphael Michel
1334a570e4 Generate email confirmation secret from tagged_secret (#4480) 2024-10-07 13:58:08 +02:00
Raphael Michel
7a66aea2cb Voucher update: Allow to remove seat 2024-10-07 11:42:28 +02:00
dependabot[bot]
ee77a5e447 Bump @rollup/plugin-node-resolve from 15.2.3 to 15.3.0 in /src/pretix/static/npm_dir (#4499)
Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/HEAD/packages/node-resolve) from 15.2.3 to 15.3.0.
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/node-resolve/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/node-resolve-v15.3.0/packages/node-resolve)

---
updated-dependencies:
- dependency-name: "@rollup/plugin-node-resolve"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 11:36:23 +02:00
Raphael Michel
827e127568 Fix #4365 -- Missing date in timeline 2024-10-04 16:17:52 +02:00
dependabot[bot]
ce0e0d7fd1 Update qrcode requirement from ==7.4.* to ==8.0 (#4500)
Updates the requirements on [qrcode](https://github.com/lincolnloop/python-qrcode) to permit the latest version.
- [Changelog](https://github.com/lincolnloop/python-qrcode/blob/main/CHANGES.rst)
- [Commits](https://github.com/lincolnloop/python-qrcode/compare/v7.4...v8.0)

---
updated-dependencies:
- dependency-name: qrcode
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-04 16:03:04 +02:00
dependabot[bot]
152a956dc5 Update sentry-sdk requirement from ==2.14.* to ==2.15.*
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.14.0...2.15.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-04 11:53:13 +02:00
Patrick Chilton
68e2c355e6 Translations: Update Hungarian
Currently translated at 10.3% (594 of 5745 strings)

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

powered by weblate
2024-10-04 11:48:19 +02:00
Patrick Chilton
171615558f Translations: Update Hungarian
Currently translated at 8.8% (506 of 5745 strings)

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

powered by weblate
2024-10-04 11:48:19 +02:00
Patrick Chilton
a1765910ea Translations: Update Hungarian
Currently translated at 45.2% (105 of 232 strings)

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

powered by weblate
2024-10-04 11:48:19 +02:00
Patrick Chilton
417277958b Translations: Update Hungarian
Currently translated at 8.7% (504 of 5745 strings)

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

powered by weblate
2024-10-04 11:48:19 +02:00
Patrick Chilton
0d50494e89 Translations: Update Hungarian
Currently translated at 43.9% (102 of 232 strings)

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

powered by weblate
2024-10-04 11:48:19 +02:00
Patrick Chilton
c6f634ce72 Translations: Update Hungarian
Currently translated at 7.9% (456 of 5745 strings)

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

powered by weblate
2024-10-04 11:48:19 +02:00
Patrick Chilton
adc78c14ab Translations: Update Hungarian
Currently translated at 2.9% (170 of 5745 strings)

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

powered by weblate
2024-10-04 11:48:19 +02:00
dependabot[bot]
b4ca2bdbb4 Update pycryptodome requirement from ==3.20.* to ==3.21.* (#4504)
Updates the requirements on [pycryptodome](https://github.com/Legrandin/pycryptodome) to permit the latest version.
- [Release notes](https://github.com/Legrandin/pycryptodome/releases)
- [Changelog](https://github.com/Legrandin/pycryptodome/blob/master/Changelog.rst)
- [Commits](https://github.com/Legrandin/pycryptodome/compare/v3.20.0x...v3.21.0)

---
updated-dependencies:
- dependency-name: pycryptodome
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-04 11:48:04 +02:00
dependabot[bot]
9a7ff592af Update redis requirement from ==5.0.* to ==5.1.* (#4491)
Updates the requirements on [redis](https://github.com/redis/redis-py) to permit the latest version.
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](https://github.com/redis/redis-py/compare/v5.0.0b1...v5.1.0)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 16:30:49 +02:00
Rosariocastellana
548b54cca6 Translations: Update Italian
Currently translated at 20.6% (1188 of 5745 strings)

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

powered by weblate
2024-09-30 13:51:23 +02:00
Anarion Dunedain
e736791446 Translations: Update Polish
Currently translated at 100.0% (5745 of 5745 strings)

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

powered by weblate
2024-09-30 13:51:23 +02:00
Raphael Michel
7bd945b2e6 Waiting list: Add warning when sold out products are hidden 2024-09-30 13:24:43 +02:00
Raphael Michel
a07d5aaf05 Add-on step: Minimal performance improvement 2024-09-27 13:26:30 +02:00
Raphael Michel
0cf1a32902 Bump version to 2024.10.0.dev0 2024-09-27 09:46:47 +02:00
Raphael Michel
be6aae8577 Bump version to 2024.9.0 2024-09-27 09:46:18 +02:00
Mira
fe80f5fb78 Utils for internal plugin (#4483)
* Add full_code property to OrderPosition

* Add inline "json_script" as supported data source for select2

* Use shorter OrderPosition.code
2024-09-26 19:29:33 +02:00
Raphael Michel
a2c15ad89e Check-in: Prevent duplicate auto check-outs (Z#23167003) (#4488) 2024-09-26 17:54:27 +02:00
Raphael Michel
cab0f37830 Translations: Update German
Currently translated at 100.0% (5745 of 5745 strings)

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

powered by weblate
2024-09-26 17:51:57 +02:00
Raphael Michel
0423980058 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5745 of 5745 strings)

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

powered by weblate
2024-09-26 17:51:57 +02:00
Raphael Michel
63983b1b68 Translations: Update spellcheck lists 2024-09-26 16:52:28 +02:00
Raphael Michel
61241c2a1e Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-09-26 13:23:02 +02:00
Raphael Michel
4069c61054 runperiodic: Allow to list all tasks 2024-09-26 10:32:53 +02:00
Raphael Michel
9bf4fb2d0f Sendmail: Fix incorrect filtering of subevents when mailing to attendees (Z#23166274) (#4474) 2024-09-25 17:55:33 +02:00
Raphael Michel
ff910f293f Item list: Show tax rate even for products with 0 price
Might be neccessary for price input, variations, etc
2024-09-25 12:42:45 +02:00
Raphael Michel
74f7bec617 Change wording in a headline to avoid double-using a term 2024-09-25 12:42:45 +02:00
Albizuri
467a35e353 Translations: Update Basque
Currently translated at 56.0% (3213 of 5737 strings)

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

powered by weblate
2024-09-24 10:13:22 +02:00
dependabot[bot]
770c13a4f0 Update pypdf requirement from ==4.3.* to ==5.0.* (#4471)
Updates the requirements on [pypdf](https://github.com/py-pdf/pypdf) to permit the latest version.
- [Release notes](https://github.com/py-pdf/pypdf/releases)
- [Changelog](https://github.com/py-pdf/pypdf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/py-pdf/pypdf/compare/4.3.0...5.0.0)

---
updated-dependencies:
- dependency-name: pypdf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-19 11:34:23 +02:00
dependabot[bot]
5373d4d8ba Bump django-bootstrap3 from 24.2 to 24.3
Bumps [django-bootstrap3](https://github.com/zostera/django-bootstrap3) from 24.2 to 24.3.
- [Changelog](https://github.com/zostera/django-bootstrap3/blob/main/CHANGELOG.md)
- [Commits](https://github.com/zostera/django-bootstrap3/compare/v24.2...v24.3)

---
updated-dependencies:
- dependency-name: django-bootstrap3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-19 11:28:15 +02:00
Raphael Michel
42e673b5f6 Fix missing JS file name 2024-09-19 09:39:49 +02:00
Albizuri
7af2f2a87b Translations: Update Basque
Currently translated at 56.0% (3213 of 5737 strings)

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

powered by weblate
2024-09-18 16:25:32 +02:00
Tinna Sandström
e408521769 Translations: Update Swedish
Currently translated at 99.1% (5686 of 5736 strings)

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

powered by weblate
2024-09-18 16:25:32 +02:00
Raphael Michel
8ed0d36346 Fix another wrong waiting_list_active reference 2024-09-18 10:28:41 +02:00
Raphael Michel
14cbe99667 Stripe: Fix a leftover } 2024-09-17 17:54:57 +02:00
Raphael Michel
b059995eff Widget: Fix waiting list disable time based on subevent 2024-09-17 17:39:22 +02:00
Raphael Michel
100e8d0a4b Fix #4454 -- Resolve deprecation warning from celery 2024-09-17 13:36:35 +02:00
Raphael Michel
eb92e4d8e6 Render progress info on non-javascript fallback page for celery tasks (#4452)
* Render progress info on non-javascript fallback page for celery tasks

* Review notes
2024-09-17 13:29:27 +02:00
Raphael Michel
32d6ded003 Stricten password validation to match PCI DSS requirements (#4467)
* Stricten password validation to match PCI DSS requirements

* Review fix

* Fix a file header
2024-09-17 13:29:17 +02:00
Raphael Michel
aa07533693 Order import: Allow to set check-in attention and text (Z#23165806) (#4469) 2024-09-17 11:50:19 +02:00
dependabot[bot]
e7d01f91a6 Bump @babel/preset-env from 7.25.3 to 7.25.4 in /src/pretix/static/npm_dir (#4438)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.25.3 to 7.25.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.4/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 11:42:33 +02:00
Raphael Michel
9616369f07 Formally deprecate eu_reverse_charge (#4470) 2024-09-17 11:39:32 +02:00
dependabot[bot]
af606090ba Update sentry-sdk requirement from ==2.13.* to ==2.14.* (#4455)
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.13.0...2.14.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 16:46:28 +02:00
Svyatoslav
931f3eca1b Translations: Update Estonian
Currently translated at 0.1% (1 of 5736 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Svyatoslav
36f306120e Translations: Update Latvian
Currently translated at 37.2% (2134 of 5736 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Svyatoslav
a3ba0c97e9 Translations: Update Russian
Currently translated at 19.1% (1097 of 5736 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Richard Schreiber
484d24b66c Translations: Add Estonian 2024-09-16 16:44:46 +02:00
Tinna Sandström
2d39d3cc8e Translations: Update Swedish
Currently translated at 99.1% (5686 of 5736 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Kristian Feldsam
78b1adf423 Translations: Update Slovak
Currently translated at 29.3% (68 of 232 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Kristian Feldsam
c3eedcc396 Translations: Update Slovak
Currently translated at 92.5% (5307 of 5736 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Tinna Sandström
682c328390 Translations: Update Swedish
Currently translated at 99.1% (5686 of 5736 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Wikinaut
5230827f5e Translations: Update German
Currently translated at 100.0% (5736 of 5736 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Arthur Nunes
dad9915435 Translations: Update Portuguese (Brazil)
Currently translated at 13.2% (759 of 5736 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Wikinaut
a9d2c1eb34 Translations: Update German
Currently translated at 100.0% (5736 of 5736 strings)

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

powered by weblate
2024-09-16 16:44:46 +02:00
Tinna Sandström
66fe45a478 Translations: Update Swedish
Currently translated at 86.2% (200 of 232 strings)

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

powered by weblate
2024-09-12 09:32:41 +02:00
Raphael Michel
24e2b1b9ab Import: Parse iso dates 2024-09-12 09:32:25 +02:00
Martin Gross
eebdce80cd OIDC: Respect token_endpoint_auth_methods_supported (Z#2164777) (#4459) 2024-09-11 16:40:42 +02:00
Kristian Feldsam
09af95ec20 Translations: Update Slovak
Currently translated at 92.1% (5283 of 5736 strings)

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

powered by weblate
2024-09-11 16:01:16 +02:00
Tinna Sandström
1ade674beb Translations: Update Swedish
Currently translated at 99.1% (5686 of 5736 strings)

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

powered by weblate
2024-09-11 16:01:16 +02:00
Kristian Feldsam
76ff59f9c2 Translations: Update Slovak
Currently translated at 6.4% (15 of 232 strings)

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

powered by weblate
2024-09-11 16:01:16 +02:00
Kristian Feldsam
0986522c2f Translations: Update Slovak
Currently translated at 6.0% (14 of 232 strings)

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

powered by weblate
2024-09-11 16:01:16 +02:00
Kristian Feldsam
91f4e731da Translations: Update Slovak
Currently translated at 92.0% (5280 of 5736 strings)

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

powered by weblate
2024-09-11 16:01:16 +02:00
Raphael Michel
98709286c6 Order cancellation: Fix crash on deleted order (PRETIXEU-AHP) 2024-09-10 14:00:11 +02:00
Raphael Michel
667c2555b2 AsyncMixin: Fix crash on invalid query string (PRETIXEU-AHG) 2024-09-10 14:00:08 +02:00
Raphael Michel
6f5acb1ca7 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-09-10 09:19:12 +02:00
Raphael Michel
65ec3e3fd6 Translations: Update German
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-09-10 09:19:12 +02:00
Richard Schreiber
1a8d0a973d Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5736 of 5736 strings)

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

powered by weblate
2024-09-10 09:19:12 +02:00
Richard Schreiber
3c94631405 Translations: Update German
Currently translated at 100.0% (5736 of 5736 strings)

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

powered by weblate
2024-09-10 09:19:12 +02:00
Kristian Feldsam
1dda7732a5 Translations: Update Slovak
Currently translated at 4.7% (11 of 232 strings)

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

powered by weblate
2024-09-10 09:19:12 +02:00
Kristian Feldsam
33accf5f99 Translations: Update Slovak
Currently translated at 92.0% (5279 of 5736 strings)

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

powered by weblate
2024-09-10 09:19:12 +02:00
Tinna Sandström
be2efd9df2 Translations: Update Swedish
Currently translated at 99.1% (5686 of 5736 strings)

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

powered by weblate
2024-09-10 09:19:12 +02:00
Ahmad AlHarthi
fe69137a4e Translations: Update Arabic
Currently translated at 65.1% (3736 of 5736 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Ayden Jahola
7ccfb3a27a Translations: Update Arabic
Currently translated at 65.1% (3736 of 5736 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Ayden Jahola
b7205622dc Translations: Update Arabic
Currently translated at 65.1% (3736 of 5736 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Ahmad AlHarthi
44da5b81b1 Translations: Update Arabic
Currently translated at 65.1% (3736 of 5736 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Martin Gross
5a058342a6 Translations: Update Portuguese (Portugal)
Currently translated at 56.8% (132 of 232 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Martin Gross
2d15dc7ce5 Translations: Update Portuguese (Portugal)
Currently translated at 56.8% (132 of 232 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Martin Gross
dd4ccc864e Translations: Update Portuguese (Portugal)
Currently translated at 86.7% (4977 of 5736 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Kristian Feldsam
b812f0affe Translations: Update Slovak
Currently translated at 3.8% (9 of 232 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Kristian Feldsam
2af4183ce6 Translations: Update Slovak
Currently translated at 92.0% (5279 of 5736 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Albizuri
8ac0b93ca5 Translations: Update Basque
Currently translated at 55.9% (3209 of 5737 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Ronisson Cabral
51a1193f32 Translations: Update Portuguese (Portugal)
Currently translated at 56.0% (130 of 232 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Ronisson Cabral
002da2c9b7 Translations: Update Portuguese (Portugal)
Currently translated at 86.2% (4948 of 5736 strings)

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

powered by weblate
2024-09-09 11:18:16 +02:00
Martin Gross
9a2ebe4e95 Refunds: Fix sortkey to work with not provided BICs (Z#23165187) (#4451) 2024-09-09 10:48:53 +02:00
Albizuri
bc6da2512a Translations: Update Basque
Currently translated at 48.4% (113 of 233 strings)

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

powered by weblate
2024-09-06 10:51:38 +02:00
Albizuri
6378dc69b8 Translations: Update Basque
Currently translated at 56.3% (3231 of 5737 strings)

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

powered by weblate
2024-09-06 10:51:38 +02:00
Richard Schreiber
2b53d04a19 Improve labels in mail settings for incomplete payments (#4444) 2024-09-06 08:53:08 +02:00
Richard Schreiber
7efe7b5ff7 Docs: fix typo for SSO provider instead of client 2024-09-05 13:02:20 +02:00
Alberto Ortega
ae5464d486 Translations: Update Spanish
Currently translated at 87.8% (5038 of 5736 strings)

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

powered by weblate
2024-09-05 11:09:47 +02:00
Tinna Sandström
67fec8d1f6 Translations: Update Swedish
Currently translated at 99.1% (5686 of 5736 strings)

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

powered by weblate
2024-09-05 11:09:47 +02:00
Albizuri
95a081676b Translations: Update Basque
Currently translated at 56.2% (3225 of 5737 strings)

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

powered by weblate
2024-08-30 09:17:49 +02:00
dependabot[bot]
7228a6304d Update protobuf requirement from ==5.27.* to ==5.28.* (#4430)
Updates the requirements on [protobuf](https://github.com/protocolbuffers/protobuf) to permit the latest version.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v5.27.0-rc1...v5.28.0)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-30 09:11:23 +02:00
Raphael Michel
04b9134e36 Add Basque to languages 2024-08-29 16:52:54 +02:00
Albizuri
2e0769bc41 Translations: Update Basque
Currently translated at 54.4% (3126 of 5737 strings)

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

powered by weblate
2024-08-29 14:51:49 +02:00
Albizuri
4d2f854710 Translations: Update Basque
Currently translated at 48.0% (112 of 233 strings)

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

powered by weblate
2024-08-29 14:51:49 +02:00
Albizuri
b9ac9496d2 Translations: Update Basque
Currently translated at 52.7% (3026 of 5737 strings)

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

powered by weblate
2024-08-29 14:51:49 +02:00
Albizuri
a975f5dc50 Translations: Update Basque
Currently translated at 47.2% (110 of 233 strings)

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

powered by weblate
2024-08-29 14:51:49 +02:00
Albizuri
4ea1f6284a Translations: Update Basque
Currently translated at 47.5% (2726 of 5737 strings)

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

powered by weblate
2024-08-29 14:51:49 +02:00
Raphael Michel
a01d105829 Bank Transfer: Fix weird error code on duplicate match (Z#23164140) (#4428) 2024-08-29 13:58:37 +02:00
Raphael Michel
b1bfa1acee API: Provide useful device_id in checkins (Z#23163025) (#4427) 2024-08-29 12:36:27 +02:00
Raphael Michel
0b4e99c2d8 Fix inprecise wording 2024-08-28 16:41:37 +02:00
Raphael Michel
0cdce7a9cd Add payment_explanation to payment method change (Z#23164112) (#4424) 2024-08-28 15:12:16 +02:00
Richard Schreiber
464f625301 Seating: visually remove waitinglist headline for soldout seating 2024-08-28 14:36:30 +02:00
Richard Schreiber
0c1072503c A11y: improve/unify html-source for uncategorized products 2024-08-28 14:35:35 +02:00
Raphael Michel
9ead82839a API: Add show_dates_on_frontpage to device event settings 2024-08-28 13:47:24 +02:00
462 changed files with 403392 additions and 204320 deletions

View File

@@ -38,7 +38,7 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install system dependencies
run: sudo apt update && sudo apt install gettext unzip
run: sudo apt update && sudo apt install -y gettext unzip
- name: Install Python dependencies
run: pip3 install -U setuptools build pip check-manifest
- name: Run check-manifest

View File

@@ -37,7 +37,7 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install system packages
run: sudo apt update && sudo apt install enchant-2 hunspell aspell-en
run: sudo apt update && sudo apt install -y enchant-2 hunspell aspell-en
- name: Install Dependencies
run: pip3 install -Ur requirements.txt
working-directory: ./doc

View File

@@ -35,9 +35,9 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install system packages
run: sudo apt update && sudo apt install gettext
run: sudo apt update && sudo apt -y install gettext
- name: Install Dependencies
run: pip3 install -e ".[dev]"
run: pip3 install uv && uv pip install --system -e ".[dev]"
- name: Compile messages
run: python manage.py compilemessages
working-directory: ./src
@@ -62,7 +62,7 @@ jobs:
- name: Install system packages
run: sudo apt update && sudo apt install enchant-2 hunspell hunspell-de-de aspell-en aspell-de
- name: Install Dependencies
run: pip3 install -e ".[dev]"
run: pip3 install uv && uv pip install --system -e ".[dev]"
- name: Spellcheck translations
run: potypo
working-directory: ./src

View File

@@ -35,7 +35,7 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install Dependencies
run: pip3 install -e ".[dev]" psycopg2-binary
run: pip3 install uv && uv pip install --system -e ".[dev]" psycopg2-binary
- name: Run isort
run: isort -c .
working-directory: ./src
@@ -55,7 +55,7 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install Dependencies
run: pip3 install -e ".[dev]" psycopg2-binary
run: pip3 install uv && uv pip install --system -e ".[dev]" psycopg2-binary
- name: Run flake8
run: flake8 .
working-directory: ./src

View File

@@ -30,15 +30,21 @@ jobs:
python-version: "3.9"
- database: sqlite
python-version: "3.10"
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: pretix
options: >-
--health-cmd "pg_isready -U postgres -d pretix"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: harmon758/postgresql-action@v1
with:
postgresql version: '15'
postgresql db: 'pretix'
postgresql user: 'postgres'
postgresql password: 'postgres'
if: matrix.database == 'postgres'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
@@ -50,9 +56,9 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install system dependencies
run: sudo apt update && sudo apt install gettext
run: sudo apt update && sudo apt install -y gettext
- name: Install Python dependencies
run: pip3 install --ignore-requires-python -e ".[dev]" psycopg2-binary # We ignore that flake8 needs newer python as we don't run flake8 during tests
run: pip3 install uv && uv pip install --system -e ".[dev]" psycopg2-binary
- name: Run checks
run: python manage.py check
working-directory: ./src
@@ -64,15 +70,15 @@ jobs:
run: make all compress
- name: Run tests
working-directory: ./src
run: PRETIX_CONFIG_FILE=tests/travis_${{ matrix.database }}.cfg py.test -n 3 -p no:sugar --cov=./ --cov-report=xml --reruns 3 tests --maxfail=100
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test -n 3 -p no:sugar --cov=./ --cov-report=xml tests --maxfail=100
- name: Run concurrency tests
working-directory: ./src
run: PRETIX_CONFIG_FILE=tests/travis_${{ matrix.database }}.cfg py.test tests/concurrency_tests/ --reruns 0 --reuse-db
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test tests/concurrency_tests/ --reuse-db
if: matrix.database == 'postgres'
- name: Upload coverage
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v4
with:
file: src/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
fail_ci_if_error: false
if: matrix.database == 'postgres' && matrix.python-version == '3.11'

View File

@@ -10,7 +10,7 @@ tests:
- cd src
- python manage.py check
- make all compress
- PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg py.test --reruns 3 -n 3 tests --maxfail=100
- PRETIX_CONFIG_FILE=tests/ci_sqlite.cfg py.test -n 3 tests --maxfail=100
except:
- pypi
pypi:

View File

@@ -60,6 +60,14 @@ http {
deny all;
return 404;
}
location /static/staticfiles.json {
deny all;
return 404;
}
location /static/CACHE/manifest.json {
deny all;
return 404;
}
location /static/ {
alias /pretix/src/pretix/static.dist/;
access_log off;

View File

@@ -288,17 +288,26 @@ Example::
[django]
secret=j1kjps5a5&4ilpn912s7a1!e2h!duz^i3&idu@_907s$wrz@x-
debug=off
passwords_argon2=on
``secret``
The secret to be used by Django for signing and verification purposes. If this
setting is not provided, pretix will generate a random secret on the first start
and will store it in the filesystem for later usage.
``secret_fallback0`` ... ``secret_fallback9``
Prior versions of the secret to be used by Django for signing and verification purposes that will still
be accepted but no longer be used for new signing.
``debug``
Whether or not to run in debug mode. Default is ``False``.
.. WARNING:: Never set this to ``True`` in production!
``passwords_argon``
Use the ``argon2`` algorithm for password hashing. Disable on systems with a small number of CPU cores (currently
less than 8).
``profile``
Enable code profiling for a random subset of requests. Disabled by default, see
:ref:`perf-monitoring` for details.

View File

@@ -231,11 +231,10 @@ The following snippet is an example on how to configure a nginx proxy for pretix
}
}
server {
listen 443 default_server;
listen [::]:443 ipv6only=on default_server;
listen 443 ssl default_server;
listen [::]:443 ipv6only=on ssl default_server;
server_name pretix.mydomain.com;
ssl on;
ssl_certificate /path/to/cert.chain.pem;
ssl_certificate_key /path/to/key.pem;

View File

@@ -216,11 +216,10 @@ The following snippet is an example on how to configure a nginx proxy for pretix
}
}
server {
listen 443 default_server;
listen [::]:443 ipv6only=on default_server;
listen 443 ssl default_server;
listen [::]:443 ipv6only=on ssl default_server;
server_name pretix.mydomain.com;
ssl on;
ssl_certificate /path/to/cert.chain.pem;
ssl_certificate_key /path/to/key.pem;
@@ -249,6 +248,14 @@ The following snippet is an example on how to configure a nginx proxy for pretix
return 404;
}
location /static/staticfiles.json {
deny all;
return 404;
}
location /static/CACHE/manifest.json {
deny all;
return 404;
}
location /static/ {
alias /var/pretix/venv/lib/python3.11/site-packages/pretix/static.dist/;
access_log off;

View File

@@ -71,7 +71,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,
@@ -113,7 +113,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,
@@ -146,7 +146,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,
@@ -167,7 +167,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,
@@ -216,7 +216,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,

View File

@@ -23,6 +23,22 @@ position integer An integer, use
is_addon boolean If ``true``, items within this category are not on sale
on their own but the category provides a source for
defining add-ons for other products.
cross_selling_mode string If ``null``, cross-selling is disabled for this category.
If ``"only"``, it is only visible in the cross-selling
step.
If ``"both"``, it is visible on the normal index page
as well.
Only available if ``is_addon`` is ``false``.
cross_selling_condition string Only relevant if ``cross_selling_mode`` is not ``null``.
If ``"always"``, always show in cross-selling step.
If ``"products"``, only show if the cart contains one of
the products listed in ``cross_selling_match_products``.
If ``"discounts"``, only show products that qualify for
a discount according to discount rules.
cross_selling_match_products list of integer Only relevant if ``cross_selling_condition`` is
``"products"``. Internal ID of the items of which at
least one needs to be in the cart for this category to
be shown.
===================================== ========================== =======================================================
@@ -60,7 +76,10 @@ Endpoints
"internal_name": "",
"description": {"en": "Tickets are what you need to get in."},
"position": 1,
"is_addon": false
"is_addon": false,
"cross_selling_mode": null,
"cross_selling_condition": null,
"cross_selling_match_products": []
}
]
}
@@ -102,7 +121,10 @@ Endpoints
"internal_name": "",
"description": {"en": "Tickets are what you need to get in."},
"position": 1,
"is_addon": false
"is_addon": false,
"cross_selling_mode": null,
"cross_selling_condition": null,
"cross_selling_match_products": []
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -130,7 +152,10 @@ Endpoints
"internal_name": "",
"description": {"en": "Tickets are what you need to get in."},
"position": 1,
"is_addon": false
"is_addon": false,
"cross_selling_mode": null,
"cross_selling_condition": null,
"cross_selling_match_products": []
}
**Example response**:
@@ -147,7 +172,10 @@ Endpoints
"internal_name": "",
"description": {"en": "Tickets are what you need to get in."},
"position": 1,
"is_addon": false
"is_addon": false,
"cross_selling_mode": null,
"cross_selling_condition": null,
"cross_selling_match_products": []
}
:param organizer: The ``slug`` field of the organizer of the event to create a category for
@@ -193,7 +221,10 @@ Endpoints
"internal_name": "",
"description": {"en": "Tickets are what you need to get in."},
"position": 1,
"is_addon": true
"is_addon": true,
"cross_selling_mode": null,
"cross_selling_condition": null,
"cross_selling_match_products": []
}
:param organizer: The ``slug`` field of the organizer to modify

View File

@@ -31,8 +31,6 @@ subevent integer ID of the date
position_count integer Number of tickets that match this list (read-only).
checkin_count integer Number of check-ins performed on this list (read-only).
include_pending boolean If ``true``, the check-in list also contains tickets from orders in pending state.
auto_checkin_sales_channels list of strings All items on the check-in list will be automatically marked as checked-in when purchased through any of the listed sales channels.
**Deprecated, will be removed in pretix 2024.10.** Use :ref:`rest-autocheckinrules`: instead.
allow_multiple_entries boolean If ``true``, subsequent scans of a ticket on this list should not show a warning but instead be stored as an additional check-in.
allow_entry_after_exit boolean If ``true``, subsequent scans of a ticket on this list are valid if the last scan of the ticket was an exit scan.
rules object Custom check-in logic. The contents of this field are currently not considered a stable API and modifications through the API are highly discouraged.
@@ -91,10 +89,7 @@ Endpoints
"allow_entry_after_exit": true,
"exit_all_at": null,
"rules": {},
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
]
}
@@ -146,10 +141,7 @@ Endpoints
"allow_entry_after_exit": true,
"exit_all_at": null,
"rules": {},
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -246,10 +238,7 @@ Endpoints
"subevent": null,
"allow_multiple_entries": false,
"allow_entry_after_exit": true,
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
**Example response**:
@@ -271,10 +260,7 @@ Endpoints
"subevent": null,
"allow_multiple_entries": false,
"allow_entry_after_exit": true,
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
:param organizer: The ``slug`` field of the organizer of the event/item to create a list for
@@ -326,10 +312,7 @@ Endpoints
"subevent": null,
"allow_multiple_entries": false,
"allow_entry_after_exit": true,
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
:param organizer: The ``slug`` field of the organizer to modify
@@ -342,7 +325,7 @@ Endpoints
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/checkinlist/(id)/
Delete a check-in list. Note that this also deletes the information on all check-ins performed via this list.
Delete a check-in list. **Note that this also deletes the information on all check-ins performed via this list.**
**Example request**:

View File

@@ -97,6 +97,7 @@ lines list of objects The actual invo
├ gross_value money (string) Price including taxes
├ tax_value money (string) Tax amount included
├ tax_name string Name of used tax rate (e.g. "VAT")
├ tax_code string Codified reason for tax rate (or ``null``), see :ref:`rest-taxcodes`.
└ tax_rate decimal (string) Used tax rate
foreign_currency_display string If the invoice should also show the total and tax
amount in a different currency, this contains the
@@ -126,6 +127,10 @@ internal_reference string Customer's refe
The ``event`` attribute has been added. The organizer-level endpoint has been added.
.. versionchanged:: 2024.8
The ``tax_code`` attribute has been added.
List of all invoices
--------------------
@@ -203,6 +208,7 @@ List of all invoices
"gross_value": "23.00",
"tax_value": "0.00",
"tax_name": "VAT",
"tax_code": "S/standard",
"tax_rate": "0.00"
}
],
@@ -342,6 +348,7 @@ Fetching individual invoices
"gross_value": "23.00",
"tax_value": "0.00",
"tax_name": "VAT",
"tax_code": "S/standard",
"tax_rate": "0.00"
}
],
@@ -352,12 +359,12 @@ Fetching individual invoices
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice to fetch
:param number: The ``number`` field of the invoice to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/download/
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/download/
Download an invoice in PDF format.
@@ -384,7 +391,7 @@ Fetching individual invoices
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice to fetch
:param number: The ``number`` field of the invoice to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
@@ -397,7 +404,7 @@ Modifying invoices
Invoices cannot be edited directly, but the following actions can be triggered:
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/reissue/
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/reissue/
Cancels the invoice and creates a new one.
@@ -419,13 +426,13 @@ Invoices cannot be edited directly, but the following actions can be triggered:
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice to reissue
:param number: The ``number`` field of the invoice to reissue
:statuscode 200: no error
:statuscode 400: The invoice has already been canceled
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/regenerate/
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/regenerate/
Re-generates the invoice from order data.
@@ -447,7 +454,7 @@ Invoices cannot be edited directly, but the following actions can be triggered:
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice to regenerate
:param number: The ``number`` field of the invoice to regenerate
:statuscode 200: no error
:statuscode 400: The invoice has already been canceled
:statuscode 401: Authentication failure

View File

@@ -69,6 +69,10 @@ hidden_if_available integer **DEPRECATED*
hidden_if_item_available integer The internal ID of a different item, or ``null``. If
set, this item won't be shown publicly as long as this
other item is available.
hidden_if_item_available_mode string If ``hide`` (the default), this item is hidden in the shop
if unavailable due to the ``hidden_if_item_available`` setting.
If ``info``, the item is visible, but can't be purchased,
and a note explaining the unavailability is displayed.
require_voucher boolean If ``true``, this item can only be bought using a
voucher that is specifically assigned to this item.
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
@@ -239,6 +243,10 @@ meta_data object Values set fo
The ``hidden_if_item_available`` attributes has been added, the ``hidden_if_available`` attribute has been
deprecated.
.. versionchanged:: 2025.01
The ``hidden_if_item_available_mode`` attributes has been added.
Notes
-----
@@ -308,6 +316,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -459,6 +468,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -589,6 +599,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -705,6 +716,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -855,6 +867,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"generate_tickets": null,

View File

@@ -84,6 +84,7 @@ fees list of objects List of fees in
├ tax_rate decimal (string) VAT rate applied for this fee
├ tax_value money (string) VAT included in this fee
├ tax_rule integer The ID of the used tax rule (or ``null``)
├ tax_code string Codified reason for tax rate (or ``null``), see :ref:`rest-taxcodes`.
└ canceled boolean Whether or not this fee has been canceled.
downloads list of objects List of ticket download options for order-wise ticket
downloading. This might be a multi-page PDF or a ZIP
@@ -104,6 +105,10 @@ url string The full URL to
payments list of objects List of payment processes (see below)
refunds list of objects List of refund processes (see below)
last_modified datetime Last modification of this object
cancellation_date datetime Time of order cancellation (or ``null``). **Note**:
Will not be set for partial cancellations and is not
reliable for orders that have been cancelled,
reactivated and cancelled again.
===================================== ========================== =======================================================
@@ -151,6 +156,13 @@ last_modified datetime Last modificati
The ``expires`` attribute can now be passed during order creation.
.. versionchanged:: 2024.11
The ``cancellation_date`` attribute has been added and can also be used as an ordering key.
.. versionchanged:: 2025.1
The ``tax_code`` attribute has been added.
.. _order-position-resource:
@@ -188,6 +200,7 @@ voucher_budget_use money (string) Amount of money
are changed *after* the order was created. Can be ``null``.
tax_rate decimal (string) VAT rate applied for this position
tax_value money (string) VAT included in this position
tax_code string Codified reason for tax rate (or ``null``), see :ref:`rest-taxcodes`.
tax_rule integer The ID of the used tax rule (or ``null``)
secret string Secret code printed on the tickets for validation
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
@@ -203,8 +216,20 @@ checkins list of objects List of **succe
├ 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``.
├ device integer Internal ID of the device. Can be ``null``. **Deprecated**, since this ID is not otherwise used in the API and is therefore not very useful.
├ device_id integer Attribute ``device_id`` of the device. Can be ``null``.
└ auto_checked_in boolean Indicates if this check-in been performed automatically by the system
print_logs list of objects List of print jobs recorded e.g. by the pretix apps
├ id integer Internal ID of the print job
├ successful boolean Whether the print job successfully resulted in a print.
This is not expected to be 100 % reliable information (since
printer feedback is never perfect) and there is no guarantee
that unsuccessful jobs will be logged.
├ device_id integer Attribute ``device_id`` of the device that recorded the print. Can be ``null``.
├ datetime datetime Time of printing
├ source string Source of print job, e.g. name of the app used.
├ type string Type of print (currently ``badge``, ``ticket``, ``certificate``, or ``other``)
└ info object Additional data with client-dependent structure.
downloads list of objects List of ticket download options
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
└ url string Download URL
@@ -232,6 +257,14 @@ pdf_data object Data object req
The attributes ``blocked``, ``valid_from`` and ``valid_until`` have been added.
.. versionchanged:: 2024.9
The attribute ``print_logs`` has been added.
.. versionchanged:: 2025.1
The ``tax_code`` attribute has been added.
.. _order-payment-resource:
Order payment resource
@@ -383,6 +416,7 @@ List of all orders
"tax_rate": "0.00",
"tax_value": "0.00",
"tax_rule": null,
"tax_code": null,
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
@@ -398,10 +432,21 @@ List of all orders
"type": "entry",
"gate": null,
"device": 2,
"device_id": 1,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"print_logs": [
{
"id": 1,
"type": "badge",
"datetime": "2017-12-25T12:45:23Z",
"device_id": 1,
"source": "pretixSCAN",
"info": {}
}
],
"answers": [
{
"question": 12,
@@ -437,14 +482,15 @@ List of all orders
"provider": "banktransfer"
}
],
"refunds": []
"refunds": [],
"cancellation_date": null
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``code``,
``last_modified``, and ``status``. Default: ``datetime``
``last_modified``, ``status`` and ``cancellation_date``. Default: ``datetime``
: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 (matching for names, email addresses, and company names)
@@ -610,6 +656,7 @@ Fetching individual orders
"tax_rate": "0.00",
"tax_rule": null,
"tax_value": "0.00",
"tax_code": null,
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
@@ -625,10 +672,22 @@ Fetching individual orders
"type": "entry",
"gate": null,
"device": 2,
"device_id": 1,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"print_logs": [
{
"id": 1,
"type": "badge",
"successful": true,
"datetime": "2017-12-25T12:45:23Z",
"device_id": 1,
"source": "pretixSCAN",
"info": {}
}
],
"answers": [
{
"question": 12,
@@ -664,7 +723,8 @@ Fetching individual orders
"provider": "banktransfer"
}
],
"refunds": []
"refunds": [],
"cancellation_date": null
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -795,7 +855,7 @@ Generating new secrets
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/regenerate_secrets/
Triggers generation of new ``secret`` attributes for both the order and all order positions.
Triggers generation of new ``secret`` and ``web_secret`` attributes for both the order and all order positions.
**Example request**:
@@ -826,7 +886,7 @@ Generating new secrets
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/regenerate_secrets/
Triggers generation of a new ``secret`` attribute for a single order position.
Triggers generation of a new ``secret`` and ``web_secret`` attribute for a single order position.
**Example request**:
@@ -976,8 +1036,8 @@ Creating orders
* ``internal_reference``
* ``vat_id``
* ``vat_id_validated`` (optional) If you need support for reverse charge (rarely the case), you need to check
yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will
trigger reverse charge taxation. Don't forget to set ``is_business`` as well!
yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will
trigger reverse charge taxation. Don't forget to set ``is_business`` as well!
* ``positions``
@@ -1565,6 +1625,7 @@ List of all order positions
"tax_rate": "0.00",
"tax_rule": null,
"tax_value": "0.00",
"tax_code": null,
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"discount": null,
"pseudonymization_id": "MQLJvANO3B",
@@ -1580,10 +1641,22 @@ List of all order positions
"type": "entry",
"gate": null,
"device": 2,
"device_id": 1,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"print_logs": [
{
"id": 1,
"type": "badge",
"successful": true,
"datetime": "2017-12-25T12:45:23Z",
"device_id": 1,
"source": "pretixSCAN",
"info": {}
}
],
"answers": [
{
"question": 12,
@@ -1679,6 +1752,7 @@ Fetching individual positions
"tax_rate": "0.00",
"tax_rule": null,
"tax_value": "0.00",
"tax_code": null,
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
@@ -1694,10 +1768,22 @@ Fetching individual positions
"type": "entry",
"gate": null,
"device": 2,
"device_id": 1,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"print_logs": [
{
"id": 1,
"type": "badge",
"successful": true,
"datetime": "2017-12-25T12:45:23Z",
"device_id": 1,
"source": "pretixSCAN",
"info": {}
}
],
"answers": [
{
"question": 12,
@@ -1794,6 +1880,10 @@ Manipulating individual positions
The endpoints to manage blocks have been added.
.. versionchanged:: 2024.9
The API now supports logging ticket and badge prints.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/
Updates specific fields on an order position. Currently, only the following fields are supported:
@@ -2053,6 +2143,59 @@ Manipulating individual positions
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to update this order position.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/printlog/
Creates a print log, stating that this ticket has been printed.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/printlog/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"datetime": "2024-09-19T13:37:00+02:00",
"source": "pretixPOS",
"type": "badge",
"info": {
"cashier": 1234
}
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/pdf
{
"id": 1234,
"device_id": null,
"datetime": "2024-09-19T13:37:00+02:00",
"source": "pretixPOS",
"type": "badge",
"info": {
"cashier": 1234
}
}
:param organizer: The ``slug`` field of the organizer to create a log for
:param event: The ``slug`` field of the event to create a log for
:param id: The ``id`` field of the order position to create a log for
:statuscode 201: no error
: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.
Changing order contents
-----------------------

View File

@@ -51,7 +51,7 @@ Endpoints
"results": [
{
"identifier": "web",
"name": {
"label": {
"en": "Online shop"
},
"type": "web",
@@ -88,7 +88,7 @@ Endpoints
{
"identifier": "web",
"name": {
"label": {
"en": "Online shop"
},
"type": "web",
@@ -116,7 +116,7 @@ Endpoints
{
"identifier": "api.custom",
"name": {
"label": {
"en": "Custom integration"
},
"type": "api",
@@ -133,7 +133,7 @@ Endpoints
{
"identifier": "api.custom",
"name": {
"label": {
"en": "Custom integration"
},
"type": "api",
@@ -178,7 +178,7 @@ Endpoints
{
"identifier": "web",
"name": {
"label": {
"en": "Online shop"
},
"type": "web",

View File

@@ -313,7 +313,7 @@ Endpoints for event exports
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
Endpoints for organizer exports
---------------------------
-------------------------------
.. http:get:: /api/v1/organizers/(organizer)/scheduled_exports/
@@ -553,4 +553,4 @@ Endpoints for organizer exports
:statuscode 403: The requested organizer does not exist **or** you have no permission to delete this resource.
.. _RFC 5545: https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.3
.. _RFC 5545: https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.3

View File

@@ -1,4 +1,4 @@
.. _`rest-reusablemedia`:
.. _`rest-seats`:
Seats
=====
@@ -249,7 +249,7 @@ Endpoints
"orderposition": null,
"cartposition": null,
"voucher": null
},
}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
@@ -260,3 +260,114 @@ Endpoints
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to change this resource.
:statuscode 404: Seat does not exist; or the endpoint without subevent id was used for event with subevents, or vice versa.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/seats/bulk_block/
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/seats/bulk_block/
Set the ``blocked`` attribute to ``true`` for a large number of seats at once.
You can pass either a list of ``id`` values or a list of ``seat_guid`` values.
You can pass up to 10,000 seats in one request.
The endpoint will return an error if you pass a seat ID that does not exist.
However, it will not return an error if one of the passed seats is already blocked or sold.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_block/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"ids": [12, 45, 56]
}
or
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_block/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"seat_guids": ["6c0e29e5-05d6-421f-99f3-afd01478ecad", "c2899340-e2e7-4d05-8100-000a4b6d7cf4"]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param subevent_id: The ``id`` field of the subevent to modify
:statuscode 200: no error
:statuscode 400: The seat could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to change this resource.
:statuscode 404: Seat does not exist; or the endpoint without subevent id was used for event with subevents, or vice versa.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/seats/bulk_unblock/
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/seats/bulk_unblock/
Set the ``blocked`` attribute to ``false`` for a large number of seats at once.
You can pass either a list of ``id`` values or a list of ``seat_guid`` values.
You can pass up to 10,000 seats in one request.
The endpoint will return an error if you pass a seat ID that does not exist.
However, it will not return an error if one of the passed seat is already unblocked or is sold.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_unblock/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"ids": [12, 45, 56]
}
or
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_unblock/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"seat_guids": ["6c0e29e5-05d6-421f-99f3-afd01478ecad", "c2899340-e2e7-4d05-8100-000a4b6d7cf4"]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param subevent_id: The ``id`` field of the subevent to modify
:statuscode 200: no error
:statuscode 400: The seat could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to change this resource.
:statuscode 404: Seat does not exist; or the endpoint without subevent id was used for event with subevents, or vice versa.

View File

@@ -136,6 +136,7 @@ Endpoints
}
:query page: The page number in case of a multi-page result set, default is 1
:query is_public: If set to ``true``/``false``, only subevents with a matching value of ``is_public`` are returned.
:query active: If set to ``true``/``false``, only events with a matching value of ``active`` are returned.
:query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned.
:query is_past: If set to ``true`` (``false``), only events that are over are (not) returned.
@@ -467,6 +468,7 @@ Endpoints
}
:query page: The page number in case of a multi-page result set, default is 1
:query is_public: If set to ``true``/``false``, only subevents with a matching value of ``is_public`` are returned.
:query active: If set to ``true``/``false``, only events with a matching value of ``active`` are returned.
:query event__live: If set to ``true``/``false``, only events with a matching value of ``live`` on the parent event are returned.
:query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned.

View File

@@ -1,3 +1,8 @@
.. spelling:word-list::
EN16931
DSFinV-K
.. _rest-taxrules:
Tax rules
@@ -18,10 +23,12 @@ id integer Internal ID of
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
code string Codified reason for tax rate (or ``null``), see :ref:`rest-taxcodes`.
price_includes_tax boolean If ``true`` (default), tax is assumed to be included in
the specified product price
eu_reverse_charge boolean If ``true``, EU reverse charge rules are applied. Will
be ignored if custom rules are set.
eu_reverse_charge boolean **DEPRECATED**. If ``true``, EU reverse charge rules
are applied. Will be ignored if custom rules are set.
Use custom rules instead.
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
@@ -41,6 +48,42 @@ custom_rules object Dynamic rules s
The ``custom_rules`` attribute has been added.
.. versionchanged:: 2023.8
The ``code`` attribute has been added.
.. _rest-taxcodes:
Tax codes
---------
For integration with external systems, such as electronic invoicing or bookkeeping systems, the tax rate itself is often
not sufficient information. For example, there could be many different reasons why a sale has a tax rate of 0 %, but the
external handling of the transaction depends on which reason applies. Therefore, pretix allows to supply a codified
reason that allows us to understand what the specific legal situation is. These tax codes are modeled after a combination
of the code lists from the European standard EN16931 and the German standard DSFinV-K.
The following codes are supported:
- ``S/standard`` -- Standard VAT rate in the merchant country
- ``S/reduced`` -- Reduced VAT rate in the merchant country
- ``S/averaged`` -- Averaged VAT rate in the merchant country (known use case: agricultural businesses in Germany)
- ``AE`` -- Reverse charge
- ``O`` -- Services outside of scope of tax
- ``E`` -- Exempt from tax (no reason given)
- ``E/<reason>`` -- Exempt from tax, where ``<reason>`` is one of the codes listed in the `VATEX code list`_ version 5.0.
- ``Z`` -- Zero-rated goods
- ``G`` -- Free export item, VAT not charged
- ``K`` -- VAT exempt for EEA intra-community supply of goods and services
- ``L`` -- Canary Islands general indirect tax
- ``M`` -- Tax for production, services and importation in Ceuta and Melilla
- ``B`` -- Transferred (VAT), only in Italy
The code set in the ``code`` attribute of the tax rule is used by default. When ``eu_reverse_charge`` is active, the
code is replaced by ``AE`` for reverse charge sales and by ``O`` for non-EU sales. When configuring custom rules, you
should actively set a ``"code"`` key on each rule. Only for ``"action": "reverse"`` we automatically apply the code
``AE``, in all other cases the default ``code`` of the tax rule is selected.
Endpoints
---------
@@ -73,6 +116,7 @@ Endpoints
"id": 1,
"name": {"en": "VAT"},
"internal_name": "VAT",
"code": "S/standard",
"rate": "19.00",
"price_includes_tax": true,
"eu_reverse_charge": false,
@@ -114,6 +158,7 @@ Endpoints
"id": 1,
"name": {"en": "VAT"},
"internal_name": "VAT",
"code": "S/standard",
"rate": "19.00",
"price_includes_tax": true,
"eu_reverse_charge": false,
@@ -163,6 +208,7 @@ Endpoints
"id": 1,
"name": {"en": "VAT"},
"internal_name": "VAT",
"code": "S/standard",
"rate": "19.00",
"price_includes_tax": true,
"eu_reverse_charge": false,
@@ -211,6 +257,7 @@ Endpoints
"id": 1,
"name": {"en": "VAT"},
"internal_name": "VAT",
"code": "S/standard",
"rate": "20.00",
"price_includes_tax": true,
"eu_reverse_charge": false,
@@ -257,3 +304,4 @@ Endpoints
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to change it **or** this tax rule cannot be deleted since it is currently in use.
.. _here: https://github.com/pretix/pretix/blob/master/src/pretix/static/schema/tax-rules-custom.schema.json
.. _VATEX code list: https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists

View File

@@ -17,6 +17,7 @@ First, you need to declare that you are using non-essential cookies by respondin
signal:
.. automodule:: pretix.presale.signals
:no-index:
:members: register_cookie_providers
You are expected to return a list of ``CookieProvider`` objects instantiated from the following class:

View File

@@ -14,7 +14,7 @@ Core
:members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types, notification,
item_copy_data, register_sales_channel_types, register_global_settings, quota_availability, global_email_filter,
register_ticket_secret_generators, gift_card_transaction_display,
register_text_placeholders, register_mail_placeholders
register_text_placeholders, register_mail_placeholders, device_info_updated
Order events
""""""""""""
@@ -22,12 +22,14 @@ Order events
There are multiple signals that will be sent out in the ordering cycle:
.. automodule:: pretix.base.signals
:no-index:
:members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text
Check-ins
"""""""""
.. automodule:: pretix.base.signals
:no-index:
:members: checkin_created
@@ -39,18 +41,21 @@ Frontend
.. automodule:: pretix.presale.signals
:no-index:
:members: order_info, order_info_top, order_meta_from_request, order_api_meta_from_request
Request flow
""""""""""""
.. automodule:: pretix.presale.signals
:no-index:
:members: process_request, process_response
Vouchers
""""""""
.. automodule:: pretix.presale.signals
:no-index:
:members: voucher_redeem_info
Backend
@@ -62,24 +67,28 @@ Backend
item_formsets, order_search_filter_q, order_search_forms
.. automodule:: pretix.base.signals
:no-index:
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events, orderposition_blocked_display, customer_created, customer_signed_in
Vouchers
""""""""
.. automodule:: pretix.control.signals
:no-index:
:members: item_forms, voucher_form_class, voucher_form_html, voucher_form_validation
Dashboards
""""""""""
.. automodule:: pretix.control.signals
:no-index:
:members: event_dashboard_widgets, user_dashboard_widgets, event_dashboard_top
Ticket designs
""""""""""""""
.. automodule:: pretix.base.signals
:no-index:
:members: layout_text_variables, layout_image_variables
.. automodule:: pretix.plugins.ticketoutputpdf.signals
@@ -89,4 +98,9 @@ API
---
.. automodule:: pretix.base.signals
:no-index:
:members: validate_event_settings, api_event_settings_fields
.. automodule:: pretix.api.signals
:no-index:
:members: register_device_security_profile

View File

@@ -60,6 +60,7 @@ that we'll provide in this plugin:
Similar signals exist for other objects:
.. automodule:: pretix.base.signals
:no-index:
:members: voucher_import_columns

View File

@@ -84,8 +84,6 @@ convenient to you:
.. automethod:: _register_fonts
.. automethod:: _register_event_fonts
.. automethod:: _on_first_page
.. automethod:: _on_other_page

View File

@@ -86,7 +86,10 @@ Signals
-------
.. automodule:: pretix.base.signals
:no-index:
:members: register_text_placeholders
.. automodule:: pretix.base.signals
:no-index:
:members: register_mail_placeholders

View File

@@ -121,6 +121,7 @@ This will automatically make pretix discover this plugin as soon as it is instal
through ``pip``. During development, you can just run ``python setup.py develop`` inside
your plugin source directory to make it discoverable.
.. _`signals`:
Signals
-------
@@ -153,6 +154,25 @@ in the ``installed`` method:
Note that ``installed`` will *not* be called if the plugin is indirectly activated for an event
because the event is created with settings copied from another event.
.. _`registries`:
Registries
----------
Many signals in pretix are used so that plugins can "register" a class, e.g. a payment provider or a
ticket renderer.
However, for some of them (types of :ref:`Log Entries <logging>`) we use a different method to keep track of them:
In a ``Registry``, classes are collected at application startup, along with a unique key (in case
of LogEntryType, the ``action_type``) as well as which plugin registered them.
To register a class, you can use one of several decorators provided by the Registry object:
.. autoclass:: pretix.base.logentrytypes.LogEntryTypeRegistry
:members: register, new, new_from_dict
All files in which classes are registered need to be imported in the ``AppConfig.ready`` as explained
in `Signals <signals>`_ above.
Views
-----

View File

@@ -20,7 +20,8 @@ To actually log an action, you can just call the ``log_action`` method on your o
.. code-block:: python
order.log_action('pretix.event.order.canceled', user=user, data={})
order.log_action('pretix.event.order.comment', user=user,
data={"new_comment": "Hello, world."})
The positional ``action`` argument should represent the type of action and should be globally unique, we
recommend to prefix it with your package name, e.g. ``paypal.payment.rejected``. The ``user`` argument is
@@ -72,24 +73,101 @@ following ready-to-include template::
{% include "pretixcontrol/includes/logs.html" with obj=order %}
We now need a way to translate the action codes like ``pretix.event.changed`` into human-readable
strings. The :py:attr:`pretix.base.signals.logentry_display` signals allows you to do so. A simple
strings. The :py:attr:`pretix.base.logentrytypes.log_entry_types` :ref:`registry <registries>` allows you to do so. A simple
implementation could look like:
.. code-block:: python
from django.utils.translation import gettext as _
from pretix.base.signals import logentry_display
from pretix.base.logentrytypes import log_entry_types
@log_entry_types.new_from_dict({
'pretix.event.order.comment': _('The order\'s internal comment has been updated to: {new_comment}'),
'pretix.event.order.paid': _('The order has been marked as paid.'),
# ...
})
class CoreOrderLogEntryType(OrderLogEntryType):
pass
Please note that you always need to define your own inherited ``LogEntryType`` class in your plugin. If you would just
register an instance of a ``LogEntryType`` class defined in pretix core, it cannot be automatically detected as belonging
to your plugin, leading to confusing user interface situations.
Customizing log entry display
"""""""""""""""""""""""""""""
The base ``LogEntryType`` classes allow for varying degree of customization in their descendants.
If you want to add another log message for an existing core object (e.g. an :class:`Order <pretix.base.models.Order>`,
:class:`Item <pretix.base.models.Item>`, or :class:`Voucher <pretix.base.models.Voucher>`), you can inherit
from its predefined :class:`LogEntryType <pretix.base.logentrytypes.LogEntryType>`, e.g.
:class:`OrderLogEntryType <pretix.base.logentrytypes.OrderLogEntryType>`, and just specify a new plaintext string.
You can use format strings to insert information from the LogEntry's `data` object as shown in the section above.
If you define a new model object in your plugin, you should make sure proper object links in the user interface are
displayed for it. If your model object belongs logically to a pretix :class:`Event <pretix.base.models.Event>`, you can inherit from :class:`EventLogEntryType <pretix.base.logentrytypes.EventLogEntryType>`,
and set the ``object_link_*`` fields accordingly. ``object_link_viewname`` refers to a django url name, which needs to
accept the arguments `organizer` and `event`, containing the respective slugs, and additional arguments provided by
``object_link_args``. The default implementation of ``object_link_args`` will return an argument named by
````object_link_argname``, with a value of ``content_object.pk`` (the primary key of the model object).
If you want to customize the name displayed for the object (instead of the result of calling ``str()`` on it),
overwrite ``object_link_display_name``.
.. code-block:: python
class ItemLogEntryType(EventLogEntryType):
object_link_wrapper = _('Product {val}')
# link will be generated as reverse('control:event.item', {'organizer': ..., 'event': ..., 'item': item.pk})
object_link_viewname = 'control:event.item'
object_link_argname = 'item'
.. code-block:: python
class OrderLogEntryType(EventLogEntryType):
object_link_wrapper = _('Order {val}')
# link will be generated as reverse('control:event.order', {'organizer': ..., 'event': ..., 'code': order.code})
object_link_viewname = 'control:event.order'
def object_link_args(self, order):
return {'code': order.code}
def object_link_display_name(self, order):
return order.code
To show more sophisticated message strings, e.g. varying the message depending on information from the :class:`LogEntry <pretix.base.models.log.LogEntry>`'s
`data` object, override the `display` method:
.. code-block:: python
@log_entry_types.new()
class PaypalEventLogEntryType(EventLogEntryType):
action_type = 'pretix.plugins.paypal.event'
def display(self, logentry):
event_type = logentry.parsed_data.get('event_type')
text = {
'PAYMENT.SALE.COMPLETED': _('Payment completed.'),
'PAYMENT.SALE.DENIED': _('Payment denied.'),
# ...
}.get(event_type, f"({event_type})")
return _('PayPal reported an event: {}').format(text)
.. automethod:: pretix.base.logentrytypes.LogEntryType.display
If your new model object does not belong to an :class:`Event <pretix.base.models.Event>`, you need to inherit directly from ``LogEntryType`` instead
of ``EventLogEntryType``, providing your own implementation of ``get_object_link_info`` if object links should be
displayed.
.. autoclass:: pretix.base.logentrytypes.LogEntryType
:members: get_object_link_info
@receiver(signal=logentry_display)
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
plains = {
'pretix.event.order.paid': _('The order has been marked as paid.'),
'pretix.event.order.refunded': _('The order has been refunded.'),
'pretix.event.order.canceled': _('The order has been canceled.'),
...
}
if logentry.action_type in plains:
return plains[logentry.action_type]
Sending notifications
---------------------

View File

@@ -1,5 +1,5 @@
KulturPass
=========
==========
.. note::

View File

@@ -158,7 +158,7 @@ expects and - more importantly - supports.
for a sample configuration in an academic context.
Note, that you can have multiple attributes with the same ``friendlyName``
but different ``name``s. This is often used in systems, where the same
but different ``name`` value. This is often used in systems, where the same
information (for example a persons name) is saved in different fields -
for example because one institution is returning SAML 1.0 and other
institutions are returning SAML 2.0 style attributes. Typically, this only

View File

@@ -29,8 +29,8 @@ item_assignments list of objects Products this l
===================================== ========================== =======================================================
Endpoints
---------
Layout endpoints
----------------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/ticketlayouts/
@@ -268,5 +268,75 @@ Endpoints
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
Ticket rendering endpoint
-----------------------------
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/ticketpdfrenderer/render_batch/
With this API call, you can instruct the system to render a set of tickets into one combined PDF file. To specify
which tickets to render, you need to submit a list of "parts". For every part, the following fields are supported:
* ``orderposition`` (``integer``, required): The ID of the order position to render.
* ``override_channel`` (``string``, optional): The sales channel ID to be used for layout selection instead of the
original channel of the order.
* ``override_layout`` (``integer``, optional): The ticket layout ID to be used instead of the auto-selected one.
If your input parameters validate correctly, a ``202 Accepted`` status code is returned.
The body points you to the download URL of the result. Running a ``GET`` request on that result URL will
yield one of the following status codes:
* ``200 OK`` The export succeeded. The body will be your resulting file. Might be large!
* ``409 Conflict`` Your export is still running. The body will be JSON with the structure ``{"status": "running"}``. ``status`` can be ``waiting`` before the task is actually being processed. Please retry, but wait at least one second before you do.
* ``410 Gone`` Running the export has failed permanently. The body will be JSON with the structure ``{"status": "failed", "message": "Error message"}``
* ``404 Not Found`` The export does not exist / is expired.
.. warning:: This endpoint is considered **experimental**. It might change at any time without prior notice.
.. note:: To avoid performance issues, a maximum number of 1000 parts is currently allowed.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/ticketpdfrenderer/render_batch/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"parts": [
{
"orderposition": 55412
},
{
"orderposition": 55412,
"override_channel": "web"
},
{
"orderposition": 55412,
"override_layout": 56
}
]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"download": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/ticketpdfrenderer/download/29891ede-196f-4942-9e26-d055a36e98b8/3f279f13-c198-4137-b49b-9b360ce9fcce/"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:statuscode 202: no error
:statuscode 400: Invalid input options
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
.. _here: https://github.com/pretix/pretix/blob/master/src/pretix/static/schema/pdf-layout.schema.json

View File

@@ -175,7 +175,7 @@ without any special behavior.
Connecting SSO providers (pretix as the SSO client)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To connect an external application as a SSO client, go to "Customer accounts" → "SSO providers" → "Create a new SSO provider"
To connect an external application as a SSO provider, go to "Customer accounts" → "SSO providers" → "Create a new SSO provider"
in your organizer account.
.. thumbnail:: ../../screens/organizer/customer_ssoprovider_add.png

View File

@@ -96,6 +96,18 @@ attribute::
<pretix-widget event="https://pretix.eu/demo/democon/" disable-iframe></pretix-widget>
Always show events info
------------------------
If you want the widget to show the events info such as title, location and frontpage text, you can pass the optional
``display-event-info`` attribute with either a value of ``"false"``, ``"true"`` or ``"auto"`` the latter being the
default if the attribute is not present at all.
Note that any other value than ``"false"`` or ``"auto"`` means ``"true"``::
<pretix-widget event="https://pretix.eu/demo/democon/" display-event-info></pretix-widget>
Pre-selecting a voucher
-----------------------

View File

@@ -29,22 +29,22 @@ dependencies = [
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab
"babel",
"BeautifulSoup4==4.12.*",
"bleach==5.0.*",
"bleach==6.2.*",
"celery==5.4.*",
"chardet==5.2.*",
"cryptography>=3.4.2",
"cryptography>=44.0.0",
"css-inline==0.14.*",
"defusedcsv>=1.1.0",
"Django[argon2]==4.2.*,>=4.2.15",
"django-bootstrap3==24.2",
"django-bootstrap3==24.3",
"django-compressor==4.5.1",
"django-countries==7.6.*",
"django-filter==24.3",
"django-formset-js-improved==0.5.0.3",
"django-formtools==2.5.1",
"django-hierarkey==1.2.*",
"django-hijack==3.6.*",
"django-i18nfield==1.9.*,>=1.9.4",
"django-hijack==3.7.*",
"django-i18nfield==1.10.*",
"django-libsass==0.9",
"django-localflavor==4.0",
"django-markup",
@@ -53,9 +53,9 @@ dependencies = [
"django-phonenumber-field==7.3.*",
"django-redis==5.4.*",
"django-scopes==2.0.*",
"django-statici18n==2.5.*",
"django-statici18n==2.6.*",
"djangorestframework==3.15.*",
"dnspython==2.6.*",
"dnspython==2.7.*",
"drf_ujson2==1.7.*",
"geoip2==4.*",
"importlib_metadata==8.*", # Polyfill, we can probably drop this once we require Python 3.10+
@@ -72,57 +72,55 @@ dependencies = [
"packaging",
"paypalrestsdk==1.13.*",
"paypal-checkout-serversdk==1.0.*",
"PyJWT==2.9.*",
"PyJWT==2.10.*",
"phonenumberslite==8.13.*",
"Pillow==10.4.*",
"Pillow==11.1.*",
"pretix-plugin-build",
"protobuf==5.27.*",
"protobuf==5.29.*",
"psycopg2-binary",
"pycountry",
"pycparser==2.22",
"pycryptodome==3.20.*",
"pypdf==4.3.*",
"pycryptodome==3.21.*",
"pypdf==5.1.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*",
"pytz",
"pytz-deprecation-shim==0.1.*",
"pyuca",
"qrcode==7.4.*",
"redis==5.0.*",
"qrcode==8.0",
"redis==5.2.*",
"reportlab==4.2.*",
"requests==2.31.*",
"sentry-sdk==2.13.*",
"sentry-sdk==2.20.*",
"sepaxml==2.6.*",
"slimit",
"stripe==7.9.*",
"text-unidecode==1.*",
"tlds>=2020041600",
"tqdm==4.*",
"ua-parser==0.18.*",
"ua-parser==1.0.*",
"vat_moss_forked==2020.3.20.0.11.0",
"vobject==0.9.*",
"webauthn==2.2.*",
"zeep==4.2.*"
"webauthn==2.5.*",
"zeep==4.3.*"
]
[project.optional-dependencies]
memcached = ["pylibmc"]
dev = [
"aiohttp==3.10.*",
"aiohttp==3.11.*",
"coverage",
"coveralls",
"fakeredis==2.24.*",
"fakeredis==2.26.*",
"flake8==7.1.*",
"freezegun",
"isort==5.13.*",
"pep8-naming==0.14.*",
"potypo",
"pytest-asyncio",
"pytest-asyncio>=0.24",
"pytest-cache",
"pytest-cov",
"pytest-django==4.*",
"pytest-mock==3.14.*",
"pytest-rerunfailures==14.*",
"pytest-sugar",
"pytest-xdist==3.6.*",
"pytest==8.3.*",

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__ = "2024.9.0.dev0"
__version__ = "2024.12.0.dev0"

View File

@@ -75,11 +75,20 @@ FORMAT_MODULE_PATH = [
'pretix.helpers.formats',
]
CORE_MODULES = {
"pretix.base",
"pretix.presale",
"pretix.control",
"pretix.plugins.checkinlists",
"pretix.plugins.reports",
}
ALL_LANGUAGES = [
('en', _('English')),
('de', _('German')),
('de-informal', _('German (informal)')),
('ar', _('Arabic')),
('eu', _('Basque')),
('ca', _('Catalan')),
('zh-hans', _('Chinese (simplified)')),
('zh-hant', _('Chinese (traditional)')),
@@ -93,6 +102,7 @@ ALL_LANGUAGES = [
('el', _('Greek')),
('id', _('Indonesian')),
('it', _('Italian')),
('ja', _('Japanese')),
('lv', _('Latvian')),
('nb-no', _('Norwegian Bokmål')),
('pl', _('Polish')),
@@ -153,6 +163,12 @@ EXTRA_LANG_INFO = {
'name': 'Portuguese',
'name_local': 'Português',
},
'nb-no': {
'bidi': False,
'code': 'nb-no',
'name': 'Norwegian Bokmal',
'name_local': 'norsk (bokmål)',
},
}
django.conf.locale.LANG_INFO.update(EXTRA_LANG_INFO)
@@ -286,7 +302,7 @@ PILLOW_FORMATS_QUESTIONS_IMAGE = ('PNG', 'GIF', 'JPEG', 'BMP', 'TIFF')
FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT = (
".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
".bmp", ".tif", ".tiff"
".bmp", ".tif", ".tiff", ".ics",
)
FILE_UPLOAD_EXTENSIONS_OTHER = FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT

View File

@@ -27,7 +27,7 @@ from rest_framework import exceptions
from rest_framework.authentication import TokenAuthentication
from pretix.api.auth.devicesecurity import (
DEVICE_SECURITY_PROFILES, FullAccessSecurityProfile,
FullAccessSecurityProfile, get_all_security_profiles,
)
from pretix.base.models import Device
@@ -58,7 +58,8 @@ class DeviceTokenAuthentication(TokenAuthentication):
def authenticate(self, request):
r = super().authenticate(request)
if r and isinstance(r[1], Device):
profile = DEVICE_SECURITY_PROFILES.get(r[1].security_profile, FullAccessSecurityProfile)
profiles = get_all_security_profiles()
profile = profiles.get(r[1].security_profile, FullAccessSecurityProfile())
if not profile.is_allowed(request):
raise exceptions.PermissionDenied('Request denied by device security profile.')
return r

View File

@@ -20,13 +20,40 @@
# <https://www.gnu.org/licenses/>.
#
import logging
from collections import OrderedDict
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from pretix.api.signals import register_device_security_profile
logger = logging.getLogger(__name__)
_ALL_PROFILES = None
class FullAccessSecurityProfile:
class BaseSecurityProfile:
@property
def identifier(self) -> str:
"""
Unique identifier for this profile.
"""
raise NotImplementedError()
@property
def verbose_name(self) -> str:
"""
Human-readable name (can be a ``gettext_lazy`` object).
"""
raise NotImplementedError()
def is_allowed(self, request) -> bool:
"""
Return whether a given request should be allowed.
"""
raise NotImplementedError()
class FullAccessSecurityProfile(BaseSecurityProfile):
identifier = 'full'
verbose_name = _('Full device access (reading and changing orders and gift cards, reading of products and settings)')
@@ -34,7 +61,7 @@ class FullAccessSecurityProfile:
return True
class AllowListSecurityProfile:
class AllowListSecurityProfile(BaseSecurityProfile):
allowlist = ()
def is_allowed(self, request):
@@ -77,6 +104,7 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:order-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('POST', 'api-v1:orderposition-printlog'),
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
@@ -112,6 +140,7 @@ class PretixScanNoSyncNoSearchSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('POST', 'api-v1:orderposition-printlog'),
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
@@ -147,6 +176,7 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('POST', 'api-v1:orderposition-printlog'),
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
@@ -154,87 +184,28 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
)
class PretixPosSecurityProfile(AllowListSecurityProfile):
identifier = 'pretixpos'
verbose_name = _('pretixPOS')
allowlist = (
('GET', 'api-v1:version'),
('GET', 'api-v1:device.eventselection'),
('GET', 'api-v1:idempotency.query'),
('GET', 'api-v1:device.info'),
('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:quota-list'),
('GET', 'api-v1:taxrule-list'),
('GET', 'api-v1:ticketlayout-list'),
('GET', 'api-v1:ticketlayoutitem-list'),
('GET', 'api-v1:badgelayout-list'),
('GET', 'api-v1:badgeitem-list'),
('GET', 'api-v1:voucher-list'),
('GET', 'api-v1:voucher-detail'),
('GET', 'api-v1:order-list'),
('POST', 'api-v1:order-list'),
('GET', 'api-v1:order-detail'),
('DELETE', 'api-v1:orderposition-detail'),
('PATCH', 'api-v1:orderposition-detail'),
('GET', 'api-v1:orderposition-list'),
('GET', 'api-v1:orderposition-answer'),
('GET', 'api-v1:orderposition-pdf_image'),
('POST', 'api-v1:order-mark-canceled'),
('POST', 'api-v1:orderpayment-list'),
('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'),
('POST', 'plugins:pretix_posbackend:order.poslock'),
('DELETE', 'plugins:pretix_posbackend:order.poslock'),
('DELETE', 'api-v1:cartposition-detail'),
('GET', 'api-v1:giftcard-list'),
('POST', 'api-v1:giftcard-transact'),
('PATCH', 'api-v1:giftcard-detail'),
('GET', 'plugins:pretix_posbackend:posclosing-list'),
('POST', 'plugins:pretix_posbackend:posreceipt-list'),
('POST', 'plugins:pretix_posbackend:posclosing-list'),
('POST', 'plugins:pretix_posbackend:posdebugdump-list'),
('POST', 'plugins:pretix_posbackend:posdebuglogentry-list'),
('POST', 'plugins:pretix_posbackend:posdebuglogentry-bulk-create'),
('GET', 'plugins:pretix_posbackend:poscashier-list'),
('POST', 'plugins:pretix_posbackend:stripeterminal.token'),
('POST', 'plugins:pretix_posbackend:stripeterminal.paymentintent'),
('PUT', 'plugins:pretix_posbackend:file.upload'),
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:event.settings'),
('GET', 'plugins:pretix_seating:event.event'),
('GET', 'plugins:pretix_seating:event.event.subevent'),
('GET', 'plugins:pretix_seating:event.plan'),
('GET', 'plugins:pretix_seating:selection.simple'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
('GET', 'api-v1:checkinrpc.search'),
('POST', 'api-v1:reusablemedium-lookup'),
('GET', 'api-v1:reusablemedium-list'),
('POST', 'api-v1:reusablemedium-list'),
)
def get_all_security_profiles():
global _ALL_PROFILES
if _ALL_PROFILES:
return _ALL_PROFILES
types = OrderedDict()
for recv, ret in register_device_security_profile.send(None):
if isinstance(ret, (list, tuple)):
for r in ret:
types[r.identifier] = r
else:
types[ret.identifier] = ret
_ALL_PROFILES = types
return types
DEVICE_SECURITY_PROFILES = {
k.identifier: k() for k in (
FullAccessSecurityProfile,
PretixScanSecurityProfile,
PretixScanNoSyncSecurityProfile,
PretixScanNoSyncNoSearchSecurityProfile,
PretixPosSecurityProfile,
@receiver(register_device_security_profile, dispatch_uid="base_register_default_security_profiles")
def register_default_webhook_events(sender, **kwargs):
return (
FullAccessSecurityProfile(),
PretixScanSecurityProfile(),
PretixScanNoSyncSecurityProfile(),
PretixScanNoSyncNoSearchSecurityProfile(),
)
}

View File

@@ -88,24 +88,32 @@ class SalesChannelMigrationMixin:
}
if data.get("all_sales_channels") and set(data["sales_channels"]) != all_channels:
raise ValidationError(
"If 'all_sales_channels' is set, the legacy attribute 'sales_channels' must not be set or set to "
"the list of all sales channels."
)
raise ValidationError({
"limit_sales_channels": [
"If 'all_sales_channels' is set, the legacy attribute 'sales_channels' must not be set or set to "
"the list of all sales channels."
]
})
if data.get("limit_sales_channels") and set(data["sales_channels"]) != set(data["limit_sales_channels"]):
raise ValidationError(
"If 'limit_sales_channels' is set, the legacy attribute 'sales_channels' must not be set or set to "
"the same list."
)
raise ValidationError({
"limit_sales_channels": [
"If 'limit_sales_channels' is set, the legacy attribute 'sales_channels' must not be set or set to "
"the same list."
]
})
if data["sales_channels"] == all_channels:
if set(data["sales_channels"]) == all_channels:
data["all_sales_channels"] = True
data["limit_sales_channels"] = []
else:
data["all_sales_channels"] = False
data["limit_sales_channels"] = data["sales_channels"]
del data["sales_channels"]
if data.get("all_sales_channels"):
data["limit_sales_channels"] = []
return super().to_internal_value(data)
def to_representation(self, value):

View File

@@ -235,7 +235,7 @@ class CartPositionCreateSerializer(BaseCartPositionCreateSerializer):
return cid
def create(self, validated_data):
validated_data.pop('sales_channel')
validated_data.pop('sales_channel', None)
addons_data = validated_data.pop('addons', None)
bundled_data = validated_data.pop('bundled', None)

View File

@@ -26,31 +26,22 @@ from rest_framework.exceptions import ValidationError
from pretix.api.serializers.event import SubEventSerializer
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.media import MEDIA_TYPES
from pretix.base.models import Checkin, CheckinList, SalesChannel
from pretix.base.models import Checkin, CheckinList
class CheckinListSerializer(I18nAwareModelSerializer):
checkin_count = serializers.IntegerField(read_only=True)
position_count = serializers.IntegerField(read_only=True)
auto_checkin_sales_channels = serializers.SlugRelatedField(
slug_field="identifier",
queryset=SalesChannel.objects.none(),
required=False,
allow_empty=True,
many=True,
)
class Meta:
model = CheckinList
fields = ('id', 'name', 'all_products', 'limit_products', 'subevent', 'checkin_count', 'position_count',
'include_pending', 'auto_checkin_sales_channels', 'allow_multiple_entries', 'allow_entry_after_exit',
'include_pending', 'allow_multiple_entries', 'allow_entry_after_exit',
'rules', 'exit_all_at', 'addon_match', 'ignore_in_statistics', 'consider_tickets_used')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['auto_checkin_sales_channels'].child_relation.queryset = self.context['event'].organizer.sales_channels.all()
if 'subevent' in self.context['request'].query_params.getlist('expand'):
self.fields['subevent'] = SubEventSerializer(read_only=True)

View File

@@ -35,7 +35,7 @@
import logging
from django.conf import settings
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
@@ -43,6 +43,7 @@ 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.exceptions import ValidationError
from rest_framework.fields import ChoiceField, Field
from rest_framework.relations import SlugRelatedField
@@ -436,7 +437,8 @@ class CloneEventSerializer(EventSerializer):
testmode = validated_data.pop('testmode', None)
has_subevents = validated_data.pop('has_subevents', None)
tz = validated_data.pop('timezone', None)
sales_channels = validated_data.pop('sales_channels', None)
all_sales_channels = validated_data.pop('all_sales_channels', None)
limit_sales_channels = validated_data.pop('limit_sales_channels', None)
date_admission = validated_data.pop('date_admission', None)
new_event = super().create({**validated_data, 'plugins': None})
@@ -449,8 +451,9 @@ class CloneEventSerializer(EventSerializer):
new_event.is_public = is_public
if testmode is not None:
new_event.testmode = testmode
if sales_channels is not None:
new_event.sales_channels = sales_channels
if all_sales_channels is not None or limit_sales_channels is not None:
new_event.all_sales_channels = all_sales_channels
new_event.limit_sales_channels.set(limit_sales_channels)
if has_subevents is not None:
new_event.has_subevents = has_subevents
if has_subevents is not None:
@@ -678,8 +681,8 @@ class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
class Meta:
model = TaxRule
fields = ('id', 'name', 'rate', 'price_includes_tax', 'eu_reverse_charge', 'home_country', 'internal_name',
'keep_gross_if_rate_changes', 'custom_rules')
fields = ('id', 'name', 'rate', 'code', 'price_includes_tax', 'eu_reverse_charge', 'home_country',
'internal_name', 'keep_gross_if_rate_changes', 'custom_rules')
class EventSettingsSerializer(SettingsSerializer):
@@ -772,6 +775,7 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_address_company_required',
'invoice_address_beneficiary',
'invoice_address_custom_field',
'invoice_address_custom_field_helptext',
'invoice_name_required',
'invoice_address_not_asked_free',
'invoice_show_payments',
@@ -896,6 +900,7 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
'locale',
'last_order_modification_date',
'show_quota_left',
'show_dates_on_frontpage',
'max_items_per_order',
'attendee_names_asked',
'attendee_names_required',
@@ -915,6 +920,7 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
'invoice_address_company_required',
'invoice_address_beneficiary',
'invoice_address_custom_field',
'invoice_address_custom_field_helptext',
'invoice_name_required',
'invoice_address_not_asked_free',
'invoice_address_from_name',
@@ -986,6 +992,40 @@ def prefetch_by_id(items, qs, id_attr, target_attr):
setattr(item, target_attr, result.get(getattr(item, id_attr)))
class SeatBulkBlockInputSerializer(serializers.Serializer):
ids = serializers.ListField(child=serializers.IntegerField(), required=False, allow_empty=True)
seat_guids = serializers.ListField(child=serializers.CharField(), required=False, allow_empty=True)
def to_internal_value(self, data):
data = super().to_internal_value(data)
if data.get("seat_guids") and data.get("ids"):
raise ValidationError("Please pass either seat_guids or ids.")
if data.get("seat_guids"):
seat_ids = data["seat_guids"]
if len(seat_ids) > 10000:
raise ValidationError({"seat_guids": ["Please do not pass over 10000 seats."]})
seats = {s.seat_guid: s for s in self.context["queryset"].filter(seat_guid__in=seat_ids)}
for s in seat_ids:
if s not in seats:
raise ValidationError({"seat_guids": [f"The seat '{s}' does not exist."]})
elif data.get("ids"):
seat_ids = data["ids"]
if len(seat_ids) > 10000:
raise ValidationError({"ids": ["Please do not pass over 10000 seats."]})
seats = self.context["queryset"].in_bulk(seat_ids)
for s in seat_ids:
if s not in seats:
raise ValidationError({"ids": [f"The seat '{s}' does not exist."]})
else:
raise ValidationError("Please pass either seat_guids or ids.")
return {"seats": seats.values()}
class SeatSerializer(I18nAwareModelSerializer):
orderposition = serializers.IntegerField(source='orderposition_id')
cartposition = serializers.IntegerField(source='cartposition_id')

View File

@@ -19,57 +19,8 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django.conf import settings
from django.core.validators import URLValidator
from i18nfield.fields import I18nCharField, I18nTextField
from i18nfield.strings import LazyI18nString
from rest_framework.exceptions import ValidationError
from rest_framework.fields import Field
from rest_framework.serializers import ModelSerializer
class I18nField(Field):
def __init__(self, **kwargs):
self.allow_blank = kwargs.pop('allow_blank', False)
self.trim_whitespace = kwargs.pop('trim_whitespace', True)
self.max_length = kwargs.pop('max_length', None)
self.min_length = kwargs.pop('min_length', None)
super().__init__(**kwargs)
def to_representation(self, value):
if hasattr(value, 'data'):
if isinstance(value.data, dict):
return value.data
elif value.data is None:
return None
else:
return {
settings.LANGUAGE_CODE: str(value.data)
}
elif value is None:
return None
else:
return {
settings.LANGUAGE_CODE: str(value)
}
def to_internal_value(self, data):
if isinstance(data, str):
return LazyI18nString(data)
elif isinstance(data, dict):
if any([k not in dict(settings.LANGUAGES) for k in data.keys()]):
raise ValidationError('Invalid languages included.')
return LazyI18nString(data)
else:
raise ValidationError('Invalid data type.')
class I18nAwareModelSerializer(ModelSerializer):
pass
I18nAwareModelSerializer.serializer_field_mapping[I18nCharField] = I18nField
I18nAwareModelSerializer.serializer_field_mapping[I18nTextField] = I18nField
from i18nfield.rest_framework import I18nAwareModelSerializer, I18nField
class I18nURLField(I18nField):
@@ -84,3 +35,10 @@ class I18nURLField(I18nField):
else:
URLValidator()(value.data)
return value
__all__ = [
"I18nAwareModelSerializer", # for backwards compatibility
"I18nField", # for backwards compatibility
"I18nURLField",
]

View File

@@ -272,7 +272,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations',
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'allow_waitinglist',
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'hidden_if_item_available_mode', 'allow_waitinglist',
'issue_giftcard', 'meta_data',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
'grant_membership_duration_like_event', 'grant_membership_duration_days',
@@ -369,7 +369,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
require_membership_types = validated_data.pop('require_membership_types', [])
limit_sales_channels = validated_data.pop('limit_sales_channels', [])
item = Item.objects.create(**validated_data)
if limit_sales_channels:
if limit_sales_channels and not validated_data.get('all_sales_channels'):
item.limit_sales_channels.add(*limit_sales_channels)
if picture:
item.picture.save(os.path.basename(picture.name), picture)
@@ -441,7 +441,22 @@ class ItemCategorySerializer(I18nAwareModelSerializer):
class Meta:
model = ItemCategory
fields = ('id', 'name', 'internal_name', 'description', 'position', 'is_addon')
fields = (
'id', 'name', 'internal_name', 'description', 'position',
'is_addon', 'cross_selling_mode',
'cross_selling_condition', 'cross_selling_match_products'
)
def validate(self, data):
data = super().validate(data)
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
if full_data.get('is_addon') and full_data.get('cross_selling_mode'):
raise ValidationError('is_addon and cross_selling_mode are mutually exclusive')
return data
class QuestionOptionSerializer(I18nAwareModelSerializer):

View File

@@ -55,7 +55,7 @@ from pretix.base.models import (
)
from pretix.base.models.orders import (
BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund,
RevokedTicketSecret,
PrintLog, RevokedTicketSecret,
)
from pretix.base.pdf import get_images, get_variables
from pretix.base.services.cart import error_messages
@@ -273,9 +273,35 @@ class AnswerSerializer(I18nAwareModelSerializer):
class CheckinSerializer(I18nAwareModelSerializer):
device_id = serializers.SlugRelatedField(
source='device',
slug_field='device_id',
read_only=True,
)
class Meta:
model = Checkin
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'type')
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'device_id', 'type')
class PrintLogSerializer(serializers.ModelSerializer):
device_id = serializers.SlugRelatedField(
source='device',
slug_field='device_id',
read_only=True,
)
class Meta:
model = PrintLog
fields = (
"id",
"successful",
"datetime",
"source",
"type",
"device_id",
"info",
)
class FailedCheckinSerializer(I18nAwareModelSerializer):
@@ -470,6 +496,7 @@ class OrderPositionListSerializer(serializers.ListSerializer):
class OrderPositionSerializer(I18nAwareModelSerializer):
checkins = CheckinSerializer(many=True, read_only=True)
print_logs = PrintLogSerializer(many=True, read_only=True)
answers = AnswerSerializer(many=True)
downloads = PositionDownloadsField(source='*', read_only=True)
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
@@ -484,12 +511,13 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
'valid_from', 'valid_until', 'blocked', 'voucher_budget_use')
'print_logs', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
'print_logs', 'downloads', 'answers', 'tax_rule', 'tax_code', 'pseudonymization_id', 'pdf_data', 'seat',
'canceled', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use')
read_only_fields = (
'id', 'order', 'positionid', 'item', 'variation', 'price', 'voucher', 'tax_rate', 'tax_value', 'secret',
'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data',
'seat', 'canceled', 'discount', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use'
'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'tax_code', 'pseudonymization_id',
'pdf_data', 'seat', 'canceled', 'discount', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use'
)
def __init__(self, *args, **kwargs):
@@ -571,9 +599,9 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'company', 'street', 'zipcode', 'city', 'country', 'state',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'require_attention',
'order__status', 'order__valid_if_pending', 'order__require_approval', 'valid_from', 'valid_until',
'blocked')
'print_logs', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat',
'require_attention', 'order__status', 'order__valid_if_pending', 'order__require_approval',
'valid_from', 'valid_until', 'blocked')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -615,7 +643,8 @@ class OrderPaymentDateField(serializers.DateField):
class OrderFeeSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderFee
fields = ('id', 'fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule', 'canceled')
fields = ('id', 'fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule',
'tax_code', 'canceled')
class PaymentURLField(serializers.URLField):
@@ -726,12 +755,12 @@ class OrderSerializer(I18nAwareModelSerializer):
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer', 'valid_if_pending', 'api_meta'
'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date'
)
read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer',
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel'
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date'
)
def __init__(self, *args, **kwargs):
@@ -1488,6 +1517,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
pos.answers = answers
pos.pseudonymization_id = "PREVIEW"
pos.checkins = []
pos.print_logs = []
pos_map[pos.positionid] = pos
else:
if pos.voucher:
@@ -1648,7 +1678,7 @@ class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
class Meta:
model = InvoiceLine
fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from',
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_name', 'fee_type',
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_code', 'tax_name', 'fee_type',
'fee_internal_type', 'event_location')

View File

@@ -29,6 +29,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.auth.devicesecurity import get_all_security_profiles
from pretix.api.serializers import AsymmetricField
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import CompatibleJSONField
@@ -297,6 +298,7 @@ class DeviceSerializer(serializers.ModelSerializer):
revoked = serializers.BooleanField(read_only=True)
initialized = serializers.DateTimeField(read_only=True)
initialization_token = serializers.DateTimeField(read_only=True)
security_profile = serializers.ChoiceField(choices=[], required=False, default="full")
class Meta:
model = Device
@@ -306,6 +308,10 @@ class DeviceSerializer(serializers.ModelSerializer):
'os_name', 'os_version', 'software_brand', 'software_version', 'security_profile'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['security_profile'].choices = [(k, v.verbose_name) for k, v in get_all_security_profiles().items()]
class TeamInviteSerializer(serializers.ModelSerializer):
class Meta:

View File

@@ -32,10 +32,17 @@ from pretix.helpers.periodic import minimum_interval
register_webhook_events = Signal()
"""
This signal is sent out to get all known webhook events. Receivers should return an
instance of a subclass of pretix.api.webhooks.WebhookEvent or a list of such
instance of a subclass of ``pretix.api.webhooks.WebhookEvent`` or a list of such
instances.
"""
register_device_security_profile = Signal()
"""
This signal is sent out to get all known device security_profiles. Receivers should
return an instance of a subclass of ``pretix.api.auth.devicesecurity.BaseSecurityProfile``
or a list of such instances.
"""
@receiver(periodic_task)
@scopes_disabled()

View File

@@ -62,6 +62,7 @@ from pretix.base.models import (
CachedFile, Checkin, CheckinList, Device, Event, Order, OrderPosition,
Question, ReusableMedium, RevokedTicketSecret, TeamAPIToken,
)
from pretix.base.models.orders import PrintLog
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
)
@@ -115,7 +116,7 @@ class CheckinListViewSet(viewsets.ModelViewSet):
if 'subevent' in self.request.query_params.getlist('expand'):
qs = qs.prefetch_related(
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
'subevent__seat_category_mappings', 'subevent__meta_values', 'auto_checkin_sales_channels'
'subevent__seat_category_mappings', 'subevent__meta_values',
)
return qs
@@ -142,7 +143,9 @@ class CheckinListViewSet(viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic
def perform_destroy(self, instance):
instance.checkins.all().delete()
instance.log_action(
'pretix.event.checkinlist.deleted',
user=self.request.user,
@@ -365,8 +368,9 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists])
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
@@ -377,7 +381,8 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
Prefetch(
'positions',
OrderPosition.objects.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.all()),
Prefetch('checkins', queryset=Checkin.objects.select_related('device')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
@@ -389,8 +394,9 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists])
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat')

View File

@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>.
#
import base64
import copy
import logging
from cryptography.hazmat.backends.openssl.backend import Backend
@@ -146,6 +147,8 @@ class InitializeView(APIView):
permission_classes = ()
def post(self, request, format=None):
from pretix.base.signals import device_info_updated
serializer = InitializationRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
@@ -160,6 +163,8 @@ class InitializeView(APIView):
if device.revoked:
raise ValidationError({'token': ['This initialization token has been revoked.']})
old_instance = copy.copy(device)
device.initialized = now()
device.hardware_brand = serializer.validated_data.get('hardware_brand')
device.hardware_model = serializer.validated_data.get('hardware_model')
@@ -174,6 +179,10 @@ class InitializeView(APIView):
device.log_action('pretix.device.initialized', data=serializer.validated_data, auth=device)
device_info_updated.send(
sender=Device, old_device=old_instance, new_device=device
)
serializer = DeviceSerializer(device)
return Response(serializer.data)
@@ -182,9 +191,12 @@ class UpdateView(APIView):
authentication_classes = (DeviceTokenAuthentication,)
def post(self, request, format=None):
from pretix.base.signals import device_info_updated
serializer = UpdateRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
device = request.auth
old_instance = copy.copy(device)
device.hardware_brand = serializer.validated_data.get('hardware_brand')
device.hardware_model = serializer.validated_data.get('hardware_model')
device.os_name = serializer.validated_data.get('os_name')
@@ -200,6 +212,10 @@ class UpdateView(APIView):
device.save()
device.log_action('pretix.device.updated', data=serializer.validated_data, auth=device)
device_info_updated.send(
sender=Device, old_device=old_instance, new_device=device
)
serializer = DeviceSerializer(device)
return Response(serializer.data)

View File

@@ -40,6 +40,7 @@ from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import serializers, views, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import (
NotFound, PermissionDenied, ValidationError,
)
@@ -50,8 +51,9 @@ from pretix.api.auth.permission import EventCRUDPermission
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.event import (
CloneEventSerializer, DeviceEventSettingsSerializer, EventSerializer,
EventSettingsSerializer, ItemMetaPropertiesSerializer, SeatSerializer,
SubEventSerializer, TaxRuleSerializer,
EventSettingsSerializer, ItemMetaPropertiesSerializer,
SeatBulkBlockInputSerializer, SeatSerializer, SubEventSerializer,
TaxRuleSerializer,
)
from pretix.api.views import ConditionalListView
from pretix.base.models import (
@@ -237,9 +239,9 @@ class EventViewSet(viewsets.ModelViewSet):
disabled = {m: 'disabled' for m in current_plugins_value if m not in updated_plugins_value}
changed = merge_dicts(enabled, disabled)
for module, action in changed.items():
for module, operation in changed.items():
serializer.instance.log_action(
'pretix.event.plugins.' + action,
'pretix.event.plugins.' + operation,
user=self.request.user,
auth=self.request.auth,
data={'plugin': module}
@@ -297,7 +299,8 @@ class EventViewSet(viewsets.ModelViewSet):
if 'all_sales_channels' in serializer.validated_data and 'sales_channels' in serializer.validated_data:
new_event.all_sales_channels = serializer.validated_data['all_sales_channels']
new_event.limit_sales_channels.set(serializer.validated_data['limit_sales_channels'])
if not new_event.all_sales_channels:
new_event.limit_sales_channels.set(serializer.validated_data['limit_sales_channels'])
else:
serializer.instance.set_defaults()
@@ -370,7 +373,7 @@ with scopes_disabled():
class Meta:
model = SubEvent
fields = ['active', 'event__live']
fields = ['is_public', 'active', 'event__live']
def ends_after_qs(self, queryset, name, value):
expr = Q(
@@ -743,3 +746,24 @@ class SeatViewSet(ConditionalListView, viewsets.ModelViewSet):
auth=self.request.auth,
data={"seats": [serializer.instance.pk]},
)
def bulk_change_blocked(self, blocked):
s = SeatBulkBlockInputSerializer(
data=self.request.data,
context={"event": self.request.event, "queryset": self.get_queryset()},
)
s.is_valid(raise_exception=True)
seats = s.validated_data["seats"]
for seat in seats:
seat.blocked = blocked
Seat.objects.bulk_update(seats, ["blocked"], batch_size=1000)
return Response({})
@action(methods=["POST"], detail=False)
def bulk_block(self, request, *args, **kwargs):
return self.bulk_change_blocked(True)
@action(methods=["POST"], detail=False)
def bulk_unblock(self, request, *args, **kwargs):
return self.bulk_change_blocked(False)

View File

@@ -619,7 +619,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
def availability(self, request, *args, **kwargs):
quota = self.get_object()
qa = QuotaAvailability()
qa = QuotaAvailability(full_results=True)
qa.queue(quota)
qa.compute()
avail = qa.results[quota]

View File

@@ -42,6 +42,7 @@ from pretix.base.models import (
Checkin, GiftCard, GiftCardAcceptance, GiftCardTransaction, OrderPosition,
ReusableMedium,
)
from pretix.base.models.orders import PrintLog
from pretix.helpers import OF_SELF
from pretix.helpers.dicts import merge_dicts
@@ -78,7 +79,8 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
queryset=OrderPosition.objects.select_related(
'order', 'order__event', 'order__event__organizer', 'seat',
).prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.all()),
Prefetch('checkins', queryset=Checkin.objects.select_related('device')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
)
),

View File

@@ -35,6 +35,7 @@ from django.db.models import (
from django.db.models.functions import Coalesce, Concat
from django.http import FileResponse, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils import formats
from django.utils.timezone import make_aware, now
from django.utils.translation import gettext as _
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
@@ -57,7 +58,8 @@ from pretix.api.serializers.order import (
OrderPaymentCreateSerializer, OrderPaymentSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer,
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer,
RevokedTicketSecretSerializer, SimulatedOrderSerializer,
PrintLogSerializer, RevokedTicketSecretSerializer,
SimulatedOrderSerializer,
)
from pretix.api.serializers.orderchange import (
BlockNameSerializer, OrderChangeOperationSerializer,
@@ -66,6 +68,7 @@ from pretix.api.serializers.orderchange import (
OrderPositionInfoPatchSerializer,
)
from pretix.api.views import RichOrderingFilter
from pretix.base.decimal import round_decimal
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Checkin, Device, EventMetaValue,
@@ -75,7 +78,7 @@ from pretix.base.models import (
TeamAPIToken, generate_secret,
)
from pretix.base.models.orders import (
BlockedTicketSecret, QuestionAnswer, RevokedTicketSecret,
BlockedTicketSecret, PrintLog, QuestionAnswer, RevokedTicketSecret,
)
from pretix.base.payment import PaymentException
from pretix.base.pdf import get_images
@@ -96,7 +99,6 @@ from pretix.base.services.tickets import generate
from pretix.base.signals import (
order_modified, order_paid, order_placed, register_ticket_outputs,
)
from pretix.base.templatetags.money import money_filter
from pretix.control.signals import order_search_filter_q
from pretix.helpers import OF_SELF
@@ -214,7 +216,7 @@ class OrderViewSetMixin:
queryset = Order.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('datetime',)
ordering_fields = ('datetime', 'code', 'status', 'last_modified')
ordering_fields = ('datetime', 'code', 'status', 'last_modified', 'cancellation_date')
filterset_class = OrderFilter
lookup_field = 'code'
@@ -258,7 +260,8 @@ class OrderViewSetMixin:
return Prefetch(
'positions',
opq.all().prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.all()),
Prefetch('checkins', queryset=Checkin.objects.select_related('device')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
Prefetch('item', queryset=self.request.event.items.prefetch_related(
Prefetch('meta_values', ItemMetaValue.objects.select_related('property'), to_attr='meta_values_cached')
)),
@@ -279,7 +282,8 @@ class OrderViewSetMixin:
return Prefetch(
'positions',
opq.all().prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.all()),
Prefetch('checkins', queryset=Checkin.objects.select_related('device')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'item', 'variation',
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options', 'question').order_by('question__position')),
'seat',
@@ -598,7 +602,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
order.status in (Order.STATUS_PAID, Order.STATUS_PENDING)
and order.invoices.filter(is_cancellation=True).count() >= order.invoices.filter(is_cancellation=False).count()
)
if self.request.event.settings.get('invoice_generate') not in ('admin', 'user', 'paid', 'True') or not invoice_qualified(order):
if self.request.event.settings.get('invoice_generate') not in ('admin', 'user', 'paid', 'user_paid', 'True') or not invoice_qualified(order):
return Response(
{'detail': _('You cannot generate an invoice for this order.')},
status=status.HTTP_400_BAD_REQUEST
@@ -643,6 +647,8 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
order = self.get_object()
order.secret = generate_secret()
for op in order.all_positions.all():
op.web_secret = generate_secret()
op.save(update_fields=["web_secret"])
assign_ticket_secret(
request.event, op, force_invalidate=True, save=True
)
@@ -1092,7 +1098,8 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
'item_meta_properties',
)
qs = qs.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.all()),
Prefetch('checkins', queryset=Checkin.objects.select_related("device")),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
Prefetch('item', queryset=self.request.event.items.prefetch_related(
Prefetch('meta_values', ItemMetaValue.objects.select_related('property'),
to_attr='meta_values_cached')
@@ -1111,7 +1118,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
Prefetch(
'positions',
qs.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.all()),
Prefetch('checkins', queryset=Checkin.objects.select_related('device')),
Prefetch('item', queryset=self.request.event.items.prefetch_related(
Prefetch('meta_values', ItemMetaValue.objects.select_related('property'),
to_attr='meta_values_cached')
@@ -1135,7 +1142,8 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
)
else:
qs = qs.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.all()),
Prefetch('checkins', queryset=Checkin.objects.select_related("device")),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
).select_related(
'item', 'order', 'order__event', 'order__event__organizer', 'seat'
@@ -1223,9 +1231,10 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
price = get_price(**kwargs)
tr = kwargs.get('tax_rule', kwargs.get('item').tax_rule)
with language(data.get('locale') or self.request.event.settings.locale, self.request.event.settings.region):
gross_formatted = formats.localize_input(round_decimal(price.gross, self.request.event.currency))
return Response({
'gross': price.gross,
'gross_formatted': money_filter(price.gross, self.request.event.currency, hide_currency=True),
'gross_formatted': gross_formatted,
'net': price.net,
'rate': price.rate,
'name': str(price.name),
@@ -1254,6 +1263,34 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
)
return resp
@action(detail=True, url_name="printlog", url_path="printlog", methods=["POST"])
def printlog(self, request, **kwargs):
pos = self.get_object()
serializer = PrintLogSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
with transaction.atomic():
serializer.save(
position=pos,
device=request.auth if isinstance(request.auth, Device) else None,
user=request.user if request.user.is_authenticated else None,
api_token=request.auth if isinstance(request.auth, TeamAPIToken) else None,
oauth_application=request.auth.application if isinstance(request.auth, OAuthAccessToken) else None,
)
pos.order.log_action(
"pretix.event.order.print",
data={
"position": pos.pk,
"positionid": pos.positionid,
**serializer.validated_data,
},
auth=request.auth,
user=request.user,
)
return Response(serializer.data, status=status.HTTP_201_CREATED)
@action(detail=True, url_name='pdf_image', url_path=r'pdf_image/(?P<key>[^/]+)')
def pdf_image(self, request, key, **kwargs):
pos = self.get_object()

View File

@@ -32,13 +32,16 @@
# 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 string
from collections import OrderedDict
from importlib import import_module
from django import forms
from django.conf import settings
from django.contrib.auth import authenticate
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.hashers import check_password, make_password
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _, ngettext
def get_auth_backends():
@@ -149,7 +152,7 @@ class NativeAuthBackend(BaseAuthBackend):
to log in.
"""
d = OrderedDict([
('email', forms.EmailField(label=_("E-mail"), max_length=254,
('email', forms.EmailField(label=_("Email"), max_length=254,
widget=forms.EmailInput(attrs={'autofocus': 'autofocus'}))),
('password', forms.CharField(label=_("Password"), widget=forms.PasswordInput,
max_length=4096)),
@@ -160,3 +163,74 @@ class NativeAuthBackend(BaseAuthBackend):
u = authenticate(request=request, email=form_data['email'].lower(), password=form_data['password'])
if u and u.auth_backend == self.identifier:
return u
class NumericAndAlphabeticPasswordValidator:
def validate(self, password, user=None):
has_numeric = any(c in string.digits for c in password)
has_alpha = any(c in string.ascii_letters for c in password)
if not has_numeric or not has_alpha:
raise ValidationError(
_(
"Your password must contain both numeric and alphabetic characters.",
),
code="password_numeric_and_alphabetic",
)
def get_help_text(self):
return _(
"Your password must contain both numeric and alphabetic characters.",
)
class HistoryPasswordValidator:
def __init__(self, history_length=4):
self.history_length = history_length
def validate(self, password, user=None):
from pretix.base.models import User
if not user or not user.pk or not isinstance(user, User):
return
for hp in user.historic_passwords.order_by("-created")[:self.history_length]:
if check_password(password, hp.password):
raise ValidationError(
ngettext(
"Your password may not be the same as your previous password.",
"Your password may not be the same as one of your %(history_length)s previous passwords.",
self.history_length,
),
code="password_history",
params={"history_length": self.history_length},
)
def get_help_text(self):
return ngettext(
"Your password may not be the same as your previous password.",
"Your password may not be the same as one of your %(history_length)s previous passwords.",
self.history_length,
) % {"history_length": self.history_length}
def password_changed(self, password, user=None):
if not user:
pass
user.historic_passwords.create(password=make_password(password))
user.historic_passwords.filter(
pk__in=user.historic_passwords.order_by("-created")[self.history_length:].values_list("pk", flat=True),
).delete()
def has_event_access_permission(request, permission='can_change_event_settings'):
return (
request.user.is_authenticated and
request.user.has_event_permission(request.organizer, request.event, permission, request=request)
) or (
getattr(request, 'event_access_user', None) and
request.event_access_user.is_authenticated and
request.event_access_user.has_event_permission(request.organizer, request.event, permission,
session_key=request.event_access_parent_session_key)
)

View File

@@ -46,6 +46,8 @@ This module contains utilities for implementing OpenID Connect for customer auth
as well as an OpenID Provider (OP).
"""
pretix_token_endpoint_auth_methods = ['client_secret_basic', 'client_secret_post']
def _urljoin(base, path):
if not base.endswith("/"):
@@ -127,6 +129,16 @@ def oidc_validate_and_complete_config(config):
fields=", ".join(provider_config.get("claims_supported", []))
))
if "token_endpoint_auth_methods_supported" in provider_config:
token_endpoint_auth_methods_supported = provider_config.get("token_endpoint_auth_methods_supported",
["client_secret_basic"])
if not any(x in pretix_token_endpoint_auth_methods for x in token_endpoint_auth_methods_supported):
raise ValidationError(
_(f'No supported Token Endpoint Auth Methods supported: {token_endpoint_auth_methods_supported}').format(
token_endpoint_auth_methods_supported=", ".join(token_endpoint_auth_methods_supported)
)
)
config['provider_config'] = provider_config
return config
@@ -147,6 +159,18 @@ def oidc_authorize_url(provider, state, redirect_uri):
def oidc_validate_authorization(provider, code, redirect_uri):
endpoint = provider.configuration['provider_config']['token_endpoint']
# Wall of shame and RFC ignorant IDPs
if endpoint == 'https://www.linkedin.com/oauth/v2/accessToken':
token_endpoint_auth_method = 'client_secret_post'
else:
token_endpoint_auth_methods = provider.configuration['provider_config'].get(
'token_endpoint_auth_methods_supported', ['client_secret_basic']
)
token_endpoint_auth_method = [
x for x in pretix_token_endpoint_auth_methods if x in token_endpoint_auth_methods
][0]
params = {
# https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
# https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
@@ -154,6 +178,11 @@ def oidc_validate_authorization(provider, code, redirect_uri):
'code': code,
'redirect_uri': redirect_uri,
}
if token_endpoint_auth_method == 'client_secret_post':
params['client_id'] = provider.configuration['client_id']
params['client_secret'] = provider.configuration['client_secret']
try:
resp = requests.post(
endpoint,
@@ -161,7 +190,10 @@ def oidc_validate_authorization(provider, code, redirect_uri):
headers={
'Accept': 'application/json',
},
auth=(provider.configuration['client_id'], provider.configuration['client_secret']),
auth=(
provider.configuration['client_id'],
provider.configuration['client_secret']
) if token_endpoint_auth_method == 'client_secret_basic' else None,
)
resp.raise_for_status()
data = resp.json()

View File

@@ -35,6 +35,7 @@ from django.utils.translation import get_language, gettext_lazy as _
from pretix.base.models import Event
from pretix.base.signals import register_html_mail_renderers
from pretix.base.templatetags.rich_text import markdown_compile_email
from pretix.helpers.format import SafeFormatter, format_map
from pretix.base.services.placeholders import ( # noqa
get_available_placeholders, PlaceholderContext
@@ -68,7 +69,7 @@ def test_custom_smtp_backend(backend: T, from_addr: str) -> None:
class BaseHTMLMailRenderer:
"""
This is the base class for all HTML e-mail renderers.
This is the base class for all HTML email renderers.
"""
def __init__(self, event: Event, organizer=None):
@@ -79,7 +80,7 @@ class BaseHTMLMailRenderer:
return self.identifier
def render(self, plain_body: str, plain_signature: str, subject: str, order=None,
position=None) -> str:
position=None, context=None) -> str:
"""
This method should generate the HTML part of the email.
@@ -88,6 +89,7 @@ class BaseHTMLMailRenderer:
:param subject: The email subject.
:param order: The order if this email is connected to one, otherwise ``None``.
:param position: The order position if this email is connected to one, otherwise ``None``.
:param context: Context to use to render placeholders in the plain body
:return: An HTML string
"""
raise NotImplementedError()
@@ -134,8 +136,10 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
def compile_markdown(self, plaintext):
return markdown_compile_email(plaintext)
def render(self, plain_body: str, plain_signature: str, subject: str, order, position) -> str:
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
body_md = self.compile_markdown(plain_body)
if context:
body_md = format_map(body_md, context=context, mode=SafeFormatter.MODE_RICH_TO_HTML)
htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL,

View File

@@ -64,7 +64,7 @@ class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
_('Customer ID'),
_('SSO provider'),
_('External identifier'),
_('E-mail'),
_('Email'),
_('Phone number'),
_('Full name'),
]

View File

@@ -199,7 +199,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
_('Invoice number'),
_('Date'),
_('Order code'),
_('E-mail address'),
_('Email address'),
_('Invoice type'),
_('Cancellation of'),
_('Language'),
@@ -326,7 +326,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
_('Event start date'),
_('Date'),
_('Order code'),
_('E-mail address'),
_('Email address'),
_('Invoice type'),
_('Cancellation of'),
_('Invoice sender:') + ' ' + _('Name'),

View File

@@ -284,7 +284,7 @@ class OrderListExporter(MultiSheetListExporter):
headers.append(_('Comment'))
headers.append(_('Follow-up date'))
headers.append(_('Positions'))
headers.append(_('E-mail address verified'))
headers.append(_('Email address verified'))
headers.append(_('External customer ID'))
headers.append(_('Payment providers'))
if form_data.get('include_payment_amounts'):
@@ -655,7 +655,7 @@ class OrderListExporter(MultiSheetListExporter):
headers += [
_('Sales channel'),
_('Order locale'),
_('E-mail address verified'),
_('Email address verified'),
_('External customer ID'),
_('Check-in lists'),
_('Payment providers'),

View File

@@ -100,7 +100,7 @@ class MarkdownTextarea(forms.Textarea):
class I18nMarkdownTextarea(i18nfield.forms.I18nTextarea):
def format_output(self, rendered_widgets) -> str:
def format_output(self, rendered_widgets, id_) -> str:
rendered_widgets = rendered_widgets + [
'<div class="i18n-field-markdown-note">%s</div>' % (
_("You can use {markup_name} in this field.").format(
@@ -108,11 +108,11 @@ class I18nMarkdownTextarea(i18nfield.forms.I18nTextarea):
)
)
]
return super().format_output(rendered_widgets)
return super().format_output(rendered_widgets, id_)
class I18nMarkdownTextInput(i18nfield.forms.I18nTextInput):
def format_output(self, rendered_widgets) -> str:
def format_output(self, rendered_widgets, id_) -> str:
rendered_widgets = rendered_widgets + [
'<div class="i18n-field-markdown-note">%s</div>' % (
_("You can use {markup_name} in this field.").format(
@@ -120,7 +120,7 @@ class I18nMarkdownTextInput(i18nfield.forms.I18nTextInput):
)
)
]
return super().format_output(rendered_widgets)
return super().format_output(rendered_widgets, id_)
SECRET_REDACTED = '*****'

View File

@@ -254,7 +254,7 @@ class PasswordRecoverForm(forms.Form):
class PasswordForgotForm(forms.Form):
email = forms.EmailField(
label=_('E-mail'),
label=_('Email'),
)
def __init__(self, *args, **kwargs):

View File

@@ -54,6 +54,7 @@ from django.core.validators import (
from django.db.models import QuerySet
from django.forms import Select, widgets
from django.forms.widgets import FILE_INPUT_CONTRADICTION
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.html import escape
from django.utils.safestring import mark_safe
@@ -77,7 +78,7 @@ from pretix.base.i18n import (
get_babel_locale, get_language_without_region, language,
)
from pretix.base.models import InvoiceAddress, Item, Question, QuestionOption
from pretix.base.models.tax import VAT_ID_COUNTRIES, ask_for_vat_id
from pretix.base.models.tax import ask_for_vat_id
from pretix.base.services.tax import (
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
)
@@ -276,6 +277,10 @@ class NamePartsFormField(forms.MultiValueField):
return value
def name_parts_is_empty(name_parts_dict):
return not any(k != "_scheme" and v for k, v in name_parts_dict.items())
class WrappedPhonePrefixSelect(Select):
initial = None
@@ -602,6 +607,7 @@ class BaseQuestionsForm(forms.Form):
questions = pos.item.questions_to_ask
event = kwargs.pop('event')
self.all_optional = kwargs.pop('all_optional', False)
self.attendee_addresses_required = event.settings.attendee_addresses_required and not self.all_optional
super().__init__(*args, **kwargs)
@@ -676,7 +682,7 @@ class BaseQuestionsForm(forms.Form):
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
add_fields['street'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional,
required=self.attendee_addresses_required,
label=_('Address'),
widget=forms.Textarea(attrs={
'rows': 2,
@@ -686,7 +692,7 @@ class BaseQuestionsForm(forms.Form):
initial=(cartpos.street if cartpos else orderpos.street),
)
add_fields['zipcode'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional,
required=False,
max_length=30,
label=_('ZIP code'),
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
@@ -695,7 +701,7 @@ class BaseQuestionsForm(forms.Form):
}),
)
add_fields['city'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional,
required=False,
label=_('City'),
max_length=255,
initial=(cartpos.city if cartpos else orderpos.city),
@@ -707,11 +713,12 @@ class BaseQuestionsForm(forms.Form):
add_fields['country'] = CountryField(
countries=CachedCountries
).formfield(
required=event.settings.attendee_addresses_required and not self.all_optional,
required=self.attendee_addresses_required,
label=_('Country'),
initial=country,
widget=forms.Select(attrs={
'autocomplete': 'country',
'data-country-information-url': reverse('js_helpers.states'),
}),
)
c = [('', pgettext_lazy('address', 'Select state'))]
@@ -946,9 +953,9 @@ class BaseQuestionsForm(forms.Form):
d = super().clean()
if self.address_validation:
self.cleaned_data = d = validate_address(d, True)
self.cleaned_data = d = validate_address(d, all_optional=not self.attendee_addresses_required)
if d.get('city') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
if d.get('street') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
if not d.get('state'):
self.add_error('state', _('This field is required.'))
@@ -1005,7 +1012,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
'street': forms.Textarea(attrs={
'rows': 2,
'placeholder': _('Street and Number'),
'autocomplete': 'street-address'
'autocomplete': 'street-address',
}),
'beneficiary': forms.Textarea(attrs={'rows': 3}),
'country': forms.Select(attrs={
@@ -1021,13 +1028,25 @@ 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-with-vat-id': ','.join(VAT_ID_COUNTRIES)}),
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
'internal_reference': forms.TextInput,
}
labels = {
'is_business': ''
}
@property
def ask_vat_id(self):
return self.event.settings.invoice_address_vatid
@property
def address_required(self):
return self.event.settings.invoice_address_required
@property
def company_required(self):
return self.event.settings.invoice_address_company_required
def __init__(self, *args, **kwargs):
self.event = event = kwargs.pop('event')
self.request = kwargs.pop('request', None)
@@ -1039,7 +1058,13 @@ class BaseInvoiceAddressForm(forms.ModelForm):
kwargs['initial']['country'] = guess_country_from_request(self.request, self.event)
super().__init__(*args, **kwargs)
if not event.settings.invoice_address_vatid:
# If an individual or company address is acceptable, #id_is_business_0 == individual, _1 == company.
# However, if only company addresses are acceptable, #id_is_business_0 == company and is the only choice
self.fields["company"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_{int(not self.company_required)}'
self.fields["vat_id"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_{int(not self.company_required)}'
if not self.ask_vat_id:
del self.fields['vat_id']
elif self.validate_vat_id:
self.fields['vat_id'].help_text = '<br/>'.join([
@@ -1055,6 +1080,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
])
self.fields['country'].choices = CachedCountries()
self.fields['country'].widget.attrs['data-country-information-url'] = reverse('js_helpers.states')
c = [('', pgettext_lazy('address', 'Select state'))]
fprefix = self.prefix + '-' if self.prefix else ''
@@ -1083,18 +1109,22 @@ class BaseInvoiceAddressForm(forms.ModelForm):
)
self.fields['state'].widget.is_required = True
self.fields['street'].required = False
self.fields['zipcode'].required = False
self.fields['city'].required = False
# 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 ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data:
self.data = self.data.copy()
del self.data[fprefix + 'vat_id']
if not event.settings.invoice_address_required or self.all_optional:
if not self.address_required or self.all_optional:
for k, f in self.fields.items():
f.required = False
f.widget.is_required = False
if 'required' in f.widget.attrs:
del f.widget.attrs['required']
elif event.settings.invoice_address_company_required and not self.all_optional:
elif self.company_required and not self.all_optional:
self.initial['is_business'] = True
self.fields['is_business'].widget = BusinessBooleanRadio(require_business=True)
@@ -1111,17 +1141,18 @@ class BaseInvoiceAddressForm(forms.ModelForm):
label=_('Name'),
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 self.address_required and not self.company_required and not self.all_optional:
if not event.settings.invoice_name_required:
self.fields['name_parts'].widget.attrs['data-required-if'] = '#id_is_business_0'
self.fields['name_parts'].widget.attrs['data-required-if'] = f'#id_{self.add_prefix("is_business")}_0'
self.fields['name_parts'].widget.attrs['data-no-required-attr'] = '1'
self.fields['company'].widget.attrs['data-required-if'] = '#id_is_business_1'
self.fields['company'].widget.attrs['data-required-if'] = f'#id_{self.add_prefix("is_business")}_1'
if not event.settings.invoice_address_beneficiary:
del self.fields['beneficiary']
if event.settings.invoice_address_custom_field:
self.fields['custom_field'].label = event.settings.invoice_address_custom_field
self.fields['custom_field'].help_text = event.settings.invoice_address_custom_field_helptext
else:
del self.fields['custom_field']
@@ -1134,16 +1165,19 @@ class BaseInvoiceAddressForm(forms.ModelForm):
validate_address # local import to prevent impact on startup time
data = self.cleaned_data
if not data.get('is_business'):
data['company'] = ''
data['vat_id'] = ''
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 self.address_validation and self.address_required and not self.all_optional:
if data.get('is_business') and not data.get('company'):
raise ValidationError(_('You need to provide a company name.'))
if not data.get('is_business') and not data.get('name_parts'):
raise ValidationError({"company": _('You need to provide a company name.')})
if not data.get('is_business') and name_parts_is_empty(data.get('name_parts', {})):
raise ValidationError(_('You need to provide your name.'))
if not data.get('street') and not data.get('zipcode') and not data.get('city'):
raise ValidationError({"street": _('This field is required.')})
if 'vat_id' in self.changed_data or not data.get('vat_id'):
self.instance.vat_id_validated = False
@@ -1155,7 +1189,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if all(
not v for k, v in data.items() if k not in ('is_business', 'country', 'name_parts')
) and len(data.get('name_parts', {})) == 1:
) and name_parts_is_empty(data.get('name_parts', {})):
# 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'] = ''

View File

@@ -48,10 +48,10 @@ from pretix.control.forms import SingleLanguageWidget
class UserSettingsForm(forms.ModelForm):
error_messages = {
'duplicate_identifier': _("There already is an account associated with this e-mail address. "
'duplicate_identifier': _("There already is an account associated with this email address. "
"Please choose a different one."),
'pw_current': _("Please enter your current password if you want to change your e-mail "
"address or password."),
'pw_current': _("Please enter your current password if you want to change your email address "
"or password."),
'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."),

View File

@@ -289,7 +289,7 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
def _clean_text(self, text, tags=None):
return self._normalize(bleach.clean(
text,
tags=tags or []
tags=set(tags) if tags else set()
).strip().replace('<br>', '<br />').replace('\n', '<br />\n'))
@@ -388,6 +388,15 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
except:
logger.exception("Can not resize image")
pass
try:
# Valid ZUGFeRD invoices must be compliant with PDF/A-3. pretix-zugferd ensures this by passing them
# through ghost script. Unfortunately, if the logo contains transparency, this will still fail.
# I was unable to figure out a way to fix this in GhostScript, so the easy fix is to remove the
# transparency, as our invoices always have a white background anyways.
ir.remove_transparency()
except:
logger.exception("Can not remove transparency from logo")
pass
canvas.drawImage(ir,
self.logo_left,
self.pagesize[1] - self.logo_height - self.logo_top,
@@ -461,7 +470,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
def _draw_event(self, canvas):
def shorten(txt):
txt = str(txt)
txt = bleach.clean(txt, tags=[]).strip()
txt = bleach.clean(txt, tags=set()).strip()
p = Paragraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
p_size = p.wrap(self.event_width, self.event_height)
@@ -775,7 +784,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
for idx, gross in grossvalue_map.items():
rate, name = idx
if rate == 0:
if rate == 0 and gross == 0:
continue
tax = taxvalue_map[idx]
tdata.append([
@@ -792,7 +801,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
except ValueError:
return localize(val) + ' ' + self.invoice.foreign_currency_display
if len(tdata) > 1 and has_taxes:
if any(rate != 0 and gross != 0 for (rate, name), gross in grossvalue_map.items()) and has_taxes:
colwidths = [a * doc.width for a in (.25, .15, .15, .15, .3)]
table = Table(tdata, colWidths=colwidths, repeatRows=2, hAlign=TA_LEFT)
table.setStyle(TableStyle(tstyledata))

View File

@@ -0,0 +1,165 @@
#
# 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 collections import defaultdict
from typing import Optional
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from pretix.base.signals import EventPluginRegistry
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
if a_map:
if 'href' not in a_map:
a_map['val'] = format_html('<i>{val}</i>', **a_map)
elif is_active:
a_map['val'] = format_html('<a href="{href}">{val}</a>', **a_map)
elif event and plugin_name:
a_map['val'] = format_html(
'<i>{val}</i> <a href="{plugin_href}">'
'<span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span></a>',
**a_map,
errmes=_("The relevant plugin is currently not active. To activate it, click here to go to the plugin settings."),
plugin_href=reverse('control:event.settings.plugins', kwargs={
'organizer': event.organizer.slug,
'event': event.slug,
}) + '#plugin_' + plugin_name,
)
else:
a_map['val'] = format_html(
'<i>{val}</i> <span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span>',
**a_map,
errmes=_("The relevant plugin is currently not active."),
)
return format_html(wrapper, **a_map)
class LogEntryTypeRegistry(EventPluginRegistry):
def __init__(self):
super().__init__({'action_type': lambda o: getattr(o, 'action_type')})
def register(self, *objs):
for obj in objs:
if not isinstance(obj, LogEntryType):
raise TypeError('Entries must be derived from LogEntryType')
if obj.__module__.startswith('pretix.base.'):
raise TypeError('Must not register base classes, only derived ones')
return super().register(*objs)
def new_from_dict(self, data):
"""
Register multiple instance of a `LogEntryType` class with different `action_type`
and plain text strings, as given by the items of the specified data dictionary.
This method is designed to be used as a decorator as follows:
.. code-block:: python
@log_entry_types.new_from_dict({
'pretix.event.item.added': _('The product has been created.'),
'pretix.event.item.changed': _('The product has been changed.'),
# ...
})
class CoreItemLogEntryType(ItemLogEntryType):
# ...
:param data: action types and descriptions
``{"some_action_type": "Plain text description", ...}``
"""
def reg(clz):
for action_type, plain in data.items():
self.register(clz(action_type=action_type, plain=plain))
return clz
return reg
"""
Registry for LogEntry types.
Each entry in this registry should be an instance of a subclass of ``LogEntryType``.
They are annotated with their ``action_type`` and the defining ``plugin``.
"""
log_entry_types = LogEntryTypeRegistry()
class LogEntryType:
"""
Base class for a type of LogEntry, identified by its action_type.
"""
def __init__(self, action_type=None, plain=None):
if action_type:
self.action_type = action_type
if plain:
self.plain = plain
def display(self, logentry, data):
"""
Returns the message to be displayed for a given logentry of this type.
:return: `str` or `LazyI18nString`
"""
if hasattr(self, 'plain'):
plain = str(self.plain)
if '{' in plain:
data = defaultdict(lambda: '?', data)
return plain.format_map(data)
else:
return plain
def get_object_link_info(self, logentry) -> Optional[dict]:
"""
Return information to generate a link to the `content_object` of a given log entry.
Not implemented in the base class, causing the object link to be omitted.
:return: Dictionary with the keys ``href`` (URL to view/edit the object) and
``val`` (text for the anchor element)
"""
pass
def get_object_link(self, logentry):
a_map = self.get_object_link_info(logentry)
return make_link(a_map, self.object_link_wrapper)
object_link_wrapper = '{val}'
def shred_pii(self, logentry):
"""
To be used for shredding personally identified information contained in the data field of a LogEntry of this
type.
"""
raise NotImplementedError
class NoOpShredderMixin:
def shred_pii(self, logentry):
pass
class ClearDataShredderMixin:
def shred_pii(self, logentry):
logentry.data = None

View File

@@ -0,0 +1,147 @@
#
# 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 typing import Optional
from django.urls import reverse
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.models import (
Discount, Item, ItemCategory, Order, Question, Quota, SubEvent, TaxRule,
Voucher,
)
from .logentrytype_registry import ( # noqa
ClearDataShredderMixin, LogEntryType, NoOpShredderMixin, log_entry_types,
make_link, LogEntryTypeRegistry,
)
class EventLogEntryType(LogEntryType):
"""
Base class for any `LogEntry` type whose `content_object` is either an `Event` itself or belongs to a specific `Event`.
"""
def get_object_link_info(self, logentry) -> Optional[dict]:
if hasattr(self, 'object_link_viewname'):
content = logentry.content_object
if not content:
if logentry.content_type_id:
return {
'val': _('(deleted)'),
}
else:
return
if hasattr(self, 'content_type') and not isinstance(content, self.content_type):
return
return {
'href': reverse(self.object_link_viewname, kwargs={
'event': logentry.event.slug,
'organizer': logentry.event.organizer.slug,
**self.object_link_args(content),
}),
'val': self.object_link_display_name(logentry.content_object),
}
def object_link_args(self, content_object):
"""Return the kwargs for the url used in a link to content_object."""
if hasattr(self, 'object_link_argname'):
return {self.object_link_argname: content_object.pk}
return {}
def object_link_display_name(self, content_object):
"""Return the display name to refer to content_object in the user interface."""
return str(content_object)
class OrderLogEntryType(EventLogEntryType):
object_link_wrapper = _('Order {val}')
object_link_viewname = 'control:event.order'
content_type = Order
def object_link_args(self, order):
return {'code': order.code}
def object_link_display_name(self, order):
return order.code
class VoucherLogEntryType(EventLogEntryType):
object_link_wrapper = _('Voucher {val}')
object_link_viewname = 'control:event.voucher'
object_link_argname = 'voucher'
content_type = Voucher
def object_link_display_name(self, voucher):
if len(voucher.code) > 6:
return voucher.code[:6] + ""
return voucher.code
class ItemLogEntryType(EventLogEntryType):
object_link_wrapper = _('Product {val}')
object_link_viewname = 'control:event.item'
object_link_argname = 'item'
content_type = Item
class SubEventLogEntryType(EventLogEntryType):
object_link_wrapper = pgettext_lazy('subevent', 'Date {val}')
object_link_viewname = 'control:event.subevent'
object_link_argname = 'subevent'
content_type = SubEvent
class QuotaLogEntryType(EventLogEntryType):
object_link_wrapper = _('Quota {val}')
object_link_viewname = 'control:event.items.quotas.show'
object_link_argname = 'quota'
content_type = Quota
class DiscountLogEntryType(EventLogEntryType):
object_link_wrapper = _('Discount {val}')
object_link_viewname = 'control:event.items.discounts.edit'
object_link_argname = 'discount'
content_type = Discount
class ItemCategoryLogEntryType(EventLogEntryType):
object_link_wrapper = _('Category {val}')
object_link_viewname = 'control:event.items.categories.edit'
object_link_argname = 'category'
content_type = ItemCategory
class QuestionLogEntryType(EventLogEntryType):
object_link_wrapper = _('Question {val}')
object_link_viewname = 'control:event.items.questions.show'
object_link_argname = 'question'
content_type = Question
class TaxRuleLogEntryType(EventLogEntryType):
object_link_wrapper = _('Tax rule {val}')
object_link_viewname = 'control:event.settings.tax.edit'
object_link_argname = 'rule'
content_type = TaxRule

View File

@@ -36,6 +36,7 @@ import time
import traceback
from django.conf import settings
from django.core.cache import cache
from django.core.management.base import BaseCommand
from django.dispatch.dispatcher import NO_RECEIVERS
@@ -50,17 +51,23 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--tasks', action='store', type=str, help='Only execute the tasks with this name '
'(dotted path, comma separation)')
parser.add_argument('--list-tasks', action='store_true', help='Only list all tasks')
parser.add_argument('--exclude', action='store', type=str, help='Exclude the tasks with this name '
'(dotted path, comma separation)')
def handle(self, *args, **options):
verbosity = int(options['verbosity'])
cache.set("pretix_runperiodic_executed", True, 3600 * 12)
if not periodic_task.receivers or periodic_task.sender_receivers_cache.get(self) is NO_RECEIVERS:
return
for receiver in periodic_task._live_receivers(self):
name = f'{receiver.__module__}.{receiver.__name__}'
if options['list_tasks']:
print(name)
continue
if options.get('tasks'):
if name not in options.get('tasks').split(','):
continue
@@ -74,7 +81,7 @@ class Command(BaseCommand):
try:
r = receiver(signal=periodic_task, sender=self)
except Exception as err:
if isinstance(Exception, KeyboardInterrupt):
if isinstance(err, KeyboardInterrupt):
raise err
if settings.SENTRY_ENABLED:
from sentry_sdk import capture_exception

View File

@@ -37,6 +37,16 @@ class BaseMediaType:
def verbose_name(self):
raise NotImplementedError()
@property
def icon(self):
"""
This can be:
- The name of a Font Awesome icon to represent this channel type.
- The name of a SVG icon file that is resolvable through the static file system. We recommend to design for a size of 18x14 pixels.
"""
return "circle"
def generate_identifier(self, organizer):
if self.medium_created_by_server:
raise NotImplementedError()
@@ -59,6 +69,7 @@ class BaseMediaType:
class BarcodePlainMediaType(BaseMediaType):
identifier = 'barcode'
verbose_name = _('Barcode / QR-Code')
icon = 'qrcode'
medium_created_by_server = True
supports_giftcard = False
supports_orderposition = True
@@ -75,6 +86,7 @@ class BarcodePlainMediaType(BaseMediaType):
class NfcUidMediaType(BaseMediaType):
identifier = 'nfc_uid'
verbose_name = _('NFC UID-based')
icon = 'pretixbase/img/media/nfc_uid.svg'
medium_created_by_server = False
supports_giftcard = True
supports_orderposition = False
@@ -114,6 +126,7 @@ class NfcUidMediaType(BaseMediaType):
class NfcMf0aesMediaType(BaseMediaType):
identifier = 'nfc_mf0aes'
verbose_name = 'NFC Mifare Ultralight AES'
icon = 'pretixbase/img/media/nfc_secure.svg'
medium_created_by_server = False
supports_giftcard = True
supports_orderposition = False

View File

@@ -29,7 +29,7 @@ class Migration(migrations.Migration):
('password', models.CharField(verbose_name='password', max_length=128)),
('last_login', models.DateTimeField(verbose_name='last login', blank=True, null=True)),
('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')),
('email', models.EmailField(max_length=191, blank=True, unique=True, verbose_name='E-mail', null=True,
('email', models.EmailField(max_length=191, blank=True, unique=True, verbose_name='Email', null=True,
db_index=True)),
('givenname', models.CharField(verbose_name='Given name', max_length=255, blank=True, null=True)),
('familyname', models.CharField(verbose_name='Family name', max_length=255, blank=True, null=True)),

View File

@@ -9,6 +9,7 @@ from decimal import Decimal
import django.core.validators
import django.db.models.deletion
import i18nfield.fields
from argon2.exceptions import HashingError
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.db import migrations, models
@@ -25,7 +26,14 @@ def initial_user(apps, schema_editor):
user = User(email='admin@localhost')
user.is_staff = True
user.is_superuser = True
user.password = make_password('admin')
try:
user.password = make_password('admin')
except HashingError:
raise Exception(
"Could not hash password of initial user with argon2id. If this is a system with less than 8 CPU cores, "
"you might need to disable argon2id by setting `passwords_argon2=off` in the `[django]` section of the "
"pretix.cfg configuration file."
)
user.save()
@@ -48,7 +56,7 @@ class Migration(migrations.Migration):
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, unique=True, verbose_name='E-mail')),
('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, unique=True, verbose_name='Email')),
('givenname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Given name')),
('familyname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Family name')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
@@ -232,7 +240,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=16, verbose_name='Order code')),
('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-mail')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')),
('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)),
('datetime', models.DateTimeField(verbose_name='Date')),

View File

@@ -187,7 +187,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=16, verbose_name='Order code')),
('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-mail')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')),
('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)),
('datetime', models.DateTimeField(verbose_name='Date')),

View File

@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')),
('email', models.EmailField(max_length=254, verbose_name='E-mail address')),
('email', models.EmailField(max_length=254, verbose_name='Email address')),
('locale', models.CharField(default='en', max_length=190)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')),
('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')),

View File

@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')),
('email', models.EmailField(max_length=254, verbose_name='E-mail address')),
('email', models.EmailField(max_length=254, verbose_name='Email address')),
('locale', models.CharField(default='en', max_length=190)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')),
('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')),

View File

@@ -163,7 +163,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('action_type', models.CharField(max_length=255)),
('method', models.CharField(choices=[('mail', 'E-mail')], max_length=255)),
('method', models.CharField(choices=[('mail', 'Email')], max_length=255)),
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
to='pretixbase.Event')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),

View File

@@ -21,7 +21,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('action_type', models.CharField(max_length=255)),
('method', models.CharField(choices=[('mail', 'E-mail')], max_length=255)),
('method', models.CharField(choices=[('mail', 'Email')], max_length=255)),
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('enabled', models.BooleanField(default=True)),

View File

@@ -0,0 +1,36 @@
# Generated by Django 4.2.15 on 2024-09-16 15:10
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0269_order_api_meta"),
]
operations = [
migrations.CreateModel(
name="HistoricPassword",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("created", models.DateTimeField(auto_now_add=True)),
("password", models.CharField(max_length=128)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="historic_passwords",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@@ -0,0 +1,32 @@
# Generated by Django 4.2.11 on 2024-05-27 13:19
from django.db import migrations, models
import pretix.base.models.orders
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0270_historicpassword"),
]
operations = [
migrations.AddField(
model_name="itemcategory",
name="cross_selling_condition",
field=models.CharField(null=True, max_length=10),
),
migrations.AddField(
model_name="itemcategory",
name="cross_selling_mode",
field=models.CharField(null=True, max_length=5),
),
migrations.AddField(
model_name="itemcategory",
name="cross_selling_match_products",
field=models.ManyToManyField(
related_name="matched_by_cross_selling_categories", to="pretixbase.item"
),
),
]

View File

@@ -0,0 +1,79 @@
# Generated by Django 4.2.16 on 2024-09-19 10:41
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
("pretixbase", "0271_itemcategory_cross_selling"),
]
operations = [
migrations.CreateModel(
name="PrintLog",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("datetime", models.DateTimeField(default=django.utils.timezone.now)),
("created", models.DateTimeField(auto_now_add=True, null=True)),
("successful", models.BooleanField(default=True)),
("source", models.CharField(max_length=255)),
("type", models.CharField(max_length=255)),
("info", models.JSONField(default=dict)),
(
"api_token",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="pretixbase.teamapitoken",
),
),
(
"device",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="print_logs",
to="pretixbase.device",
),
),
(
"oauth_application",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL,
),
),
(
"position",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="print_logs",
to="pretixbase.orderposition",
),
),
(
"user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="print_logs",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"ordering": ("-datetime",),
},
),
]

View File

@@ -0,0 +1,48 @@
# Generated by Django 4.2.16 on 2024-10-29 15:03
from django.db import migrations
def migrate_autocheckin(apps, schema_editor):
CheckinList = apps.get_model("pretixbase", "CheckinList")
AutoCheckinRule = apps.get_model("autocheckin", "AutoCheckinRule")
for cl in CheckinList.objects.filter(auto_checkin_sales_channels__isnull=False).select_related("event", "event__organizer"):
sales_channels = cl.auto_checkin_sales_channels.all()
all_sales_channels = cl.event.organizer.sales_channels.all()
if "pretix.plugins.autocheckin" not in cl.event.plugins:
cl.event.plugins = cl.event.plugins + ",pretix.plugins.autocheckin"
cl.event.save()
r = AutoCheckinRule.objects.get_or_create(
list=cl,
event=cl.event,
all_products=True,
all_payment_methods=True,
defaults=dict(
mode="placed",
all_sales_channels=len(sales_channels) == len(all_sales_channels),
)
)[0]
if len(sales_channels) != len(all_sales_channels):
r.limit_sales_channels.set(sales_channels)
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0272_printlog"),
("autocheckin", "0001_initial"),
]
operations = [
migrations.RunPython(
migrate_autocheckin,
migrations.RunPython.noop,
),
migrations.RemoveField(
model_name="checkinlist",
name="auto_checkin_sales_channels",
),
]

View File

@@ -0,0 +1,41 @@
# Generated by Django 4.2.8 on 2024-07-02 10:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"pretixbase",
"0273_remove_checkinlist_auto_checkin_sales_channels",
),
]
operations = [
migrations.AddField(
model_name="invoiceline",
name="tax_code",
field=models.CharField(max_length=190, null=True),
),
migrations.AddField(
model_name="orderfee",
name="tax_code",
field=models.CharField(max_length=190, null=True),
),
migrations.AddField(
model_name="orderposition",
name="tax_code",
field=models.CharField(max_length=190, null=True),
),
migrations.AddField(
model_name="taxrule",
name="code",
field=models.CharField(max_length=190, null=True),
),
migrations.AddField(
model_name="transaction",
name="tax_code",
field=models.CharField(max_length=190, null=True),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.17 on 2025-01-13 14:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0274_tax_codes"),
]
operations = [
migrations.AlterField(
model_name="question",
name="valid_number_max",
field=models.DecimalField(decimal_places=6, max_digits=30, null=True),
),
migrations.AlterField(
model_name="question",
name="valid_number_min",
field=models.DecimalField(decimal_places=6, max_digits=30, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2025-01-23 11:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0275_alter_question_valid_number_max_and_more"),
]
operations = [
migrations.AddField(
model_name="item",
name="hidden_if_item_available_mode",
field=models.CharField(default="hide", max_length=16, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2025-01-24 12:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0276_item_hidden_if_item_available_mode"),
]
operations = [
migrations.AlterField(
model_name="item",
name="hidden_if_item_available_mode",
field=models.CharField(default="hide", max_length=16),
),
]

View File

@@ -213,7 +213,13 @@ class DatetimeColumnMixin:
except (ValueError, TypeError):
pass
else:
raise ValidationError(_("Could not parse {value} as a date and time.").format(value=value))
try:
d = datetime.datetime.fromisoformat(value)
if not d.tzinfo:
d = d.replace(tzinfo=self.timezone)
return d
except (ValueError, TypeError):
raise ValidationError(_("Could not parse {value} as a date and time.").format(value=value))
class DecimalColumnMixin:
@@ -250,6 +256,9 @@ class SubeventColumnMixin:
]
def clean(self, value, previous_values):
if not value:
return None
if value in self._subevent_cache:
return self._subevent_cache[value]

View File

@@ -40,8 +40,8 @@ from phonenumbers import SUPPORTED_REGIONS
from pretix.base.forms.questions import guess_country
from pretix.base.modelimport import (
DatetimeColumnMixin, DecimalColumnMixin, ImportColumn, SubeventColumnMixin,
i18n_flat,
BooleanColumnMixin, DatetimeColumnMixin, DecimalColumnMixin, ImportColumn,
SubeventColumnMixin, i18n_flat,
)
from pretix.base.models import (
Customer, ItemVariation, OrderPosition, Question, QuestionAnswer,
@@ -56,7 +56,7 @@ from pretix.base.signals import order_import_columns
class EmailColumn(ImportColumn):
identifier = 'email'
verbose_name = gettext_lazy('E-mail address')
verbose_name = gettext_lazy('Email address')
def clean(self, value, previous_values):
if value:
@@ -322,7 +322,7 @@ class AttendeeNamePart(ImportColumn):
class AttendeeEmail(ImportColumn):
identifier = 'attendee_email'
verbose_name = gettext_lazy('Attendee e-mail address')
verbose_name = gettext_lazy('Attendee email address')
def clean(self, value, previous_values):
if value:
@@ -441,6 +441,7 @@ class Price(DecimalColumnMixin, ImportColumn):
position.price = p.gross
position.tax_rule = position.item.tax_rule
position.tax_rate = p.rate
position.tax_code = p.code
position.tax_value = p.tax
@@ -584,7 +585,7 @@ class SeatColumn(ImportColumn):
raise ValidationError(_('Multiple matching seats were found.'))
except Seat.DoesNotExist:
raise ValidationError(_('No matching seat was found.'))
if not value.is_available() or value in self._cached:
if not value.is_available(sales_channel=previous_values.get('sales_channel')) or value in self._cached:
raise ValidationError(
_('The seat you selected has already been taken. Please select a different seat.'))
self._cached.add(value)
@@ -604,6 +605,22 @@ class Comment(ImportColumn):
order.comment = value or ''
class CheckinAttentionColumn(BooleanColumnMixin, ImportColumn):
identifier = 'checkin_attention'
verbose_name = gettext_lazy('Requires special attention')
def assign(self, value, order, position, invoice_address, **kwargs):
order.checkin_attention = value
class CheckinTextColumn(ImportColumn):
identifier = 'checkin_text'
verbose_name = gettext_lazy('Check-in text')
def assign(self, value, order, position, invoice_address, **kwargs):
order.checkin_text = value
class QuestionColumn(ImportColumn):
def __init__(self, event, q):
self.q = q
@@ -737,11 +754,13 @@ def get_order_import_columns(event):
AttendeeState(event),
Price(event),
Secret(event),
Saleschannel(event),
SeatColumn(event),
ValidFrom(event),
ValidUntil(event),
Locale(event),
Saleschannel(event),
CheckinAttentionColumn(event),
CheckinTextColumn(event),
Expires(event),
Comment(event),
]

View File

@@ -241,7 +241,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
REQUIRED_FIELDS = []
email = models.EmailField(unique=True, db_index=True, null=True, blank=True,
verbose_name=_('E-mail'), max_length=190)
verbose_name=_('Email'), max_length=190)
fullname = models.CharField(max_length=255, blank=True, null=True,
verbose_name=_('Full name'))
is_active = models.BooleanField(default=True,
@@ -571,13 +571,23 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
def get_session_auth_hash(self):
"""
Return an HMAC that needs to
Return an HMAC that needs to be the same throughout the session, used e.g. for forced
logout after every password change.
"""
return self._get_session_auth_hash(secret=settings.SECRET_KEY)
def get_session_auth_fallback_hash(self):
for fallback_secret in settings.SECRET_KEY_FALLBACKS:
yield self._get_session_auth_hash(secret=fallback_secret)
def _get_session_auth_hash(self, secret):
"""
"""
key_salt = "pretix.base.models.User.get_session_auth_hash"
payload = self.password
payload += self.email
payload += self.session_token
return salted_hmac(key_salt, payload).hexdigest()
return salted_hmac(key_salt, payload, secret=secret).hexdigest()
def update_session_token(self):
self.session_token = generate_session_token()
@@ -654,3 +664,9 @@ class WebAuthnDevice(Device):
@property
def webauthnpubkey(self):
return websafe_decode(self.pub_key)
class HistoricPassword(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="historic_passwords")
created = models.DateTimeField(auto_now_add=True)
password = models.CharField(verbose_name=_("Password"), max_length=128)

View File

@@ -99,14 +99,6 @@ class CheckinList(LoggedModel):
verbose_name=_('Automatically check out everyone at'),
null=True, blank=True
)
auto_checkin_sales_channels = models.ManyToManyField(
"SalesChannel",
verbose_name=_('Sales channels to automatically check in'),
help_text=_('This option is deprecated and will be removed in the next months. As a replacement, our new plugin '
'"Auto check-in" can be used. When we remove this option, we will automatically migrate your event '
'to use the new plugin.'),
blank=True,
)
rules = models.JSONField(default=dict, blank=True)
objects = ScopedManager(organizer='event__organizer')
@@ -141,7 +133,7 @@ class CheckinList(LoggedModel):
return self.positions_query(ignore_status=False)
@scopes_disabled()
def positions_inside_query(self, ignore_status=False, at_time=None):
def _filter_positions_inside(self, qs, at_time=None):
if at_time is None:
c_q = []
else:
@@ -149,7 +141,7 @@ class CheckinList(LoggedModel):
if "postgresql" not in settings.DATABASES["default"]["ENGINE"]:
# Use a simple approach that works on all databases
qs = self.positions_query(ignore_status=ignore_status).annotate(
qs = qs.annotate(
last_entry=Subquery(
Checkin.objects.filter(
*c_q,
@@ -202,7 +194,7 @@ class CheckinList(LoggedModel):
.values("position_id", "type", "datetime", "cnt_exists_after")
.query.sql_with_params()
)
return self.positions_query(ignore_status=ignore_status).filter(
return qs.filter(
pk__in=RawSQL(
f"""
SELECT "position_id"
@@ -214,6 +206,10 @@ class CheckinList(LoggedModel):
)
)
@scopes_disabled()
def positions_inside_query(self, ignore_status=False, at_time=None):
return self._filter_positions_inside(self.positions_query(ignore_status=ignore_status), at_time=at_time)
@property
def positions_inside(self):
return self.positions_inside_query(None)

View File

@@ -91,7 +91,7 @@ class Customer(LoggedModel):
),
],
)
email = models.EmailField(db_index=True, null=True, blank=False, verbose_name=_('E-mail'), max_length=190)
email = models.EmailField(db_index=True, null=True, blank=False, verbose_name=_('Email'), max_length=190)
phone = PhoneNumberField(null=True, blank=True, verbose_name=_('Phone number'))
password = models.CharField(verbose_name=_('Password'), max_length=128)
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
@@ -219,13 +219,24 @@ class Customer(LoggedModel):
return is_password_usable(self.password)
def get_session_auth_hash(self):
"""
Return an HMAC that needs to be the same throughout the session, used e.g. for forced
logout after every password change.
"""
return self._get_session_auth_hash(secret=settings.SECRET_KEY)
def get_session_auth_fallback_hash(self):
for fallback_secret in settings.SECRET_KEY_FALLBACKS:
yield self._get_session_auth_hash(secret=fallback_secret)
def _get_session_auth_hash(self, secret):
"""
Return an HMAC of the password field.
"""
key_salt = "pretix.base.models.customers.Customer.get_session_auth_hash"
payload = self.password
payload += self.email
return salted_hmac(key_salt, payload).hexdigest()
return salted_hmac(key_salt, payload, secret=secret).hexdigest()
def get_email_context(self):
from pretix.base.settings import get_name_parts_localized
@@ -381,7 +392,7 @@ class CustomerSSOClient(LoggedModel):
SCOPE_CHOICES = (
('openid', _('OpenID Connect access (required)')),
('profile', _('Profile data (name, addresses)')),
('email', _('E-mail address')),
('email', _('Email address')),
('phone', _('Phone number')),
)

View File

@@ -28,7 +28,6 @@ from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled
from pretix.api.auth.devicesecurity import DEVICE_SECURITY_PROFILES
from pretix.base.models import LoggedModel
@@ -161,7 +160,6 @@ class Device(LoggedModel):
)
security_profile = models.CharField(
max_length=190,
choices=[(k, v.verbose_name) for k, v in DEVICE_SECURITY_PROFILES.items()],
default='full',
null=True,
blank=False

View File

@@ -20,11 +20,11 @@
# <https://www.gnu.org/licenses/>.
#
from collections import defaultdict
from collections import defaultdict, namedtuple
from decimal import Decimal
from itertools import groupby
from math import ceil
from typing import Dict, Optional, Tuple
from math import ceil, inf
from typing import Dict
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
@@ -36,6 +36,8 @@ from django_scopes import ScopedManager
from pretix.base.decimal import round_decimal
from pretix.base.models.base import LoggedModel
PositionInfo = namedtuple('PositionInfo', ['item_id', 'subevent_id', 'line_price_gross', 'is_addon_to', 'voucher_discount'])
class Discount(LoggedModel):
SUBEVENT_MODE_MIXED = 'mixed'
@@ -245,22 +247,26 @@ class Discount(LoggedModel):
return False
return True
def _apply_min_value(self, positions, condition_idx_group, benefit_idx_group, result):
if self.condition_min_value and sum(positions[idx][2] for idx in condition_idx_group) < self.condition_min_value:
def _apply_min_value(self, positions, condition_idx_group, benefit_idx_group, result, collect_potential_discounts, subevent_id):
if self.condition_min_value and sum(positions[idx].line_price_gross for idx in condition_idx_group) < self.condition_min_value:
return
if self.condition_min_count or self.benefit_only_apply_to_cheapest_n_matches:
raise ValueError('Validation invariant violated.')
for idx in benefit_idx_group:
previous_price = positions[idx][2]
previous_price = positions[idx].line_price_gross
new_price = round_decimal(
previous_price * (Decimal('100.00') - self.benefit_discount_matching_percent) / Decimal('100.00'),
self.event.currency,
)
result[idx] = new_price
def _apply_min_count(self, positions, condition_idx_group, benefit_idx_group, result):
if collect_potential_discounts is not None:
for idx in condition_idx_group:
collect_potential_discounts[idx] = [(self, inf, -1, subevent_id)]
def _apply_min_count(self, positions, condition_idx_group, benefit_idx_group, result, collect_potential_discounts, subevent_id):
if len(condition_idx_group) < self.condition_min_count:
return
@@ -268,23 +274,53 @@ class Discount(LoggedModel):
raise ValueError('Validation invariant violated.')
if self.benefit_only_apply_to_cheapest_n_matches:
if not self.condition_min_count:
raise ValueError('Validation invariant violated.')
condition_idx_group = sorted(condition_idx_group, key=lambda idx: (positions[idx][2], -idx)) # sort by line_price
benefit_idx_group = sorted(benefit_idx_group, key=lambda idx: (positions[idx][2], -idx)) # sort by line_price
# sort by line_price
condition_idx_group = sorted(condition_idx_group, key=lambda idx: (positions[idx].line_price_gross, -idx))
benefit_idx_group = sorted(benefit_idx_group, key=lambda idx: (positions[idx].line_price_gross, -idx))
# Prevent over-consuming of items, i.e. if our discount is "buy 2, get 1 free", we only
# want to match multiples of 3
n_groups = min(len(condition_idx_group) // self.condition_min_count, ceil(len(benefit_idx_group) / self.benefit_only_apply_to_cheapest_n_matches))
# how many discount applications are allowed according to condition products in cart
possible_applications_cond = len(condition_idx_group) // self.condition_min_count
# how many discount applications are possible according to benefitting products in cart
possible_applications_benefit = ceil(len(benefit_idx_group) / self.benefit_only_apply_to_cheapest_n_matches)
n_groups = min(possible_applications_cond, possible_applications_benefit)
consume_idx = condition_idx_group[:n_groups * self.condition_min_count]
benefit_idx = benefit_idx_group[:n_groups * self.benefit_only_apply_to_cheapest_n_matches]
if collect_potential_discounts is not None:
if n_groups * self.benefit_only_apply_to_cheapest_n_matches > len(benefit_idx_group):
# partially used discount ("for each 1 ticket you buy, get 50% on 2 t-shirts", cart content: 1 ticket
# but only 1 t-shirt) -> 1 shirt definitiv potential discount
for idx in consume_idx:
collect_potential_discounts[idx] = [
(self, n_groups * self.benefit_only_apply_to_cheapest_n_matches - len(benefit_idx_group), -1, subevent_id)
]
if possible_applications_cond * self.benefit_only_apply_to_cheapest_n_matches > len(benefit_idx_group):
# unused discount ("for each 1 ticket you buy, get 50% on 2 t-shirts", cart content: 1 ticket
# but 0 t-shirts) -> 2 shirt maybe potential discount (if the 1 ticket is not consumed by a later discount)
for i, idx in enumerate(condition_idx_group[
n_groups * self.condition_min_count:
possible_applications_cond * self.condition_min_count
]):
collect_potential_discounts[idx] += [
(self, self.benefit_only_apply_to_cheapest_n_matches, i // self.condition_min_count, subevent_id)
]
else:
consume_idx = condition_idx_group
benefit_idx = benefit_idx_group
if collect_potential_discounts is not None:
for idx in consume_idx:
collect_potential_discounts[idx] = [(self, inf, -1, subevent_id)]
for idx in benefit_idx:
previous_price = positions[idx][2]
previous_price = positions[idx].line_price_gross
new_price = round_decimal(
previous_price * (Decimal('100.00') - self.benefit_discount_matching_percent) / Decimal('100.00'),
self.event.currency,
@@ -292,15 +328,16 @@ class Discount(LoggedModel):
result[idx] = new_price
for idx in consume_idx:
result.setdefault(idx, positions[idx][2])
result.setdefault(idx, positions[idx].line_price_gross)
def apply(self, positions: Dict[int, Tuple[int, Optional[int], Decimal, bool, Decimal]]) -> Dict[int, Decimal]:
def apply(self, positions: Dict[int, PositionInfo],
collect_potential_discounts=None) -> Dict[int, Decimal]:
"""
Tries to apply this discount to a cart
:param positions: Dictionary mapping IDs to tuples of the form
``(item_id, subevent_id, line_price_gross, is_addon_to, voucher_discount)``.
:param positions: Dictionary mapping IDs to PositionInfo tuples.
Bundled positions may not be included.
:param collect_potential_discounts: For detailed description, see pretix.base.services.pricing.apply_discounts
:return: A dictionary mapping keys from the input dictionary to new prices. All positions
contained in this dictionary are considered "consumed" and should not be considered
@@ -342,13 +379,13 @@ class Discount(LoggedModel):
if self.subevent_mode == self.SUBEVENT_MODE_MIXED: # also applies to non-series events
if self.condition_min_count:
self._apply_min_count(positions, condition_candidates, benefit_candidates, result)
self._apply_min_count(positions, condition_candidates, benefit_candidates, result, collect_potential_discounts, None)
else:
self._apply_min_value(positions, condition_candidates, benefit_candidates, result)
self._apply_min_value(positions, condition_candidates, benefit_candidates, result, collect_potential_discounts, None)
elif self.subevent_mode == self.SUBEVENT_MODE_SAME:
def key(idx):
return positions[idx][1] or 0 # subevent_id
return positions[idx].subevent_id or 0
# Build groups of candidates with the same subevent, then apply our regular algorithm
# to each group
@@ -357,11 +394,11 @@ class Discount(LoggedModel):
candidate_groups = [(k, list(g)) for k, g in _groups]
for subevent_id, g in candidate_groups:
benefit_g = [idx for idx in benefit_candidates if positions[idx][1] == subevent_id]
benefit_g = [idx for idx in benefit_candidates if positions[idx].subevent_id == subevent_id]
if self.condition_min_count:
self._apply_min_count(positions, g, benefit_g, result)
self._apply_min_count(positions, g, benefit_g, result, collect_potential_discounts, subevent_id)
else:
self._apply_min_value(positions, g, benefit_g, result)
self._apply_min_value(positions, g, benefit_g, result, collect_potential_discounts, subevent_id)
elif self.subevent_mode == self.SUBEVENT_MODE_DISTINCT:
if self.condition_min_value or not self.benefit_same_products:
@@ -377,9 +414,9 @@ class Discount(LoggedModel):
# Build a list of subevent IDs in descending order of frequency
subevent_to_idx = defaultdict(list)
for idx, p in positions.items():
subevent_to_idx[p[1]].append(idx)
subevent_to_idx[p.subevent_id].append(idx)
for v in subevent_to_idx.values():
v.sort(key=lambda idx: positions[idx][2])
v.sort(key=lambda idx: positions[idx].line_price_gross)
subevent_order = sorted(list(subevent_to_idx.keys()), key=lambda s: len(subevent_to_idx[s]), reverse=True)
# Build groups of exactly condition_min_count distinct subevents
@@ -394,7 +431,7 @@ class Discount(LoggedModel):
l = [ll for ll in l if ll in condition_candidates and ll not in current_group]
if cardinality and len(l) != cardinality:
continue
if se not in {positions[idx][1] for idx in current_group}:
if se not in {positions[idx].subevent_id for idx in current_group}:
candidates += l
cardinality = len(l)
@@ -403,7 +440,7 @@ class Discount(LoggedModel):
# Sort the list by prices, then pick one. For "buy 2 get 1 free" we apply a "pick 1 from the start
# and 2 from the end" scheme to optimize price distribution among groups
candidates = sorted(candidates, key=lambda idx: positions[idx][2])
candidates = sorted(candidates, key=lambda idx: positions[idx].line_price_gross)
if len(current_group) < (self.benefit_only_apply_to_cheapest_n_matches or 0):
candidate = candidates[0]
else:
@@ -415,14 +452,14 @@ class Discount(LoggedModel):
if len(current_group) >= max(self.condition_min_count, 1):
candidate_groups.append(current_group)
for c in current_group:
subevent_to_idx[positions[c][1]].remove(c)
subevent_to_idx[positions[c].subevent_id].remove(c)
current_group = []
# Distribute "leftovers"
for se in subevent_order:
if subevent_to_idx[se]:
for group in candidate_groups:
if se not in {positions[idx][1] for idx in group}:
if se not in {positions[idx].subevent_id for idx in group}:
group.append(subevent_to_idx[se].pop())
if not subevent_to_idx[se]:
break
@@ -432,6 +469,8 @@ class Discount(LoggedModel):
positions,
[idx for idx in g if idx in condition_candidates],
[idx for idx in g if idx in benefit_candidates],
result
result,
None,
None
)
return result

View File

@@ -823,6 +823,9 @@ class Event(EventMixin, LoggedModel):
self.save()
self.log_action('pretix.object.cloned', data={'source': other.slug, 'source_id': other.pk})
if hasattr(other, 'alternative_domain_assignment'):
other.alternative_domain_assignment.domain.event_assignments.create(event=self)
if not self.all_sales_channels:
self.limit_sales_channels.set(
self.organizer.sales_channels.filter(
@@ -870,10 +873,12 @@ class Event(EventMixin, LoggedModel):
for i in Item.objects.filter(event=other).prefetch_related(
'variations', 'limit_sales_channels', 'require_membership_types',
'variations__limit_sales_channels', 'variations__require_membership_types',
'matched_by_cross_selling_categories',
):
vars = list(i.variations.all())
require_membership_types = list(i.require_membership_types.all())
limit_sales_channels = list(i.limit_sales_channels.all())
matched_by_cross_selling_categories = list(i.matched_by_cross_selling_categories.all())
item_map[i.pk] = i
i.pk = None
i.event = self
@@ -911,6 +916,9 @@ class Event(EventMixin, LoggedModel):
if not v.all_sales_channels:
v.limit_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in limit_sales_channels]))
if matched_by_cross_selling_categories:
i.matched_by_cross_selling_categories.set([category_map[c.pk] for c in matched_by_cross_selling_categories])
for i in self.items.filter(hidden_if_item_available__isnull=False):
i.hidden_if_item_available = item_map[i.hidden_if_item_available_id]
i.save()
@@ -1019,10 +1027,9 @@ class Event(EventMixin, LoggedModel):
checkin_list_map = {}
for cl in other.checkin_lists.filter(subevent__isnull=True).prefetch_related(
'limit_products', 'auto_checkin_sales_channels'
'limit_products'
):
items = list(cl.limit_products.all())
auto_checkin_sales_channels = list(cl.auto_checkin_sales_channels.all())
checkin_list_map[cl.pk] = cl
cl.pk = None
cl._prefetched_objects_cache = {}
@@ -1034,8 +1041,6 @@ class Event(EventMixin, LoggedModel):
cl.log_action('pretix.object.cloned')
for i in items:
cl.limit_products.add(item_map[i.pk])
if auto_checkin_sales_channels:
cl.auto_checkin_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in auto_checkin_sales_channels]))
if other.seating_plan:
if other.seating_plan.organizer_id == self.organizer_id:

View File

@@ -362,6 +362,7 @@ class InvoiceLine(models.Model):
tax_value = models.DecimalField(max_digits=13, decimal_places=2, default=Decimal('0.00'))
tax_rate = models.DecimalField(max_digits=7, decimal_places=2, default=Decimal('0.00'))
tax_name = models.CharField(max_length=190)
tax_code = models.CharField(max_length=190, null=True, blank=True)
subevent = models.ForeignKey('SubEvent', null=True, blank=True, on_delete=models.PROTECT)
event_date_from = models.DateTimeField(null=True)
event_date_to = models.DateTimeField(null=True)

View File

@@ -63,14 +63,13 @@ from django_countries.fields import Country
from django_scopes import ScopedManager
from i18nfield.fields import I18nCharField, I18nTextField
from pretix.base.media import MEDIA_TYPES
from pretix.base.models import Event, SubEvent
from pretix.base.models.base import LoggedModel
from pretix.base.models.fields import MultiStringField
from pretix.base.models.tax import TaxedPrice
from pretix.base.timemachine import time_machine_now
from ...helpers.images import ImageSizeValidator
from ..media import MEDIA_TYPES
from .event import Event, SubEvent
from pretix.helpers.images import ImageSizeValidator
class ItemCategory(LoggedModel):
@@ -111,6 +110,33 @@ class ItemCategory(LoggedModel):
'only be bought in combination with a product that has this category configured as a possible '
'source for add-ons.')
)
CROSS_SELLING_MODES = (
(None, _('Normal category')),
('both', _('Normal + cross-selling category')),
('only', _('Cross-selling category')),
)
cross_selling_mode = models.CharField(
choices=CROSS_SELLING_MODES,
null=True,
max_length=5
)
CROSS_SELLING_CONDITION = (
('always', _('Always show in cross-selling step')),
('discounts', _('Only show products that qualify for a discount according to discount rules')),
('products', _('Only show if the cart contains one of the following products')),
)
cross_selling_condition = models.CharField(
verbose_name=_("Cross-selling condition"),
choices=CROSS_SELLING_CONDITION,
null=True,
max_length=10,
)
cross_selling_match_products = models.ManyToManyField(
'pretixbase.Item',
blank=True,
verbose_name=_("Cross-selling condition products"),
related_name="matched_by_cross_selling_categories",
)
class Meta:
verbose_name = _("Product category")
@@ -119,19 +145,31 @@ class ItemCategory(LoggedModel):
def __str__(self):
name = self.internal_name or self.name
if self.is_addon:
return _('{category} (Add-On products)').format(category=str(name))
if self.category_type != 'normal':
return _('{category} ({category_type})').format(category=str(name),
category_type=self.get_category_type_display())
return str(name)
def get_category_type_display(self):
if self.is_addon:
return _('Add-On products')
return _('Add-on category')
elif self.cross_selling_mode:
return self.get_cross_selling_mode_display()
else:
return None
return _('Normal category')
@property
def category_type(self):
return 'addon' if self.is_addon else 'normal'
return 'addon' if self.is_addon else self.cross_selling_mode or 'normal'
@category_type.setter
def category_type(self, new_value):
if new_value == 'addon':
self.is_addon = True
self.cross_selling_mode = None
else:
self.is_addon = False
self.cross_selling_mode = None if new_value == 'normal' else new_value
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
@@ -270,7 +308,7 @@ class SubEventItemVariation(models.Model):
return True
def filter_available(qs, channel='web', voucher=None, allow_addons=False):
def filter_available(qs, channel='web', voucher=None, allow_addons=False, allow_cross_sell=False):
# Channel can currently be a SalesChannel or a str, since we need that compatibility, but a SalesChannel
# makes the query SIGNIFICANTLY faster
from .organizer import SalesChannel
@@ -291,6 +329,8 @@ def filter_available(qs, channel='web', voucher=None, allow_addons=False):
if not allow_addons:
q &= Q(Q(category__isnull=True) | Q(category__is_addon=False))
if not allow_cross_sell:
q &= Q(Q(category__isnull=True) | ~Q(category__cross_selling_mode='only'))
if voucher:
if voucher.item_id:
@@ -304,8 +344,8 @@ def filter_available(qs, channel='web', voucher=None, allow_addons=False):
class ItemQuerySet(models.QuerySet):
def filter_available(self, channel='web', voucher=None, allow_addons=False):
return filter_available(self, channel, voucher, allow_addons)
def filter_available(self, channel='web', voucher=None, allow_addons=False, allow_cross_sell=False):
return filter_available(self, channel, voucher, allow_addons, allow_cross_sell)
class ItemQuerySetManager(ScopedManager(organizer='event__organizer').__class__):
@@ -313,8 +353,8 @@ class ItemQuerySetManager(ScopedManager(organizer='event__organizer').__class__)
super().__init__()
self._queryset_class = ItemQuerySet
def filter_available(self, channel='web', voucher=None, allow_addons=False):
return filter_available(self.get_queryset(), channel, voucher, allow_addons)
def filter_available(self, channel='web', voucher=None, allow_addons=False, allow_cross_sell=False):
return filter_available(self.get_queryset(), channel, voucher, allow_addons, allow_cross_sell)
class Item(LoggedModel):
@@ -402,8 +442,12 @@ class Item(LoggedModel):
UNAVAIL_MODE_INFO = "info"
UNAVAIL_MODES = (
(UNAVAIL_MODE_HIDDEN, _("Hide product if unavailable")),
(UNAVAIL_MODE_INFO, _("Show info text if unavailable")),
(UNAVAIL_MODE_INFO, _("Show product with info on why its unavailable")),
)
UNAVAIL_MODE_ICONS = {
UNAVAIL_MODE_HIDDEN: 'eye-slash',
UNAVAIL_MODE_INFO: 'info'
}
MEDIA_POLICY_REUSE = 'reuse'
MEDIA_POLICY_NEW = 'new'
@@ -556,6 +600,11 @@ class Item(LoggedModel):
"be a short period in which both products are visible while all tickets of the referenced "
"product are reserved, but not yet sold.")
)
hidden_if_item_available_mode = models.CharField(
choices=UNAVAIL_MODES,
default=UNAVAIL_MODE_HIDDEN,
max_length=16,
)
require_voucher = models.BooleanField(
verbose_name=_('This product can only be bought using a voucher.'),
default=False,
@@ -797,7 +846,7 @@ class Item(LoggedModel):
if not self.tax_rule:
t = TaxedPrice(gross=price - bundled_sum, net=price - bundled_sum, tax=Decimal('0.00'),
rate=Decimal('0.00'), name='')
rate=Decimal('0.00'), name='', code=None)
else:
t = self.tax_rule.tax(price, base_price_is=base_price_is, invoice_address=invoice_address,
override_tax_rate=override_tax_rate, currency=currency or self.event.currency,
@@ -805,6 +854,7 @@ class Item(LoggedModel):
if bundled_sum:
t.name = "MIXED!"
t.code = None
t.gross += bundled_sum
t.net += bundled_sum_net
t.tax += bundled_sum_tax
@@ -844,6 +894,8 @@ class Item(LoggedModel):
return 'available_from'
elif subevent_item and subevent_item.available_until and subevent_item.available_until < now_dt:
return 'available_until'
elif self.hidden_if_item_available and self._dependency_available:
return 'hidden_if_item_available'
else:
return None
@@ -1078,13 +1130,12 @@ class ItemVariation(models.Model):
:param original_price: The item's "original" price. Will not be used for any calculations, will just be shown.
:type original_price: decimal.Decimal
:param require_approval: If set to ``True``, orders containing this variation can only be processed and paid after
approval by an administrator
approval by an administrator
:type require_approval: bool
:param all_sales_channels: A flag indicating that this variation is available on all channels and limit_sales_channels will be ignored.
:type all_sales_channels: bool
:param limit_sales_channels: A list of sales channel identifiers, that this variation is available for sale on.
:type limit_sales_channels: list
"""
item = models.ForeignKey(
Item,
@@ -1219,7 +1270,7 @@ class ItemVariation(models.Model):
if not self.item.tax_rule:
t = TaxedPrice(gross=price, net=price, tax=Decimal('0.00'),
rate=Decimal('0.00'), name='')
rate=Decimal('0.00'), name='', code=None)
else:
t = self.item.tax_rule.tax(price, base_price_is=base_price_is, currency=currency,
override_tax_rate=override_tax_rate,
@@ -1241,6 +1292,7 @@ class ItemVariation(models.Model):
t.net += bprice.net - compare_price.net
t.tax += bprice.tax - compare_price.tax
t.name = "MIXED!"
t.code = None
return t
@@ -1677,10 +1729,10 @@ class Question(LoggedModel):
'Question', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions'
)
dependency_values = MultiStringField(default=[])
valid_number_min = models.DecimalField(decimal_places=6, max_digits=16, null=True, blank=True,
valid_number_min = models.DecimalField(decimal_places=6, max_digits=30, null=True, blank=True,
verbose_name=_('Minimum value'),
help_text=_('Currently not supported in our apps and during check-in'))
valid_number_max = models.DecimalField(decimal_places=6, max_digits=16, null=True, blank=True,
valid_number_max = models.DecimalField(decimal_places=6, max_digits=30, null=True, blank=True,
verbose_name=_('Maximum value'),
help_text=_('Currently not supported in our apps and during check-in'))
valid_date_min = models.DateField(null=True, blank=True,

View File

@@ -33,16 +33,15 @@
# License for the specific language governing permissions and limitations under the License.
import json
import logging
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.signals import logentry_object_link
from pretix.base.logentrytype_registry import log_entry_types, make_link
from pretix.base.signals import is_app_active, logentry_object_link
class VisibleOnlyManager(models.Manager):
@@ -92,6 +91,10 @@ class LogEntry(models.Model):
indexes = [models.Index(fields=["datetime", "id"])]
def display(self):
log_entry_type, meta = log_entry_types.get(action_type=self.action_type)
if log_entry_type:
return log_entry_type.display(self, self.parsed_data)
from ..signals import logentry_display
for receiver, response in logentry_display.send(self.event, logentry=self):
@@ -126,10 +129,18 @@ class LogEntry(models.Model):
@cached_property
def display_object(self):
from . import (
Discount, Event, Item, ItemCategory, Order, Question, Quota,
SubEvent, TaxRule, Voucher,
Discount, Event, Item, Order, Question, Quota, SubEvent, Voucher,
)
log_entry_type, meta = log_entry_types.get(action_type=self.action_type)
if log_entry_type:
link_info = log_entry_type.get_object_link_info(self)
if is_app_active(self.event, meta['plugin']):
return make_link(link_info, log_entry_type.object_link_wrapper)
else:
return make_link(link_info, log_entry_type.object_link_wrapper, is_active=False,
event=self.event, plugin_name=meta['plugin'] and getattr(meta['plugin'], 'name'))
try:
if self.content_type.model_class() is Event:
return ''
@@ -137,110 +148,15 @@ class LogEntry(models.Model):
co = self.content_object
except:
return ''
a_map = None
a_text = None
if isinstance(co, Order):
a_text = _('Order {val}')
a_map = {
'href': reverse('control:event.order', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'code': co.code
}),
'val': escape(co.code),
}
elif isinstance(co, Voucher):
a_text = _('Voucher {val}')
a_map = {
'href': reverse('control:event.voucher', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'voucher': co.id
}),
'val': escape(co.code[:6]),
}
elif isinstance(co, Item):
a_text = _('Product {val}')
a_map = {
'href': reverse('control:event.item', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'item': co.id
}),
'val': escape(co.name),
}
elif isinstance(co, SubEvent):
a_text = pgettext_lazy('subevent', 'Date {val}')
a_map = {
'href': reverse('control:event.subevent', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'subevent': co.id
}),
'val': escape(str(co))
}
elif isinstance(co, Quota):
a_text = _('Quota {val}')
a_map = {
'href': reverse('control:event.items.quotas.show', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'quota': co.id
}),
'val': escape(co.name),
}
elif isinstance(co, Discount):
a_text = _('Discount {val}')
a_map = {
'href': reverse('control:event.items.discounts.edit', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'discount': co.id
}),
'val': escape(co.internal_name),
}
elif isinstance(co, ItemCategory):
a_text = _('Category {val}')
a_map = {
'href': reverse('control:event.items.categories.edit', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'category': co.id
}),
'val': escape(co.name),
}
elif isinstance(co, Question):
a_text = _('Question {val}')
a_map = {
'href': reverse('control:event.items.questions.show', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'question': co.id
}),
'val': escape(co.question),
}
elif isinstance(co, TaxRule):
a_text = _('Tax rule {val}')
a_map = {
'href': reverse('control:event.settings.tax.edit', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'rule': co.id
}),
'val': escape(co.name),
}
for receiver, response in logentry_object_link.send(self.event, logentry=self):
if response:
return response
if a_text and a_map:
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
return a_text.format_map(a_map)
elif a_text:
return a_text
else:
for receiver, response in logentry_object_link.send(self.event, logentry=self):
if response:
return response
return ''
if isinstance(co, (Order, Voucher, Item, SubEvent, Quota, Discount, Question)):
logging.warning("LogEntryType missing or ill-defined: %s", self.action_type)
return ''
@cached_property
def parsed_data(self):

View File

@@ -159,10 +159,24 @@ class Membership(models.Model):
de = date_format(self.date_end, 'SHORT_DATE_FORMAT')
return f'{self.membership_type.name}: {self.attendee_name} ({ds} {de})'
@property
def percentage_used(self):
if self.membership_type.max_usages and self.usages:
return int(self.usages / self.membership_type.max_usages * 100)
return 0
@property
def attendee_name(self):
return build_name(self.attendee_name_parts, fallback_scheme=lambda: self.customer.organizer.settings.name_scheme)
@property
def expired(self):
return time_machine_now() > self.date_end
@property
def not_yet_valid(self):
return time_machine_now() < self.date_start
def is_valid(self, ev=None, ticket_valid_from=None, valid_from_not_chosen=False):
if valid_from_not_chosen:
return not self.canceled and self.date_end >= time_machine_now()

View File

@@ -43,7 +43,7 @@ class NotificationSetting(models.Model):
:type enabled: bool
"""
CHANNELS = (
('mail', _('E-mail')),
('mail', _('Email')),
)
user = models.ForeignKey('User', on_delete=models.CASCADE,
related_name='notification_settings')

View File

@@ -40,6 +40,7 @@ import json
import logging
import operator
import string
import warnings
from collections import Counter
from datetime import datetime, time, timedelta
from decimal import Decimal
@@ -61,9 +62,10 @@ from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.urls import reverse
from django.utils.crypto import get_random_string, salted_hmac
from django.utils.encoding import escape_uri_path
from django.utils.encoding import escape_uri_path, force_str
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.hashable import make_hashable
from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_countries.fields import Country
@@ -241,7 +243,7 @@ class Order(LockModel, LoggedModel):
)
email = models.EmailField(
null=True, blank=True,
verbose_name=_('E-mail')
verbose_name=_('Email')
)
phone = PhoneNumberField(
null=True, blank=True,
@@ -316,7 +318,7 @@ class Order(LockModel, LoggedModel):
)
email_known_to_work = models.BooleanField(
default=False,
verbose_name=_('E-mail address verified')
verbose_name=_('Email address verified')
)
invoice_dirty = models.BooleanField(
# Invoice needs to be re-issued when the order is paid again
@@ -353,7 +355,7 @@ class Order(LockModel, LoggedModel):
if not self.testmode:
raise TypeError("Only test mode orders can be deleted.")
self.event.log_action(
self.log_action(
'pretix.event.order.deleted', user=user, auth=auth,
data={
'code': self.code,
@@ -381,8 +383,28 @@ class Order(LockModel, LoggedModel):
self.event.cache.delete('complain_testmode_orders')
self.delete()
def email_confirm_secret(self):
return self.tagged_secret("email_confirm", 9)
def email_confirm_hash(self):
return hashlib.sha256(settings.SECRET_KEY.encode() + self.secret.encode()).hexdigest()[:9]
warnings.warn('Use email_confirm_secret() instead of email_confirm_hash().',
DeprecationWarning)
return self.email_confirm_secret()
def check_email_confirm_secret(self, received_secret):
return (
hmac.compare_digest(
self.tagged_secret("email_confirm", 9),
received_secret[:9].lower()
) or any(
# TODO: remove this clause after a while (compatibility with old secrets currently in flight)
hmac.compare_digest(
hashlib.sha256(sk.encode() + self.secret.encode()).hexdigest()[:9],
received_secret
)
for sk in [settings.SECRET_KEY, *settings.SECRET_KEY_FALLBACKS]
)
)
def get_extended_status_display(self):
# Changes in this method should to be replicated in pretixcontrol/orders/fragment_order_status.html
@@ -1065,7 +1087,7 @@ class Order(LockModel, LoggedModel):
for i, op in enumerate(positions):
if op.seat:
if not op.seat.is_available(ignore_orderpos=op):
if not op.seat.is_available(ignore_orderpos=op, sales_channel=self.sales_channel.identifier):
raise Quota.QuotaExceededException(error_messages['seat_unavailable'].format(seat=op.seat))
if force:
continue
@@ -1235,7 +1257,7 @@ class Order(LockModel, LoggedModel):
keys = set(target_transaction_count.keys()) | set(current_transaction_count.keys())
create = []
for k in keys:
positionid, itemid, variationid, subeventid, price, taxrate, taxruleid, taxvalue, feetype, internaltype = k
positionid, itemid, variationid, subeventid, price, taxrate, taxruleid, taxvalue, feetype, internaltype, taxcode = k
d = target_transaction_count[k] - current_transaction_count[k]
if d:
create.append(Transaction(
@@ -1251,6 +1273,7 @@ class Order(LockModel, LoggedModel):
tax_rate=taxrate,
tax_rule_id=taxruleid,
tax_value=taxvalue,
tax_code=taxcode,
fee_type=feetype,
internal_type=internaltype,
))
@@ -2254,14 +2277,16 @@ class OrderFee(models.Model):
FEE_TYPE_SERVICE = "service"
FEE_TYPE_CANCELLATION = "cancellation"
FEE_TYPE_INSURANCE = "insurance"
FEE_TYPE_LATE = "late"
FEE_TYPE_OTHER = "other"
FEE_TYPE_GIFTCARD = "giftcard"
FEE_TYPES = (
(FEE_TYPE_SERVICE, _("Service fee")),
(FEE_TYPE_PAYMENT, _("Payment fee")),
(FEE_TYPE_SHIPPING, _("Shipping fee")),
(FEE_TYPE_SERVICE, _("Service fee")),
(FEE_TYPE_CANCELLATION, _("Cancellation fee")),
(FEE_TYPE_INSURANCE, _("Insurance fee")),
(FEE_TYPE_LATE, _("Late fee")),
(FEE_TYPE_OTHER, _("Other fees")),
(FEE_TYPE_GIFTCARD, _("Gift card")),
)
@@ -2290,6 +2315,10 @@ class OrderFee(models.Model):
on_delete=models.PROTECT,
null=True, blank=True
)
tax_code = models.CharField(
max_length=190,
null=True, blank=True,
)
tax_value = models.DecimalField(
max_digits=13, decimal_places=2,
verbose_name=_('Tax value')
@@ -2317,6 +2346,16 @@ class OrderFee(models.Model):
self._transaction_key_reset()
return super().refresh_from_db(using, fields)
def get_tax_code_display(self):
from pretix.base.models.tax import get_tax_code_labels
if self.tax_code:
choices_dict = get_tax_code_labels()
return force_str(
choices_dict.get(make_hashable(self.tax_code), self.tax_code), strings_only=True
)
return ""
def _transaction_key_reset(self):
self.__initial_transaction_key = Transaction.key(self)
self.__initial_canceled = self.canceled
@@ -2347,9 +2386,11 @@ class OrderFee(models.Model):
if self.tax_rule:
tax = self.tax_rule.tax(self.value, base_price_is='gross', invoice_address=ia, force_fixed_gross_price=True)
self.tax_rate = tax.rate
self.tax_code = tax.code
self.tax_value = tax.tax
else:
self.tax_value = Decimal('0.00')
self.tax_code = None
self.tax_rate = Decimal('0.00')
def save(self, *args, **kwargs):
@@ -2358,6 +2399,7 @@ class OrderFee(models.Model):
if self.tax_rate is None:
self._calculate_tax()
self.order.touch()
if not self.get_deferred_fields():
@@ -2445,6 +2487,10 @@ class OrderPosition(AbstractPosition):
on_delete=models.PROTECT,
null=True, blank=True
)
tax_code = models.CharField(
max_length=190,
null=True, blank=True,
)
tax_value = models.DecimalField(
max_digits=13, decimal_places=2,
verbose_name=_('Tax value')
@@ -2502,6 +2548,16 @@ class OrderPosition(AbstractPosition):
models.UniqueConstraint("organizer", "secret", name="orderposition_organizer_secret_uniq")
]
def get_tax_code_display(self):
from pretix.base.models.tax import get_tax_code_labels
if self.tax_code:
choices_dict = get_tax_code_labels()
return force_str(
choices_dict.get(make_hashable(self.tax_code), self.tax_code), strings_only=True
)
return ""
@cached_property
def sort_key(self):
return self.addon_to.positionid if self.addon_to else self.positionid, self.addon_to_id or 0, self.positionid
@@ -2674,11 +2730,13 @@ class OrderPosition(AbstractPosition):
if self.tax_rule:
tax = self.tax_rule.tax(self.price, invoice_address=ia, base_price_is='gross', force_fixed_gross_price=True)
self.tax_rate = tax.rate
self.tax_code = tax.code
self.tax_value = tax.tax
if tax.gross != self.price:
raise ValueError('Invalid tax calculation')
else:
self.tax_value = Decimal('0.00')
self.tax_code = None
self.tax_rate = Decimal('0.00')
def save(self, *args, **kwargs):
@@ -2835,6 +2893,14 @@ class OrderPosition(AbstractPosition):
(self.order.event.settings.change_allow_user_addons and ItemAddOn.objects.filter(base_item_id__in=[op.item_id for op in positions]).exists())
)
@property
def code(self):
"""
A ticket code which is unique among all events of a single organizer,
built by the order code and the position number.
"""
return '{order_code}-{position}'.format(order_code=self.order.code, position=self.positionid)
class Transaction(models.Model):
"""
@@ -2941,6 +3007,10 @@ class Transaction(models.Model):
on_delete=models.PROTECT,
null=True, blank=True
)
tax_code = models.CharField(
max_length=190,
null=True, blank=True,
)
tax_value = models.DecimalField(
max_digits=13, decimal_places=2,
verbose_name=_('Tax value')
@@ -2961,17 +3031,27 @@ class Transaction(models.Model):
raise ValidationError('Should set either item or fee type')
return super().save(*args, **kwargs)
def get_tax_code_display(self):
from pretix.base.models.tax import get_tax_code_labels
if self.tax_code:
choices_dict = get_tax_code_labels()
return force_str(
choices_dict.get(make_hashable(self.tax_code), self.tax_code), strings_only=True
)
return ""
@staticmethod
def key(obj):
if isinstance(obj, Transaction):
return (obj.positionid, obj.item_id, obj.variation_id, obj.subevent_id, obj.price, obj.tax_rate,
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type)
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type, obj.tax_code)
elif isinstance(obj, OrderPosition):
return (obj.positionid, obj.item_id, obj.variation_id, obj.subevent_id, obj.price, obj.tax_rate,
obj.tax_rule_id, obj.tax_value, None, None)
obj.tax_rule_id, obj.tax_value, None, None, obj.tax_code)
elif isinstance(obj, OrderFee):
return (None, None, None, None, obj.value, obj.tax_rate,
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type)
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type, obj.tax_code)
raise ValueError('invalid state') # noqa
@property
@@ -3135,8 +3215,15 @@ class CartPosition(AbstractPosition):
if line_price.gross != self.line_price_gross or line_price.rate != self.tax_rate:
self.line_price_gross = line_price.gross
self.tax_rate = line_price.rate
self.tax_code = line_price.code
self.save(update_fields=['line_price_gross', 'tax_rate'])
@property
def discount_percentage(self):
if not self.line_price_gross:
return 0
return (self.line_price_gross - self.price) / self.line_price_gross * 100
@property
def addons_without_bundled(self):
addons = [op for op in self.addons.all() if not op.is_bundled]
@@ -3175,9 +3262,9 @@ class InvoiceAddress(models.Model):
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'))
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
name_parts = models.JSONField(default=dict)
street = models.TextField(verbose_name=_('Address'), blank=False)
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False)
city = models.CharField(max_length=255, verbose_name=_('City'), blank=False)
street = models.TextField(verbose_name=_('Address'), blank=True)
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=True)
city = models.CharField(max_length=255, verbose_name=_('City'), blank=True)
country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False)
country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
countries=CachedCountries)
@@ -3362,6 +3449,74 @@ class BlockedTicketSecret(models.Model):
unique_together = (('event', 'secret'),)
class PrintLog(models.Model):
"""
A print log object is created when a ticket or badge is printed with our apps.
"""
TYPE_BADGE = 'badge'
TYPE_TICKET = 'ticket'
TYPE_CERTIFICATE = 'certificate'
TYPE_OTHER = 'other'
PRINT_TYPES = (
(TYPE_BADGE, _('Badge')),
(TYPE_TICKET, _('Ticket')),
(TYPE_CERTIFICATE, _('Certificate')),
(TYPE_OTHER, _('Other')),
)
position = models.ForeignKey(
'pretixbase.OrderPosition',
related_name='print_logs',
on_delete=models.CASCADE,
)
successful = models.BooleanField(
default=True,
)
# Datetime of checkin, might be different from created if past scans are uploaded
datetime = models.DateTimeField(default=now)
# Datetime of creation on server
created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
# Who printed?
device = models.ForeignKey('Device', related_name='print_logs', null=True, blank=True, on_delete=models.PROTECT)
user = models.ForeignKey('User', related_name='print_logs', null=True, blank=True, on_delete=models.PROTECT)
api_token = models.ForeignKey('TeamAPIToken', null=True, blank=True, on_delete=models.PROTECT)
oauth_application = models.ForeignKey('pretixapi.OAuthApplication', null=True, blank=True, on_delete=models.PROTECT)
# Source = Tag field with undefined values, e.g. name of app ("pretixscan")
source = models.CharField(max_length=255)
# Type = Type of object printed ("badge", "ticket")
type = models.CharField(max_length=255, choices=PRINT_TYPES)
info = models.JSONField(default=dict)
objects = ScopedManager(organizer='position__order__event__organizer')
class Meta:
ordering = (('-datetime'),)
def __repr__(self):
return "<PrintLog: pos {} at {} from {}>".format(
self.position, self.datetime, self.source
)
def save(self, **kwargs):
super().save(**kwargs)
if self.position:
self.position.order.touch()
def delete(self, **kwargs):
super().delete(**kwargs)
self.position.order.touch()
@property
def is_late_upload(self):
return self.created and abs(self.created - self.datetime) > timedelta(minutes=2)
@receiver(post_delete, sender=CachedTicket)
def cachedticket_delete(sender, instance, **kwargs):
if instance.file:

View File

@@ -53,6 +53,30 @@ class SeatingPlanLayoutValidator:
e = str(e).replace('%', '%%')
raise ValidationError(_('Your layout file is not a valid seating plan. Error message: {}').format(e))
try:
seat_guids = set()
for z in val["zones"]:
for r in z["rows"]:
for s in r["seats"]:
if not s.get("seat_guid"):
raise ValidationError(
_("Seat with zone {zone}, row {row}, and number {number} has no seat ID.").format(
zone=z["name"],
row=r["row_number"],
number=s["seat_number"],
)
)
elif s["seat_guid"] in seat_guids:
raise ValidationError(
_("Multiple seats have the same ID: {id}").format(
id=s["seat_guid"],
)
)
seat_guids.add(s["seat_guid"])
except ValidationError as e:
raise ValidationError(_('Your layout file is not a valid seating plan. Error message: {}').format(", ".join(e.message for e in e.error_list)))
class SeatingPlan(LoggedModel):
"""

View File

@@ -21,6 +21,7 @@
#
import json
from decimal import Decimal
from typing import Optional
import jsonschema
from django.contrib.staticfiles import finders
@@ -29,7 +30,10 @@ from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.utils.deconstruct import deconstructible
from django.utils.formats import localize
from django.utils.translation import gettext_lazy as _, pgettext
from django.utils.functional import lazy
from django.utils.hashable import make_hashable
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _, pgettext, pgettext_lazy
from i18nfield.fields import I18nCharField
from i18nfield.strings import LazyI18nString
@@ -40,7 +44,7 @@ from pretix.helpers.countries import FastCountryField
class TaxedPrice:
def __init__(self, *, gross: Decimal, net: Decimal, tax: Decimal, rate: Decimal, name: str):
def __init__(self, *, gross: Decimal, net: Decimal, tax: Decimal, rate: Decimal, name: str, code: Optional[str]):
if net + tax != gross:
raise ValueError('Net value and tax value need to add to the gross value')
self.gross = gross
@@ -48,6 +52,7 @@ class TaxedPrice:
self.tax = tax
self.rate = rate
self.name = name
self.code = code
def __repr__(self):
return '{} + {}% = {}'.format(localize(self.net), localize(self.rate), localize(self.gross))
@@ -70,6 +75,7 @@ class TaxedPrice:
tax=newgross - newnet,
rate=self.rate,
name=self.name,
code=self.code,
)
def __mul__(self, other):
@@ -83,6 +89,7 @@ class TaxedPrice:
tax=newgross - newnet,
rate=self.rate,
name=self.name,
code=self.code,
)
def __eq__(self, other):
@@ -91,7 +98,8 @@ class TaxedPrice:
self.net == other.net and
self.tax == other.tax and
self.rate == other.rate and
self.name == other.name
self.name == other.name and
self.code == other.code
)
@@ -100,7 +108,8 @@ TAXED_ZERO = TaxedPrice(
net=Decimal('0.00'),
tax=Decimal('0.00'),
rate=Decimal('0.00'),
name=''
name='',
code=None,
)
EU_COUNTRIES = {
@@ -120,6 +129,154 @@ EU_CURRENCIES = {
}
VAT_ID_COUNTRIES = EU_COUNTRIES | {'CH', 'NO'}
format_html_lazy = lazy(format_html, str)
TAX_CODE_LISTS = (
# Sources:
# https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists#RegistryofsupportingartefactstoimplementEN16931-Codelists
# https://docs.peppol.eu/poacc/billing/3.0/codelist/vatex/
# https://docs.peppol.eu/poacc/billing/3.0/codelist/UNCL5305/
# https://www.bzst.de/DE/Unternehmen/Aussenpruefungen/DigitaleSchnittstelleFinV/digitaleschnittstellefinv_node.html#js-toc-entry2
#
# !! When changed, also update tax-rules-custom.schema.json and doc/api/resources/taxrules.rst !!
(
_("Standard rates"),
(
# Standard rate in any country, such as 19% in Germany or 20% in Austria
# DSFinV-K mapping: 1
("S/standard", pgettext_lazy("tax_code", "Standard rate")),
# Reduced rate in any country, such as 7% in Germany or both 10% and 13% in Austria
# DSFinV-K mapping: 2
("S/reduced", pgettext_lazy("tax_code", "Reduced rate")),
# Averaged rate, for example Germany § 24 (1) Nr. 3 UStG "für die übrigen Umsätze" in agricultural and silvicultural businesses
# DSFinV-K mapping: 3
("S/averaged", pgettext_lazy("tax_code", "Averaged rate (other revenue in a agricultural and silvicultural business)")),
# We ignore the German special case of the actual silvicultural products as they won't be sold through pretix (DSFinV-K mapping: 4)
)
),
(
_("Reverse charge"),
(
("AE", pgettext_lazy("tax_code", "Reverse charge")),
)
),
(
_("Tax free"),
(
# DSFinV-K mapping: 5
("O", pgettext_lazy("tax_code", "Services outside of scope of tax")),
# DSFinV-K mapping: 6
("E", pgettext_lazy("tax_code", "Exempt from tax (no reason given)")),
# DSFinV-K mapping: 6
("Z", pgettext_lazy("tax_code", "Zero-rated goods")),
# DSFinV-K mapping: 5
("G", pgettext_lazy("tax_code", "Free export item, VAT not charged")),
# DSFinV-K mapping: 6?
("K", pgettext_lazy("tax_code", "VAT exempt for EEA intra-community supply of goods and services")),
)
),
(
_("Special cases"),
(
("L", pgettext_lazy("tax_code", "Canary Islands general indirect tax")),
("M", pgettext_lazy("tax_code", "Tax for production, services and importation in Ceuta and Melilla")),
("B", pgettext_lazy("tax_code", "Transferred (VAT), only in Italy")),
)
),
(
_("Exempt with specific reason"),
(
("E/VATEX-EU-79-C",
pgettext_lazy("tax_code", "Exempt based on article 79, point c of Council Directive 2006/112/EC")),
*[
(
f"E/VATEX-EU-132-1{letter.upper()}",
lazy(
lambda let: pgettext(
"tax_code",
"Exempt based on article {article}, section {section} ({letter}) of Council "
"Directive 2006/112/EC"
).format(article="132", section="1", letter=let),
str
)(letter)
) for letter in ("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q")
],
*[
(
f"E/VATEX-EU-143-1{letter.upper()}",
lazy(
lambda let: pgettext(
"tax_code",
"Exempt based on article {article}, section {section} ({letter}) of Council "
"Directive 2006/112/EC"
).format(article="143", section="1", letter=let),
str
)(letter)
) for letter in ("a", "b", "c", "d", "e", "f", "fa", "g", "h", "i", "j", "k", "l")
],
*[
(
f"E/VATEX-EU-148-{letter.upper()}",
lazy(
lambda let: pgettext(
"tax_code",
"Exempt based on article {article}, section ({letter}) of Council "
"Directive 2006/112/EC"
).format(article="148", letter=let),
str
)(letter)
) for letter in ("a", "b", "c", "d", "e", "f", "g")
],
*[
(
f"E/VATEX-EU-151-1{letter.upper()}",
lazy(
lambda let: pgettext(
"tax_code",
"Exempt based on article {article}, section {section} ({letter}) of Council "
"Directive 2006/112/EC"
).format(article="151", section="1", letter=let),
str
)(letter)
) for letter in ("a", "aa", "b", "c", "d", "e")
],
("E/VATEX-EU-309",
pgettext_lazy("tax_code", "Exempt based on article 309 of Council Directive 2006/112/EC")),
("E/VATEX-EU-D",
pgettext_lazy("tax_code", "Intra-Community acquisition from second hand means of transport")),
("E/VATEX-EU-F",
pgettext_lazy("tax_code", "Intra-Community acquisition of second hand goods")),
("E/VATEX-EU-I",
pgettext_lazy("tax_code", "Intra-Community acquisition of works of art")),
("E/VATEX-EU-J",
pgettext_lazy("tax_code", "Intra-Community acquisition of collectors items and antiques")),
("E/VATEX-FR-FRANCHISE",
pgettext_lazy("tax_code", "France domestic VAT franchise in base")),
("E/VATEX-FR-CNWVAT",
pgettext_lazy("tax_code", "France domestic Credit Notes without VAT, due to supplier forfeit of VAT for discount")),
)
),
)
def get_tax_code_labels():
flat = []
for choice, value in TAX_CODE_LISTS:
if isinstance(value, (list, tuple)):
flat.extend(value)
else:
flat.append((choice, value))
return dict(make_hashable(flat))
def is_eu_country(cc):
cc = str(cc)
@@ -169,6 +326,14 @@ class TaxRule(LoggedModel):
help_text=_('Should be short, e.g. "VAT"'),
max_length=190,
)
code = models.CharField(
verbose_name=_('Tax code'),
help_text=_('If you help us understand what this tax rules legally is, we can use this information for '
'eInvoices, exporting to accounting system, etc.'),
null=True, blank=True,
max_length=190,
choices=TAX_CODE_LISTS,
)
rate = models.DecimalField(
max_digits=10,
decimal_places=2,
@@ -193,11 +358,17 @@ class TaxRule(LoggedModel):
eu_reverse_charge = models.BooleanField(
verbose_name=_("Use EU reverse charge taxation rules"),
default=False,
help_text=_("Not recommended. Most events will NOT be qualified for reverse charge since the place of "
"taxation is the location of the event. This option disables charging VAT for all customers "
"outside the EU and for business customers in different EU countries who entered a valid EU VAT "
"ID. Only enable this option after consulting a tax counsel. No warranty given for correct tax "
"calculation. USE AT YOUR OWN RISK.")
help_text=format_html_lazy(
'<span class="label label-warning" data-toggle="tooltip" title="{}">{}</span> {}',
_('This feature will be removed in the future as it does not handle VAT for non-business customers in '
'other EU countries in a way that works for all organizers. Use custom rules instead.'),
_('DEPRECATED'),
_("Not recommended. Most events will NOT be qualified for reverse charge since the place of "
"taxation is the location of the event. This option disables charging VAT for all customers "
"outside the EU and for business customers in different EU countries who entered a valid EU VAT "
"ID. Only enable this option after consulting a tax counsel. No warranty given for correct tax "
"calculation. USE AT YOUR OWN RISK.")
),
)
home_country = FastCountryField(
verbose_name=_('Merchant country'),
@@ -240,6 +411,16 @@ class TaxRule(LoggedModel):
if self.eu_reverse_charge and not self.home_country:
raise ValidationError(_('You need to set your home country to use the reverse charge feature.'))
if self.rate != Decimal("0.00") and self.code and (self.code.split("/")[0] in ("O", "E", "Z", "G", "K", "AE")):
raise ValidationError({
"code": _("A combination of this tax code with a non-zero tax rate does not make sense.")
})
if self.rate == Decimal("0.00") and self.code and (self.code.split("/")[0] in ("S", "L", "M", "B")):
raise ValidationError({
"code": _("A combination of this tax code with a zero tax rate does not make sense.")
})
def __str__(self):
if self.price_includes_tax:
s = _('incl. {rate}% {name}').format(rate=self.rate, name=self.name)
@@ -266,8 +447,9 @@ class TaxRule(LoggedModel):
return Decimal(rule.get('rate'))
return Decimal(self.rate)
def tax(self, base_price, base_price_is='auto', currency=None, override_tax_rate=None, invoice_address=None,
subtract_from_gross=Decimal('0.00'), gross_price_is_tax_rate: Decimal = None, force_fixed_gross_price=False):
def tax(self, base_price, base_price_is='auto', currency=None, override_tax_rate=None, override_tax_code=None,
invoice_address=None, subtract_from_gross=Decimal('0.00'), gross_price_is_tax_rate: Decimal = None,
force_fixed_gross_price=False):
from .event import Event
try:
currency = currency or self.event.currency
@@ -275,6 +457,13 @@ class TaxRule(LoggedModel):
pass
rate = Decimal(self.rate)
code = self.code
if override_tax_code is not None:
code = override_tax_code
elif invoice_address:
code = self.tax_code_for(invoice_address)
if override_tax_rate is not None:
rate = override_tax_rate
elif invoice_address:
@@ -294,10 +483,21 @@ class TaxRule(LoggedModel):
subtract_from_gross = Decimal('0.00')
rate = adjust_rate
def _limit_subtract(base_price, subtract_from_gross):
if not subtract_from_gross:
return base_price
if base_price >= Decimal('0.00'):
# For positive prices, make sure they don't go negative because of bundles
return max(Decimal('0.00'), base_price - subtract_from_gross)
else:
# If the price is already negative, we don't really care any more
return base_price - subtract_from_gross
if rate == Decimal('0.00'):
gross = _limit_subtract(base_price, subtract_from_gross)
return TaxedPrice(
net=base_price - subtract_from_gross, gross=base_price - subtract_from_gross, tax=Decimal('0.00'),
rate=rate, name=self.name
net=gross, gross=gross, tax=Decimal('0.00'),
rate=rate, name=self.name, code=code,
)
if base_price_is == 'auto':
@@ -307,19 +507,14 @@ class TaxRule(LoggedModel):
base_price_is = 'net'
if base_price_is == 'gross':
if base_price >= Decimal('0.00'):
# For positive prices, make sure they don't go negative because of bundles
gross = max(Decimal('0.00'), base_price - subtract_from_gross)
else:
# If the price is already negative, we don't really care any more
gross = base_price - subtract_from_gross
gross = _limit_subtract(base_price, subtract_from_gross)
net = round_decimal(gross - (gross * (1 - 100 / (100 + rate))),
currency)
elif base_price_is == 'net':
net = base_price
gross = round_decimal((net * (1 + rate / 100)), currency)
if subtract_from_gross:
gross -= subtract_from_gross
gross = _limit_subtract(gross, subtract_from_gross)
net = round_decimal(gross - (gross * (1 - 100 / (100 + rate))),
currency)
else:
@@ -327,7 +522,7 @@ class TaxRule(LoggedModel):
return TaxedPrice(
net=net, gross=gross, tax=gross - net,
rate=rate, name=self.name
rate=rate, name=self.name, code=code,
)
@property
@@ -408,6 +603,38 @@ class TaxRule(LoggedModel):
return True
return False
def tax_code_for(self, invoice_address):
if self._custom_rules:
rule = self.get_matching_rule(invoice_address)
if rule.get("code"):
return rule["code"]
if rule.get("action", "vat") == "reverse":
return "AE"
return self.code
if not self.eu_reverse_charge:
# No reverse charge rules? Always apply VAT!
return self.code
if not invoice_address or not invoice_address.country:
# No country specified? Always apply VAT!
return self.code
if not is_eu_country(invoice_address.country):
# Non-EU country? "Non-taxable" since not in scope
return "O"
if invoice_address.country == self.home_country:
# Within same EU country? Always apply VAT!
return self.code
if invoice_address.is_business and invoice_address.vat_id and invoice_address.vat_id_validated:
# Reverse charge case
return "AE"
# Consumer in different EU country / invalid VAT
return self.code
def _tax_applicable(self, invoice_address):
if self._custom_rules:
rule = self.get_matching_rule(invoice_address)

View File

@@ -73,7 +73,7 @@ class WaitingListEntry(LoggedModel):
blank=True, default=dict
)
email = models.EmailField(
verbose_name=_("E-mail address")
verbose_name=_("Email address")
)
phone = PhoneNumberField(
null=True, blank=True,

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