Compare commits

..
267 Commits
Author SHA1 Message Date
Kara Engelhardt 5dc60a715a WIP: simplify preview rendering 2026-10-16 17:39:26 +02:00
Kara Engelhardt f42a60f43c Add booleansettingsfield; use for shimmer animation 2026-10-16 14:03:39 +02:00
Kara Engelhardt eb8b9e5b8e Add details field to generic google event ticket 2026-10-16 13:51:09 +02:00
Kara Engelhardt 5092fafb40 no details limit 2026-10-15 18:26:00 +02:00
Kara Engelhardt 2fe97a2ee5 Start with custom google pass layout 2026-10-15 18:24:27 +02:00
Kara Engelhardt 896e7dac72 File settings handling; nicer file upload button 2026-10-15 16:42:16 +02:00
Kara Engelhardt 043e2a1b8e Bugfixes 2026-10-15 14:54:12 +02:00
Kara Engelhardt 276bb34528 cleanup 2026-10-15 14:33:15 +02:00
Kara Engelhardt 99b1b4cfe2 google: ticket type in preview 2026-10-15 14:33:15 +02:00
Kara Engelhardt 97153d3b4c google: show website 2026-10-15 14:33:15 +02:00
Kara Engelhardt c794e199f4 bugfixing; update types 2026-10-15 14:33:15 +02:00
Kara Engelhardt 4bc5b2941b cleanup 2026-10-15 14:33:15 +02:00
Kara Engelhardt 4a9c2ef20c localization; bugfixes; google event ticket 2026-10-15 14:33:15 +02:00
Kara Engelhardt 2ff639ae1e continue google event ticket 2026-10-15 14:33:15 +02:00
Kara Engelhardt e8ce084e29 Background preview 2026-10-15 14:33:15 +02:00
Kara Engelhardt 7e2922afd3 show default as first layout 2026-10-15 14:33:15 +02:00
Kara Engelhardt 82fe80f5e0 formatting 2026-10-15 14:33:15 +02:00
Kara Engelhardt a7fc470c5b Show default placeholder label 2026-10-15 14:33:15 +02:00
Kara Engelhardt 30e306f46d Add default for ImageSettingsField 2026-10-15 14:33:15 +02:00
Kara Engelhardt 336bd10691 Continue apple pass; Add float and color settings 2026-10-15 14:33:15 +02:00
Kara Engelhardt e29d27a343 add more fields for apple default pass layout 2026-10-15 14:33:15 +02:00
Kara Engelhardt 3a2bc0dec0 Add ticketoutput confirmation to control 2026-10-15 14:33:15 +02:00
Kara Engelhardt 8ae090c2da add wallet layout chooser to item settings 2026-10-15 14:33:15 +02:00
Kara Engelhardt 966b05dab2 fix layout settings styling, image setting preview, clear image 2026-10-15 14:33:15 +02:00
Kara Engelhardt a990f69a3f cleanup wallet placeholder signal & image placeholder handling 2026-10-15 14:33:15 +02:00
Kara Engelhardt 4c47bc0c28 add missing import 2026-10-15 14:33:15 +02:00
Kara Engelhardt 6a2a846766 fix walletsettings panel header spacing 2026-10-15 14:33:15 +02:00
Kara Engelhardt 41269ec406 show max_entries in overflow help text 2026-10-15 14:33:15 +02:00
Kara Engelhardt a37225343d missing file 2026-10-15 14:33:15 +02:00
Kara Engelhardt 6fe0f5b1c6 button styling 2026-10-15 14:33:15 +02:00
Kara Engelhardt 93ffea78ec ticketoutput confirmation_text; is_cacheable 2026-10-15 14:33:15 +02:00
Kara Engelhardt 1247557714 bugfixes, start moving wallet to core 2026-10-15 14:33:15 +02:00
Kara Engelhardt 6cb329cacd filesettings; placeholder context transformations; bugfixes 2026-10-15 14:33:15 +02:00
Kara Engelhardt 832f314df9 WIP
proper fileupload handling, better store
2026-10-15 14:33:15 +02:00
Kara Engelhardt 81410289be WIP
mvp (file)settings
2026-10-15 14:33:15 +02:00
Kara Engelhardt 0df51cbf67 WIP
prettify & cleanup preview

support multiline fields in preview

add venue

store active state for predefined fieldgroups
2026-10-15 14:33:15 +02:00
Kara Engelhardt d40112c730 WIP 2026-10-15 14:33:15 +02:00
Kara Engelhardt 7c5155b0f9 WIP 2026-10-15 14:33:15 +02:00
Kara Engelhardt 4d27452725 WIP: predefined preview, google api, custom placeholder class 2026-10-15 14:33:15 +02:00
Kara Engelhardt 273c4493ad WIP 2026-10-15 14:33:15 +02:00
Kara Engelhardt 50211faee9 WIP 2026-10-15 14:33:15 +02:00
Kara Engelhardt b63dbf9166 WIP 2026-10-15 14:33:15 +02:00
Kara Engelhardt 716a0f30f7 preview from server, qrcode in apple event pass 2026-10-15 14:33:15 +02:00
Kara Engelhardt 261d8cd2b8 WIP preview 2026-10-15 14:33:15 +02:00
Kara Engelhardt c0b3743ee4 WIP preview 2026-10-15 14:33:15 +02:00
Kara Engelhardt 903f2bcca8 start preview 2026-10-15 14:33:15 +02:00
Kara Engelhardt d7943f37f2 wallet wip 2026-10-15 14:33:14 +02:00
Kara Engelhardt 20a0ae9e49 Fix typo 2026-10-15 14:33:14 +02:00
Kara Engelhardt 3ffd66484c vite dev server: do not clear screen 2026-10-15 14:33:14 +02:00
Kara Engelhardt 2c42e4db2e WIP 2026-10-15 14:33:14 +02:00
Kara Engelhardt d84f4f0103 Remove static layout name from editor page 2026-10-15 14:33:14 +02:00
Kara Engelhardt ad0cbbc003 use placeholder name as label default 2026-10-15 14:33:14 +02:00
Kara Engelhardt 0d41dbf468 WIP: first successful pass creation 2026-10-15 14:33:14 +02:00
Kara Engelhardt d52ed0676b WalletLayout now contain layouts for all platforms 2026-10-15 14:33:14 +02:00
Kara Engelhardt 58240068c3 WIP 2026-10-15 14:33:14 +02:00
Kara Engelhardt e2882f7617 WIP: i18n editor, start apple wallet generation 2026-10-15 14:33:14 +02:00
Kara Engelhardt 34b53402a8 WIP: i18nfields, refactoring, jsonschema-validatoin 2026-10-15 14:33:14 +02:00
Kara Engelhardt 7800591681 WIP: use api 2026-10-15 14:33:14 +02:00
Kara Engelhardt 9a417a0fb0 WIP 2026-10-15 14:33:14 +02:00
Kara Engelhardt 4a1e86e21d WIP 2026-10-15 14:33:14 +02:00
Kara Engelhardt 91d9511e3e WIP 2026-10-15 14:33:14 +02:00
Kara Engelhardt 637d46471d WIP 2026-10-15 14:33:14 +02:00
Kara Engelhardt e4aa11caea WIP 2026-10-15 14:33:14 +02:00
Kara Engelhardt 53fa2974f4 Add wallet plugins stub 2026-10-15 14:33:14 +02:00
Martin Gross 2e4e60db34 Stripe: Enable Wero 2026-10-14 10:10:44 +02:00
Raphael Michel 6b7f2ad684 API: Fix PATCH on item addon sets without min_count or max_count (fixes #6650) (#6651) 2026-10-10 09:53:49 +02:00
dependabot[bot] 54b249acf5 Update pycryptodome requirement from ==3.23.* to ==3.24.* (#6657)
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.23.0...v3.24.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-09 22:56:43 +02:00
dependabot[bot] 0cf2ba763b Update sphinxcontrib-httpdomain requirement from ~=2.0.0 to ~=2.1.0 (#6660)
Updates the requirements on [sphinxcontrib-httpdomain](https://github.com/sphinx-contrib/httpdomain) to permit the latest version.
- [Release notes](https://github.com/sphinx-contrib/httpdomain/releases)
- [Changelog](https://github.com/sphinx-contrib/httpdomain/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/sphinx-contrib/httpdomain/compare/2.0.0...2.1.0)

---
updated-dependencies:
- dependency-name: sphinxcontrib-httpdomain
  dependency-version: 2.1.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-09 22:56:27 +02:00
dependabot[bot] b44c3d707e Update fakeredis requirement from ==2.38.* to ==2.39.* (#6654)
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.38.0...v2.39.0)

---
updated-dependencies:
- dependency-name: fakeredis
  dependency-version: 2.39.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-09 22:56:17 +02:00
dependabot[bot] 45603c6624 Update css-inline requirement from ==0.21.* to ==0.22.* (#6655)
Updates the requirements on [css-inline](https://github.com/Stranger6667/css-inline) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/css-inline/releases)
- [Changelog](https://github.com/Stranger6667/css-inline/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/css-inline/compare/c-v0.21.0...c-v0.22.0)

---
updated-dependencies:
- dependency-name: css-inline
  dependency-version: 0.22.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-09 22:56:13 +02:00
Richard Schreiber ba36d649c6 Fix sendmail migration activating plugin on events (#6656) 2026-10-08 12:35:41 +02:00
Richard Schreiber ca90b2af09 Fix prefix for event notifications (#6653) 2026-10-08 10:09:38 +02:00
Phin Wolkwitz fe43862fa4 Sendmail: Handle scheduled mails in inactive plugin (Z#23243588) (#6587)
* Activate plugin where rules are currently active (migration)
* Check if plugin is active before sending mails
* Disable rules when the plugin gets (manually) uninstalled
2026-10-07 10:33:16 +02:00
Raphael MichelandRichard Schreiber 411cd764a6 Order changes: Log more information about taxes (#6637)
* Order changes: Log more information about taxes

* Apply batched suggestions from code review

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

* Add to tax_rule change as well

---------

Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
2026-10-07 09:28:35 +02:00
6618e3a039 Organizer-level notifications (#6462)
* Organizer-level notifications

* Update signals

* Update signal docs

* Add tests

* Apply batched suggestions from code review

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

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

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

---------

Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
2026-10-07 09:28:15 +02:00
Richard Schreiber b7cd97c33e API: fix payment confirm if request.data is empty string (#6634)
* API: fix payment confirm if request.data is empty string

* fix fallback value
2026-10-07 09:21:30 +02:00
dependabot[bot] eae3229e68 Update pytest-mock requirement from ==3.15.* to ==3.16.* (#6628)
Updates the requirements on [pytest-mock](https://github.com/pytest-dev/pytest-mock) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.15.0...v3.16.0)

---
updated-dependencies:
- dependency-name: pytest-mock
  dependency-version: 3.16.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-07 09:13:05 +02:00
Raphael Michel d291cd02e7 Docs: Fix invalid example of i18n string 2026-10-07 08:55:26 +02:00
dependabot[bot] e5dcb50229 Bump @vue/server-renderer and vue (#6646)
Bumps [@vue/server-renderer](https://github.com/vuejs/core/tree/HEAD/packages/server-renderer) to 3.5.43 and updates ancestor dependency [vue](https://github.com/vuejs/core). These dependencies need to be updated together.


Updates `@vue/server-renderer` from 3.5.30 to 3.5.43
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits/v3.5.43/packages/server-renderer)

Updates `vue` from 3.5.30 to 3.5.43
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.30...v3.5.43)

---
updated-dependencies:
- dependency-name: "@vue/server-renderer"
  dependency-version: 3.5.43
  dependency-type: indirect
- dependency-name: vue
  dependency-version: 3.5.43
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-07 08:54:01 +02:00
dependabot[bot] 9d333bc2cf Update cryptography requirement from >=50.0.1 to >=50.0.2 (#6648)
Updates the requirements on [cryptography](https://github.com/pyca/cryptography) to permit the latest version.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/50.0.1...50.0.2)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 50.0.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 22:33:33 +02:00
dependabot[bot] 45038d07f3 Bump django-filter from 26.1 to 26.2 (#6647)
Bumps [django-filter](https://github.com/carltongibson/django-filter) from 26.1 to 26.2.
- [Release notes](https://github.com/carltongibson/django-filter/releases)
- [Changelog](https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst)
- [Commits](https://github.com/carltongibson/django-filter/compare/26.1...26.2)

---
updated-dependencies:
- dependency-name: django-filter
  dependency-version: '26.2'
  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>
2026-10-06 22:33:30 +02:00
dependabot[bot] 00f0dfec88 Bump postcss-selector-parser from 7.1.5 to 7.1.6 (#6644)
Bumps [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser) from 7.1.5 to 7.1.6.
- [Release notes](https://github.com/postcss/postcss-selector-parser/releases)
- [Changelog](https://github.com/postcss/postcss-selector-parser/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss-selector-parser/compare/7.1.5...7.1.6)

---
updated-dependencies:
- dependency-name: postcss-selector-parser
  dependency-version: 7.1.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 22:30:52 +02:00
dependabot[bot] 1975a9d891 Bump source-map-js from 1.2.1 to 1.2.2 (#6645)
Bumps [source-map-js](https://github.com/7rulnik/source-map-js) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/7rulnik/source-map-js/releases)
- [Changelog](https://github.com/7rulnik/source-map-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/7rulnik/source-map-js/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: source-map-js
  dependency-version: 1.2.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 22:30:42 +02:00
dependabot[bot] 02b9b6941d Bump smol-toml from 1.7.1 to 1.9.0 (#6643)
Bumps [smol-toml](https://github.com/squirrelchat/smol-toml) from 1.7.1 to 1.9.0.
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.7.1...v1.9.0)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-version: 1.9.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 22:30:34 +02:00
dependabot[bot] da2ac6c386 Update tlds requirement from >=2026072401 to >=2026093003 (#6642)
Updates the requirements on [tlds](https://github.com/kichik/tlds) to permit the latest version.
- [Commits](https://github.com/kichik/tlds/commits)

---
updated-dependencies:
- dependency-name: tlds
  dependency-version: '2026093003'
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 11:39:19 +02:00
Hijiri Umemoto b9fbe699ab Translations: Update Japanese
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Oksigenia 8e3f3cc8c8 Translations: Update Spanish
Currently translated at 100.0% (260 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Oksigenia 945f80b288 Translations: Update Spanish
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 25799da70d Translations: Update Russian
Currently translated at 25.9% (1672 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 6b01023d16 Translations: Update Thai
Currently translated at 100.0% (260 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 98644db976 Translations: Update Thai
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 814ae7f4d9 Translations: Update Russian
Currently translated at 44.6% (116 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 ae39a1e112 Translations: Update Russian
Currently translated at 25.9% (1670 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 61b94f1128 Translations: Update Russian
Currently translated at 41.9% (109 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 dbb586ad5a Translations: Update Russian
Currently translated at 24.3% (1569 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 295b0443fb Translations: Update Thai
Currently translated at 99.9% (6441 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 31031225d3 Translations: Update Thai
Currently translated at 97.7% (6299 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst d600115ea5 Translations: Update Thai
Currently translated at 95.2% (6134 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
CVZ-es 3d9ebb3072 Translations: Update Spanish
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
CVZ-es c9667e77c3 Translations: Update French
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst b2b797f4d3 Translations: Update Thai
Currently translated at 83.2% (5366 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 693adbc727 Translations: Update Thai
Currently translated at 81.3% (5242 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
szurofkamarciidfbe08444ef04788 8d9840be8d Translations: Update Hungarian
Currently translated at 98.7% (6364 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Raphael Michel 73c0c61b6d Translations: Update Swedish
Currently translated at 98.4% (256 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Raphael Michel a813269d1f Translations: Update Swedish
Currently translated at 85.4% (5503 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 d6bc1fa977 Translations: Update Russian
Currently translated at 38.8% (101 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 249edfa2c4 Translations: Update Russian
Currently translated at 20.5% (1322 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
dependabot[bot] 0c28151d30 Update sentry-sdk requirement from ==2.70.* to ==2.71.* (#6632)
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.70.0...2.71.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 10:59:40 +02:00
pajowu 5bb3b7e35e Fix help_text showing as 'None' in orderimport&permissions (#6631) 2026-10-06 10:12:27 +02:00
Richard Schreiber 4119d04fba Add border to and increase text-contrast on disabled button (#6622)
* Add border to and increase text-contrast on disabled button

* Do not add border to disabled btn-link

* Add background to disabled button to further distinguish
2026-10-02 09:15:05 +02:00
84858bc048 Allow mails to be sent to addon-attendees (Z#23213551) (#6235)
Remove restrictions that prevent mails to be sent to addon-product-attendees while reducing amount of mails sent to the same email-addresses

Co-authored-by: Richard Schreiber <schreiber@rami.io>
Co-authored-by: Kara Engelhardt <engelhardt@pretix.eu>
2026-10-01 13:13:55 +02:00
Raphael Michel e8c091741b Invoice export: Fix crash PRETIXEU-FGE 2026-10-01 11:43:36 +02:00
Richard Schreiber ebdfd21b0f Fix JavaScript error description passing HTML (#6630) 2026-10-01 11:28:36 +02:00
pajowuandRichard Schreiber ed0b3cab7f Fix addon legend overlapping with profile checkbox (Z#23247075) (#6568)
* Fix addon legend overlapping with profile checkbox (Z#23247075)

* Update src/pretix/static/pretixpresale/scss/_checkout.scss

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

---------

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
2026-10-01 09:49:23 +02:00
Richard Schreiber 3dd8cc5862 PDF Editor: select newly created element after insert (#6596)
* PDF Editor: select newly created element after insert

* Do not create so many savepoints
2026-09-30 12:19:03 +02:00
dependabot[bot] 23e9ca8f34 Bump brace-expansion (#6625)
Bumps  and [brace-expansion](https://github.com/juliangruber/brace-expansion). These dependencies needed to be updated together.

Updates `brace-expansion` from 5.0.4 to 5.0.12
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.4...v5.0.12)

Updates `brace-expansion` from 2.0.2 to 2.1.7
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.4...v5.0.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.12
  dependency-type: indirect
- dependency-name: brace-expansion
  dependency-version: 2.1.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-30 10:53:41 +02:00
Raphael Michel e19afd39fb Bump version to 2026.9.0.dev0 2026-09-30 10:50:09 +02:00
Raphael Michel f7863c9fee Bump version to 2026.8.0 2026-09-30 10:50:03 +02:00
Raphael Michel fc3150fecc Revert "DE: Update Wordlist"
This reverts commit 89318bcc24.
2026-09-30 10:49:57 +02:00
Martin Gross 89318bcc24 DE: Update Wordlist 2026-09-30 10:24:13 +02:00
dependabot[bot] a388391f06 Update pyjwt requirement from ==2.14.* to ==2.15.* (#6620) 2026-09-30 10:12:38 +02:00
Raphael Michel b4d000379c Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Phin Wolkwitz ba9bba49c6 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Daniel Musketa 824624cce6 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Phin Wolkwitz 8601f62581 Translations: Update German
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Daniel Musketa 7547d64dea Translations: Update German
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Matthias Schrumpf d1186c83ef Translations: Update German
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Raphael Michel e4782ff217 Translations: Update German
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Matthias Schrumpf 3328dc28c7 Translations: Update German
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Raphael Michel 6c83b4288a Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Raphael Michel 6ff37796c3 Translations: Update German
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Raphael Michel fa60c320b9 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Raphael Michel ca9d9faef4 Translations: Update German
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-09-30 10:10:52 +02:00
Raphael Michel 9718d1ee83 Translations: Update wordlist 2026-09-30 10:09:23 +02:00
pajowu 7bdb2c1555 Email: always use quoted-printable (#6617) 2026-09-30 09:54:21 +02:00
Raphael Michel da6753b53f Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@pretix.eu>
2026-09-30 09:26:26 +02:00
CVZ-es 33df9d13be Translations: Update French
Currently translated at 100.0% (6419 of 6419 strings)

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

powered by weblate
2026-09-30 09:25:43 +02:00
Nate Horst 582f9ac642 Translations: Update Thai
Currently translated at 81.1% (5206 of 6419 strings)

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

powered by weblate
2026-09-30 09:25:00 +02:00
Translate pretix user 594 5ce2957965 Translations: Update Russian
Currently translated at 20.0% (1285 of 6419 strings)

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

powered by weblate
2026-09-30 09:25:00 +02:00
Ыукпун eb9d6ad9a3 Translations: Update Russian
Currently translated at 20.0% (1285 of 6419 strings)

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

powered by weblate
2026-09-30 09:25:00 +02:00
Raphael Michel d339e1d594 Hotfix: Perform validation on the selected step, not on all steps before 2026-09-29 20:11:51 +02:00
Raphael Michel cb15559ac6 Scheduled export: Fix copying of email subject and text (Z#23221224) (#6616) 2026-09-29 16:44:42 +02:00
pajowu 3c95f1fee7 Add HERMA 9012 90 x 90mm Badge format (Z#23248189) (#6619) 2026-09-29 16:44:02 +02:00
Richard Schreiber 6a380c9356 Presale: add js-helper to disable submit for x seconds (#6592)
* Presale: add js-helper to disable submit for x seconds

* Remove disabled-class from button, if any

* Use Intl.RelativeTimeFormat for time formatting
2026-09-29 16:36:09 +02:00
Kara Engelhardt eeea01b31a Fix linter errors 2026-09-29 15:34:26 +02:00
Kara Engelhardt 470621b5cb Add missing licenseheader 2026-09-29 15:34:23 +02:00
dependabot[bot] 5802153101 Update sentry-sdk requirement from ==2.69.* to ==2.70.* (#6600)
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.69.0...2.70.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 15:33:24 +02:00
Nate Horst e9f89d5d92 Translations: Update Thai
Currently translated at 70.0% (4494 of 6419 strings)

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

powered by weblate
2026-09-29 15:33:15 +02:00
Nate Horst 695689ed06 Translations: Update Thai
Currently translated at 98.8% (257 of 260 strings)

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

powered by weblate
2026-09-29 15:33:15 +02:00
Nate Horst 12d405d861 Translations: Update Thai
Currently translated at 69.5% (4463 of 6419 strings)

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

powered by weblate
2026-09-29 15:33:15 +02:00
Nate Horst 649eee3025 Translations: Update Thai
Currently translated at 65.8% (4230 of 6419 strings)

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

powered by weblate
2026-09-29 15:33:15 +02:00
Tim 149b4f23aa Translations: Update Spanish
Currently translated at 100.0% (6419 of 6419 strings)

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

powered by weblate
2026-09-29 15:33:15 +02:00
Nate Horst fa40ccc623 Translations: Update Thai
Currently translated at 55.9% (3593 of 6419 strings)

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

powered by weblate
2026-09-29 15:33:15 +02:00
Mira Weller 2ba74b1697 Fix various redirects for events on MODE_ORG_ALT_DOMAIN 2026-09-29 14:34:49 +02:00
Mira Weller e54cd6af44 Fix redirect to customer login page when:
- event on custom domain (MODE_EVENT_DOMAIN or MODE_ORG_ALT_DOMAIN)
- organizer on system domain
2026-09-29 14:34:49 +02:00
Mira Weller d2b58d428e Fix customer logout when:
- event on custom domain (MODE_EVENT_DOMAIN or MODE_ORG_ALT_DOMAIN)
- organizer on system domain
2026-09-29 14:34:49 +02:00
Raphael Michel 74332faa7b [SECURITY] API: Fix session validation for uploaded files (CVE-2026-101269, Z#23247174) 2026-09-29 14:30:54 +02:00
Mira Weller bbf391f7d5 Block out of bounds image crop dimensions (Z#23245937 / PRT-009) 2026-09-29 14:30:54 +02:00
Mira Weller d78d5b52fa Prevent parsing non-standard-compliant JSON float values (Z#23245937 / PRT-021) 2026-09-29 14:30:54 +02:00
Mira Weller 9c0fef3d72 Fix potential infinite loop in pdf render (Z#23245937 / PRT-021) 2026-09-29 14:30:54 +02:00
Mira Weller c7d6630979 Fix inefficient loop in compute_validity (Z#23245937 / PRT-013) 2026-09-29 14:30:53 +02:00
Raphael Michel 4edd3c4654 [SECURITY] OAuth: Disable existing tokens when deactivating Application (CVE-2026-101271, Z#23247296) 2026-09-29 14:30:53 +02:00
Mira Weller b1ae638394 [SECURITY] Escape help texts (CVE-2026-101270) 2026-09-29 14:30:53 +02:00
Raphael Michel ae9bb68645 [SECURITY] Fix customer session fixation on cross-domain login (CVE-2026-101268, Z#23247268) 2026-09-29 14:30:53 +02:00
Raphael Michel 48f5a7c8cc [SECURITY] Fix information leak in widgets.json on dashboard (CVE-2026-101267, Z#23247172)
Thanks to Wenhao Wu, Southeast University
2026-09-29 14:30:53 +02:00
Raphael Michel d43032274b [SECURITY] Fix checkout validation bypass (CVE-2026-101266, Z#23245008) 2026-09-29 14:30:53 +02:00
Raphael Michel ae9a744fd9 Revert "Always base64-encode email attachments (Z#23242540) (#6476)"
This reverts commit ff4d3cd403.
2026-09-29 14:07:57 +02:00
pajowu ff4d3cd403 Always base64-encode email attachments (Z#23242540) (#6476)
It might sound unneccesary to base64-encode plaintext attachments, but some smtp providers modify them otherwise
2026-09-29 12:27:43 +02:00
pajowu 3440ee16f2 Plugin List: Apply search filter if prefilled by browser (#6543)
* Plugin List: Apply searchfilter if prefilled by browser

Browsers often prefill the form fields, e.g. with their old content when reloading or going back from the previous page. However these filters were not applied until you changed one of the form fields

* Fix linting errors in plugins.js

* Apply review suggestions
2026-09-29 11:51:29 +02:00
pajowu 43e5b62db3 Banktransfer: Make actionvie atomic (Z#23246414) (#6604) 2026-09-29 11:51:12 +02:00
dependabot[bot] b249c7b417 Bump markdown from 3.10.3 to 3.11 (#6608)
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.10.3 to 3.11.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.10.3...3.11.0)

---
updated-dependencies:
- dependency-name: markdown
  dependency-version: '3.11'
  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>
2026-09-29 10:17:48 +02:00
dependabot[bot] b68c324389 Update flake8 requirement from ==7.3.* to ==7.4.* (#6609)
Updates the requirements on [flake8](https://github.com/pycqa/flake8) to permit the latest version.
- [Commits](https://github.com/pycqa/flake8/compare/7.3.0...7.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 10:17:29 +02:00
Aodhán Burke 4b320b6dab Fix typo: then -> than (#6615) 2026-09-29 10:16:54 +02:00
Richard Schreiber df74cbf5fc Remove Vue2-based widget (#6610)
* Remove Vue2-based widget

* move floatformat.js

* Delete docready.js

* Update widget.py
2026-09-29 10:14:21 +02:00
pajowu c875d758f9 Fix crash on too old time machine date (Z#23245824) (#6599)
* Fix crash on too old time machine date (Z#23245824)

dateutil.parser seems to reject dates with second-precision timezone-offsets, which datetime produces for some dates aroung the year 200. datetime.fromisoformat has no problem parsing them

* Review comments

* Add min date
2026-09-28 17:28:26 +02:00
Martin Gross 2ebdd048c2 PayPal2: Drop maximum length of client ID (Rel: #6424) 2026-09-28 16:12:55 +02:00
Raphael Michel ca40b09080 Fix API documentation bug 2026-09-28 15:46:41 +02:00
pajowuandRaphael Michel 558bf910fd Use fragment_product_list in voucher redemption view (Z#23246929) (#6567)
* Use fragment_product_list in voucher redemption view (Z#23246929)

* Formatting

* Fix usage in templates

* Review comments

* handle form prefix in fragment_product_list.html

---------

Co-authored-by: Raphael Michel <michel@pretix.eu>
2026-09-28 13:01:15 +02:00
pajowu 7e1e472691 Monkeypatch stock csrfmiddleware (#6401)
* Monkeypatch stock csrfmiddleware

* Add test for ensure_csrf_cookie
2026-09-28 11:46:23 +02:00
Richard Schreiber b06c9ef463 Control: do not fail on missing reason when user confirm (#6602) 2026-09-28 11:43:52 +02:00
pajowu 2af8d29ca9 Add pdf render tests (#6566)
* Add pdf tests

* pdf tests: Save temporary pdfs for failed test debugging

* Review comments
2026-09-25 16:54:58 +02:00
Raphael Michel 2791d5e49e Celery: Fix missing log if workers are recycled due to RAM usage (#6588) 2026-09-25 08:56:49 +02:00
Richard Schreiber ed68719731 Widget: fix calendar view showing old events due to non-unique key (Z#23247433) (#6585)
* Widget: fix calendar view showing old events due to cache-key (Z#23247433)

* Update EventCalendarCell.vue
2026-09-24 12:00:57 +02:00
9aeb4b9731 Organizer API: Add endpoint for event-meta-properties
* Add API-endpoint for event-meta-properties

* Add new doc-file to index, fix spelling and description

* Fix logentry

* Fix logentry again

* validate and add tests

* add meta_properties from organizer only

* fix choices validation

* filter unknown keys from choices due to django-formsets

* Apply batched suggestions from code review

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

* add safe-guard normalization to None to to_representation

* Apply batched suggestions from code review

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>

* update tests to check for error-messages as well

* fix flake8

* fix permission tests

* Make ObjectListField more flexibel for re-use

* fix validation result

* Improve validation

* Change to I18nField for validation

* update MetaPropertyDictField

* fix docs for i18n strings

* make label_child configurable if MetaPropertyDictField should contain non-localized stuff

* undo test changes in events test

* fix flake8

* Apply batched suggestions from code review

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>

* improve code formatting

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2026-09-24 10:51:23 +02:00
Minding 9324887790 Translate OAuth buttons (#6590)
* Translate OAuth buttons

* Revert .po changes
2026-09-24 09:52:47 +02:00
Nate Horst d353384555 Translations: Update Thai
Currently translated at 53.0% (3407 of 6419 strings)

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

powered by weblate
2026-09-24 09:16:49 +02:00
Raphael Michel b8f8e49cce API: Add additional tests for modifying meta data (Z#23247634) 2026-09-23 19:01:52 +02:00
Raphael Michel 806d0a5748 User details: Show list of 2FA devices and allow to reset drift (#6569)
* User details: Show list of 2FA devices

* Add reset button

* Reset throttle

* Refactoring

* Fix delete paths in tests
2026-09-23 17:51:10 +02:00
Raphael Michel 69e541e5af Product list: Fix edge case in dependent availabilities (Z#23243701) (#6532) 2026-09-23 17:22:54 +02:00
Raphael Michel 0723e6015c Event header: Remove date directly above other date (Z#23245589) (#6524)
* Event header: Fix wrapping of date in h2 header (Z#23245589)

This was already correct if the header was a h1

* Revert "Event header: Fix wrapping of date in h2 header (Z#23245589)"

This reverts commit 3d25316fe3.

* Event header: Remove date directly above other date

This removes the date from the <h2> option used when a header image is
uploaded and the header should still be shown. Rendering the date here
makes little sense, since the infobox with the date is always just a few
lines below. It still makes sense for the <h1> option where it becomes
part of the site header.
2026-09-23 15:54:47 +02:00
Raphael Michel b8e1ab8258 Voucher API: Add search parameter (Z#23240589) (#6523) 2026-09-23 15:54:44 +02:00
Raphael Michel 81764278ae Email settings: Collapse attendee texts if unused (Z#23243417) (#6535) 2026-09-23 14:00:56 +02:00
Nate Horst f3068e627a Translations: Update Thai
Currently translated at 52.2% (3356 of 6419 strings)

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

powered by weblate
2026-09-23 11:41:45 +02:00
Nate Horst 993a3f96e9 Translations: Update Thai
Currently translated at 52.1% (3346 of 6419 strings)

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

powered by weblate
2026-09-23 11:41:45 +02:00
Nate Horst 1af0b5e442 Translations: Update Thai
Currently translated at 51.8% (3330 of 6419 strings)

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

powered by weblate
2026-09-23 11:41:45 +02:00
Nate Horst 71b6bf630f Translations: Update Thai
Currently translated at 45.6% (2930 of 6419 strings)

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

powered by weblate
2026-09-23 11:41:45 +02:00
Raphael MichelandMartin Gross 59026da06b Delete PayPal integration v1 (#6530)
* Delete PayPal integration v1

Leaving only migrations behind

* Try to fix CI

* Fix tests some more

* Rename more plugin name instances in tests and docs

---------

Co-authored-by: Martin Gross <martin@pc-coholic.de>
2026-09-23 11:38:16 +02:00
Raphael Michel b3e0892d76 Conditionally import sentry SDK during error page rendering 2026-09-23 11:33:05 +02:00
Raphael Michel 3029a6839b Prevent Sentry SDK from being loaded during tests 2026-09-23 11:29:28 +02:00
Raphael Michel a826b0a1bd Customer area: Hide password change link for SSO users (fixes #6572) (#6580) 2026-09-22 15:22:43 +02:00
Raphael Michel 7d7474c07d Refund form: Do not allow individual negative amounts (#6579) 2026-09-22 12:47:06 +02:00
Raphael Michel 58023381b9 Merge branch 'refund-offset-perm-check' into 'master'
Offset refund: Do not allow to offset to order without access to

See merge request pretix/pretix!52
2026-09-22 12:46:20 +02:00
Raphael Michel e5c44e7aed Offset refund: Do not allow to offset to order without access to 2026-09-22 12:46:20 +02:00
Raphael Michel d77a179606 2FA: Show info on throttled request (#6581)
* 2FA: Show info on throttled request

* Round up the time
2026-09-22 10:38:26 +02:00
Raphael Michel 29614db1e2 InvoiceShredder: Fix shredding of new fields (#6577)
* InvoiceShredder: Fix shredding of new fields

* Update src/pretix/base/migrations/0311_fix_unshredded_invoices.py
2026-09-22 10:37:58 +02:00
Raphael Michel 365051cc21 Event metadata: Fix server-side validation of write protection (#6578) 2026-09-22 10:00:15 +02:00
Raphael Michel 5c6b0eef6f Plugin configuration: Allow organizers to see all events (#6576) 2026-09-22 09:51:18 +02:00
Raphael Michel bc0a6b662b Checkout: Fix VAT ID revalidation after country change (#6575) 2026-09-22 09:51:10 +02:00
dependabot[bot]andRichard Schreiber 0bc1aed1a0 Update pypdf requirement from ==6.11.* to ==6.19.* (#6570)
* Update pypdf requirement from ==6.11.* to ==6.18.*

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/6.11.0...6.18.1)

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

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

* Update pyproject.toml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
2026-09-22 09:38:28 +02:00
Rita Gimenez 0eaa5a081c Translations: Update Catalan
Currently translated at 29.4% (1892 of 6419 strings)

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

powered by weblate
2026-09-21 14:56:27 +02:00
Nate Horst 688c434c4f Translations: Update Thai
Currently translated at 37.1% (2387 of 6419 strings)

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

powered by weblate
2026-09-21 14:56:27 +02:00
dependabot[bot]andRaphael Michel c0474604a1 Update isort requirement from ==8.0.* to ==9.0.* (#6510)
* Update isort requirement from ==8.0.* to ==9.0.*

Updates the requirements on [isort](https://github.com/PyCQA/isort) to permit the latest version.
- [Release notes](https://github.com/PyCQA/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PyCQA/isort/compare/8.0.0...9.0.1)

---
updated-dependencies:
- dependency-name: isort
  dependency-version: 9.0.1
  dependency-type: direct:development
...

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

* Run isort

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Raphael Michel <michel@pretix.eu>
2026-09-21 14:56:14 +02:00
dependabot[bot] 1741d08c2c Update cryptography requirement from >=50.0.0 to >=50.0.1 (#6509)
Updates the requirements on [cryptography](https://github.com/pyca/cryptography) to permit the latest version.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/50.0.0...50.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 50.0.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-21 14:14:06 +02:00
dependabot[bot] 6bbd808aaa Bump postcss-selector-parser from 7.1.1 to 7.1.5 (#6513)
Bumps [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser) from 7.1.1 to 7.1.5.
- [Release notes](https://github.com/postcss/postcss-selector-parser/releases)
- [Changelog](https://github.com/postcss/postcss-selector-parser/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss-selector-parser/compare/v7.1.1...7.1.5)

---
updated-dependencies:
- dependency-name: postcss-selector-parser
  dependency-version: 7.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-21 14:13:46 +02:00
dependabot[bot] aa90c5b3b1 Bump @humanfs/node from 0.16.7 to 0.16.8 (#6517)
Bumps [@humanfs/node](https://github.com/humanwhocodes/humanfs/tree/HEAD/packages/node) from 0.16.7 to 0.16.8.
- [Release notes](https://github.com/humanwhocodes/humanfs/releases)
- [Changelog](https://github.com/humanwhocodes/humanfs/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/humanwhocodes/humanfs/commits/node-v0.16.8/packages/node)

---
updated-dependencies:
- dependency-name: "@humanfs/node"
  dependency-version: 0.16.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-21 14:13:39 +02:00
dependabot[bot] bfbabe0e58 Update fakeredis requirement from ==2.37.* to ==2.38.* (#6563)
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.37.0...v2.38.0)

---
updated-dependencies:
- dependency-name: fakeredis
  dependency-version: 2.38.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-21 14:08:32 +02:00
Raphael Michelandpajowu aa14505d2c Money representation in templates: Allow more precision (#6454)
* Money representation in templates: Allow more precision

When rendering money in templates, we used to have the following logic:

- When the decimal places fit the currency, render with Babel
- When they don't, e.g. we stored 123.67 JPY, even though there are no
  fractional Yens, render without Babel with a custom format, but render
  the fractional Yens because we'd rather *show* wrong data and make the
  bug obvious than hide it.

However, we only did that up to a prevision of two places, we never
showed more. This is still sufficient for core pretix, but we have
plugins that need to operate in fractional cents. Also, we CAN render
everything through babel for consistent formatting.

There is one **risk**: This might cause weird results on SQLite. Since
SQLite has no concept of precise decimal math, results of in-SQL
computations can sometimes experience floating point errors and show
with A LOT of decimal palces. This used to be invisible since the UI
performed the rounding. With this PR – not any more. We'll need to see
how annoying it is, but it should only affect development mode.

This PR also fixes a bug in tax_rate_format that for some reason did not
do what it was supposed to do, even though I tested it back then, weird.
Might even be a Python version thing?

* Update src/pretix/base/templatetags/money.py

Co-authored-by: pajowu <engelhardt@pretix.eu>

* Apply suggestion from @pajowu

Co-authored-by: pajowu <engelhardt@pretix.eu>

* Fix typing stuff

* Fix precision issue

---------

Co-authored-by: pajowu <engelhardt@pretix.eu>
2026-09-18 11:49:12 +02:00
Kara Engelhardt df69656364 PDF: Fix background page being rotated multiple times
transfer_rotation_to_content (or rather add_transformation/replace_content) do not properly duplicate the contentstream they're modifying. this means that if you pass a pdf-level copy of the same bg_page multiple times, the content will be rotated multiple times but the rotation-property ('/Rotate') will only be changed for the first page

pypdfs docs explicitly say that you should not to add_transformation + merge, but instead use merge_transformed_page, which this now does for everything
2026-09-18 11:35:01 +02:00
Kara Engelhardt 82cffd1519 pdf: calculate sizebox based on cropbox and mediabox 2026-09-18 11:33:34 +02:00
Richard Schreiber 7fc527135d PDF: use artbox before trimbox before mediabox from background-PDF as size for canvas 2026-09-18 11:33:34 +02:00
Raphael Michel 8e39b67ee9 Fix missing translatable field label 2026-09-17 11:56:12 +02:00
dependabot[bot] 289cbe5bfc Update django-countries requirement from ==9.0.* to ==9.1.* (#6562)
Updates the requirements on [django-countries](https://github.com/SmileyChris/django-countries) to permit the latest version.
- [Changelog](https://github.com/SmileyChris/django-countries/blob/main/CHANGES.md)
- [Commits](https://github.com/SmileyChris/django-countries/compare/v9.0.0...v9.1.0)

---
updated-dependencies:
- dependency-name: django-countries
  dependency-version: 9.1.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-17 10:43:55 +02:00
Raphael Michel 7143b877c8 Translations: Update German
Currently translated at 100.0% (6419 of 6419 strings)

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

powered by weblate
2026-09-16 15:46:28 +02:00
dependabot[bot] e2a78100af Update pyjwt requirement from ==2.13.* to ==2.14.* (#6557)
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.13.0...2.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-16 15:46:16 +02:00
dependabot[bot] ca77467e48 Update sentry-sdk requirement from ==2.68.* to ==2.69.* (#6556)
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.68.0...2.69.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-16 15:46:07 +02:00
Raphael Michel d3cbfcd4da Invoice export: Add transmission-specific data (Z#23239732) (#6522) 2026-09-15 16:44:00 +02:00
Richard Schreiber 3648ef28d7 Fix deanonymizing emails with multiple dots (#6559) 2026-09-15 13:16:36 +02:00
Richard Schreiber 689b99c3aa PDF: fix inline merge_background on fg_pdf instead of its own PdfWriter (#6555)
* PDF: fix inline merge_background on fg_pdf instead of its own PdfWriter

* fix pdf_header

* fix flake8
2026-09-14 15:45:54 +02:00
Raphael Michel 6c40daa863 Hotfix for JS issue when submit() is called from JS 2026-09-14 15:33:25 +02:00
dependabot[bot]andRaphael Michel efdc83f72a Update pypdf requirement from ==6.5.* to ==6.11.* (#6176)
* Update pypdf requirement from ==6.5.* to ==6.11.*

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/6.5.0...6.11.0)

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

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

* Try to adjust code

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Raphael Michel <michel@rami.io>
2026-09-14 14:09:28 +02:00
dependabot[bot] 8b10534c74 Bump smol-toml from 1.6.1 to 1.7.1 (#6546)
Bumps [smol-toml](https://github.com/squirrelchat/smol-toml) from 1.6.1 to 1.7.1.
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.6.1...v1.7.1)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-version: 1.7.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-14 10:31:12 +02:00
Raphael Michel bcacff2ee7 Voucher list: Add budget and budget used to CSV export (Z#23237857) (#6520)
* Voucher list: Add budget and budget used to CSV export (Z#23237857)

* Fix failing test
2026-09-14 10:31:07 +02:00
Kara Engelhardt 47096f7f1b sendmail: fix query performance when filtering on checkin (Z#23244045) 2026-09-14 10:24:58 +02:00
Raphael Michel c240ebcb8e Force Django update 2026-09-14 09:51:32 +02:00
Raphael Michel ce3e59e020 PDF reports: Fix headers for overlong event names (Z#23239566) (#6521) 2026-09-14 09:38:44 +02:00
Raphael Michel 4a9da40028 [A11y] Form JavaScript: Support for <button formnovalidate> (#6536) 2026-09-14 09:38:34 +02:00
Raphael Michelandpajowu eab1efd5f4 Phone number input: Fix workaround for prefixes shared between countries (fixes #6544) (#6545)
* Phone number input: Fix workaround for prefixes shared between countries (fixes #6544)

* Update src/pretix/base/forms/questions.py

Co-authored-by: pajowu <engelhardt@pretix.eu>

---------

Co-authored-by: pajowu <engelhardt@pretix.eu>
2026-09-14 09:35:32 +02:00
Richard Schreiber c7290b614e Show payment-QR-codes on mobile (#6503) 2026-09-14 08:50:45 +02:00
Raphael Michel 1aea5633eb Discounts: Fix ManyToMany fields when cloning (#6547) 2026-09-11 18:35:37 +02:00
Richard Schreiber 86cc719651 Widget: serve vite by default, origin-whitelist for vue2 (#6403) 2026-09-11 08:53:42 +02:00
Mira Weller 742a69f09f Add git-blame-ignore-revs file (see #6541) 2026-09-10 12:54:51 +02:00
Yasunobu YesNo Kawaguchi c05dee7af5 Translations: Update Japanese
Currently translated at 100.0% (6419 of 6419 strings)

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

powered by weblate
2026-09-10 12:36:49 +02:00
Albizuri e399a49c8b Translations: Update Basque
Currently translated at 48.0% (3086 of 6419 strings)

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

powered by weblate
2026-09-10 12:36:49 +02:00
Linnea Thelander b9b951e56b Translations: Update Swedish
Currently translated at 98.0% (255 of 260 strings)

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

powered by weblate
2026-09-10 12:36:49 +02:00
Linnea Thelander 88bb1f4008 Translations: Update Swedish
Currently translated at 85.8% (5509 of 6419 strings)

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

powered by weblate
2026-09-10 12:36:49 +02:00
Renne Rocha 24e7db7ddd Translations: Update Portuguese (Brazil)
Currently translated at 92.4% (5937 of 6419 strings)

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

powered by weblate
2026-09-10 12:36:49 +02:00
Hijiri Umemoto 96ed6fe1a6 Translations: Update Japanese
Currently translated at 100.0% (6419 of 6419 strings)

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

powered by weblate
2026-09-10 12:36:49 +02:00
Raphael MichelandKara Engelhardt d85e52c83e ESlint: Ignore vendored and pre-vue code (#6541)
* ESlint: Ignore vendored and pre-vue code

* Format pre-vue code with eslint where possible

---------

Co-authored-by: Kara Engelhardt <engelhardt@pretix.eu>
2026-09-10 12:35:10 +02:00
Raphael Michel d9b5fa5b16 Fix incomplete warning message 2026-09-10 12:16:10 +02:00
Kara Engelhardt fb948cc7f4 sendmail: small performance winds in checkin query 2026-09-10 11:17:32 +02:00
Kara Engelhardt c358f412fd sendmail: code cleanup 2026-09-10 11:17:32 +02:00
Kara Engelhardt b78de6386b sendmail: fix quadratic checkin query 2026-09-10 11:17:32 +02:00
Raphael MichelandRichard Schreiber 50c00b47ae Event index: Add subevent time and name to <title> (Z#23246174) (#6542)
* Event index: Add subevent time and name to <title> (Z#23246174)

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

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

---------

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
2026-09-10 10:05:55 +02:00
Ferdinand Malcher 6025c7942f Product pictures: fix CSS to always fit image to viewport (#6527) 2026-09-09 14:10:15 +02:00
dependabot[bot] 23a3412154 Update django-statici18n requirement from ==2.7.* to ==2.8.* (#6529)
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.7.0...v2.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-09 14:08:54 +02:00
Raphael MichelandRichard Schreiber 3635f6fb0c Widget: Add data-fix equivalent for attendee data (Z#23202714) (#6533)
* Widget: Add data-fix equivalent for attendee data (Z#23202714)

* Update src/pretix/presale/checkoutflow.py

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

* Update src/pretix/presale/checkoutflow.py

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

* Update src/pretix/presale/checkoutflow.py

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

---------

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
2026-09-09 14:06:41 +02:00
Kara Engelhardt 2aadfa2ce4 RRuleForm: allow selecting the fourth occurence of a weekday of a month (Z#23245893)
The fourth often is the same as the last, but not always. A weekday can appear five times in a month
2026-09-09 12:21:42 +02:00
Raphael Michel dd0d78242e PDF editor: Harden cached file access for background (Z#23242291) (#6446) 2026-09-09 11:13:29 +02:00
Raphael Michelandluelista 42c5de895c CachedFileField: Bind file access to session (Z#23242301) (#6447)
* CachedFileField: Bind file access to session (Z#23242301)

* Apply suggestion from @luelista

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

---------

Co-authored-by: luelista <weller@rami.io>
2026-09-09 09:52:40 +02:00
Raphael Michelandluelista e91718e73b Password reset: Prevent race condition that uses token twice (Z#23242132) (#6448)
* Password reset: Prevent race condition that uses token twice

* Apply suggestion from @luelista

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

---------

Co-authored-by: luelista <weller@rami.io>
2026-09-09 09:52:38 +02:00
Martin GrossandRaphael Michel 2c15d8b074 Event Settings: Make contact_url and imprint_url I18nURLField (Z#23238623) (#6506)
* Event Settings: Make contact_url and imprint_url I18nURLField (Z#23238623)

* isort

* Update doc/api/resources/events.rst

---------

Co-authored-by: Raphael Michel <michel@pretix.eu>
2026-09-09 09:14:37 +02:00
Raphael MichelandLukas Bockstaller edb4069e18 Payment step: Allow to postpone payment choice on some sales channels (#6516)
* Payment step: Allow to postpone payment choice on some sales channels

* Add tests

* handle payment provider (de-)selection and partial payments (#6526)

---------

Co-authored-by: Lukas Bockstaller <bockstaller@pretix.eu>
2026-09-08 09:32:15 +02:00
Raphael MichelandRichard Schreiber dc7d5c6029 Event calendar: consider events without products "not available" for filtering (#6515)
* Event calendar: consider events without products "not available" for filtering

Also, drop the help text of the flag that is no longer accurate.

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

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

---------

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
2026-09-04 16:29:46 +02:00
Raphael Michel caa6fb187b VAT ID validation: Alternative API for German shops (#6507) 2026-09-02 17:48:38 +02:00
Lukas Bockstaller 7d93cae2a9 set the _required attribute on the ModelMultipleChoiceField (#6514)
setting _required forces the CheckoutFieldRenderer to add the "required" bit to the label.
We only need to remove it from the widget attrs to avoid the html input field validation that would force us to check every single box
2026-09-02 10:35:19 +02:00
Richard Schreiber 58a58eff83 Widget: fix show taxline for 0 or 0.00 tax-rate (#6502) 2026-09-01 13:19:32 +02:00
Martin Gross a84a02c298 Vite: Add dev CORS (#6511) 2026-09-01 11:22:19 +02:00
429 changed files with 47453 additions and 38619 deletions
+2
View File
@@ -0,0 +1,2 @@
# Format pre-vue code with eslint where possible (2026-09-10)
d85e52c83ed3639e040372fd0052e842e4899d90
+2
View File
@@ -7,6 +7,7 @@ on:
- 'src/pretix/static/pretixpresale/widget/**'
- 'src/pretix/static/pretixcontrol/js/ui/checkinrules/**'
- 'src/pretix/plugins/webcheckin/**'
- 'src/pretix/plugins/wallet/**'
- 'eslint.config.mjs'
- 'package.json'
- 'package-lock.json'
@@ -16,6 +17,7 @@ on:
- 'src/pretix/static/pretixpresale/widget/**'
- 'src/pretix/static/pretixcontrol/js/ui/checkinrules/**'
- 'src/pretix/plugins/webcheckin/**'
- 'src/pretix/plugins/wallet/**'
- 'eslint.config.mjs'
- 'package.json'
- 'package-lock.json'
+1 -2
View File
@@ -16,8 +16,6 @@ recursive-include src/pretix/plugins/banktransfer/templates *
recursive-include src/pretix/plugins/banktransfer/static *
recursive-include src/pretix/plugins/manualpayment/templates *
recursive-include src/pretix/plugins/manualpayment/static *
recursive-include src/pretix/plugins/paypal/templates *
recursive-include src/pretix/plugins/paypal/static *
recursive-include src/pretix/plugins/paypal2/templates *
recursive-include src/pretix/plugins/paypal2/static *
recursive-include src/pretix/plugins/src/pretixdroid/templates *
@@ -44,6 +42,7 @@ recursive-include src *.py
recursive-include src *.svg
recursive-include src *.txt
recursive-include src Makefile
recursive-include src *.pdf
recursive-exclude doc *
recursive-exclude deployment *
+1 -1
View File
@@ -173,7 +173,7 @@ Datetime String in ISO 8601 format ``"2017-12-27T10:00:00Z"``
with timezone (normally UTC) ``"2017-12-27T10:00:00.596934Z"``,
``"2017-12-27T10:00:00+02:00"``
Date String in ISO 8601 format ``2017-12-27``
Multi-lingual string Object of strings ``{"en": "red", "de": "rot", "de_Informal": "rot"}``
Multi-lingual string Object of strings ``{"en": "red", "de": "rot", "de-informal": "rot"}``
Money String with decimal number ``"23.42"``
Currency String with ISO 4217 code ``"EUR"``, ``"USD"``
Relative datetime *either* String in ISO 8601 ``"2017-12-27T10:00:00.596934Z"``,
+2 -2
View File
@@ -53,6 +53,8 @@ Checking a ticket in
Defaults to ``false`` in which case the server will determine the language (currently
the event default language, might change in the future with support for the
``Accept-Language`` header).
:<json boolean simulate: Do not actually perform the check-in, only simulate the response. The ``position`` response
object will not reflect the simulated changes.
:>json string status: ``"ok"``, ``"incomplete"``, ``"exchange"``, or ``"error"``
:>json string reason: Reason code, only set on status ``"error"``, see below for possible values.
:>json string reason_explanation: Human-readable explanation, only set on status ``"error"`` and reason ``"rules"``, can be null.
@@ -71,8 +73,6 @@ Checking a ticket in
:>json object questions: List of questions to be answered for check-in, only set on status ``"incomplete"``.
:>json object media_policy: Reusable media policy (see documentation on items), only set on status ``"exchange"``.
:>json object media_type: Reusable media type (see documentation on items), only set on status ``"exchange"``.
:>json boolean simulate: Do not actually perform the check-in, only simulate the response. The ``position`` response
object will not reflect the simulated changes.
**Example request**:
+241
View File
@@ -0,0 +1,241 @@
Event Meta Properties
=====================
Resource description
--------------------
An event meta property is used to to define meta information fields for its events.
This information can be re-used, for example, in ticket layouts.
The event meta property resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Unique ID for this property
name string Name of the property
default string Value of the default option
required boolean If ``true``, an event can only be taken live if the
property is set. In event series, it's always optional
to set a value for individual dates
protected boolean If ``true``, the value for an event can only be changed
by organizer-level administrators
filter_public boolean If ``true``, this property will be shown to filter
events in the public event list and calendar
public_label string Public name of the property
filter_allowed boolean If ``true``, this property will be shown to filter
events or reports in the backend, and it can also be
used for hidden filter parameters in the frontend
choices list of objects List of JSON objects representing all permitted values
for this property, or ``null`` for no limitation.
Each choice object has a required internal name named
``key`` and optional public name named ``label``
consisting of a dictionary of i18n string translations
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/event_meta_properties/
Returns a list of all meta properties for the organizer.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/meta_properties/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "Color",
"default": "blue",
"required": false,
"protected": false,
"filter_public": false,
"public_label": {},
"filter_allowed": true,
"choices": [
{
"key": "blue",
"label": {
"en": "Blue"
},
}
]
}
]
}
:param organizer: The ``slug`` field of the organizer
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/event_meta_properties/(id)/
Returns information on one property, identified by its id.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/event_meta_properties/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
{
"id": 1,
"name": "Color",
"default": "blue",
"required": false,
"protected": false,
"filter_public": false,
"public_label": {},
"filter_allowed": true,
"choices": null
}
:param organizer: The ``slug`` field of the organizer
:param id: The ``id`` field of the meta property to retrieve
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:post:: /api/v1/organizers/(organizer)/event_meta_properties/
Creates a new meta property
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/event_meta_properties/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"name": "ref-code",
"default": "abcde",
"required": true,
"choices": null
}
**Example response**:
.. sourcecode:: http
{
"id": 2,
"name": "reference",
"default": "abcde",
"required": true,
"protected": false,
"filter_public": false,
"public_label": null,
"filter_allowed": true,
"choices": null
}
:param organizer: The ``slug`` field of the organizer
:statuscode 201: no error
:statuscode 400: The meta property could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
.. http:patch:: /api/v1/organizers/(organizer)/event_meta_properties/(id)/
Update a meta property. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide
all fields of the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the
fields that you want to change.
You can change all fields of the resource except the ``id`` field.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/event_meta_properties/2/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"required": false
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 3,
"name": "reference",
"default": "abcde",
"required": false,
"protected": false,
"filter_public": false,
"public_label": null,
"filter_allowed": true,
"choices": null
}
:param organizer: The ``slug`` field of the organizer
:param id: The ``id`` field of the meta property to modify
:statuscode 200: no error
:statuscode 400: The property could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource.
.. http:delete:: /api/v1/organizers/(organizer)/event_meta_properties/(id)/
Delete a meta property.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/event_meta_properties/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Vary: Accept
:param organizer: The ``slug`` field of the organizer
:param id: The ``id`` field of the meta property to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to delete this resource.
+17 -15
View File
@@ -110,7 +110,7 @@ Endpoints
"plugins": [
"pretix.plugins.banktransfer",
"pretix.plugins.stripe",
"pretix.plugins.paypal",
"pretix.plugins.paypal2",
"pretix.plugins.ticketoutputpdf"
],
"all_sales_channels": false,
@@ -199,7 +199,7 @@ Endpoints
"plugins": [
"pretix.plugins.banktransfer",
"pretix.plugins.stripe",
"pretix.plugins.paypal",
"pretix.plugins.paypal2",
"pretix.plugins.ticketoutputpdf"
],
"valid_keys": {
@@ -262,7 +262,7 @@ Endpoints
"item_meta_properties": {},
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
"pretix.plugins.paypal2"
],
"all_sales_channels": true,
"limit_sales_channels": []
@@ -299,7 +299,7 @@ Endpoints
"item_meta_properties": {},
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
"pretix.plugins.paypal2"
],
"all_sales_channels": true,
"limit_sales_channels": [],
@@ -364,7 +364,7 @@ Endpoints
"item_meta_properties": {},
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
"pretix.plugins.paypal2"
],
"all_sales_channels": true,
"limit_sales_channels": []
@@ -401,7 +401,7 @@ Endpoints
"item_meta_properties": {},
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
"pretix.plugins.paypal2"
],
"all_sales_channels": true,
"limit_sales_channels": [],
@@ -438,7 +438,7 @@ Endpoints
"plugins": [
"pretix.plugins.banktransfer",
"pretix.plugins.stripe",
"pretix.plugins.paypal",
"pretix.plugins.paypal2",
"pretix.plugins.pretixdroid"
]
}
@@ -475,7 +475,7 @@ Endpoints
"plugins": [
"pretix.plugins.banktransfer",
"pretix.plugins.stripe",
"pretix.plugins.paypal",
"pretix.plugins.paypal2",
"pretix.plugins.pretixdroid"
],
"all_sales_channels": true,
@@ -566,7 +566,7 @@ organizer level.
Content-Type: application/json
{
"imprint_url": "https://pretix.eu",
"region": "DE",
…
}
@@ -579,12 +579,14 @@ organizer level.
Content-Type: application/json
{
"imprint_url":
"region":
{
"value": "https://pretix.eu",
"label": "Imprint URL",
"value": "DE",
"label": "Region",
"readonly": false,
"help_text": "This should point e.g. to a part of your website that has your contact details and legal information."
"help_text": "Will be used to determine date and time formatting as well as default country for customer
addresses and phone numbers. For formatting, this takes less priority than the language and
is therefore mostly relevant for languages used in different regions globally (like English)."
}
},
…
@@ -620,7 +622,7 @@ organizer level.
Content-Type: application/json
{
"imprint_url": "https://example.org/imprint/"
"region": "DE"
}
**Example response**:
@@ -632,7 +634,7 @@ organizer level.
Content-Type: application/json
{
"imprint_url": "https://example.org/imprint/",
"region": "DE",
…
}
+1
View File
@@ -12,6 +12,7 @@ at :ref:`plugin-docs`.
organizers
events
subevents
event_meta_properties
taxrules
categories
items
+1
View File
@@ -116,6 +116,7 @@ Endpoints
:query integer page: The page number in case of a multi-page result set, default is 1
:query string code: Only show the voucher with the given voucher code.
:query string search: Only show the voucher with the given query found in the code, tag, or comment.
:query integer max_usages: Only show vouchers with the given maximal number of usages.
:query integer redeemed: Only show vouchers with the given number of redemptions. Note that this doesn't tell you if
the voucher can still be redeemed, as this also depends on ``max_usages``. See the
+2 -1
View File
@@ -11,7 +11,8 @@ Core
----
.. automodule:: pretix.base.signals
:members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types, notification,
:members: periodic_task, event_live_issues, event_copy_data, email_filter,
register_notification_types, event_notification_sent, organizer_notification_sent,
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, device_info_updated,
+1 -1
View File
@@ -1,6 +1,6 @@
sphinx==9.1.*
sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-httpdomain~=2.1.0
sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2
+1 -1
View File
@@ -1,7 +1,7 @@
-e ../
sphinx==9.1.*
sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-httpdomain~=2.1.0
sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2
+57 -1
View File
@@ -8,7 +8,63 @@ import vuePug from 'eslint-plugin-vue-pug'
const ignores = globalIgnores([
'**/node_modules',
'**/dist'
'**/dist',
// Vendored code
'src/pretix/static/leaflet',
'src/pretix/static/clipboard',
'src/pretix/static/cropper',
'src/pretix/static/lightbox',
'src/pretix/static/are-you-sure',
'src/pretix/static/vuejs',
'src/pretix/static/fontawesome',
'src/pretix/static/typeahead',
'src/pretix/static/moment',
'src/pretix/static/pdfjs',
'src/pretix/static/sortable',
'src/pretix/static/iframeresizer',
'src/pretix/static/bootstrap',
'src/pretix/static/d3',
'src/pretix/static/jsi18n',
'src/pretix/static/fabric',
'src/pretix/static/datetimepicker',
'src/pretix/static/charts',
'src/pretix/static/fileupload',
'src/pretix/static/seating',
'src/pretix/static/rest_framework',
'src/pretix/static/select2',
'src/pretix/static/schema',
'src/pretix/static/slider',
'src/pretix/static/jquery',
'src/pretix/static/colorpicker',
'src/pretix/static/rrule',
'src/pretix/static/pretixcontrol/js/jquery.qrcode.min.js',
'src/pretix/static/pretixpresale/js/widget/docready.js',
// Pre-vue JS code
'src/pretix/static/pretixbase/js/addressform.js',
'src/pretix/static/pretixbase/js/asynctask.js',
'src/pretix/static/pretixbase/js/details.js',
'src/pretix/static/pretixbase/js/gettextstub.js',
'src/pretix/static/pretixbase/js/i18nstring.js',
'src/pretix/static/pretixcontrol/js/menu.js',
'src/pretix/static/pretixcontrol/js/ui/editor.js',
'src/pretix/static/pretixcontrol/js/ui/geo.js',
'src/pretix/static/pretixcontrol/js/ui/main.js',
'src/pretix/static/pretixcontrol/js/ui/subevent.js',
'src/pretix/static/pretixcontrol/js/ui/variations.js',
'src/pretix/static/pretixcontrol/js/ui/webauthn.js',
'src/pretix/static/pretixpresale/js/ui/cart.js',
'src/pretix/static/pretixpresale/js/ui/main.js',
'src/pretix/static/pretixpresale/js/ui/questions.js',
'src/pretix/static/pretixpresale/js/widget/floatformat.js',
'src/pretix/static/pretixpresale/js/widget/widget.js',
'src/pretix/plugins/banktransfer/static',
'src/pretix/plugins/paypal2/static',
'src/pretix/plugins/statistics/static',
'src/pretix/plugins/stripe/static',
// Plugin checkouts
'local',
// docs
'doc',
])
export default defineConfig([
+127 -115
View File
@@ -40,30 +40,30 @@
"license": "MIT"
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
"integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
"version": "7.29.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
"integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
"@babel/types": "^7.29.8"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -73,13 +73,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
"integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
"@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -194,16 +194,16 @@
}
},
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
"node": "20 || >=22"
}
},
"node_modules/@eslint/config-array/node_modules/minimatch": {
@@ -294,29 +294,43 @@
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
"integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/types": "^0.15.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node": {
"version": "0.16.7",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
"integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/core": "^0.19.1",
"@humanfs/core": "^0.19.2",
"@humanfs/types": "^0.15.0",
"@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/types": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
"integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -364,9 +378,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
"integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": {
@@ -1325,16 +1339,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
"node": "20 || >=22"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
@@ -1433,53 +1447,53 @@
"license": "MIT"
},
"node_modules/@vue/compiler-core": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz",
"integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.43.tgz",
"integrity": "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
"@vue/shared": "3.5.30",
"@babel/parser": "^7.29.8",
"@vue/shared": "3.5.43",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz",
"integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.43.tgz",
"integrity": "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.30",
"@vue/shared": "3.5.30"
"@vue/compiler-core": "3.5.43",
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz",
"integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.43.tgz",
"integrity": "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
"@vue/compiler-core": "3.5.30",
"@vue/compiler-dom": "3.5.30",
"@vue/compiler-ssr": "3.5.30",
"@vue/shared": "3.5.30",
"@babel/parser": "^7.29.8",
"@vue/compiler-core": "3.5.43",
"@vue/compiler-dom": "3.5.43",
"@vue/compiler-ssr": "3.5.43",
"@vue/shared": "3.5.43",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.8",
"postcss": "^8.5.28",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz",
"integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.43.tgz",
"integrity": "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.30",
"@vue/shared": "3.5.30"
"@vue/compiler-dom": "3.5.43",
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/eslint-config-typescript": {
@@ -1523,53 +1537,51 @@
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz",
"integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.43.tgz",
"integrity": "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.30"
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz",
"integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.43.tgz",
"integrity": "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.30",
"@vue/shared": "3.5.30"
"@vue/reactivity": "3.5.43",
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz",
"integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.43.tgz",
"integrity": "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.30",
"@vue/runtime-core": "3.5.30",
"@vue/shared": "3.5.30",
"@vue/reactivity": "3.5.43",
"@vue/runtime-core": "3.5.43",
"@vue/shared": "3.5.43",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz",
"integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.43.tgz",
"integrity": "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.30",
"@vue/shared": "3.5.30"
},
"peerDependencies": {
"vue": "3.5.30"
"@vue/compiler-ssr": "3.5.43",
"@vue/runtime-dom": "3.5.43",
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/shared": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz",
"integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.43.tgz",
"integrity": "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==",
"license": "MIT"
},
"node_modules/acorn": {
@@ -1683,9 +1695,9 @@
"license": "ISC"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz",
"integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2108,16 +2120,16 @@
}
},
"node_modules/eslint/node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
"node": "20 || >=22"
}
},
"node_modules/eslint/node_modules/eslint-visitor-keys": {
@@ -3176,9 +3188,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"version": "3.3.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
"integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
"funding": [
{
"type": "github",
@@ -3352,9 +3364,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
"version": "8.5.29",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz",
"integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==",
"funding": [
{
"type": "opencollective",
@@ -3371,18 +3383,18 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.16",
"nanoid": "^3.3.19",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
"source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-selector-parser": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
"integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4148,9 +4160,9 @@
}
},
"node_modules/smol-toml": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz",
"integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.0.tgz",
"integrity": "sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@@ -4171,9 +4183,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -4578,16 +4590,16 @@
"license": "MIT"
},
"node_modules/vue": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
"integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.43.tgz",
"integrity": "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.30",
"@vue/compiler-sfc": "3.5.30",
"@vue/runtime-dom": "3.5.30",
"@vue/server-renderer": "3.5.30",
"@vue/shared": "3.5.30"
"@vue/compiler-dom": "3.5.43",
"@vue/compiler-sfc": "3.5.43",
"@vue/runtime-dom": "3.5.43",
"@vue/server-renderer": "3.5.43",
"@vue/shared": "3.5.43"
},
"peerDependencies": {
"typescript": "*"
+2 -2
View File
@@ -18,12 +18,12 @@
"doc": "doc"
},
"scripts": {
"dev:control": "vite",
"dev:control": "vite --clearScreen=false",
"dev:widget": "vite src/pretix/static/pretixpresale/widget",
"build": "npm run build:control -s && npm run build:widget -s",
"build:control": "vite build",
"build:widget": "vite build src/pretix/static/pretixpresale/widget",
"lint:eslint": "eslint src/pretix/static/pretixpresale/widget src/pretix/static/pretixcontrol/js/ui/checkinrules src/pretix/plugins/webcheckin",
"lint:eslint": "eslint src/pretix/static/pretixpresale/widget src/pretix/static/pretixcontrol/js/ui/checkinrules src/pretix/plugins/webcheckin src/pretix/plugins/wallet",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
+17 -16
View File
@@ -33,15 +33,15 @@ dependencies = [
"bleach==6.4.*",
"celery==5.6.*",
"chardet==5.2.*",
"cryptography>=50.0.0",
"css-inline==0.21.*",
"cryptography>=50.0.2",
"css-inline==0.22.*",
"defusedcsv>=3.0.0",
"dnspython==2.*",
"Django[argon2]==5.2.*",
"Django[argon2]==5.2.*,>=5.2.17",
"django-bootstrap3==26.2",
"django-compressor==4.6.0",
"django-countries==9.0.*",
"django-filter==26.1",
"django-countries==9.1.*",
"django-filter==26.2",
"django-formset-js-improved==0.5.0.5",
"django-formtools==2.7",
"django-hierarkey==2.0.*,>=2.0.2",
@@ -56,7 +56,7 @@ dependencies = [
"django-querytagger==0.0.3",
"django-redis==7.0.*",
"django-scopes==2.1.*",
"django-statici18n==2.7.*",
"django-statici18n==2.8.*",
"djangorestframework==3.17.*",
"dnspython==2.8.*",
"drf_ujson2==1.7.*",
@@ -67,7 +67,7 @@ dependencies = [
"kombu==5.6.*",
"libsass==0.23.*",
"lxml",
"markdown==3.10.3", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
"markdown==3.11", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*",
"oauthlib==3.3.*",
@@ -75,7 +75,7 @@ dependencies = [
"packaging",
"paypalrestsdk==1.13.*",
"paypal-checkout-serversdk==1.0.*",
"PyJWT==2.13.*",
"PyJWT==2.15.*",
"phonenumberslite==9.0.*",
"Pillow==12.3.*",
"pretix-plugin-build",
@@ -83,8 +83,8 @@ dependencies = [
"psycopg2-binary",
"pycountry",
"pycparser==3.0",
"pycryptodome==3.23.*",
"pypdf==6.5.*",
"pycryptodome==3.24.*",
"pypdf==6.19.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*",
"pytz",
@@ -94,11 +94,11 @@ dependencies = [
"redis==7.4.*",
"reportlab==5.0.*",
"requests==2.34.*",
"sentry-sdk==2.68.*",
"sentry-sdk==2.71.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
"tlds>=2026072401",
"tlds>=2026093003",
"tqdm==4.*",
"ua-parser==1.0.*",
"vobject==0.9.*",
@@ -112,23 +112,24 @@ dev = [
"aiohttp==3.14.*",
"coverage",
"coveralls",
"fakeredis==2.37.*",
"flake8==7.3.*",
"fakeredis==2.39.*",
"flake8==7.4.*",
"freezegun",
"isort==8.0.*",
"isort==9.0.*",
"pep8-naming==0.15.*",
"potypo",
"pytest-asyncio>=1.4.0",
"pytest-cache",
"pytest-cov",
"pytest-django==4.*",
"pytest-mock==3.15.*",
"pytest-mock==3.16.*",
"pytest-sugar",
"pytest-xdist==3.8.*",
"pytest-playwright",
"pytest==9.1.*",
"playwright",
"responses",
"pypdfium2"
]
[project.entry-points."distutils.commands"]
+1 -1
View File
@@ -26,12 +26,12 @@ ignore =
src/tests/plugins/*
src/tests/plugins/badges/*
src/tests/plugins/banktransfer/*
src/tests/plugins/paypal/*
src/tests/plugins/paypal2/*
src/tests/plugins/pretixdroid/*
src/tests/plugins/stripe/*
src/tests/plugins/sendmail/*
src/tests/plugins/ticketoutputpdf/*
src/tests/plugins/wallet/*
.*
CODE_OF_CONDUCT.md
CONTRIBUTING.md
+2
View File
@@ -30,3 +30,5 @@ npminstall:
npmbuild:
npm run build
licenseheaders:
licenseheaders -t ../.licenseheader -E .py -x "*/migrations/*.py"
+1 -1
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__ = "2026.8.0.dev0"
__version__ = "2026.9.0.dev0"
+1
View File
@@ -66,6 +66,7 @@ INSTALLED_APPS = [
'pretix.plugins.returnurl',
'pretix.plugins.autocheckin',
'pretix.plugins.webcheckin',
'pretix.plugins.wallet',
'django_countries',
'oauth2_provider',
'phonenumber_field',
@@ -19,15 +19,12 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
def get_session_key_for_api_auth(user, auth):
if user.is_authenticated:
return f'api-upload-User-{user.pk}'
else:
return f'api-upload-{str(type(auth))}-{auth.pk}'
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
#
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
#
# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze
#
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
def get_session_key_for_api_request(request):
return get_session_key_for_api_auth(request.user, request.auth)
+33
View File
@@ -0,0 +1,33 @@
from pretix.base.models import (
CachedFile
)
from rest_framework.exceptions import ValidationError
from django.core.exceptions import ValidationError as DjangoValidationError
from django.conf import settings
from pretix.api.auth.utils import get_session_key_for_api_auth
def handle_file_upload(data, user, auth, allowed_types):
try:
cf = CachedFile.objects.get(
file__isnull=False,
pk=data[len("file:"):],
)
if cf.session_key == "api-upload-<class 'django.contrib.auth.models.AnonymousUser'>-None":
# OK, backwards-compatibility of a security bug fixed 2026-09, delete this at some point, but should
# also be harmless because all files with this key are expired one day after deployment of this fix
# and no new files with this key are created
pass
elif cf.session_key != get_session_key_for_api_auth(user, auth):
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data))
except (ValidationError, DjangoValidationError, IndexError): # invalid uuid
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data))
except CachedFile.DoesNotExist:
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data))
if cf.type not in allowed_types:
raise ValidationError('The submitted file "{fid}" has a file type that is not allowed in this field.'.format(fid=data))
if cf.file.size > settings.FILE_UPLOAD_MAX_SIZE_OTHER:
raise ValidationError('The submitted file "{fid}" is too large to be used in this field.'.format(fid=data))
return cf.file
+4
View File
@@ -101,6 +101,10 @@ class OAuthAccessToken(AbstractAccessToken):
self.expires = now() - timedelta(hours=1)
self.save(update_fields=['expires'])
def is_valid(self, scopes=None):
# Can maybe be removed after upgrading django-oauth-toolkit to 3.4.1
return super().is_valid(scopes) and self.application.is_usable(None)
class OAuthRefreshToken(AbstractRefreshToken):
application = models.ForeignKey(
+9 -1
View File
@@ -37,6 +37,8 @@ from collections import OrderedDict
from django.core.exceptions import ValidationError
from rest_framework import serializers
from pretix.api.auth.utils import get_session_key_for_api_request
def remove_duplicates_from_list(data):
return list(OrderedDict.fromkeys(data))
@@ -83,10 +85,16 @@ class UploadedFileField(serializers.Field):
request = self.context.get('request', None)
try:
cf = CachedFile.objects.get(
session_key=f'api-upload-{str(type(request.user or request.auth))}-{(request.user or request.auth).pk}',
file__isnull=False,
pk=data[len("file:"):],
)
if cf.session_key == "api-upload-<class 'django.contrib.auth.models.AnonymousUser'>-None":
# OK, backwards-compatibility of a security bug fixed 2026-09, delete this at some point, but should
# also be harmless because all files with this key are expired one day after deployment of this fix
# and no new files with this key are created
pass
elif cf.session_key != get_session_key_for_api_request(request):
self.fail('not_found')
except (ValidationError, IndexError): # invalid uuid
self.fail('not_found')
except CachedFile.DoesNotExist:
+3 -1
View File
@@ -261,7 +261,9 @@ class ItemAddOnSerializer(serializers.ModelSerializer):
def validate(self, data):
data = super().validate(data)
ItemAddOn.clean_max_min_count(data.get('max_count'), data.get('min_count'))
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
ItemAddOn.clean_max_min_count(full_data.get('max_count'), full_data.get('min_count'))
return data
+10 -4
View File
@@ -41,6 +41,7 @@ from rest_framework.exceptions import ValidationError
from rest_framework.relations import SlugRelatedField
from rest_framework.reverse import reverse
from pretix.api.auth.utils import get_session_key_for_api_request
from pretix.api.serializers import CompatDecimalField, CompatibleJSONField
from pretix.api.serializers.event import SubEventSerializer
from pretix.api.serializers.forms import form_field_to_serializer_field
@@ -258,16 +259,21 @@ class AnswerSerializer(I18nAwareModelSerializer):
if data['answer'] == 'file:keep':
return data
try:
ao = self.context["request"].user or self.context["request"].auth
cf = CachedFile.objects.get(
session_key=f'api-upload-{str(type(ao))}-{ao.pk}',
file__isnull=False,
pk=data['answer'][len("file:"):],
)
if cf.session_key == "api-upload-<class 'django.contrib.auth.models.AnonymousUser'>-None":
# OK, backwards-compatibility of a security bug fixed 2026-09, delete this at some point, but should
# also be harmless because all files with this key are expired one day after deployment of this fix
# and no new files with this key are created
pass
elif cf.session_key != get_session_key_for_api_request(self.context["request"]):
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data['answer']))
except (ValidationError, IndexError): # invalid uuid
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data))
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data['answer']))
except CachedFile.DoesNotExist:
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data))
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data['answer']))
allowed_types = (
'image/png', 'image/jpeg', 'image/gif', 'application/pdf'
+90 -3
View File
@@ -28,6 +28,7 @@ from django.db import transaction
from django.db.models import Q
from django.utils.crypto import get_random_string
from django.utils.translation import gettext, gettext_lazy as _
from i18nfield.rest_framework import I18nField
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
@@ -40,9 +41,10 @@ from pretix.api.serializers.settings import SettingsSerializer
from pretix.base.auth import get_auth_backends
from pretix.base.i18n import get_language_without_region
from pretix.base.models import (
Customer, Device, GiftCard, GiftCardAcceptance, GiftCardTransaction,
Membership, MembershipType, OrderPosition, Organizer, ReusableMedium,
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
Customer, Device, EventMetaProperty, GiftCard, GiftCardAcceptance,
GiftCardTransaction, Membership, MembershipType, OrderPosition, Organizer,
ReusableMedium, SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite,
User,
)
from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.permissions import (
@@ -640,3 +642,88 @@ class OrganizerSettingsSerializer(SettingsSerializer):
)
# TODO: make sure pub is always correct
return 'pub/' + fname
class MetaPropertyListField(serializers.ListField):
def __init__(self, *args, **kwargs):
kwargs["validators"] = kwargs.pop("validators", [])
def validate_keys_unique(choices):
if not choices:
return
keys = [c.get("key") for c in choices]
if len(set(keys)) < len(keys):
raise ValidationError("The key for each meta property value option must be unique.")
kwargs["validators"].append(
validate_keys_unique
)
super().__init__(*args, **kwargs)
class MetaPropertyDictField(serializers.DictField):
def __init__(self, **kwargs):
self.label_child = kwargs.pop("label_child", I18nField())
super().__init__(**kwargs)
def to_representation(self, value):
# django added unneccessary keys DELETE, ORDER through formsets, filter them here for backwards compat
d = {
"key": value["key"]
}
if "label" in value:
d["label"] = self.label_child.to_representation(value["label"])
return super().to_representation(d)
def to_internal_value(self, data):
if not isinstance(data, dict):
raise ValidationError("Meta property value options must be a dict.")
if not isinstance(data.get("key"), str):
raise ValidationError("Meta property value options must have a key of type string.")
if any(k not in {"key", "label"} for k in data.keys()):
raise ValidationError("Meta property value options may only have a key and optionally a label.")
if "label" in data:
try:
data["label"] = self.label_child.to_internal_value(data["label"])
except ValidationError as e:
raise ValidationError({"label": e.detail})
return super().to_internal_value(data)
class EventMetaPropertiesSerializer(I18nAwareModelSerializer):
choices = MetaPropertyListField(
child=MetaPropertyDictField(
label_child=I18nField()
),
allow_null=True,
)
class Meta:
model = EventMetaProperty
fields = (
'id', 'name', 'default', 'required', 'protected', 'filter_public', 'public_label', 'filter_allowed',
'choices'
)
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)
choices = full_data.get("choices")
default = full_data.get("default")
if choices and default:
choice_keys = [c.get("key") for c in choices]
if default not in choice_keys:
raise ValidationError("You cannot set a default value that is not a valid value.")
if not choices and "choices" in data:
# normalize empty dict to None
data["choices"] = None
return data
+1
View File
@@ -68,6 +68,7 @@ orga_router.register(r'scheduled_exports', exporters.ScheduledOrganizerExportVie
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
orga_router.register(r'transactions', order.OrganizerTransactionViewSet)
orga_router.register(r'orderpositions', order.OrganizerOrderPositionViewSet, basename='orderpositions')
orga_router.register(r'event_meta_properties', organizer.EventMetaPropertiesViewSet)
team_router = routers.DefaultRouter()
team_router.register(r'members', organizer.TeamMemberViewSet)
+9 -25
View File
@@ -50,6 +50,7 @@ from rest_framework.generics import ListAPIView
from rest_framework.permissions import SAFE_METHODS
from rest_framework.response import Response
from pretix.api.auth.utils import get_session_key_for_api_auth
from pretix.api.serializers.checkin import (
CheckinListSerializer, CheckinRPCAnnulInputSerializer,
CheckinRPCRedeemInputSerializer, MiniCheckinListSerializer,
@@ -63,7 +64,7 @@ from pretix.api.views import RichOrderingFilter
from pretix.api.views.order import OrderPositionFilter
from pretix.base.i18n import language
from pretix.base.models import (
CachedFile, Checkin, CheckinList, Device, Event, Order, OrderPosition,
Checkin, CheckinList, Device, Event, Order, OrderPosition,
Question, ReusableMedium, RevokedTicketSecret, TeamAPIToken,
)
from pretix.base.models.orders import PrintLog
@@ -75,6 +76,7 @@ from pretix.base.services.checkin import (
from pretix.base.services.media import perform_media_exchange
from pretix.base.signals import checkin_annulled
from pretix.helpers import OF_SELF
from pretix.api.helpers import handle_file_upload
with scopes_disabled():
class CheckinListFilter(FilterSet):
@@ -328,29 +330,6 @@ with scopes_disabled():
)
def _handle_file_upload(data, user, auth):
try:
cf = CachedFile.objects.get(
session_key=f'api-upload-{str(type(user or auth))}-{(user or auth).pk}',
file__isnull=False,
pk=data[len("file:"):],
)
except (ValidationError, BaseValidationError, IndexError): # invalid uuid
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data))
except CachedFile.DoesNotExist:
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data))
allowed_types = (
'image/png', 'image/jpeg', 'image/gif', 'application/pdf'
)
if cf.type not in allowed_types:
raise ValidationError('The submitted file "{fid}" has a file type that is not allowed in this field.'.format(fid=data))
if cf.file.size > settings.FILE_UPLOAD_MAX_SIZE_OTHER:
raise ValidationError('The submitted file "{fid}" is too large to be used in this field.'.format(fid=data))
return cf.file
def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_products=False, pdf_data=False, expand=None):
list_by_event = {cl.event_id: cl for cl in checkinlists}
if not checkinlists:
@@ -795,7 +774,12 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
try:
if q.type == Question.TYPE_FILE:
if answers_data[str(q.pk)]:
given_answers[q] = _handle_file_upload(answers_data[str(q.pk)], user, auth)
given_answers[q] = handle_file_upload(
answers_data[str(q.pk)],
user,
auth,
allowed_types=('image/png', 'image/jpeg', 'image/gif', 'application/pdf')
)
else:
given_answers[q] = None
else:
+1 -1
View File
@@ -1683,7 +1683,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
@action(detail=True, methods=['POST'])
def confirm(self, request, **kwargs):
payment = self.get_object()
force = request.data.get('force', False)
force = request.data.get('force', False) if request.data else False
send_mail = request.data.get('send_email', True) if request.data else True
if payment.state not in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
+51 -4
View File
@@ -44,15 +44,16 @@ from pretix.api.models import OAuthAccessToken
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.organizer import (
CustomerCreateSerializer, CustomerSerializer, DeviceSerializer,
GiftCardSerializer, GiftCardTransactionSerializer, MembershipSerializer,
EventMetaPropertiesSerializer, GiftCardSerializer,
GiftCardTransactionSerializer, MembershipSerializer,
MembershipTypeSerializer, OrganizerSerializer, OrganizerSettingsSerializer,
SalesChannelSerializer, SeatingPlanSerializer, TeamAPITokenSerializer,
TeamInviteSerializer, TeamMemberSerializer, TeamSerializer,
)
from pretix.base.models import (
Customer, Device, Event, GiftCard, GiftCardTransaction, LogEntry,
Membership, MembershipType, Organizer, SalesChannel, SeatingPlan, Team,
TeamAPIToken, TeamInvite, User,
Customer, Device, Event, EventMetaProperty, GiftCard, GiftCardTransaction,
LogEntry, Membership, MembershipType, Organizer, SalesChannel, SeatingPlan,
Team, TeamAPIToken, TeamInvite, User,
)
from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
@@ -846,3 +847,49 @@ class SalesChannelViewSet(viewsets.ModelViewSet):
data={'id': instance.pk}
)
instance.delete()
class EventMetaPropertiesViewSet(viewsets.ModelViewSet):
serializer_class = EventMetaPropertiesSerializer
queryset = EventMetaProperty.objects.none()
write_permission = 'organizer.settings.general:write'
def get_queryset(self):
return self.request.organizer.meta_properties.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
return ctx
@transaction.atomic()
def perform_destroy(self, instance):
instance.log_action(
'pretix.property.deleted',
user=self.request.user,
auth=self.request.auth,
data={'id': instance.pk}
)
instance.delete()
@transaction.atomic()
def perform_create(self, serializer):
inst = serializer.save(organizer_id=self.request.organizer.pk)
serializer.instance.log_action(
'pretix.property.created',
user=self.request.user,
auth=self.request.auth,
data=self.request.data,
)
return inst
@transaction.atomic()
def perform_update(self, serializer):
inst = serializer.save(organizer_id=self.request.organizer.pk)
serializer.instance.log_action(
'pretix.property.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data,
)
return inst
+2 -1
View File
@@ -33,6 +33,7 @@ from rest_framework.views import APIView
from pretix.api.auth.device import DeviceTokenAuthentication
from pretix.api.auth.permission import AnyAuthenticatedClientPermission
from pretix.api.auth.token import TeamTokenAuthentication
from pretix.api.auth.utils import get_session_key_for_api_request
from pretix.base.models import CachedFile
from pretix.helpers.images import (
IMAGE_TYPES, validate_uploaded_file_for_valid_image,
@@ -78,7 +79,7 @@ class UploadView(APIView):
web_download=False,
filename=file_obj.name,
type=content_type,
session_key=f'api-upload-{str(type(request.user or request.auth))}-{(request.user or request.auth).pk}'
session_key=get_session_key_for_api_request(request)
)
cf.file.save(file_obj.name, file_obj)
cf.save()
+4
View File
@@ -40,6 +40,7 @@ with scopes_disabled():
class VoucherFilter(FilterSet):
active = BooleanFilter(method='filter_active')
code = CharFilter(lookup_expr='iexact')
search = CharFilter(method='search_qs')
class Meta:
model = Voucher
@@ -54,6 +55,9 @@ with scopes_disabled():
return queryset.filter(Q(redeemed__gte=F('max_usages')) |
(Q(valid_until__isnull=False) & Q(valid_until__lte=now())))
def search_qs(self, qs, name, value):
return qs.filter(Q(code__icontains=value) | Q(tag__icontains=value) | Q(comment__icontains=value))
class VoucherViewSet(viewsets.ModelViewSet):
serializer_class = VoucherSerializer
+7 -3
View File
@@ -27,7 +27,7 @@ from datetime import timedelta
from functools import cached_property
from typing import List, Optional, Protocol
import sentry_sdk
from django.conf import settings
from django.db import DatabaseError, transaction
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -236,7 +236,9 @@ class OutboundSyncProvider:
# model changes saved by set_sync_error / clear_in_flight calls below
if sq.failed_attempts >= self.max_attempts:
logger.exception('Failed to sync order (max attempts exceeded)')
sentry_sdk.capture_exception(e)
if settings.SENTRY_ENABLED:
import sentry_sdk
sentry_sdk.capture_exception(e)
sq.set_sync_error("exceeded", e.messages, e.full_message)
else:
logger.info(
@@ -247,7 +249,9 @@ class OutboundSyncProvider:
sq.clear_in_flight()
except Exception as e:
logger.exception('Failed to sync order (unhandled exception)')
sentry_sdk.capture_exception(e)
if settings.SENTRY_ENABLED:
import sentry_sdk
sentry_sdk.capture_exception(e)
sq.set_sync_error("internal", [], str(e))
@cached_property
+27 -4
View File
@@ -54,6 +54,7 @@ from ...control.forms.filter import get_all_payment_providers
from ...helpers import GroupConcat
from ...helpers.iter import chunked_iterable
from ..exporter import BaseExporter, MultiSheetListExporter
from ..invoicing.transmission import get_transmission_types
from ..services.export import ExportError
from ..services.invoices import invoice_pdf_task
from ..signals import (
@@ -197,7 +198,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
def iterate_sheet(self, form_data, sheet):
_ = gettext
if sheet == 'invoices':
yield [
headers = [
_('Invoice number'),
_('Date'),
_('Order code'),
@@ -230,8 +231,18 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
_('Total value (without taxes)'),
_('Payment matching IDs'),
_('Payment providers'),
_('Transmission type'),
_('Transmission status'),
_('Transmission date'),
]
transmission_types = get_transmission_types()
for tt in transmission_types:
for c in tt.describe_info_columns():
headers.append(str(tt.verbose_name) + ': ' + str(c))
yield headers
p_providers = OrderPayment.objects.filter(
order=OuterRef('order'),
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED,
@@ -242,7 +253,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
'm'
).order_by()
base_qs = self.invoices_queryset(form_data)\
base_qs = self.invoices_queryset(form_data)
qs = base_qs.select_related(
'order', 'refers'
@@ -280,7 +291,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
if mid:
pmis.append(mid)
pmi = '\n'.join(pmis)
yield [
line = [
i.full_invoice_no,
date_format(i.date, "SHORT_DATE_FORMAT"),
i.order.code,
@@ -315,8 +326,20 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
', '.join([
str(self.providers.get(p, p)) for p in sorted(set((i.payment_providers or '').split(',')))
if p and p != 'free'
])
]),
i.transmission_type_instance.verbose_name,
i.get_transmission_status_display(),
date_format(i.transmission_date, "SHORT_DATETIME_FORMAT") if i.transmission_date else "",
]
for tt in transmission_types:
if tt.identifier == i.transmission_type:
described = dict(tt.describe_info(i.invoice_to_transmission_info, i.invoice_to_country, i.invoice_to_is_business))
for c in tt.describe_info_columns():
line.append(described.get(c, ""))
else:
for c in tt.describe_info_columns():
line.append("")
yield line
elif sheet == 'lines':
yield [
_('Invoice number'),
+30 -17
View File
@@ -350,16 +350,22 @@ class WrappedPhonePrefixSelect(Select):
return super().render(name, value or self.initial, *args, **kwargs)
def get_context(self, name, value, attrs):
if value and self.choices[1][0] != value:
matching_choices = len([1 for p, c in self.choices if p == value])
# self.choices is lazy evaluated, needs to be realized to be modifiable
choices = list(self.choices)
if value and choices[1][0] != value:
matching_choices = len([1 for p, c in choices if p == value])
if matching_choices > 1:
# Some countries share a phone prefix, for example +1 is used all over the Americas.
# This causes a UX problem: If the default value or the existing data is +12125552368,
# the widget will just show the first <option> entry with value="+1" as selected,
# which alphabetically is America Samoa, although most numbers statistically are from
# the US. As a workaround, we detect this case and add an aditional choice value with
# the US. As a workaround, we detect this case and add an additional choice value with
# just <option value="+1">+1</option> without an explicit country.
self.choices.insert(1, (value, value))
self.choices = [
choices[0],
(value, value),
*choices[1:],
]
context = super().get_context(name, value, attrs)
return context
@@ -594,12 +600,16 @@ class PortraitImageField(SizeValidationMixin, ExtValidationMixin, forms.FileFiel
image = ImageOps.exif_transpose(image)
if f._cropdata:
image = image.crop((
f._cropdata.get('x', 0),
f._cropdata.get('y', 0),
f._cropdata.get('x', 0) + f._cropdata.get('width', image.width),
f._cropdata.get('y', 0) + f._cropdata.get('height', image.height),
))
left = int(f._cropdata.get('x', 0))
top = int(f._cropdata.get('y', 0))
right = left + int(f._cropdata.get('width', image.width))
bottom = top + int(f._cropdata.get('height', image.height))
if left >= image.width or top >= image.height or right > image.width or bottom > image.height:
raise ValidationError(
self.error_messages['max_dimension'],
code='max_dimension',
)
image = image.crop((left, top, right, bottom))
with BytesIO() as output:
# This might use a lot of memory, but temporary files are not a good option since
# we don't control the cleanup
@@ -899,7 +909,7 @@ class BaseQuestionsForm(forms.Form):
field.widget.attrs['data-question-dependency-values'] = escapejson_attr(json.dumps(q.dependency_values))
if q.type != 'M':
field.widget.attrs['required'] = q.required and not self.all_optional
field._required = q.required and not self.all_optional
field._required = q.required and not self.all_optional
field.required = False
return field
@@ -1196,8 +1206,9 @@ class TicketLevelQuestionsForm(BaseQuestionsForm):
return field
def clean(self):
from pretix.base.addressvalidation import \
validate_address # local import to prevent impact on startup time
from pretix.base.addressvalidation import ( # local import to prevent impact on startup time
validate_address,
)
d = super().clean()
@@ -1438,8 +1449,9 @@ class BaseInvoiceAddressForm(forms.ModelForm):
self.fields['transmission_type'].widget.attrs['data-trigger-address-info'] = 'on'
def clean(self):
from pretix.base.addressvalidation import \
validate_address # local import to prevent impact on startup time
from pretix.base.addressvalidation import ( # local import to prevent impact on startup time
validate_address,
)
data = self.cleaned_data
@@ -1493,11 +1505,12 @@ class BaseInvoiceAddressForm(forms.ModelForm):
"vat_id": _("This field is required.")
})
if self.validate_vat_id and self.instance.vat_id_validated and 'vat_id' not in self.changed_data:
if self.validate_vat_id and self.instance.vat_id_validated and not any(v in self.changed_data for v in ('is_business', 'vat_id', 'country')):
pass # Skip re-validation if it is validated
elif self.validate_vat_id and vat_id_applicable:
try:
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')))
requester_id = self.request.event.settings.invoice_address_from_vat_id
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')), requester_id)
self.instance.vat_id_validated = bool(normalized_id)
self.instance.vat_id = data['vat_id'] = normalized_id
except VATIDFinalError as e:
+2 -2
View File
@@ -82,8 +82,8 @@ class UserSettingsForm(forms.ModelForm):
class User2FADeviceAddForm(forms.Form):
name = forms.CharField(label=_('Device name'), max_length=64)
devicetype = forms.ChoiceField(label=_('Device type'), widget=forms.RadioSelect, choices=(
('totp', _('Smartphone with the Authenticator application')),
('webauthn', _('WebAuthn-compatible hardware token (e.g. Yubikey)')),
('otp_totp.totpdevice', _('Smartphone with the Authenticator application')),
('pretixbase.webauthndevice', _('WebAuthn-compatible hardware token (e.g. Yubikey)')),
))
+1 -1
View File
@@ -109,7 +109,7 @@ ALLOWED_LANGUAGES = dict(settings.LANGUAGES)
def get_babel_locale():
# Babel, and therefore also django-phonenumberfield, do not support our custom locales such das de_Informal
# Babel, and therefore also django-phonenumberfield, do not support our custom locales such as de_Informal
# Also, this returns best-effort region information for number formatting etc
current_language = translation.get_language()
current_region = getattr(_active_region, "value", None)
+2 -2
View File
@@ -71,8 +71,8 @@ class EmailTransmissionType(TransmissionType):
def transmission_info_to_form_data(self, transmission_info: dict) -> dict:
return {
"transmission_email_other": bool(transmission_info.get("transmission_email_address")),
"transmission_email_address": transmission_info.get("transmission_email_address"),
"transmission_email_other": bool((transmission_info or {}).get("transmission_email_address")),
"transmission_email_address": (transmission_info or {}).get("transmission_email_address"),
}
def form_data_to_transmission_info(self, form_data: dict) -> dict:
@@ -107,6 +107,9 @@ class TransmissionType:
def transmission_info_to_form_data(self, transmission_info: dict) -> dict:
return transmission_info
def describe_info_columns(self):
return [f.label for f in self.invoice_address_form_fields.values()]
def describe_info(self, transmission_info: dict, country: Country, is_business: bool):
form_data = self.transmission_info_to_form_data(transmission_info)
data = []
@@ -0,0 +1,49 @@
# Generated by Django 5.2.17 on 2026-09-21 11:30
import django.db.models.deletion
from django.db import migrations, models
def fix_unshredded_invoices(apps, _):
Invoice = apps.get_model("pretixbase", "Invoice")
InvoiceLine = apps.get_model("pretixbase", "InvoiceLine")
ignore_fields = (
# bool/int fields are not listed and skipped automatically
'prefix', 'invoice_no', 'full_invoice_no', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
'invoice_from_city', 'invoice_from_state', 'invoice_from_country', 'invoice_from_tax_id',
'invoice_from_vat_id', 'locale', 'payment_provider_stamp', 'footer_text', 'foreign_currency_display',
'foreign_currency_source', 'transmission_type', 'transmission_provider', 'transmission_status',
)
for i in Invoice.objects.filter(shredded=True):
for f in Invoice._meta.fields:
if f.name in ignore_fields:
continue
val = getattr(i, f.name, None)
if val and isinstance(val, str):
setattr(i, f.name, "█")
elif val and isinstance(val, list): # jsonfield
setattr(i, f.name, [])
elif val and isinstance(val, dict): # jsonfield
setattr(i, f.name, {"_shredded": True})
i.save()
InvoiceLine.objects.filter(
attendee_name__isnull=False,
invoice__shredded=True
).update(attendee_name="█")
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0310_question_valid_string_length_min"),
]
operations = [
migrations.RunPython(
fix_unshredded_invoices,
migrations.RunPython.noop,
),
]
+1
View File
@@ -166,6 +166,7 @@ class Device(LoggedModel):
)
security_profile = models.CharField(
max_length=190,
verbose_name=_('Security profile'),
default='full',
null=True,
blank=False
+2 -4
View File
@@ -1050,10 +1050,8 @@ class Item(LoggedModel):
replace_year = valid_until.year
replace_month = valid_until.month + self.validity_dynamic_duration_months
while replace_month > 12:
replace_month -= 12
replace_year += 1
replace_year += (replace_month - 1) // 12
replace_month = ((replace_month - 1) % 12) + 1
max_day = calendar.monthrange(replace_year, replace_month)[1]
replace_date = date(
year=replace_year,
+7 -40
View File
@@ -626,47 +626,14 @@ class Order(LockModel, LoggedModel):
self.save(update_fields=['last_modified'])
def set_expires(self, now_dt=None, subevents=None):
now_dt = now_dt or now()
tz = ZoneInfo(self.event.settings.timezone)
from pretix.base.services.payment import compute_payment_deadline
sales_channel_suffix = "_" + self.sales_channel.identifier.replace(".", "_")
if not (mode := self.event.settings.get(f'payment_term_mode{sales_channel_suffix}')):
mode = self.event.settings.get('payment_term_mode')
sales_channel_suffix = ""
if mode == 'days':
exp_by_date = now_dt.astimezone(tz) + timedelta(days=self.event.settings.get(f'payment_term_days{sales_channel_suffix}', as_type=int))
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
if self.event.settings.get('payment_term_weekdays'):
if exp_by_date.weekday() == 5:
exp_by_date += timedelta(days=2)
elif exp_by_date.weekday() == 6:
exp_by_date += timedelta(days=1)
elif mode == 'minutes':
exp_by_date = now_dt.astimezone(tz) + timedelta(minutes=self.event.settings.get(f'payment_term_minutes{sales_channel_suffix}', as_type=int))
else:
raise ValueError("'payment_term_mode' has an invalid value '{}'.".format(mode))
self.expires = exp_by_date
term_last = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
if term_last:
if self.event.has_subevents and subevents:
terms = [
term_last.datetime(se).date()
for se in subevents
]
if not terms:
return
term_last = min(terms)
else:
term_last = term_last.datetime(self.event).date()
term_last = make_aware(datetime.combine(
term_last,
time(hour=23, minute=59, second=59)
), tz)
if term_last < self.expires:
self.expires = term_last
self.expires = compute_payment_deadline(
event=self.event,
sales_channel=self.sales_channel,
now_dt=now_dt,
subevents=subevents,
)
@cached_property
def tax_total(self):
+20 -1
View File
@@ -42,7 +42,7 @@ from django.conf import settings
from django.core.mail import get_connection
from django.core.validators import MinLengthValidator, RegexValidator
from django.db import models
from django.db.models import Q
from django.db.models import Exists, OuterRef, Q
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
@@ -312,6 +312,25 @@ class Organizer(LoggedModel):
)
i += 1
def get_users_with_permission(self, permission):
"""
Returns a queryset of users who have a specific permission to this organizer.
:return: Iterable of User
"""
from .auth import User
if permission:
qs = Team.objects.with_organizer_permission(permission)
else:
qs = Team.objects.all()
team_with_perm = qs.filter(
members__pk=OuterRef('pk'),
organizer=self,
)
return User.objects.filter(Exists(team_with_perm))
def generate_invite_token():
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
+34 -11
View File
@@ -35,12 +35,13 @@
import logging
from collections import OrderedDict, namedtuple
from itertools import groupby
from typing import Optional, Union
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.models import Event, LogEntry
from pretix.base.models import Event, LogEntry, Organizer
from pretix.base.signals import register_notification_types
from pretix.base.templatetags.money import money_filter
from pretix.helpers.urls import mainreverse_absolute
@@ -57,7 +58,8 @@ class Notification:
"""
Represents a notification that is sent/shown to a user. A notification consists of:
* one ``event`` reference
* one ``event`` reference (can be ``Ǹone``)
* one ``organizer`` reference
* one ``title`` text that is shown e.g. in the email subject or in a headline
* optionally one ``detail`` text that may or may not be shown depending on the notification method
* optionally one ``url`` that should be absolute and point to the context of an notification (e.g. an order)
@@ -67,8 +69,12 @@ class Notification:
each consisting of a button label and an absolute URL to point to.
"""
def __init__(self, event: Event, title: str, detail: str=None, url: str=None):
def __init__(self, event: Optional[Event], title: str, detail: Optional[str]=None, url: Optional[str]=None, organizer: Optional[Organizer]=None):
assert event or organizer, "You need to pass either event or organizer"
if event and organizer:
assert event.organizer_id == organizer.id, "Organizer and event mismatch"
self.title = title
self.organizer = organizer or event.organizer
self.event = event
self.detail = detail
self.url = url
@@ -91,8 +97,16 @@ class Notification:
class NotificationType:
def __init__(self, event: Event = None):
self.event = event
def __init__(self, event_or_organizer: Union[Event, Organizer] = None):
if isinstance(event_or_organizer, Event):
self.event = event_or_organizer
self.organizer = event_or_organizer.organizer
elif isinstance(event_or_organizer, Organizer):
self.event = None
self.organizer = event_or_organizer
else:
self.event = None
self.organizer = None
def __repr__(self):
return '<NotificationType: {}>'.format(self.action_type)
@@ -121,31 +135,39 @@ class NotificationType:
"""
raise NotImplementedError() # NOQA
@property
def is_event_level(self) -> bool:
"""
Return `True` if this notification type can be configured per event (the default).
"""
return True
def build_notification(self, logentry: LogEntry) -> Notification:
"""
This is the main function that you should override. It is supposed to turn a log entry
object into a notification object that can then be rendered e.g. into an email.
"""
return Notification(
logentry.event,
logentry.display()
event=logentry.event,
title=logentry.display(),
organizer=logentry.organizer,
)
def get_all_notification_types(event=None):
def get_all_notification_types(event_or_organizer=None):
global _ALL_TYPES
if event is None and _ALL_TYPES:
if event_or_organizer is None and _ALL_TYPES:
return _ALL_TYPES
types = OrderedDict()
for recv, ret in register_notification_types.send(event):
for recv, ret in register_notification_types.send(event_or_organizer):
if isinstance(ret, (list, tuple)):
for r in ret:
types[r.action_type] = r
else:
types[ret.action_type] = ret
if event is None:
if event_or_organizer is None:
_ALL_TYPES = types
return types
@@ -181,6 +203,7 @@ class ParametrizedOrderNotificationType(NotificationType):
n = Notification(
event=logentry.event,
organizer=logentry.organizer,
title=self._title.format(order=order, event=logentry.event),
url=order_url
)
+41 -35
View File
@@ -801,6 +801,18 @@ def generate_compressed_addon_list(op, order, event, only_checked_in=False):
return addonlist
def get_sizebox(page: pypdf.PageObject):
mediabox = page.mediabox
cropbox = page.cropbox
return pypdf.generic.RectangleObject((
max(mediabox[0], cropbox[0]),
max(mediabox[1], cropbox[1]),
min(mediabox[2], cropbox[2]),
min(mediabox[3], cropbox[3]),
))
class Renderer:
def __init__(self, event, layout, background_file):
@@ -1079,7 +1091,7 @@ class Renderer:
fontsize = float(o['fontsize'])
height = float(o['height']) * mm
width = float(o['width']) * mm
while True:
for _i in range(25): # try adapting the font size at most 25 times
p, ad, lineheight = self._text_paragraph(op, order, o, override_fontsize=fontsize)
w, h = p.wrapOn(canvas, width, 1000 * mm)
widths = p.getActualLineWidths0()
@@ -1153,11 +1165,10 @@ class Renderer:
elif o['type'] == "poweredby":
self._draw_poweredby(canvas, op, o)
if self.bg_pdf:
page_size = (
self.bg_pdf.pages[0].mediabox[2] - self.bg_pdf.pages[0].mediabox[0],
self.bg_pdf.pages[0].mediabox[3] - self.bg_pdf.pages[0].mediabox[1]
)
if self.bg_pdf.pages[0].get('/Rotate') in (90, 270):
first_page = self.bg_pdf.pages[0]
sizebox = get_sizebox(first_page)
page_size = (sizebox.width, sizebox.height)
if first_page.rotation in (90, 270):
# swap dimensions due to pdf being rotated
page_size = page_size[::-1]
canvas.setPageSize(page_size)
@@ -1232,9 +1243,7 @@ class Renderer:
for i, page in enumerate(fg_pdf.pages):
bg_page = self.bg_pdf.pages[i]
_correct_page_media_box(bg_page)
page.merge_page(bg_page, over=False)
output.add_page(page)
_merge_with_correct_page_media_box(output, page, bg_page)
# pdf_header is a string like "%pdf-X.X"
if float(self.bg_pdf.pdf_header[5:]) > float(fg_pdf.pdf_header[5:]):
@@ -1299,39 +1308,36 @@ def merge_background(fg_pdf: PdfWriter, bg_pdf: PdfWriter, out_file, compress):
bg_pdf.write(bg_filename)
subprocess.run(pdftk_cmd, check=True, stdout=out_file)
else:
output = PdfWriter()
for i, page in enumerate(fg_pdf.pages):
bg_page = bg_pdf.pages[i]
_correct_page_media_box(bg_page)
page.merge_page(bg_page, over=False)
_merge_with_correct_page_media_box(output, page, bg_page)
# pdf_header is a string like "%pdf-X.X"
if float(bg_pdf.pdf_header[5:]) > float(fg_pdf.pdf_header[5:]):
fg_pdf.pdf_header = bg_pdf.pdf_header
fg_pdf.write(out_file)
output.pdf_header = (
bg_pdf.pdf_header
if float(bg_pdf.pdf_header[5:]) > float(fg_pdf.pdf_header[5:])
else fg_pdf.pdf_header
)
output.write(out_file)
def _correct_page_media_box(page: pypdf.PageObject):
if page.rotation != 0:
page.transfer_rotation_to_content()
media_box = page.mediabox
def _merge_with_correct_page_media_box(output: pypdf.PdfWriter, fg_page: pypdf.PageObject, bg_page: pypdf.PageObject):
"""
Adds fg_page to output, merging bg_page behind it.
If bg_page has a non-zero mergebox/cropbox or is rotated via /Rotate, a transformation is applied to fix this."""
trsf = pypdf.Transformation()
if media_box.bottom != 0:
trsf = trsf.translate(0, -media_box.bottom)
if media_box.left != 0:
trsf = trsf.translate(-media_box.left, 0)
page.add_transformation(trsf, False)
for b in ["/MediaBox", "/CropBox", "/BleedBox", "/TrimBox", "/ArtBox"]:
if b in page:
rr = pypdf.generic.RectangleObject(page[b])
pt1 = trsf.apply_on(rr.lower_left)
pt2 = trsf.apply_on(rr.upper_right)
page[pypdf.generic.NameObject(b)] = pypdf.generic.RectangleObject((
min(pt1[0], pt2[0]),
min(pt1[1], pt2[1]),
max(pt1[0], pt2[0]),
max(pt1[1], pt2[1]),
))
if bg_page.rotation != 0:
trsf = trsf.rotate(-bg_page.rotation)
mb = get_sizebox(bg_page)
pt1 = trsf.apply_on(mb.lower_left)
pt2 = trsf.apply_on(mb.upper_right)
trsf = trsf.translate(-min(pt1[0], pt2[0]), -min(pt1[1], pt2[1]))
fg_page = output.add_page(fg_page)
fg_page.merge_transformed_page(bg_page, trsf, over=False, expand=False)
@deconstructible
+1
View File
@@ -1605,6 +1605,7 @@ def add_payment_to_cart_session(cart_session, provider, min_value: Decimal=None,
'max_value': str(max_value) if max_value is not None else None,
'info_data': info_data or {},
})
cart_session['payments_postpone'] = False
def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None):
+5 -3
View File
@@ -54,7 +54,7 @@ from celery.exceptions import MaxRetriesExceededError
from django.conf import settings
from django.core.files.storage import default_storage
from django.core.mail import EmailMultiAlternatives, SafeMIMEMultipart
from django.core.mail.message import SafeMIMEText
from django.core.mail.message import SafeMIMEText, utf8_charset_qp
from django.db import connection, transaction
from django.db.models import Q
from django.dispatch import receiver
@@ -380,6 +380,8 @@ def mail(email: Union[str, Sequence[str]], subject: Union[str, FormattedString],
class CustomEmail(EmailMultiAlternatives):
encoding = utf8_charset_qp
def _create_mime_attachment(self, content, mimetype):
"""
Convert the content, mimetype pair into a MIME attachment object.
@@ -449,9 +451,9 @@ def mail_send_task(self, **kwargs) -> bool:
# Rewrite all <img> tags from real URLs or data URLs to inline attachments referred to by content ID
if outgoing_mail.body_html is not None:
html_message = SafeMIMEMultipart(_subtype='related', encoding=settings.DEFAULT_CHARSET)
html_message = SafeMIMEMultipart(_subtype='related')
html_with_cid, cid_images = replace_images_with_cid_paths(outgoing_mail.body_html)
html_message.attach(SafeMIMEText(html_with_cid, 'html', settings.DEFAULT_CHARSET))
html_message.attach(SafeMIMEText(html_with_cid, 'html', utf8_charset_qp))
attach_cid_images(html_message, cid_images, verify_ssl=True)
email.attach_alternative(html_message, "multipart/related")
+37 -18
View File
@@ -34,7 +34,9 @@ from pretix.base.models import (
from pretix.base.notifications import Notification, get_all_notification_types
from pretix.base.services.mail import mail_send_task
from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
from pretix.base.signals import notification
from pretix.base.signals import (
event_notification_sent, organizer_notification_sent,
)
from pretix.celery_app import app
from pretix.helpers.celery import get_task_priority
from pretix.helpers.urls import mainreverse_absolute
@@ -47,40 +49,47 @@ def notify(logentry_ids: list):
logentry_ids = [logentry_ids]
qs = LogEntry.all.select_related(
'event', 'event__organizer'
'event', 'event__organizer', 'organizer'
).order_by(
'action_type', 'event_id',
'action_type', 'event_id', 'organizer_id',
).filter(id__in=logentry_ids)
_event, _at, notify_specific, notify_global = None, None, None, None
_event, _organizer, _at, notify_specific, notify_global = None, None, None, None, None
for logentry in qs:
if not logentry.event:
break # Ignore, we only have event-related notifications right now
if not logentry.event and not logentry.organizer:
break # Ignore, we only have event- or organizer-related notifications right now
notification_type = logentry.notification_type
if not notification_type:
break # No suitable plugin
if _event != logentry.event or _at != logentry.action_type or notify_global is None:
if _event != logentry.event or _organizer != logentry.organizer or _at != logentry.action_type or notify_global is None:
_event = logentry.event
_organizer = logentry.organizer
_at = logentry.action_type
event_or_organizer = logentry.event or logentry.organizer
# All users that have the permission to get the notification
users = logentry.event.get_users_with_permission(
users = event_or_organizer.get_users_with_permission(
notification_type.required_permission
).filter(notifications_send=True, is_active=True)
if logentry.user:
users = users.exclude(pk=logentry.user.pk)
# Get all notification settings, both specific to this event as well as global
notify_specific = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
event=logentry.event,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
if logentry.event:
notify_specific = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
event=logentry.event,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
else:
notify_specific = {}
notify_global = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
@@ -106,7 +115,10 @@ def notify(logentry_ids: list):
priority=get_task_priority("notifications", logentry.organizer_id),
)
notification.send(logentry.event, logentry_id=logentry.id, notification_type=notification_type.action_type)
if logentry.event:
event_notification_sent.send(logentry.event, logentry_id=logentry.id, notification_type=notification_type.action_type)
elif logentry.organizer:
organizer_notification_sent.send(logentry.organizer, logentry_id=logentry.id, notification_type=notification_type.action_type)
@app.task(base=ProfiledTask, acks_late=True, max_retries=9, default_retry_delay=900)
@@ -158,13 +170,20 @@ def send_notification_mail(notification: Notification, user: User):
body_plain = tpl_plain.render(ctx)
guid = uuid.uuid4()
settings_holder = notification.event or notification.organizer
prefix = settings_holder.settings.mail_prefix
if not prefix:
if notification.event:
prefix = notification.event.slug.upper()
elif notification.organizer:
prefix = notification.organizer.name
m = OutgoingMail.objects.create(
guid=guid,
user=user,
to=[user.email],
subject='[{}] {}: {}'.format(
settings.PRETIX_INSTANCE_NAME,
notification.event.settings.mail_prefix or notification.event.slug.upper(),
prefix,
notification.title
),
body_plain=body_plain,
+53 -12
View File
@@ -961,7 +961,7 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: List[dict], address: InvoiceAddress,
meta_info: dict, event: Event, require_approval=False):
meta_info: dict, event: Event, sales_channel: SalesChannel, require_approval=False):
fees = []
# Pre-rounding, pre-fee total is used for fee calculation
total = sum([c.gross_price_before_rounding for c in positions])
@@ -1021,7 +1021,14 @@ def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: Li
payments_assigned += to_pay
p['payment_amount'] = to_pay
if total != payments_assigned and not require_approval:
allow_postponed_payment = (
require_approval or
(
sales_channel.identifier in event.settings.payment_choice_postpone_allowed_channels and not payment_requests
)
)
if total != payments_assigned and not allow_postponed_payment:
raise OrderError(_("The selected payment methods do not cover the total balance."))
return fees
@@ -1043,7 +1050,15 @@ def _create_order(event: Event, *, email: str, positions: List[CartPosition], no
# Final calculation of fees, also performs final rounding
try:
fees = _apply_rounding_and_fees(positions, payment_requests, address, meta_info, event, require_approval=require_approval)
fees = _apply_rounding_and_fees(
positions,
payment_requests,
address,
meta_info,
event,
sales_channel=sales_channel,
require_approval=require_approval
)
except TaxRule.SaleNotAllowed:
raise OrderError(error_messages['country_blocked'])
@@ -2459,7 +2474,11 @@ class OrderChangeManager:
self.order.log_action('pretix.event.order.changed.feevalue', user=self.user, auth=self.auth, data={
'fee': fee.pk,
'old_price': fee.value,
'new_price': op.value.gross
'new_price': op.value.gross,
'old_tax_rate': fee.tax_rate,
'new_tax_rate': op.value.rate,
'old_tax_value': fee.tax_value,
'new_tax_value': op.value.tax,
})
fee.value = op.value.gross
fee._calculate_tax()
@@ -2469,9 +2488,13 @@ class OrderChangeManager:
self.order.log_action('pretix.event.order.changed.price', user=self.user, auth=self.auth, data={
'position': position.pk,
'positionid': position.positionid,
'old_price': position.price,
'addon_to': position.addon_to_id,
'new_price': op.price.gross
'old_price': position.price,
'new_price': op.price.gross,
'old_tax_rate': position.tax_rate,
'new_tax_rate': op.price.rate,
'old_tax_value': position.tax_value,
'new_tax_value': op.price.tax,
})
position.price = op.price.gross
position.price_includes_rounding_correction = Decimal("0.00")
@@ -2486,25 +2509,43 @@ class OrderChangeManager:
elif isinstance(op, self.TaxRuleOperation):
if isinstance(op.position, OrderPosition):
position = position_cache.setdefault(op.position.pk, op.position)
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data={
log = {
'position': position.pk,
'positionid': position.positionid,
'addon_to': position.addon_to_id,
'old_taxrule': position.tax_rule.pk if position.tax_rule else None,
'new_taxrule': op.tax_rule.pk
})
'new_taxrule': op.tax_rule.pk,
'old_price': position.price,
'old_tax_rate': position.tax_rate,
'old_tax_value': position.tax_value,
}
position._calculate_tax(op.tax_rule)
position.save()
log.update({
'new_price': position.price,
'new_tax_rate': position.tax_rate,
'new_tax_value': position.tax_value,
})
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data=log)
elif isinstance(op.position, OrderFee):
fee = fee_cache.setdefault(op.position.pk, op.position)
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data={
log = {
'fee': fee.pk,
'fee_type': fee.fee_type,
'old_taxrule': fee.tax_rule.pk if fee.tax_rule else None,
'new_taxrule': op.tax_rule.pk
})
'new_taxrule': op.tax_rule.pk,
'old_price': fee.value,
'old_tax_rate': fee.tax_rate,
'old_tax_value': fee.tax_value,
}
fee._calculate_tax(op.tax_rule)
fee.save()
log.update({
'new_price': fee.value,
'new_tax_rate': fee.tax_rate,
'new_tax_value': fee.tax_value,
})
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data=log)
elif isinstance(op, self.CancelFeeOperation):
fee = fee_cache.setdefault(op.fee.pk, op.fee)
self.order.log_action('pretix.event.order.changed.cancelfee', user=self.user, auth=self.auth, data={
+76
View File
@@ -0,0 +1,76 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo
from django.utils.timezone import make_aware, now
from pretix.base.models import Event, SalesChannel
from pretix.base.reldate import RelativeDateWrapper
def compute_payment_deadline(event: Event, sales_channel: SalesChannel, now_dt=None, subevents=None) -> datetime:
now_dt = now_dt or now()
tz = ZoneInfo(event.settings.timezone)
sales_channel_suffix = "_" + sales_channel.identifier.replace(".", "_")
if not (mode := event.settings.get(f'payment_term_mode{sales_channel_suffix}')):
mode = event.settings.get('payment_term_mode')
sales_channel_suffix = ""
if mode == 'days':
exp_by_date = now_dt.astimezone(tz) + timedelta(
days=event.settings.get(f'payment_term_days{sales_channel_suffix}', as_type=int))
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
if event.settings.get('payment_term_weekdays'):
if exp_by_date.weekday() == 5:
exp_by_date += timedelta(days=2)
elif exp_by_date.weekday() == 6:
exp_by_date += timedelta(days=1)
elif mode == 'minutes':
exp_by_date = now_dt.astimezone(tz) + timedelta(
minutes=event.settings.get(f'payment_term_minutes{sales_channel_suffix}', as_type=int))
else:
raise ValueError("'payment_term_mode' has an invalid value '{}'.".format(mode))
expires = exp_by_date
term_last = event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
if term_last:
if event.has_subevents and subevents:
terms = [
term_last.datetime(se).date()
for se in subevents
]
if not terms:
return expires
term_last = min(terms)
else:
term_last = term_last.datetime(event).date()
term_last = make_aware(datetime.combine(
term_last,
time(hour=23, minute=59, second=59)
), tz)
if term_last < expires:
return term_last
return expires
+68 -2
View File
@@ -343,6 +343,66 @@ def _validate_vat_id_EU(vat_id, country_code):
return vat_id
def _validate_vat_id_EU_fallback_germany(vat_id, country_code, requester_id):
# We can skip most static validation checks because _validate_vat_id_EU always runs before
vat_id = normalize_vat_id(vat_id, country_code)
# The VIES service of the European commission is overused and down due to rate limits A LOT. There is another
# API by German BZSt, but it only works if the requester is German and the requested is not.
# https://www.bzst.de/DE/Unternehmen/Identifikationsnummern/Umsatzsteuer-Identifikationsnummer/AuslaendischeUSt-IdNr/auslaendische_ust_idnr_node.html
try:
r = requests.post(
"https://api.evatr.vies.bzst.de/app/v1/abfrage",
json={
"anfragendeUstid": requester_id,
"angefragteUstid": vat_id,
},
timeout=10,
)
d = r.json()
if r.status_code == 200:
if d['status'] in ('evatr-0000', 'evatr-2008'):
# evatr-0000: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt gültig.
# evatr-2008: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt gültig.
# Für die qualifizierte Bestätigungsanfrage liegt einer Besonderheit vor.
# Für Rückfragen wenden Sie sich an das BZSt.
return vat_id
# evatr-2002: Die angefragte USt-IdNr. ist zum Anfragezeitpunkt nicht gültig.
# Sie ist erst gültig ab dem Datum im Feld gueltigAb.
# evatr-2006: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt nicht gültig.
# Sie war gültig im Zeitraum, der durch die Werte in den Feldern gueltigAb und gueltigBis beschrieben ist.
raise VATIDFinalError(error_messages['invalid'])
elif r.status_code == 400:
if d['status'] in ('evatr-0002', 'evatr-0004', 'evatr-0008'):
# evatr-0002: Mindestens eins der Pflichtfelder ist nicht besetzt.
# evatr-0004: Die anfragende DE Ust-IdNr. ist syntaktisch falsch. Sie passt nicht in das deutsche Erzeugungsschema.
# evatr-0008: Die maximale Anzahl von qualifizierten Bestätigungsabfragen für diese Session wurde erreicht.
# Bitte starten Sie erneut mit einer einfachen Bestätigungsabfrage.
raise VATIDTemporaryError(error_messages['unavailable'])
# evatr-0005: Die angegebene angefragte Ust-IdNr. ist syntaktisch falsch.
# evatr-0012: Die angefrage USt-IdNr. ist syntaktisch falsch. Sie passt nicht in das Erzeugungsschema.
# evatr-2003: Das angegebene Länderkennzeichen der angefragten USt-IdNr. ist nicht gültig.
raise VATIDFinalError(error_messages['invalid'])
elif r.status_code == 403:
# evatr-0006: Die anfragende DE USt-IdNr. ist nicht berechtigt eine DE Ust-IdNr. anzufragen.
# evatr-0007: Fehlerhafter Aufruf.
raise VATIDTemporaryError(error_messages['unavailable'])
elif r.status_code == 404:
if d['status'] in ('evatr-2005'):
# evatr-2005: Die angegebene eigene DE Ust-IdNr. ist zum Anfragezeitpunkt nicht gültig.
raise VATIDTemporaryError(error_messages['unavailable'])
# evatr-2001: Die angefragte USt-IdNr. ist zum Anfragezeitpunkt nicht vergeben.
raise VATIDFinalError(error_messages['invalid'])
else: # 500, 503
raise VATIDTemporaryError(error_messages['unavailable'])
except requests.RequestException:
logger.exception('VAT ID checking failed for country {}'.format(country_code))
raise VATIDTemporaryError(error_messages['unavailable'])
except ValueError: # JSON parsing failed
logger.exception('VAT ID checking failed for country {}'.format(country_code))
raise VATIDTemporaryError(error_messages['unavailable'])
def _validate_vat_id_CH(vat_id, country_code):
if vat_id[:3] != 'CHE':
raise VATIDFinalError(error_messages['country_mismatch'])
@@ -394,12 +454,18 @@ def _validate_vat_id_CH(vat_id, country_code):
return vat_id
def validate_vat_id(vat_id, country_code):
def validate_vat_id(vat_id, country_code, requester_id=None):
if not vat_id:
return vat_id
country_code = str(country_code)
if is_eu_country(country_code):
return _validate_vat_id_EU(vat_id, country_code)
try:
return _validate_vat_id_EU(vat_id, country_code)
except VATIDTemporaryError:
if requester_id and requester_id.startswith("DE") and not vat_id.startswith("DE"):
return _validate_vat_id_EU_fallback_germany(vat_id, country_code, requester_id)
else:
raise
elif country_code == 'CH':
return _validate_vat_id_CH(vat_id, country_code)
elif country_code == 'NO':
+85 -41
View File
@@ -21,17 +21,21 @@
#
import logging
import os
import uuid
from datetime import datetime, timezone
from decimal import Decimal
from django.core.files.base import ContentFile
from django.db import transaction
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from i18nfield.strings import LazyI18nString
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, Order,
OrderPosition,
CachedCombinedTicket, CachedFile, CachedTicket, Event, InvoiceAddress,
Order, OrderPosition,
)
from pretix.base.services.tasks import EventTask, ProfiledTask
from pretix.base.settings import PERSON_NAME_SCHEMES
@@ -42,7 +46,7 @@ from pretix.helpers.database import rolledback_transaction
logger = logging.getLogger(__name__)
def generate_orderposition(order_position: int, provider: str):
def generate_orderposition(order_position: int, provider: str, cf: CachedFile | None = None):
order_position = OrderPosition.objects.select_related('order', 'order__event').get(id=order_position)
with language(order_position.order.locale, order_position.order.event.settings.region):
@@ -52,15 +56,23 @@ def generate_orderposition(order_position: int, provider: str):
if prov.identifier == provider:
filename, ttype, data = prov.generate(order_position)
path, ext = os.path.splitext(filename)
for ct in CachedTicket.objects.filter(order_position=order_position, provider=provider):
ct.delete()
ct = CachedTicket.objects.create(order_position=order_position, provider=provider,
extension=ext, type=ttype, file=None)
ct.file.save(filename, ContentFile(data))
return ct.pk
if cf:
cf.type = ttype
cf.filename = filename
cf.file.save(name=filename, content=ContentFile(data))
return cf.pk
if prov.is_cacheable:
for ct in CachedTicket.objects.filter(order_position=order_position, provider=provider):
ct.delete()
ct = CachedTicket.objects.create(order_position=order_position, provider=provider,
extension=ext, type=ttype, file=None)
ct.file.save(filename, ContentFile(data))
return ct.pk
def generate_order(order: int, provider: str):
def generate_order(order: int, provider: str, cf: CachedFile | None = None):
order = Order.objects.select_related('event').get(id=order)
with language(order.locale, order.event.settings.region):
@@ -70,56 +82,88 @@ def generate_order(order: int, provider: str):
if prov.identifier == provider:
filename, ttype, data = prov.generate_order(order)
path, ext = os.path.splitext(filename)
for ct in CachedCombinedTicket.objects.filter(order=order, provider=provider):
ct.delete()
ct = CachedCombinedTicket.objects.create(order=order, provider=provider, extension=ext,
type=ttype, file=None)
ct.file.save(filename, ContentFile(data))
return ct.pk
if cf:
cf.type = ttype
cf.filename = filename
cf.file.save(name=filename, content=ContentFile(data))
return cf.pk
if prov.is_cacheable:
for ct in CachedCombinedTicket.objects.filter(order=order, provider=provider):
ct.delete()
ct = CachedCombinedTicket.objects.create(order=order, provider=provider, extension=ext,
type=ttype, file=None)
ct.file.save(filename, ContentFile(data))
return ct.pk
@app.task(base=ProfiledTask)
def generate(model: str, pk: int, provider: str):
def generate(model: str, pk: int, provider: str, cachedfile_pk: int | None = None):
with scopes_disabled():
if cachedfile_pk:
cf = CachedFile.objects.get(pk=cachedfile_pk)
else:
cf = None
if model == 'order':
return generate_order(pk, provider)
return generate_order(pk, provider, cf)
elif model == 'orderposition':
return generate_orderposition(pk, provider)
return generate_orderposition(pk, provider, cf)
class DummyRollbackException(Exception):
pass
def preview(event: int, provider: str):
def get_preview_position(event):
connection = transaction.get_connection()
if not connection.in_atomic_block:
raise RuntimeError("get_preview_position needs to be called in a rolledback_transaction")
item = event.items.create(name=LazyI18nString.from_gettext("Sample product"), default_price=Decimal('42.23'),
description=LazyI18nString.from_gettext("Sample product description"))
item2 = event.items.create(name=LazyI18nString.from_gettext("Sample workshop"), default_price=Decimal('23.40'))
item.program_times.create(item=item, start=datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc),
end=datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc))
from pretix.base.models import Order, SeatingPlan
order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
email='sample@pretix.eu',
locale=event.settings.locale,
sales_channel=event.organizer.sales_channels.get(identifier="web"),
expires=now(), code="PREVIEW1234", total=119)
scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]
sample = {k: str(v) for k, v in scheme['sample'].items()}
SeatingPlan.objects.create(
name="Plan", organizer=event.organizer, layout="{}"
)
event.seat_category_mappings.create(
layout_category='Stalls', product=item
)
seat = event.seats.create(seat_number="1", row_label="C", product=item, seat_guid=uuid.uuid4())
position = order.positions.create(item=item, attendee_name_parts=sample, price=item.default_price, seat=seat)
s = event.subevents.first()
order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=position, subevent=s)
order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=position, subevent=s)
InvoiceAddress.objects.create(order=order, name_parts=sample, company=_("Sample company"))
return position
def preview(event: int, provider: str, provider_arguments: dict = {}):
event = Event.objects.get(id=event)
with rolledback_transaction(), language(event.settings.locale, event.settings.region):
item = event.items.create(name=_("Sample product"), default_price=Decimal('42.23'),
description=_("Sample product description"))
item2 = event.items.create(name=_("Sample workshop"), default_price=Decimal('23.40'))
from pretix.base.models import Order
order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
email='sample@pretix.eu',
locale=event.settings.locale,
sales_channel=event.organizer.sales_channels.get(identifier="web"),
expires=now(), code="PREVIEW1234", total=119)
scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]
sample = {k: str(v) for k, v in scheme['sample'].items()}
p = order.positions.create(item=item, attendee_name_parts=sample, price=item.default_price)
s = event.subevents.first()
order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p, subevent=s)
order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p, subevent=s)
InvoiceAddress.objects.create(order=order, name_parts=sample, company=_("Sample company"))
p = get_preview_position(event)
responses = register_ticket_outputs.send(event)
for receiver, response in responses:
prov = response(event)
if prov.identifier == provider:
return prov.generate(p)
return prov.generate(p, **provider_arguments)
def get_tickets_for_order(order, base_position=None):
@@ -143,7 +187,7 @@ def get_tickets_for_order(order, base_position=None):
]
for p in providers:
if not p.is_enabled:
if not p.is_enabled or not p.is_cacheable:
continue
if p.multi_download_enabled and not base_position:
File diff suppressed because one or more lines are too long
+22 -10
View File
@@ -50,8 +50,8 @@ from pretix.api.serializers.order import (
from pretix.api.serializers.waitinglist import WaitingListSerializer
from pretix.base.i18n import LazyLocaleException
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, OrderPayment,
OrderPosition, OrderRefund, OutgoingMail, QuestionAnswer,
CachedCombinedTicket, CachedTicket, Event, Invoice, InvoiceAddress,
OrderPayment, OrderPosition, OrderRefund, OutgoingMail, QuestionAnswer,
)
from pretix.base.services.invoices import invoice_pdf_task
from pretix.base.signals import register_data_shredders
@@ -598,18 +598,30 @@ class InvoiceShredder(BaseDataShredder):
def shred_data(self, progress_callback=None):
qs_i = self.event.invoices.filter(shredded=False)
total = qs_i.count()
ignore_fields = (
'prefix', 'invoice_no', 'full_invoice_no', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
'invoice_from_city', 'invoice_from_state', 'invoice_from_country', 'invoice_from_tax_id',
'invoice_from_vat_id', 'locale', 'payment_provider_stamp', 'footer_text', 'foreign_currency_display',
'foreign_currency_source', 'transmission_type', 'transmission_provider', 'transmission_status',
)
for i in _progress_helper(qs_i, progress_callback, 0, total):
if i.file:
i.file.delete()
i.shredded = True
i.introductory_text = "█"
i.additional_text = "█"
i.invoice_to = "█"
i.payment_provider_text = "█"
i.transmission_info = {"_shredded": True}
i.save()
i.lines.update(description="█")
i.shredded = True
for f in Invoice._meta.fields:
if f.name in ignore_fields:
continue
val = getattr(i, f.name, None)
if val and isinstance(val, str):
setattr(i, f.name, "█")
elif val and isinstance(val, list): # jsonfield
setattr(i, f.name, [])
elif val and isinstance(val, dict): # jsonfield
setattr(i, f.name, {"_shredded": True})
i.save()
i.lines.update(description="█", attendee_name="█")
class CachedTicketShredder(BaseDataShredder):
+13 -5
View File
@@ -605,15 +605,14 @@ subclass of pretix.base.ticketoutput.BaseTicketOutput
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_notification_types = EventPluginSignal()
register_notification_types = GlobalSignal()
"""
This signal is sent out to get all known notification types. Receivers should return an
instance of a subclass of pretix.base.notifications.NotificationType or a list of such
instances.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event,
however for this signal, the ``sender`` **may also be None** to allow creating the general
notification settings!
When called for actually sending notifications, ``sender`` will be the event or organizer,
depending on context.
"""
register_event_permission_groups = GlobalSignal()
@@ -628,7 +627,7 @@ This signal is sent out to get all known permissions. Receivers should return an
instance of pretix.base.permissions.PermissionGroup or a list of such instances.
"""
notification = EventPluginSignal()
event_notification_sent = EventPluginSignal()
"""
Arguments: ``logentry_id``, ``notification_type``
@@ -637,6 +636,15 @@ This signal is sent out when a notification is sent.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
organizer_notification_sent = OrganizerPluginSignal()
"""
Arguments: ``logentry_id``, ``notification_type``
This signal is sent out when a notification is sent.
As with all organizer-plugin signals, the ``sender`` keyword argument will contain the organizer.
"""
register_sales_channel_types = GlobalSignal()
"""
This signal is sent out to get all known sales channels types. Receivers should return an
+52 -23
View File
@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>.
#
from decimal import ROUND_HALF_UP, Decimal
from typing import Optional
from babel import Locale, UnknownLocaleError
from babel.numbers import format_currency
@@ -35,32 +36,32 @@ register = template.Library()
@register.filter("money")
def money_filter(value: Decimal, arg='', hide_currency=False):
if isinstance(value, (float, int)):
def money_filter(value: Optional[Decimal | float | int | str], arg='', hide_currency=False) -> str:
if isinstance(value, (float, int, str)):
if value == '':
return value # pyright: ignore[reportReturnType]
value = Decimal(value)
if value is None:
value = Decimal('0.00')
if not isinstance(value, Decimal):
if value == '':
return value
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
if not arg:
raise ValueError("No currency passed.")
arg = arg.upper()
places = settings.CURRENCY_PLACES.get(arg, 2)
rounded = value.quantize(Decimal('1') / 10 ** places, ROUND_HALF_UP)
if places < 2 and rounded != value:
# We display decimal places even if we shouldn't for this currency if rounding
# would make the numbers incorrect. If this branch executes, it's likely a bug in
# pretix, but we won't show wrong numbers!
if hide_currency:
return floatformat(value, "2g")
else:
return '{} {}'.format(arg, floatformat(value, "2g"))
if value.normalize().as_tuple().exponent < -9:
# Heuristic: It's unlikely we'll ever see values of less than 0.000000001 in any currency. Therefore, if we
# do see them, we very likely deal with a floating point error. This happens mostly in dev mode when computations
# are made in SQLite, which uses REAL precision, but it can also happen when we naively pass a float from Python
# land to this filter (even though it should not happen).
value = value.quantize(Decimal('1e-9'), ROUND_HALF_UP).normalize()
currency_places = settings.CURRENCY_PLACES.get(arg, 2)
required_places = -value.normalize().as_tuple().exponent
render_places = max(currency_places, required_places)
if hide_currency:
return floatformat(value, f"{places}g")
return floatformat(value, f"{render_places}g")
try:
locale = Locale(get_babel_locale())
@@ -68,14 +69,29 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
locale = "en"
try:
return format_currency(value, arg, locale=locale)
return format_currency(
value,
arg,
locale=locale,
# We only allow Babel to restrict the digits to the digits defined by the currency if this does not remove any
# precision in case we have sub-currency precision (which we shouldn't have in most places, but it's still
# better than showing wrong data). Note: Weird precision effects can occur after in-database arithmetic
# on SQLite, since SQLite does not have fixed-decimal computation.
currency_digits=currency_places >= required_places,
decimal_quantization=currency_places >= required_places,
)
except:
return '{} {}'.format(arg, floatformat(value, f"{places}g"))
return '{} {}'.format(arg, floatformat(value, f"{render_places}g"))
@register.filter("money_without_currency")
def money_filter_without_currency(value: Optional[Decimal | float | int | str], arg=''):
return money_filter(value, arg, hide_currency=True)
@register.filter("money_numberfield")
def money_numberfield_filter(value: Decimal, arg=''):
if isinstance(value, (float, int)):
def money_numberfield_filter(value: Optional[Decimal | float | int | str], arg=''):
if isinstance(value, (float, int, str)):
value = Decimal(value)
if not isinstance(value, Decimal):
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
@@ -87,15 +103,28 @@ def money_numberfield_filter(value: Decimal, arg=''):
@register.filter(is_safe=True)
def tax_rate_format(number):
def tax_rate_format(number: Optional[Decimal | float | int | str]):
"""
Display a Decimal to its significant decimal places, used for tax rates.
"""
assert isinstance(number, Decimal)
if isinstance(number, (float, int, str)):
if number == '':
return number
number = Decimal(number)
if number is None:
number = Decimal('0.00')
if not isinstance(number, Decimal):
raise TypeError("Invalid data type passed to tax rate format filter: %r" % type(number))
if number.normalize().as_tuple().exponent < -9:
# Heuristic: It's unlikely we'll ever see values of less than 0.000000001 in any currency. Therefore, if we
# do see them, we very likely deal with a floating point error. This happens mostly in dev mode when computations
# are made in SQLite, which uses REAL precision, but it can also happen when we naively pass a float from Python
# land to this filter (even though it should not happen).
number = number.quantize(Decimal('1e-9'), ROUND_HALF_UP).normalize()
return mark_safe(
formats.number_format(
number.normalize(),
-number.as_tuple().exponent,
number,
-number.normalize().as_tuple().exponent,
use_l10n=True,
force_grouping=False,
)
+25 -1
View File
@@ -114,7 +114,7 @@ class BaseTicketOutput:
If you override this method, make sure that positions that are addons (i.e. ``addon_to``
is set) are only outputted if the event setting ``ticket_download_addons`` is active.
Do the same for positions that are non-admission without ``ticket_download_nonadm`` active.
If you want, you can just iterate over ``order.positions_with_tickets`` which applies the
If you want, you can just iterate over ``self.get_tickets_to_print`` which applies the
appropriate filters for you.
"""
with tempfile.TemporaryDirectory() as d:
@@ -192,6 +192,17 @@ class BaseTicketOutput:
"""
pass
@property
def is_meta(self) -> bool:
"""
Returns whether or whether not this output is a "meta" output that only works as a settings holder
and should never be used directly. This is a trick to implement outputs with multiple formats but
unified settings.
.. note:: You should set is_enabled to False for meta outputs.
"""
return False
@property
def download_button_text(self) -> str:
"""
@@ -220,6 +231,19 @@ class BaseTicketOutput:
"""
return 'fa-download'
@property
def confirmation_text(self) -> str | None:
"""
A text the user has to confirm before a pass is generated.
Returning None means no confirmation screen is shown.
"""
return None
@property
def is_cacheable(self) -> bool:
return True
@property
def preview_allowed(self) -> bool:
"""
+2 -2
View File
@@ -21,8 +21,8 @@
#
import contextvars
from contextlib import contextmanager
from datetime import datetime
from dateutil.parser import parse
from django.utils.timezone import now
from pretix.base.auth import has_event_access_permission
@@ -34,7 +34,7 @@ timemachine_now_var = contextvars.ContextVar('timemachine_now', default=None)
def time_machine_now_assigned_from_request(request):
if hasattr(request, 'event') and f'timemachine_now_dt:{request.event.pk}' in request.session and \
request.event.testmode and has_event_access_permission(request):
request.now_dt = parse(request.session[f'timemachine_now_dt:{request.event.pk}'])
request.now_dt = datetime.fromisoformat(request.session[f'timemachine_now_dt:{request.event.pk}'])
request.now_dt_is_fake = True
else:
request.now_dt = now()
+7 -2
View File
@@ -19,6 +19,7 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django.conf import settings
from django.http import (
HttpResponseForbidden, HttpResponseNotFound, HttpResponseServerError,
)
@@ -27,7 +28,6 @@ from django.template import TemplateDoesNotExist, loader
from django.template.loader import get_template
from django.utils.functional import Promise
from django.utils.translation import gettext as _
from sentry_sdk import last_event_id
from pretix.base.i18n import language
from pretix.base.middleware import get_language_from_request
@@ -106,9 +106,14 @@ def server_error(request):
template = loader.get_template('500.html')
except TemplateDoesNotExist:
return HttpResponseServerError('<h1>Server Error (500)</h1>', content_type='text/html')
if settings.SENTRY_ENABLED:
from sentry_sdk import last_event_id
sentry_id = last_event_id()
else:
sentry_id = None
r = HttpResponseServerError(template.render({
'request': request,
'sentry_event_id': last_event_id(),
'sentry_event_id': sentry_id,
}))
r.xframe_options_exempt = True
return r
+16
View File
@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>.
#
import logging
import multiprocessing
import os
from celery import Celery, signals
@@ -54,6 +55,21 @@ def on_task_received(sender, request, **kwargs):
logger.info(f"Task {request.id} has trace {trace}")
@receiver(signals.after_setup_task_logger)
def on_after_setup_task_logger(sender, logger, loglevel, logfile, format, colorize, **kwargs):
# This hack seems to be required to get celery to log internal events from eg billiard/pool.py such as
# "worker killed because it used too much memory"
# You can test that it is working by starting a celery worker with a low value like
# --max-memory-per-child 300000
# and then trigger a task. Result should look like this:
# [2026-09-23 10:42:26,234: WARNING/ForkPoolWorker-16]: [???:???] child process exiting after exceeding memory limit (394540KiB / 300000KiB)
# The ???:??? are likely because by copying the handlers, we are also copying the format, but I was unable to find
# a better compatible way.
multi_logger = multiprocessing.get_logger()
multi_logger.setLevel(logging.WARNING)
multi_logger.handlers = logger.handlers
@receiver(signals.task_prerun)
def on_task_prerun(sender, task_id, task, **kwargs):
from pretix.helpers.logs import local
+12 -1
View File
@@ -172,7 +172,9 @@ class CachedFileInput(forms.ClearableFileInput):
from ...base.models import CachedFile
v = super().value_from_datadict(data, files, name)
if v is None and data.get(name + '-cachedfile'): # An explicit "[x] clear" would be False, not None
return CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
v = CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
if not v.allowed_for_session(self.request):
v = None
return v
def get_context(self, name, value, attrs):
@@ -244,6 +246,11 @@ class ExtFileField(ExtValidationMixin, SizeFileField):
class CachedFileField(ExtFileField):
widget = CachedFileInput
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request", None)
super().__init__(*args, **kwargs)
self.widget.request = self.request
def to_python(self, data):
from ...base.models import CachedFile
@@ -271,6 +278,8 @@ class CachedFileField(ExtFileField):
filename=data.name,
type=data.content_type,
)
if self.request:
cf.bind_to_session(self.request) # no salt because we want direct web access
cf.file.save(data.name, data.file)
cf.save()
data._uploaded_to = cf
@@ -294,6 +303,8 @@ class CachedFileField(ExtFileField):
filename=data.name,
type=data.content_type,
)
if self.request:
cf.bind_to_session(self.request) # no salt because we want direct web access
cf.file.save(data.name, data.file)
cf.save()
data._uploaded_to = cf
+24 -11
View File
@@ -400,10 +400,10 @@ class EventMetaValueForm(forms.ModelForm):
if self.disabled:
self.fields['value'].widget.attrs['readonly'] = 'readonly'
def clean_slug(self):
def clean_value(self):
if self.disabled:
return self.instance.value if self.instance else None
return self.cleaned_data['slug']
return self.cleaned_data['value']
class Meta:
model = EventMetaValue
@@ -838,9 +838,10 @@ class CancelSettingsForm(SettingsForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.obj.settings.giftcard_expiry_years is not None:
self.fields['cancel_allow_user_paid_refund_as_giftcard'].help_text = gettext(
'You have configured gift cards to be valid {} years plus the year the gift card is issued in.'
).format(self.obj.settings.giftcard_expiry_years)
self.fields['cancel_allow_user_paid_refund_as_giftcard'].help_text = format_html(
gettext('You have configured gift cards to be valid {} years plus the year the gift card is issued in.'),
self.obj.settings.giftcard_expiry_years
)
class PaymentSettingsForm(EventSettingsValidationMixin, SettingsForm):
@@ -855,14 +856,21 @@ class PaymentSettingsForm(EventSettingsValidationMixin, SettingsForm):
'payment_term_accept_late',
'payment_pending_hidden',
'payment_explanation',
'payment_choice_postpone_allowed_channels',
'tax_rule_payment',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
channels = list(self.obj.organizer.sales_channels.all())
self.fields['payment_choice_postpone_allowed_channels'].choices = [
(c.identifier, c.label) for c in channels
if c.type_instance.payment_restrictions_supported
]
self.term_channel_fields = {}
for c in self.obj.organizer.sales_channels.all():
for c in channels:
if c.type_instance.payment_restrictions_supported and c.identifier != "web":
# At the moment, it seems sufficient to allow this for the same channel types as other payment settings
# We can always introduce more flags later if needed
@@ -997,7 +1005,7 @@ class TaxSettingsForm(EventSettingsValidationMixin, SettingsForm):
class ProviderForm(SettingsForm):
"""
This is a SettingsForm, but if fields are set to required=True, validation
errors are only raised if the payment method is enabled.
errors are only raised if the provider is enabled.
"""
def __init__(self, *args, **kwargs):
@@ -1628,10 +1636,15 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
self._set_field_placeholders(k, v, rich=k.startswith('mail_text_') and k not in self.plain_rendering)
for k, v in list(self.fields.items()):
if k.endswith('_attendee') and not event.settings.attendee_emails_asked:
# If we don't ask for attendee emails, we can't send them anything and we don't need to clutter
# the user interface with it
del self.fields[k]
if k.endswith('_attendee'):
if not event.settings.attendee_emails_asked:
# If we don't ask for attendee emails, we can't send them anything and we don't need to clutter
# the user interface with it
del self.fields[k]
elif 'subject' in k and k.replace("subject", "send") in self.fields:
v.widget.attrs["data-display-dependency"] = f'#id_{k.replace("subject", "send")}'
elif 'text' in k and k.replace("text", "send") in self.fields:
v.widget.attrs["data-display-dependency"] = f'#id_{k.replace("text", "send")}'
class TicketSettingsForm(SettingsForm):
@@ -105,13 +105,6 @@ class GlobalSettingsForm(SettingsForm):
domain=settings.SITE_URL
)
)),
('widget_vite_origins', forms.CharField(
widget=forms.Textarea(attrs={'rows': '3'}),
required=False,
# Not translated on purpose, this is a temporary feature and contains too many special case words
label="Vite widget origins",
help_text="One origin per line (e.g. https://example.com). Requests from these origins will be served the new vite-based widget.",
))
])
responses = register_global_settings.send(self)
for r, response in sorted(responses, key=lambda r: str(r[0])):
+2 -2
View File
@@ -22,7 +22,7 @@
from django import forms
from django.core.exceptions import ValidationError
from django.utils.functional import lazy
from django.utils.html import format_html
from django.utils.html import conditional_escape, format_html
from django.utils.translation import gettext_lazy as _
from pretix.base.modelimport_orders import get_order_import_columns
@@ -66,7 +66,7 @@ class ProcessForm(forms.Form):
widget=forms.Select(
attrs={'data-static': 'true'}
),
help_text=c.help_text,
help_text=conditional_escape(c.help_text or ""),
)
def get_columns(self):
+2 -2
View File
@@ -364,7 +364,7 @@ class TeamForm(forms.ModelForm):
for opt in pg.options
],
label=pg.label,
help_text=pg.help_text,
help_text=conditional_escape(pg.help_text or ""),
initial=initial,
widget=forms.RadioSelect,
)
@@ -389,7 +389,7 @@ class TeamForm(forms.ModelForm):
for opt in pg.options
],
label=pg.label,
help_text=pg.help_text,
help_text=conditional_escape(pg.help_text or ""),
initial=initial,
widget=forms.RadioSelect,
)
+2
View File
@@ -87,6 +87,7 @@ class RRuleForm(forms.Form):
('1', pgettext_lazy('rrule', 'first')),
('2', pgettext_lazy('rrule', 'second')),
('3', pgettext_lazy('rrule', 'third')),
('4', pgettext_lazy('rrule', 'fourth')),
('-1', pgettext_lazy('rrule', 'last')),
],
required=False
@@ -134,6 +135,7 @@ class RRuleForm(forms.Form):
('1', pgettext_lazy('rrule', 'first')),
('2', pgettext_lazy('rrule', 'second')),
('3', pgettext_lazy('rrule', 'third')),
('4', pgettext_lazy('rrule', 'fourth')),
('-1', pgettext_lazy('rrule', 'last')),
],
required=False
+2 -2
View File
@@ -435,10 +435,10 @@ class SubEventMetaValueForm(forms.ModelForm):
if self.disabled:
self.fields['value'].widget.attrs['readonly'] = 'readonly'
def clean_slug(self):
def clean_value(self):
if self.disabled:
return self.instance.value if self.instance else None
return self.cleaned_data['slug']
return self.cleaned_data['value']
class Meta:
model = SubEventMetaValue
+2 -1
View File
@@ -44,6 +44,7 @@ from django.db.models import Count, F, Max
from django.db.models.functions import Upper
from django.forms.utils import ErrorDict
from django.urls import reverse
from django.utils.html import escape
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes.forms import SafeModelChoiceField
@@ -176,7 +177,7 @@ class VoucherForm(I18nModelForm):
required=False,
widget=forms.TextInput(attrs={'data-seat-guid-field': '1'}),
initial=self.instance.seat.seat_guid if self.instance.seat else '',
help_text=str(self.instance.seat) if self.instance.seat else '',
help_text=escape(str(self.instance.seat) if self.instance.seat else ''),
)
def parse_itemvar(self, data):
+5
View File
@@ -717,6 +717,10 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
'pretix.organizer.export.schedule.failed': _('A scheduled export has failed: {reason}.'),
'pretix.organizer.outgoingmails.retried': _('Failed emails have been scheduled to be retried.'),
'pretix.organizer.outgoingmails.aborted': _('Queued emails have been aborted.'),
'pretix.property.created': _('An organizer meta property has been created.'),
'pretix.property.deleted': _('An organizer meta property has been deleted.'),
'pretix.property.changed': _('An organizer meta property has been changed.'),
'pretix.property.reordered': _('An organizer meta property has been reordered.'),
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
'pretix.giftcards.acceptance.acceptor.invited': _('A new gift card acceptor has been invited.'),
@@ -774,6 +778,7 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
'pretix.user.settings.2fa.disabled': _('Two-factor authentication has been disabled.'),
'pretix.user.settings.2fa.regenemergency': _('Your two-factor emergency codes have been regenerated.'),
'pretix.user.settings.2fa.emergency': _('A two-factor emergency code has been generated.'),
'pretix.user.settings.2fa.resetdrift': _('Drift and throttle values for two-factor devices have been reset.'),
'pretix.user.settings.2fa.device.added': _('A new two-factor authentication device "{name}" has been added to '
'your account.'),
'pretix.user.settings.2fa.device.deleted': _('The two-factor authentication device "{name}" has been removed '
+2 -2
View File
@@ -85,8 +85,8 @@ class PermissionMiddleware:
"user.settings.2fa.enable",
"user.settings.2fa.disable",
"user.settings.2fa.regenemergency",
"user.settings.2fa.confirm.totp",
"user.settings.2fa.confirm.webauthn",
"user.settings.2fa.confirm.otp_totp.totpdevice",
"user.settings.2fa.confirm.pretixbase.webauthndevice",
"user.settings.2fa.delete",
"user.settings.2fa.leaveteams",
"auth.logout",
@@ -45,8 +45,8 @@
</p>
<div class="form-group buttons">
<input type="submit" class="btn btn-large btn-default" value="Cancel"/>
<input type="submit" class="btn btn-large btn-primary" name="allow" value="Authorize"/>
<input type="submit" class="btn btn-large btn-default" value="{% trans "Cancel" %}"/>
<input type="submit" class="btn btn-large btn-primary" name="allow" value="{% trans "Authorize" %}"/>
</div>
</form>
{% else %}
@@ -481,5 +481,15 @@
<div class="steps">
</div>
{% enddialog %}
{% trans "Confirmation required" as confirmation_title %}
{% dialog "confirmationmodal" confirmation_title "" icon="shield" %}
<div class="modal-card-confirm modal-card-confirm-spread">
<button type="button" class="btn btn-default btn-lg" commandfor="confirmationmodal" command="close">{% trans "Cancel" %}</button>
<button type="button" id="confirmationmodal-confirm"
class="btn btn-primary btn-lg">
{% trans "Confirm" %}
</button>
</div>
{% enddialog %}
</body>
</html>
@@ -109,6 +109,7 @@
{% bootstrap_form_errors form layout="control" %}
{% bootstrap_field form.tax_rule_payment layout="control" %}
{% bootstrap_field form.payment_explanation layout="control" %}
{% bootstrap_field form.payment_choice_postpone_allowed_channels layout="control" %}
</fieldset>
</div>
{% if "event.settings.payment:write" in request.eventpermset %}
@@ -33,7 +33,7 @@
</div>
<div class="slug-length alert alert-warning helper-display-none-soft">
{% blocktrans trimmed %}
We strongly recommend against using short forms of more then 16 characters.
We strongly recommend against using short forms of more than 16 characters.
{% endblocktrans %}
</div>
</div>
@@ -10,6 +10,7 @@
{% load l10n %}
{% load phone_format %}
{% load getitem %}
{% load dialog %}
{% block title %}
{% blocktrans trimmed with code=order.code %}
Order details: {{ code }}
@@ -568,7 +569,8 @@
class="form-inline helper-display-inline{% if b.javascript_required %} requirejs{% endif %}">
{% csrf_token %}
<button type="submit"
class="btn btn-xs btn-default">
class="btn btn-xs btn-default"
{% if b.confirmation_text %}data-confirmation-text="{{ b.confirmation_text|rich_text|force_escape }}"{% endif %}>
<span class="fa {{ b.icon }}"></span> {{ b.text }}
</button>
</form>
@@ -15,7 +15,7 @@
<div>
<div class="big-radio radio">
<label>
<input type="radio" required value="totp" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "totp" %}checked{% endif %}>
<input type="radio" required value="otp_totp.totpdevice" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "otp_totp.totpdevice" %}checked{% endif %}>
<strong>{% trans "Smartphone with Authenticator app" %}</strong><br>
<div class="help-block">
{% blocktrans trimmed %}
@@ -26,7 +26,7 @@
</div>
<div class="big-radio radio">
<label>
<input type="radio" required value="webauthn" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "webauthn" %}checked{% endif %}>
<input type="radio" required value="pretixbase.webauthndevice" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "pretixbase.webauthndevice" %}checked{% endif %}>
<strong>{% trans "WebAuthn-compatible hardware token" %}</strong><br>
<div class="help-block">
{% blocktrans trimmed %}
@@ -116,14 +116,14 @@
{% for d in devices %}
<li class="list-group-item">
<a class="btn btn-danger btn-xs pull-right flip"
href="{% url "control:user.settings.2fa.delete" devicetype=d.devicetype device=d.pk %}">
href="{% url "control:user.settings.2fa.delete" devicetype=d.model_label device=d.pk %}">
Delete
</a>
{% if d.devicetype == "totp" %}
{% if d.model_label == "otp_totp.totpdevice" %}
<span class="fa fa-mobile"></span>
{% elif d.devicetype == "webauthn" %}
{% elif d.model_label == "pretixbase.webauthndevice" %}
<span class="fa fa-usb"></span>
{% elif d.devicetype == "u2f" %}
{% elif d.model_label == "pretixbase.u2fdevice" %}
<span class="fa fa-usb"></span>
{% endif %}
{{ d.name }}
@@ -1,6 +1,7 @@
{% extends "pretixcontrol/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load icon %}
{% block title %}{% trans "User" %}{% endblock %}
{% block content %}
<h1>{% trans "User" %} {{ user.email }}</h1>
@@ -59,8 +60,83 @@
{% bootstrap_field form.is_verified layout='control' %}
{% endif %}
{% bootstrap_field form.last_login layout='control' %}
{% bootstrap_field form.require_2fa layout='control' %}
{% bootstrap_field form.needs_password_change layout='control' %}
{% bootstrap_field form.require_2fa layout='control' %}
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-heading">
<button class="btn btn-default btn-xs pull-right" type="submit" form="resetdriftthrottle">
{% trans "Reset drift and throttle" %}
</button>
<h3 class="panel-title">
{% trans "Available two-factor authentication methods" %}
</h3>
</div>
<table class="panel-body table table-hover">
{% for d in devices %}
<tr>
<td>
{% if d.model_label == 'otp_totp.totpdevice' %}
TOTP
{% elif d.model_label == 'pretixbase.u2fdevice' %}
U2F
{% elif d.model_label == 'pretixbase.webauthndevice' %}
WebAuthn
{% elif d.model_label == 'otp_static.staticdevice' %}
{% trans "Emergency tokens" %}
{% endif %}
{% if d.confirmed %}
{% icon "check" %}
{% else %}
{% icon "warning" %}
{% endif %}
</td>
<td>
{{ d.name }}
</td>
<td>
{% if d.throttling_failure_timestamp %}
{% blocktrans trimmed with date=d.throttling_failure_timestamp|date:"SHORT_DATETIME_FORMAT" count cnt=d.throttling_failure_count %}
1 failed attempt since {{ date }}
{% plural %}
{{ cnt }} failed attempts since {{ date }}
{% endblocktrans %}
<br>
{% endif %}
{% if d.throttling_enabled and not d.verify_is_allowed.0 %}
<strong>
{% blocktrans trimmed with date=d.verify_is_allowed.1.locked_until|date:"SHORT_DATETIME_FORMAT" %}
Currently locked until {{ date }}
{% endblocktrans %}
</strong>
<br>
{% endif %}
{% if d.model_label == 'otp_totp.totpdevice' %}
<small>
<code>step = {{ d.step }},
t0 = {{ d.t0 }},
digits = {{ d.digits }},
tolerance = {{ d.tolerance }},
drift = {{ d.drift }},
last_t = {{ d.last_t }}</code>
</small>
{% elif d.model_label == 'pretixbase.u2fdevice' %}
<small>
<code>sign_count = {{ d.sign_count }}</code>
</small>
{% elif d.model_label == 'otp_static.staticdevice' %}
<small>
<code>token_count = {{ d.token_set.count }}</code>
</small>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</fieldset>
<fieldset>
<legend>{% trans "Team memberships" %}</legend>
@@ -102,4 +178,8 @@
</div>
</div>
</div>
<form action="{% url "control:users.resetdriftthrottle" id=user.pk %}" id="resetdriftthrottle" method="post">
{% csrf_token %}
</form>
{% endblock %}
+3 -2
View File
@@ -78,6 +78,7 @@ urlpatterns = [
re_path(r'^users/(?P<id>\d+)/impersonate$', users.UserImpersonateView.as_view(), name='users.impersonate'),
re_path(r'^users/(?P<id>\d+)/anonymize$', users.UserAnonymizeView.as_view(), name='users.anonymize'),
re_path(r'^users/(?P<id>\d+)/emergencytoken$', users.UserEmergencyTokenView.as_view(), name='users.emergencytoken'),
re_path(r'^users/(?P<id>\d+)/resetdriftthrottle$', users.Reset2FADriftThrottleView.as_view(), name='users.resetdriftthrottle'),
re_path(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'),
re_path(r'^settings/?$', user.UserSettings.as_view(), name='user.settings'),
re_path(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'),
@@ -106,9 +107,9 @@ urlpatterns = [
re_path(r'^settings/2fa/regenemergency', user.User2FARegenerateEmergencyView.as_view(),
name='user.settings.2fa.regenemergency'),
re_path(r'^settings/2fa/totp/(?P<device>[0-9]+)/confirm', user.User2FADeviceConfirmTOTPView.as_view(),
name='user.settings.2fa.confirm.totp'),
name='user.settings.2fa.confirm.otp_totp.totpdevice'),
re_path(r'^settings/2fa/webauthn/(?P<device>[0-9]+)/confirm', user.User2FADeviceConfirmWebAuthnView.as_view(),
name='user.settings.2fa.confirm.webauthn'),
name='user.settings.2fa.confirm.pretixbase.webauthndevice'),
re_path(r'^settings/2fa/(?P<devicetype>[^/]+)/(?P<device>[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(),
name='user.settings.2fa.delete'),
re_path(r'^settings/email/confirm$', user.UserEmailConfirmView.as_view(), name='user.settings.email.confirm'),
+53 -15
View File
@@ -35,6 +35,7 @@
import base64
import json
import logging
import math
import time
from urllib.parse import quote, urljoin, urlparse
@@ -50,11 +51,12 @@ from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.translation import gettext_lazy as _
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, ngettext
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.views.generic import TemplateView
from django_otp import match_token
from django_otp import devices_for_user
from django_otp.plugins.otp_static.models import StaticDevice
from webauthn.helpers import generate_challenge
@@ -64,6 +66,7 @@ from pretix.base.forms.auth import (
)
from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins
from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice
from pretix.helpers import OF_SELF
from pretix.helpers.http import get_client_ip, redirect_to_url
from pretix.helpers.ratelimit import rate_limit, rate_limit_reset
from pretix.helpers.security import handle_login_source, session_login
@@ -395,15 +398,17 @@ class Recover(TemplateView):
def post(self, request, *args, **kwargs):
if self.form.is_valid():
try:
user = User.objects.get(id=self.request.GET.get('id'), auth_backend='native')
except User.DoesNotExist:
return self.invalid('unknownuser')
if not default_token_generator.check_token(user, self.request.GET.get('token')):
return self.invalid('invalid')
user.set_password(self.form.cleaned_data['password'])
user.needs_password_change = False
user.save()
with transaction.atomic():
# Check token in transaction to prevent race condition
try:
user = User.objects.select_for_update(of=OF_SELF).get(id=self.request.GET.get('id'), auth_backend='native')
except User.DoesNotExist:
return self.invalid('unknownuser')
if not default_token_generator.check_token(user, self.request.GET.get('token')):
return self.invalid('invalid')
user.set_password(self.form.cleaned_data['password'])
user.needs_password_change = False
user.save()
messages.success(request, _('You can now login using your new password.'))
user.log_action('pretix.control.auth.user.forgot_password.recovered')
@@ -460,6 +465,7 @@ class Login2FAView(TemplateView):
token = request.POST.get('token', '').strip().replace(' ', '')
valid = False
retry_after = None
if 'webauthn_challenge' in self.request.session and token.startswith('{'):
challenge = self.request.session['webauthn_challenge']
@@ -515,12 +521,28 @@ class Login2FAView(TemplateView):
valid = True
break
else:
valid = match_token(self.user, token)
if isinstance(valid, StaticDevice):
with transaction.atomic():
for device in devices_for_user(self.user, for_verify=True):
if isinstance(device, StaticDevice) and len(token) < 12:
# If we enter a wrong TOTP token (which is 6 characters), do not even try if it is a valid
# emergency token, which will only "lock up" the StaticDevice due to the throttling plugin
# and just locks people out without security gain.
continue
if device.verify_token(token):
valid = True
break
elif hasattr(device, 'verify_is_allowed'):
verify_allowed, reason_dict = device.verify_is_allowed()
if not verify_allowed:
if not retry_after or reason_dict['locked_until'] > retry_after:
retry_after = reason_dict['locked_until']
else:
device = None
if isinstance(device, StaticDevice):
self.user.send_security_notice([
_("A recovery code for two-factor authentification was used to log in.")
])
if valid:
logger.info(f"Backend login successful for user {self.user.pk} with 2FA.")
pretix_successful_logins.inc(1)
@@ -533,7 +555,23 @@ class Login2FAView(TemplateView):
return redirect('control:index')
else:
pretix_failed_logins.inc(1, reason="2fa")
messages.error(request, _('Invalid code, please try again.'))
msg = _('Invalid code, please try again.')
if retry_after:
seconds = (retry_after - now()).total_seconds()
minutes = seconds / 60
if minutes >= 1:
msg = ngettext(
'Invalid code. Please try again after waiting {value} minute.',
'Invalid code. Please try again after waiting {value} minutes.',
minutes,
).format(value=math.ceil(minutes))
elif seconds >= 1:
msg = ngettext(
'Invalid code. Please try again after waiting {value} second.',
'Invalid code. Please try again after waiting {value} seconds.',
seconds,
).format(value=math.ceil(seconds))
messages.error(request, msg)
return redirect('control:auth.login.2fa')
def get_context_data(self, **kwargs):
+5 -2
View File
@@ -402,9 +402,12 @@ def event_index_widgets_lazy(request, organizer, event):
except SubEvent.DoesNotExist:
pass
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
request=request)
widgets = []
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=False):
widgets.extend(result)
if can_view_orders:
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=False):
widgets.extend(result)
return build_json_response(widgets)
+3
View File
@@ -163,6 +163,9 @@ class DiscountCreate(EventPermissionRequiredMixin, CreateView):
i = modelcopy(self.copy_from)
i.pk = None
kwargs['instance'] = i
kwargs["initial"]["limit_sales_channels"] = self.copy_from.limit_sales_channels.all()
kwargs["initial"]["condition_limit_products"] = self.copy_from.condition_limit_products.all()
kwargs["initial"]["benefit_limit_products"] = self.copy_from.benefit_limit_products.all()
else:
kwargs['instance'] = Discount(event=self.request.event)
+9 -4
View File
@@ -982,7 +982,7 @@ class TicketSettingsPreview(EventPermissionRequiredMixin, View):
responses = register_ticket_outputs.send(self.request.event)
for receiver, response in responses:
provider = response(self.request.event)
if provider.identifier == self.kwargs.get('output'):
if provider.identifier == self.kwargs.get('output') and not provider.is_meta:
return provider
def get(self, request, *args, **kwargs):
@@ -1085,6 +1085,11 @@ class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
responses = register_ticket_outputs.send(self.request.event)
for receiver, response in responses:
provider = response(self.request.event)
provider_settings_fields = provider.settings_form_fields
provider_settings_content = provider.settings_content_render(self.request)
if not provider_settings_fields and not provider_settings_content:
continue
provider.form = ProviderForm(
obj=self.request.event,
settingspref='ticketoutput_%s_' % provider.identifier,
@@ -1094,17 +1099,17 @@ class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
provider.form.fields = OrderedDict(
[
('ticketoutput_%s_%s' % (provider.identifier, k), v)
for k, v in provider.settings_form_fields.items()
for k, v in provider_settings_fields.items()
]
)
provider.settings_content = provider.settings_content_render(self.request)
provider.settings_content = provider_settings_content
provider.form.prepare_fields()
provider.evaluated_preview_allowed = True
if not provider.preview_allowed:
provider.evaluated_preview_allowed = False
else:
for k, v in provider.settings_form_fields.items():
for k, v in provider_settings_fields.items():
if v.required and not self.request.event.settings.get('ticketoutput_%s_%s' % (provider.identifier, k)):
provider.evaluated_preview_allowed = False
break
+56 -14
View File
@@ -54,6 +54,7 @@ from django.db.models import (
Count, Exists, F, IntegerField, Max, OuterRef, Prefetch, ProtectedError, Q,
QuerySet, Subquery, Sum,
)
from uuid import UUID
from django.forms import formset_factory
from django.http import (
FileResponse, Http404, HttpResponseNotAllowed, HttpResponseRedirect,
@@ -582,12 +583,15 @@ class OrderDetail(OrderView):
responses = register_ticket_outputs.send(self.request.event)
for receiver, response in responses:
provider = response(self.request.event)
if provider.is_meta:
continue
buttons.append({
'text': provider.download_button_text or 'Ticket',
'icon': provider.download_button_icon or 'fa-download',
'identifier': provider.identifier,
'multi': provider.multi_download_enabled,
'javascript_required': provider.javascript_required
'javascript_required': provider.javascript_required,
'confirmation_text': provider.confirmation_text
})
return buttons
@@ -667,7 +671,18 @@ class OrderDownload(AsyncAction, OrderView):
permission = 'event.orders:read'
def get_success_url(self, value):
return self.get_self_url()
if isinstance(value, UUID):
cf = CachedFile.objects.filter(pk=value).first()
else:
cf = None
if not cf or not cf.allowed_for_session(self.request):
return self.get_self_url()
if cf.type == 'text/uri-list':
return cf.file.file.read().decode()
else:
return reverse('cachedfile.download', kwargs={"id":str(value)})
def get_error_url(self):
return self.get_order_url()
@@ -706,12 +721,19 @@ class OrderDownload(AsyncAction, OrderView):
if 'position' in kwargs and not self.order_position.generate_ticket:
return self.error(_('Ticket download is not enabled for this product.'))
ct = self.get_last_ct()
if ct:
if self.output.is_cacheable and (ct := self.get_last_ct()):
return self.success(ct)
if not self.output.is_cacheable:
cf = CachedFile(web_download=True, date=now(), expires=now() + timedelta(hours=2))
cf.bind_to_session(request)
cf.save()
else:
cf = None
return self.do('orderposition' if 'position' in kwargs else 'order',
self.order_position.pk if 'position' in kwargs else self.order.pk,
self.output.identifier)
self.output.identifier, cf.pk if cf else None)
def get_success_message(self, value):
return ""
@@ -738,12 +760,15 @@ class OrderDownload(AsyncAction, OrderView):
content_type=value.type
)
else:
return redirect(self.get_self_url())
return redirect(self.get_success_url(value))
def get_last_ct(self):
ct = CachedTicket.objects.filter(
order_position=self.order_position, provider=self.output.identifier, file__isnull=False
).last()
if self.output.is_cacheable:
ct = CachedTicket.objects.filter(
order_position=self.order_position, provider=self.output.identifier, file__isnull=False
).last()
else:
ct = None
if not ct or not ct.file:
return None
return ct
@@ -1177,6 +1202,8 @@ class OrderRefundView(OrderView):
manual_value = formats.sanitize_separators(manual_value)
try:
manual_value = Decimal(manual_value)
if manual_value < Decimal("0.00"):
raise TypeError("Please do not use negative numbers")
except (DecimalException, TypeError):
messages.error(self.request, _('You entered an invalid number.'))
is_valid = False
@@ -1206,6 +1233,8 @@ class OrderRefundView(OrderView):
giftcard_value = formats.sanitize_separators(giftcard_value)
try:
giftcard_value = Decimal(giftcard_value)
if giftcard_value < Decimal("0.00"):
raise TypeError("Please do not use negative numbers")
except (DecimalException, TypeError):
messages.error(self.request, _('You entered an invalid number.'))
is_valid = False
@@ -1255,6 +1284,8 @@ class OrderRefundView(OrderView):
offsetting_value = formats.sanitize_separators(offsetting_value)
try:
offsetting_value = Decimal(offsetting_value)
if offsetting_value < Decimal("0.00"):
raise TypeError("Please do not use negative numbers")
except (DecimalException, TypeError):
messages.error(self.request, _('You entered an invalid number.'))
is_valid = False
@@ -1271,6 +1302,9 @@ class OrderRefundView(OrderView):
if offset_order.event.currency != self.request.event.currency:
messages.error(self.request, _('You entered an order in an event with a different currency.'))
is_valid = False
if not self.request.user.has_event_permission(self.request.organizer, offset_order.event, 'event.orders:write', request=self.request):
messages.error(self.request, _('You entered an order in an event that you do not have access to.'))
is_valid = False
refunds.append(OrderRefund(
order=order,
payment=None,
@@ -1286,10 +1320,13 @@ class OrderRefundView(OrderView):
))
for identifier, prov in self.request.event.get_payment_providers().items():
# prof = process form, not a typo for prov(ider)
prof_value = self.request.POST.get(f'newrefund-{identifier}', '0') or '0'
prof_value = formats.sanitize_separators(prof_value)
try:
prof_value = Decimal(prof_value)
if prof_value < Decimal("0.00"):
raise TypeError("Please do not use negative numbers")
except (DecimalException, TypeError):
messages.error(self.request, _('You entered an invalid number.'))
is_valid = False
@@ -1313,6 +1350,8 @@ class OrderRefundView(OrderView):
value = formats.sanitize_separators(value)
try:
value = Decimal(value)
if value < Decimal("0.00"):
raise TypeError("Please do not use negative numbers")
except (DecimalException, TypeError):
messages.error(self.request, _('You entered an invalid number.'))
is_valid = False
@@ -1342,7 +1381,12 @@ class OrderRefundView(OrderView):
))
any_success = False
if refund_selected == full_refund and is_valid:
if refund_selected != full_refund:
messages.error(self.request, _('The refunds you selected do not match the selected total refund '
'amount.'))
is_valid = False
if is_valid:
for r in refunds:
r.save()
order.log_action('pretix.event.order.refund.created', {
@@ -1414,9 +1458,6 @@ class OrderRefundView(OrderView):
)
}))
return redirect(self.get_order_url())
else:
messages.error(self.request, _('The refunds you selected do not match the selected total refund '
'amount.'))
def post(self, *args, **kwargs):
if self.start_form.is_valid():
@@ -1646,7 +1687,8 @@ class OrderCheckVATID(OrderView):
return redirect(self.get_order_url())
try:
normalized_id = validate_vat_id(ia.vat_id, str(ia.country))
requester_id = self.request.event.settings.invoice_address_from_vat_id
normalized_id = validate_vat_id(ia.vat_id, str(ia.country), requester_id)
with transaction.atomic():
ia.vat_id_validated = True
ia.vat_id = normalized_id
+4 -4
View File
@@ -778,9 +778,9 @@ class OrganizerPluginEvents(OrganizerDetailViewMixin, OrganizerPermissionRequire
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["events"] = self.request.user.get_events_with_permission(
"event.settings.general:write", request=self.request
).filter(organizer=self.request.organizer)
# Assumption: Who has access to modify organizer settings may see all events and disable/enable plugins
# for them. Otherwise, inconsistent situations occur.
kwargs["events"] = self.request.organizer.events.all()
kwargs["initial"] = {
"events": self.request.organizer.events.filter(plugins__regex='(^|,)' + self.plugin.module + '(,|$)')
}
@@ -2202,7 +2202,7 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
owner=self.request.user,
timezone=str(get_current_timezone()),
)
if not self.scheduled:
if not self.scheduled and not self.scheduled_copy_from:
initial = {
"mail_subject": gettext("Export: {title}").format(title=self.exporter.verbose_name),
"mail_template": gettext(
+5
View File
@@ -27,6 +27,7 @@ from decimal import Decimal
from io import BytesIO
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.core.files import File
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
@@ -193,6 +194,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
c.expires = now() + timedelta(days=7)
c.date = now()
c.filename = 'background_preview.pdf'
c.bind_to_session(request, "ticketoutput-pdf-background")
c.type = 'application/pdf'
c.save()
c.file.save('empty.pdf', ContentFile(buffer.read()))
@@ -218,6 +220,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
c.expires = now() + timedelta(days=7)
c.date = now()
c.filename = 'background_preview.pdf'
c.bind_to_session(request, "ticketoutput-pdf-background")
c.type = 'application/pdf'
c.file = fileobj
c.save()
@@ -303,5 +306,7 @@ class FontsCSSView(TemplateView):
class PdfView(TemplateView):
def get(self, request, *args, **kwargs):
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
if not cf.allowed_for_session(request, "ticketoutput-pdf-background"):
raise PermissionDenied()
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
return resp
+5
View File
@@ -1276,6 +1276,11 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
self._default_meta = self.request.event.meta_data
for p in self.request.organizer.meta_properties.all():
if p.protected and not self.request.user.has_organizer_permission(
self.request.organizer, 'organizer.settings.general:write', request=self.request
):
continue
inst = SubEventMetaValue(property=p)
if len(matches[p.id]) == 1 and matches[p.id][0]['c'] == total:
inst.value = matches[p.id][0]['value']
+16 -25
View File
@@ -59,6 +59,7 @@ from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.decorators.cache import never_cache
from django.views.generic import FormView, ListView, TemplateView, UpdateView
from django_otp import devices_for_user
from django_otp.plugins.otp_static.models import StaticDevice
from django_otp.plugins.otp_totp.models import TOTPDevice
from django_scopes import scopes_disabled
@@ -85,7 +86,6 @@ from pretix.helpers.ratelimit import rate_limit, rate_limit_reset
from pretix.helpers.security import session_reauth
from pretix.helpers.u2f import websafe_encode
REAL_DEVICE_TYPES = (TOTPDevice, WebAuthnDevice, U2FDevice)
logger = logging.getLogger(__name__)
@@ -313,17 +313,7 @@ class User2FAMainView(RecentAuthenticationRequiredMixin, TemplateView):
except StaticDevice.DoesNotExist:
ctx['static_tokens_device'] = None
ctx['devices'] = []
for dt in REAL_DEVICE_TYPES:
objs = list(dt.objects.filter(user=self.request.user, confirmed=True))
for obj in objs:
if dt == TOTPDevice:
obj.devicetype = 'totp'
elif dt == U2FDevice:
obj.devicetype = 'u2f'
elif dt == WebAuthnDevice:
obj.devicetype = 'webauthn'
ctx['devices'] += objs
ctx['devices'] = [d for d in devices_for_user(self.request.user) if not isinstance(d, StaticDevice)]
ctx['obligatory'] = None
if settings.PRETIX_OBLIGATORY_2FA is True:
@@ -342,9 +332,9 @@ class User2FADeviceAddView(RecentAuthenticationRequiredMixin, FormView):
template_name = 'pretixcontrol/user/2fa_add.html'
def form_valid(self, form):
if form.cleaned_data['devicetype'] == 'totp':
if form.cleaned_data['devicetype'] == 'otp_totp.totpdevice':
dev = TOTPDevice.objects.create(user=self.request.user, confirmed=False, name=form.cleaned_data['name'])
elif form.cleaned_data['devicetype'] == 'webauthn':
elif form.cleaned_data['devicetype'] == 'pretixbase.webauthndevice':
if not self.request.is_secure():
messages.error(self.request,
_('Security devices are only available if pretix is served via HTTPS.'))
@@ -364,11 +354,11 @@ class User2FADeviceDeleteView(RecentAuthenticationRequiredMixin, TemplateView):
@cached_property
def device(self):
if self.kwargs['devicetype'] == 'totp':
if self.kwargs['devicetype'] == 'otp_totp.totpdevice':
return get_object_or_404(TOTPDevice, user=self.request.user, pk=self.kwargs['device'], confirmed=True)
elif self.kwargs['devicetype'] == 'webauthn':
elif self.kwargs['devicetype'] == 'pretixbase.webauthndevice':
return get_object_or_404(WebAuthnDevice, user=self.request.user, pk=self.kwargs['device'], confirmed=True)
elif self.kwargs['devicetype'] == 'u2f':
elif self.kwargs['devicetype'] == 'pretixbase.u2fdevice':
return get_object_or_404(U2FDevice, user=self.request.user, pk=self.kwargs['device'], confirmed=True)
def get_context_data(self, **kwargs):
@@ -386,7 +376,7 @@ class User2FADeviceDeleteView(RecentAuthenticationRequiredMixin, TemplateView):
msgs = [
_('A two-factor authentication device has been removed from your account.')
]
if not any(dt.objects.filter(user=self.request.user, confirmed=True) for dt in REAL_DEVICE_TYPES):
if not any(d.confirmed for d in devices_for_user(self.request.user) if not isinstance(d, StaticDevice)):
self.request.user.require_2fa = False
self.request.user.save()
self.request.user.log_action('pretix.user.settings.2fa.disabled', user=self.request.user)
@@ -461,7 +451,7 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa
).first()
if credential_id_exists:
messages.error(request, _('This security device is already registered.'))
return redirect(reverse('control:user.settings.2fa.confirm.webauthn', kwargs={
return redirect(reverse('control:user.settings.2fa.confirm.pretixbase.webauthndevice', kwargs={
'device': self.device.pk
}))
@@ -475,7 +465,7 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa
self.device.save()
self.request.user.log_action('pretix.user.settings.2fa.device.added', user=self.request.user, data={
'id': self.device.pk,
'devicetype': 'u2f',
'devicetype': 'pretixbase.webauthndevice',
'name': self.device.name,
})
notices = [
@@ -503,7 +493,7 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa
except Exception:
messages.error(request, _('The registration could not be completed. Please try again.'))
logger.exception('WebAuthn registration failed')
return redirect(reverse('control:user.settings.2fa.confirm.webauthn', kwargs={
return redirect(reverse('control:user.settings.2fa.confirm.pretixbase.webauthndevice', kwargs={
'device': self.device.pk
}))
@@ -537,7 +527,7 @@ class User2FADeviceConfirmTOTPView(RecentAuthenticationRequiredMixin, TemplateVi
self.request.user.log_action('pretix.user.settings.2fa.device.added', user=self.request.user, data={
'id': self.device.pk,
'name': self.device.name,
'devicetype': 'totp'
'devicetype': 'otp_totp.totpdevice'
})
notices = [
_('A new two-factor authentication device has been added to your account.')
@@ -563,7 +553,7 @@ class User2FADeviceConfirmTOTPView(RecentAuthenticationRequiredMixin, TemplateVi
else:
messages.error(request, _('The code you entered was not valid. If this problem persists, please check '
'that the date and time of your phone are configured correctly.'))
return redirect(reverse('control:user.settings.2fa.confirm.totp', kwargs={
return redirect(reverse('control:user.settings.2fa.confirm.otp_totp.totpdevice', kwargs={
'device': self.device.pk
}))
@@ -594,7 +584,7 @@ class User2FAEnableView(RecentAuthenticationRequiredMixin, TemplateView):
template_name = 'pretixcontrol/user/2fa_enable.html'
def dispatch(self, request, *args, **kwargs):
if not any(dt.objects.filter(user=self.request.user, confirmed=True) for dt in REAL_DEVICE_TYPES):
if not any(d.confirmed for d in devices_for_user(self.request.user) if not isinstance(d, StaticDevice)):
messages.error(request, _('Please configure at least one device before enabling two-factor '
'authentication.'))
return redirect(reverse('control:user.settings.2fa'))
@@ -771,6 +761,7 @@ class UserNotificationsEditView(TemplateView):
{k: a.get(t) for k, a in self.global_set.items()},
)
for t, tv in self.types.items()
if tv.is_event_level or not self.event
]
ctx['event'] = self.event
if self.event:
@@ -956,7 +947,7 @@ class UserEmailConfirmView(FormView):
@transaction.atomic()
def form_valid(self, form):
reason = self.request.GET['reason']
reason = self.request.GET.get('reason')
if reason not in ('email_change', 'email_verify'):
raise PermissionDenied
try:
+23
View File
@@ -40,6 +40,7 @@ from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import ListView, TemplateView
from django_otp import devices_for_user
from django_otp.plugins.otp_static.models import StaticDevice
from hijack import signals
@@ -107,6 +108,9 @@ class UserEditView(AdministratorPermissionRequiredMixin, RecentAuthenticationReq
ctx['backend'] = (
b[self.object.auth_backend].verbose_name if self.object.auth_backend in b else self.object.auth_backend
)
ctx['devices'] = devices_for_user(self.object)
return ctx
def get_success_url(self):
@@ -183,6 +187,25 @@ class UserEmergencyTokenView(AdministratorPermissionRequiredMixin, RecentAuthent
return reverse('control:users.edit', kwargs=self.kwargs)
class Reset2FADriftThrottleView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, View):
def get(self, request, *args, **kwargs):
return redirect(reverse('control:users.edit', kwargs=self.kwargs))
def post(self, request, *args, **kwargs):
self.object = get_object_or_404(User, pk=self.kwargs.get("id"))
self.object.totpdevice_set.update(drift=0, throttling_failure_timestamp=None, throttling_failure_count=0)
self.object.staticdevice_set.update(throttling_failure_timestamp=None, throttling_failure_count=0)
self.object.log_action('pretix.user.settings.2fa.resetdrift', user=self.request.user)
messages.success(request, _(
'The drift values for TOTP devices have been reset.'
))
return redirect(self.get_success_url())
def get_success_url(self):
return reverse('control:users.edit', kwargs=self.kwargs)
class UserAnonymizeView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, TemplateView):
template_name = "pretixcontrol/users/anonymize.html"
+4 -2
View File
@@ -144,7 +144,7 @@ class VoucherList(VoucherQueryMixin, PaginationMixin, EventPermissionRequiredMix
headers = [
_('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages'), _('Seat'),
_('Comment')
_('Comment'), _('Budget'), _('Budget used')
]
writer.writerow(headers)
@@ -170,7 +170,9 @@ class VoucherList(VoucherQueryMixin, PaginationMixin, EventPermissionRequiredMix
str(v.redeemed),
str(v.max_usages),
str(v.seat) if v.seat else "",
str(v.comment) if v.comment else ""
str(v.comment) if v.comment else "",
str(v.budget) if v.budget is not None else "",
str(v.budget_used) if v.budget is not None else "",
]
writer.writerow(row)
+9
View File
@@ -21,11 +21,13 @@
#
import copy
from decimal import Decimal
import typing
from django.core.files import File
from django.db import models
from django.db.models.fields import DecimalField
T = typing.TypeVar('T', bound=models.Model)
class Thumbnail(models.Model):
source = models.CharField(max_length=255)
@@ -47,6 +49,13 @@ def modelcopy(obj: models.Model, **kwargs):
setattr(n, f.name, copy.deepcopy(val))
return n
def modelclone(obj: T, **kwargs) -> T:
new = copy.copy(obj)
new.pk = None
new._state.adding = True
for k,v in kwargs.items():
setattr(new, k, v)
return new
# django 5 contains this in django.utils.choices.flatten_choices
def flatten_choices(choices):
+18
View File
@@ -27,6 +27,7 @@ from http import cookies
from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
from django.middleware.csrf import CsrfViewMiddleware as BaseCsrfMiddleware
from PIL import Image
from requests.adapters import HTTPAdapter
from urllib3.connection import HTTPConnection, HTTPSConnection
@@ -42,6 +43,7 @@ from urllib3.util.timeout import _DEFAULT_TIMEOUT
from pretix.helpers.reportlab import ThumbnailingImageReader
from pretix.helpers.ssrf import should_block_access
from pretix.multidomain.middlewares import CsrfViewMiddleware
def monkeypatch_vobject_performance():
@@ -242,6 +244,20 @@ def monkeypatch_reportlab_imagereader():
utils.ImageReader.__init__ = new_init
def monkeypatch_csrf_middleware():
# Some views from django or plugins use the middleware or the derived decorators directly,
# so we have to also patch the stock middleware
BaseCsrfMiddleware._get_secret = CsrfViewMiddleware._get_secret
BaseCsrfMiddleware._set_csrf_cookie = CsrfViewMiddleware._set_csrf_cookie
def monkeypatch_json_constants():
from json.decoder import _CONSTANTS # noqa
del _CONSTANTS['-Infinity']
del _CONSTANTS['Infinity']
del _CONSTANTS['NaN']
def monkeypatch_all_at_ready():
monkeypatch_vobject_performance()
monkeypatch_pillow_safer()
@@ -249,3 +265,5 @@ def monkeypatch_all_at_ready():
monkeypatch_urllib3_ssrf_protection()
monkeypatch_cookie_morsel()
monkeypatch_reportlab_imagereader()
monkeypatch_csrf_middleware()
monkeypatch_json_constants()
+1 -1
View File
@@ -29,7 +29,7 @@ from django.urls import reverse
def build_absolute_uri(urlname, args=None, kwargs=None):
warnings.warn(
'Usage of build_absolute_uri is confusing since there are many functions with that name. '
'Replace this usage with ',
'Replace this usage with mainreverse_absolute.',
DeprecationWarning
)
return mainreverse_absolute(urlname, args, kwargs)
+346 -274
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"PO-Revision-Date: 2026-07-15 14:20+0000\n"
"Last-Translator: Llewelyn Barkhuizen <llewelyn@akademia.ac.za>\n"
"Language-Team: Afrikaans <https://translate.pretix.eu/projects/pretix/pretix/"
@@ -198,7 +198,7 @@ msgid "Allowed Post Logout URIs list, space separated"
msgstr ""
#: pretix/api/models.py pretix/base/models/customers.py
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#: pretix/plugins/paypal2/payment.py
msgid "Client ID"
msgstr ""
@@ -1718,6 +1718,18 @@ msgstr ""
msgid "Payment providers"
msgstr ""
#: pretix/base/exporters/invoices.py pretix/base/models/orders.py
msgid "Transmission type"
msgstr ""
#: pretix/base/exporters/invoices.py
msgid "Transmission status"
msgstr ""
#: pretix/base/exporters/invoices.py
msgid "Transmission date"
msgstr ""
#: pretix/base/exporters/invoices.py
msgid "Cancellation"
msgstr ""
@@ -2361,7 +2373,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html
#: pretix/plugins/banktransfer/refund_export.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
@@ -2385,7 +2396,6 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Quantity"
msgstr ""
@@ -2467,7 +2477,6 @@ msgstr ""
#: pretix/base/exporters/orderlist.py
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Payment ID"
msgstr ""
@@ -4225,6 +4234,10 @@ msgstr ""
msgid "Initialization date"
msgstr ""
#: pretix/base/models/devices.py
msgid "Security profile"
msgstr ""
#: pretix/base/models/discount.py
msgctxt "subevent"
msgid "Dates can be mixed without limitation"
@@ -5440,6 +5453,10 @@ msgstr ""
msgid "Maximum value"
msgstr ""
#: pretix/base/models/items.py
msgid "Minimum length"
msgstr ""
#: pretix/base/models/items.py
msgid "Maximum length"
msgstr ""
@@ -5506,6 +5523,10 @@ msgstr ""
msgid "The maximum value must not be lower than the minimum value."
msgstr ""
#: pretix/base/models/items.py
msgid "The maximum length must not be shorter than the minimum length."
msgstr ""
#: pretix/base/models/items.py
msgid ""
"The system already contains answers to this question that are not compatible "
@@ -5999,10 +6020,6 @@ msgstr ""
msgid "This reference will be printed on your invoice for your convenience."
msgstr ""
#: pretix/base/models/orders.py
msgid "Transmission type"
msgstr ""
#: pretix/base/models/orders.py
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_position_buttons.html
msgid "Badge"
@@ -9476,6 +9493,17 @@ msgid ""
"'Last date of payments' configured above."
msgstr ""
#: pretix/base/settings.py
msgid "Allow postponed payment choice for sales channels"
msgstr ""
#: pretix/base/settings.py
msgid ""
"If postponed payment is allowed on a sales channel, customers can complete "
"their order without selecting a payment method. This is useful whenever "
"orders are not created by the same person who is making the payment."
msgstr ""
#: pretix/base/settings.py
msgid "Show start date"
msgstr ""
@@ -9997,12 +10025,6 @@ msgstr ""
msgid "Hide all unavailable dates from calendar or list views"
msgstr ""
#: pretix/base/settings.py
msgid ""
"This option currently only affects the calendar of this event series, not "
"the organizer-wide calendar."
msgstr ""
#: pretix/base/settings.py
msgid "Hide all past dates from calendar"
msgstr ""
@@ -14691,6 +14713,11 @@ msgctxt "rrule"
msgid "third"
msgstr ""
#: pretix/control/forms/rrule.py
msgctxt "rrule"
msgid "fourth"
msgstr ""
#: pretix/control/forms/rrule.py
msgctxt "rrule"
msgid "last"
@@ -15674,6 +15701,22 @@ msgstr ""
msgid "Queued emails have been aborted."
msgstr ""
#: pretix/control/logdisplay.py
msgid "An organizer meta property has been created."
msgstr ""
#: pretix/control/logdisplay.py
msgid "An organizer meta property has been deleted."
msgstr ""
#: pretix/control/logdisplay.py
msgid "An organizer meta property has been changed."
msgstr ""
#: pretix/control/logdisplay.py
msgid "An organizer meta property has been reordered."
msgstr ""
#: pretix/control/logdisplay.py
msgid "Gift card acceptance for another organizer has been added."
msgstr ""
@@ -15876,6 +15919,10 @@ msgstr ""
msgid "A two-factor emergency code has been generated."
msgstr ""
#: pretix/control/logdisplay.py
msgid "Drift and throttle values for two-factor devices have been reset."
msgstr ""
#: pretix/control/logdisplay.py
#, python-brace-format
msgid ""
@@ -16708,6 +16755,87 @@ msgid ""
"Granting access to your pretix account happens at your own risk."
msgstr ""
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
#: pretix/control/templates/pretixcontrol/checkin/bulk_revert_confirm.html
#: pretix/control/templates/pretixcontrol/checkin/list_delete.html
#: pretix/control/templates/pretixcontrol/checkin/reset.html
#: pretix/control/templates/pretixcontrol/datasync/control_order_info.html
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
#: pretix/control/templates/pretixcontrol/event/tax_delete.html
#: pretix/control/templates/pretixcontrol/item/delete.html
#: pretix/control/templates/pretixcontrol/items/category_delete.html
#: pretix/control/templates/pretixcontrol/items/discount_delete.html
#: pretix/control/templates/pretixcontrol/items/question_delete.html
#: pretix/control/templates/pretixcontrol/items/quota_delete.html
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html
#: pretix/control/templates/pretixcontrol/oauth/app_delete.html
#: pretix/control/templates/pretixcontrol/oauth/app_rollkeys.html
#: pretix/control/templates/pretixcontrol/oauth/auth_revoke.html
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/templates/pretixcontrol/order/change_contact.html
#: pretix/control/templates/pretixcontrol/order/change_locale.html
#: pretix/control/templates/pretixcontrol/order/change_questions.html
#: pretix/control/templates/pretixcontrol/order/extend.html
#: pretix/control/templates/pretixcontrol/order/pay.html
#: pretix/control/templates/pretixcontrol/order/pay_complete.html
#: pretix/control/templates/pretixcontrol/order/reactivate.html
#: pretix/control/templates/pretixcontrol/order/refund_choose.html
#: pretix/control/templates/pretixcontrol/order/refund_done.html
#: pretix/control/templates/pretixcontrol/order/refund_process.html
#: pretix/control/templates/pretixcontrol/order/refund_start.html
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html
#: pretix/control/templates/pretixcontrol/orders/export_delete.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/channel_delete.html
#: pretix/control/templates/pretixcontrol/organizers/customer_anonymize.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership_delete.html
#: pretix/control/templates/pretixcontrol/organizers/device_revoke.html
#: pretix/control/templates/pretixcontrol/organizers/export_delete.html
#: pretix/control/templates/pretixcontrol/organizers/gate_delete.html
#: pretix/control/templates/pretixcontrol/organizers/membershiptype_delete.html
#: pretix/control/templates/pretixcontrol/organizers/property_delete.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_delete.html
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_delete.html
#: pretix/control/templates/pretixcontrol/organizers/team_delete.html
#: pretix/control/templates/pretixcontrol/pdf/index.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/delete.html
#: pretix/control/templates/pretixcontrol/subevents/delete_bulk.html
#: pretix/control/templates/pretixcontrol/subevents/edit.html
#: pretix/control/templates/pretixcontrol/user/2fa_delete.html
#: pretix/control/templates/pretixcontrol/user/2fa_disable.html
#: pretix/control/templates/pretixcontrol/user/2fa_enable.html
#: pretix/control/templates/pretixcontrol/user/2fa_leaveteams.html
#: pretix/control/templates/pretixcontrol/user/2fa_regenemergency.html
#: pretix/control/templates/pretixcontrol/user/change_email.html
#: pretix/control/templates/pretixcontrol/user/change_password.html
#: pretix/control/templates/pretixcontrol/user/confirmation_code_dialog.html
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/delete_bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/delete_carts.html
#: pretix/control/templates/pretixcontrol/waitinglist/delete.html
#: pretix/control/templates/pretixcontrol/waitinglist/delete_bulk.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/delete.html
#: pretix/plugins/badges/templates/pretixplugins/badges/delete.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pay.html
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_delete.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca.html
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/delete.html
#: pretix/presale/templates/pretixpresale/event/order_change.html
#: pretix/presale/templates/pretixpresale/event/order_modify.html
#: pretix/presale/templates/pretixpresale/event/order_pay.html
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html
#: pretix/presale/templates/pretixpresale/event/order_pay_confirm.html
#: pretix/presale/templates/pretixpresale/event/position_change.html
#: pretix/presale/templates/pretixpresale/event/position_modify.html
msgid "Cancel"
msgstr ""
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
msgid "Authorize"
msgstr ""
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
msgid "Error:"
msgstr ""
@@ -17069,82 +17197,6 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
#: pretix/control/templates/pretixcontrol/checkin/bulk_revert_confirm.html
#: pretix/control/templates/pretixcontrol/checkin/list_delete.html
#: pretix/control/templates/pretixcontrol/checkin/reset.html
#: pretix/control/templates/pretixcontrol/datasync/control_order_info.html
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
#: pretix/control/templates/pretixcontrol/event/tax_delete.html
#: pretix/control/templates/pretixcontrol/item/delete.html
#: pretix/control/templates/pretixcontrol/items/category_delete.html
#: pretix/control/templates/pretixcontrol/items/discount_delete.html
#: pretix/control/templates/pretixcontrol/items/question_delete.html
#: pretix/control/templates/pretixcontrol/items/quota_delete.html
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html
#: pretix/control/templates/pretixcontrol/oauth/app_delete.html
#: pretix/control/templates/pretixcontrol/oauth/app_rollkeys.html
#: pretix/control/templates/pretixcontrol/oauth/auth_revoke.html
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/templates/pretixcontrol/order/change_contact.html
#: pretix/control/templates/pretixcontrol/order/change_locale.html
#: pretix/control/templates/pretixcontrol/order/change_questions.html
#: pretix/control/templates/pretixcontrol/order/extend.html
#: pretix/control/templates/pretixcontrol/order/pay.html
#: pretix/control/templates/pretixcontrol/order/pay_complete.html
#: pretix/control/templates/pretixcontrol/order/reactivate.html
#: pretix/control/templates/pretixcontrol/order/refund_choose.html
#: pretix/control/templates/pretixcontrol/order/refund_done.html
#: pretix/control/templates/pretixcontrol/order/refund_process.html
#: pretix/control/templates/pretixcontrol/order/refund_start.html
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html
#: pretix/control/templates/pretixcontrol/orders/export_delete.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/channel_delete.html
#: pretix/control/templates/pretixcontrol/organizers/customer_anonymize.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership_delete.html
#: pretix/control/templates/pretixcontrol/organizers/device_revoke.html
#: pretix/control/templates/pretixcontrol/organizers/export_delete.html
#: pretix/control/templates/pretixcontrol/organizers/gate_delete.html
#: pretix/control/templates/pretixcontrol/organizers/membershiptype_delete.html
#: pretix/control/templates/pretixcontrol/organizers/property_delete.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_delete.html
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_delete.html
#: pretix/control/templates/pretixcontrol/organizers/team_delete.html
#: pretix/control/templates/pretixcontrol/pdf/index.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/delete.html
#: pretix/control/templates/pretixcontrol/subevents/delete_bulk.html
#: pretix/control/templates/pretixcontrol/subevents/edit.html
#: pretix/control/templates/pretixcontrol/user/2fa_delete.html
#: pretix/control/templates/pretixcontrol/user/2fa_disable.html
#: pretix/control/templates/pretixcontrol/user/2fa_enable.html
#: pretix/control/templates/pretixcontrol/user/2fa_leaveteams.html
#: pretix/control/templates/pretixcontrol/user/2fa_regenemergency.html
#: pretix/control/templates/pretixcontrol/user/change_email.html
#: pretix/control/templates/pretixcontrol/user/change_password.html
#: pretix/control/templates/pretixcontrol/user/confirmation_code_dialog.html
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/delete_bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/delete_carts.html
#: pretix/control/templates/pretixcontrol/waitinglist/delete.html
#: pretix/control/templates/pretixcontrol/waitinglist/delete_bulk.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/delete.html
#: pretix/plugins/badges/templates/pretixplugins/badges/delete.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pay.html
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_delete.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca.html
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/delete.html
#: pretix/presale/templates/pretixpresale/event/order_change.html
#: pretix/presale/templates/pretixpresale/event/order_modify.html
#: pretix/presale/templates/pretixpresale/event/order_pay.html
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html
#: pretix/presale/templates/pretixpresale/event/order_pay_confirm.html
#: pretix/presale/templates/pretixpresale/event/position_change.html
#: pretix/presale/templates/pretixpresale/event/position_modify.html
msgid "Cancel"
msgstr ""
#: pretix/control/templates/pretixcontrol/checkin/bulk_revert_confirm.html
#: pretix/control/templates/pretixcontrol/checkin/list_delete.html
#: pretix/control/templates/pretixcontrol/event/delete.html
@@ -19256,7 +19308,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/events/create_basics.html
msgid ""
"We strongly recommend against using short forms of more then 16 characters."
"We strongly recommend against using short forms of more than 16 characters."
msgstr ""
#: pretix/control/templates/pretixcontrol/events/create_basics.html
@@ -24196,6 +24248,7 @@ msgid "Add a new device"
msgstr ""
#: pretix/control/templates/pretixcontrol/user/2fa_main.html
#: pretix/control/templates/pretixcontrol/users/form.html
msgid "Emergency tokens"
msgstr ""
@@ -24440,6 +24493,26 @@ msgstr ""
msgid "Authentication backend"
msgstr ""
#: pretix/control/templates/pretixcontrol/users/form.html
msgid "Reset drift and throttle"
msgstr ""
#: pretix/control/templates/pretixcontrol/users/form.html
msgid "Available two-factor authentication methods"
msgstr ""
#: pretix/control/templates/pretixcontrol/users/form.html
#, python-format
msgid "1 failed attempt since %(date)s"
msgid_plural "%(cnt)s failed attempts since %(date)s"
msgstr[0] ""
msgstr[1] ""
#: pretix/control/templates/pretixcontrol/users/form.html
#, python-format
msgid "Currently locked until %(date)s"
msgstr ""
#: pretix/control/templates/pretixcontrol/users/form.html
msgid "Team memberships"
msgstr ""
@@ -24893,6 +24966,20 @@ msgstr ""
msgid "Invalid code, please try again."
msgstr ""
#: pretix/control/views/auth.py
#, python-brace-format
msgid "Invalid code. Please try again after waiting {value} minute."
msgid_plural "Invalid code. Please try again after waiting {value} minutes."
msgstr[0] ""
msgstr[1] ""
#: pretix/control/views/auth.py
#, python-brace-format
msgid "Invalid code. Please try again after waiting {value} second."
msgid_plural "Invalid code. Please try again after waiting {value} seconds."
msgstr[0] ""
msgstr[1] ""
#: pretix/control/views/checkin.py
msgid "The selected check-ins have been reverted."
msgstr ""
@@ -25645,6 +25732,10 @@ msgstr ""
msgid "You entered an order in an event with a different currency."
msgstr ""
#: pretix/control/views/orders.py
msgid "You entered an order in an event that you do not have access to."
msgstr ""
#: pretix/control/views/orders.py
msgid ""
"You can not refund more than the amount of a payment that is not yet "
@@ -25657,6 +25748,10 @@ msgid ""
"refunds."
msgstr ""
#: pretix/control/views/orders.py
msgid "The refunds you selected do not match the selected total refund amount."
msgstr ""
#: pretix/control/views/orders.py
#, python-brace-format
msgid ""
@@ -25698,10 +25793,6 @@ msgid ""
"Your {event} team"
msgstr ""
#: pretix/control/views/orders.py
msgid "The refunds you selected do not match the selected total refund amount."
msgstr ""
#: pretix/control/views/orders.py
msgid "The payment has been created successfully."
msgstr ""
@@ -26413,6 +26504,10 @@ msgid ""
"remain active."
msgstr ""
#: pretix/control/views/users.py
msgid "The drift values for TOTP devices have been reset."
msgstr ""
#: pretix/control/views/users.py
msgid "The new user has been created."
msgstr ""
@@ -26425,6 +26520,14 @@ msgstr ""
msgid "Bypass quota"
msgstr ""
#: pretix/control/views/vouchers.py
msgid "Budget"
msgstr ""
#: pretix/control/views/vouchers.py
msgid "Budget used"
msgstr ""
#: pretix/control/views/vouchers.py
msgid "The requested voucher does not exist."
msgstr ""
@@ -26577,12 +26680,11 @@ msgstr ""
#: pretix/plugins/autocheckin/apps.py pretix/plugins/badges/apps.py
#: pretix/plugins/banktransfer/apps.py pretix/plugins/checkinlists/apps.py
#: pretix/plugins/manualpayment/apps.py pretix/plugins/paypal/apps.py
#: pretix/plugins/paypal2/apps.py pretix/plugins/pretixdroid/apps.py
#: pretix/plugins/reports/apps.py pretix/plugins/returnurl/apps.py
#: pretix/plugins/sendmail/apps.py pretix/plugins/statistics/apps.py
#: pretix/plugins/stripe/apps.py pretix/plugins/ticketoutputpdf/apps.py
#: pretix/plugins/webcheckin/apps.py
#: pretix/plugins/manualpayment/apps.py pretix/plugins/paypal2/apps.py
#: pretix/plugins/pretixdroid/apps.py pretix/plugins/reports/apps.py
#: pretix/plugins/returnurl/apps.py pretix/plugins/sendmail/apps.py
#: pretix/plugins/statistics/apps.py pretix/plugins/stripe/apps.py
#: pretix/plugins/ticketoutputpdf/apps.py pretix/plugins/webcheckin/apps.py
msgid "the pretix team"
msgstr ""
@@ -27133,7 +27235,6 @@ msgstr ""
#: pretix/plugins/banktransfer/refund_export.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Payer"
@@ -27627,8 +27728,7 @@ msgstr ""
msgid "Automatically checked in"
msgstr ""
#: pretix/plugins/checkinlists/exporters.py pretix/plugins/paypal/payment.py
#: pretix/plugins/paypal2/payment.py
#: pretix/plugins/checkinlists/exporters.py pretix/plugins/paypal2/payment.py
msgid "Secret"
msgstr ""
@@ -27687,152 +27787,6 @@ msgstr ""
msgid "A fully customizable payment method for manual processing."
msgstr ""
#: pretix/plugins/paypal/apps.py pretix/plugins/paypal/payment.py
#: pretix/plugins/paypal2/apps.py pretix/plugins/paypal2/payment.py
#: pretix/plugins/stripe/payment.py
msgid "PayPal"
msgstr ""
#: pretix/plugins/paypal/apps.py
msgid ""
"Accept payments with your PayPal account. PayPal is one of the most popular "
"payment methods world-wide."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"The PayPal sandbox is being used, you can test without actually sending "
"money but you will need a PayPal sandbox user to log in."
msgstr ""
#: pretix/plugins/paypal/payment.py
msgid "PayPal account"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#: pretix/plugins/stripe/payment.py
msgid "Endpoint"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Reference prefix"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"Any value entered here will be added in front of the regular booking "
"reference containing the order number."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Reference postfix"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"Any value entered here will be added behind the regular booking reference "
"containing the order number."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Disconnect from PayPal"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "We had trouble communicating with PayPal"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"We were unable to process your payment. See below for details on how to "
"proceed."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"PayPal has not yet approved the payment. We will inform you as soon as the "
"payment completed."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "Refunding the amount via PayPal failed: {}"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "The payment for this invoice has already been received."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "PayPal payment ID"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "PayPal sale ID"
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/checkout_payment_confirm.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_confirm.html
msgid ""
"The total amount listed above will be withdrawn from your PayPal account "
"after the confirmation of your purchase."
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/checkout_payment_form.html
msgid ""
"After you clicked continue, we will redirect you to PayPal to fill in your "
"payment details. You will then be redirected back here to review and confirm "
"your order."
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Sale ID"
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Last update"
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
msgid "Total value"
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/pending.html
msgid ""
"Our attempt to execute your Payment via PayPal has failed. Please try again "
"or contact us."
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/pending.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pending.html
msgid ""
"We're waiting for an answer from PayPal regarding your payment. Please "
"contact us, if this takes more than a few hours."
msgstr ""
#: pretix/plugins/paypal/views.py pretix/plugins/paypal2/views.py
msgid "Invalid response from PayPal received."
msgstr ""
#: pretix/plugins/paypal/views.py pretix/plugins/paypal2/views.py
msgid "It looks like you canceled the PayPal payment"
msgstr ""
#: pretix/plugins/paypal/views.py pretix/plugins/paypal2/views.py
msgid "Your PayPal account has been disconnected."
msgstr ""
#: pretix/plugins/paypal2/apps.py
msgid ""
"Accept payments with your PayPal account. In addition to regular PayPal "
@@ -27842,10 +27796,23 @@ msgid ""
"methods world-wide."
msgstr ""
#: pretix/plugins/paypal2/apps.py pretix/plugins/paypal2/payment.py
#: pretix/plugins/stripe/payment.py
msgid "PayPal"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "PayPal Merchant ID"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
#: pretix/plugins/paypal2/payment.py pretix/plugins/stripe/payment.py
msgid "Endpoint"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Even if a customer chooses an Alternative Payment Method, they will always "
@@ -27914,6 +27881,26 @@ msgid ""
"while the last payment is still under investigation."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "Reference prefix"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Any value entered here will be added in front of the regular booking "
"reference containing the order number."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "Reference postfix"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Any value entered here will be added behind the regular booking reference "
"containing the order number."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "-- Automatic --"
msgstr ""
@@ -27934,6 +27921,10 @@ msgstr ""
msgid "Connect with {icon} PayPal"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "Disconnect from PayPal"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Please configure a PayPal Webhook to the following endpoint in order to "
@@ -27962,6 +27953,12 @@ msgstr ""
msgid "An error occurred during connecting with PayPal, please try again."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"The PayPal sandbox is being used, you can test without actually sending "
"money but you will need a PayPal sandbox user to log in."
msgstr ""
#: pretix/plugins/paypal2/payment.py
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pending.html
msgid ""
@@ -27970,6 +27967,10 @@ msgid ""
"attempt. You can also try other payment methods, if available."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "We had trouble communicating with PayPal"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Something went wrong when requesting the payment status. Please try again."
@@ -27979,12 +27980,41 @@ msgstr ""
msgid "You may need to enable JavaScript for PayPal payments."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"We were unable to process your payment. See below for details on how to "
"proceed."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"PayPal has not yet approved the payment. We will inform you as soon as the "
"payment completed."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Refunding the amount via PayPal failed: The original payment does not "
"contain the required information to issue an automated refund."
msgstr ""
#: pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "Refunding the amount via PayPal failed: {}"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "The payment for this invoice has already been received."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "PayPal payment ID"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "PayPal sale ID"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "PayPal APM"
msgstr ""
@@ -28072,6 +28102,12 @@ msgstr ""
msgid "We will then charge your PayPal account and finalize the order."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_confirm.html
msgid ""
"The total amount listed above will be withdrawn from your PayPal account "
"after the confirmation of your purchase."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_confirm.html
msgid ""
"After placing your order, you will be able to select your desired payment "
@@ -28103,6 +28139,11 @@ msgid ""
"different payment method, if you prefer."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Sale ID"
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
msgid "Capture status"
msgstr ""
@@ -28113,6 +28154,17 @@ msgid ""
"money will not be disbursed and the order remain in its pending state."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Last update"
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
msgid "Total value"
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pay.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca_return.html
@@ -28146,6 +28198,12 @@ msgid ""
"to resolve this case."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pending.html
msgid ""
"We're waiting for an answer from PayPal regarding your payment. Please "
"contact us, if this takes more than a few hours."
msgstr ""
#: pretix/plugins/paypal2/views.py
msgid ""
"An error occurred returning from PayPal: request parameters missing. Please "
@@ -28176,6 +28234,18 @@ msgid ""
"in detail below."
msgstr ""
#: pretix/plugins/paypal2/views.py
msgid "Invalid response from PayPal received."
msgstr ""
#: pretix/plugins/paypal2/views.py
msgid "It looks like you canceled the PayPal payment"
msgstr ""
#: pretix/plugins/paypal2/views.py
msgid "Your PayPal account has been disconnected."
msgstr ""
#: pretix/plugins/pretixdroid/apps.py
msgid "Old check-in device API"
msgstr ""
@@ -30195,7 +30265,6 @@ msgstr ""
#: pretix/presale/forms/order.py
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "plus taxes"
msgstr ""
@@ -30616,6 +30685,31 @@ msgid ""
"Please go to the payment settings and activate one or more payment providers."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
msgid ""
"Not sure yet? You can complete your order first and then select a payment "
"method later."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
msgid ""
"To do so, please first remove the payment methods you already selected above."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
#, python-format
msgid "Your payment needs to be completed within %(minutes)s minutes."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
#, python-format
msgid "Your payment needs to be completed by %(deadline)s."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
msgid "Proceed without selection"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html
msgid "Before we continue, we need you to answer some questions."
msgstr ""
@@ -30682,14 +30776,12 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "minimum amount to order: %(num)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgctxt "price"
msgid "free"
msgstr ""
@@ -30718,66 +30810,56 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Original price:"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "New price:"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "Modify price for %(item)s, at least %(price)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "Modify price for %(item)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "incl. taxes"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "<strong>plus</strong> %(rate)s%% %(name)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "incl. %(rate)s%% %(name)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgctxt "checkbox"
msgid "Select"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Decrease quantity"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Increase quantity"
msgstr ""
@@ -31238,12 +31320,10 @@ msgid "Your order qualifies for a discount"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Uncategorized items"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "Show full-size image of %(item)s"
msgstr ""
@@ -31256,19 +31336,16 @@ msgstr[0] ""
msgstr[1] ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "%(value)s incl. taxes"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "%(value)s without taxes"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "Set price in %(currency)s for %(item)s"
msgstr ""
@@ -31927,11 +32004,6 @@ msgid ""
"products at the specified price:"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "from %(minprice)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "You need to select at least %(number)s products."
+1 -84
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-07 09:33+0000\n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -765,194 +765,163 @@ msgstr ""
msgid "Google Pay"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Quantity"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Decrease quantity"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Increase quantity"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Filter events by"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Filter"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Price"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "Original price: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "New price: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Select"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "Select %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "Select variant %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Sold out"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Buy"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Register"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Reserved"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "FREE"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "from %(currency)s %(price)s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "Image of %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "incl. %(rate)s% %(taxname)s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "plus %(rate)s% %(taxname)s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "incl. taxes"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "plus taxes"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "currently available: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Only available with a voucher"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Not yet available"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Not available anymore"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Currently not available"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "minimum amount to order: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Close ticket shop"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "The ticket shop could not be loaded."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid ""
@@ -960,25 +929,21 @@ msgid ""
"in a new tab to continue."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Open ticket shop"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Checkout"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid ""
@@ -986,13 +951,11 @@ msgid ""
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Waiting list"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid ""
@@ -1000,115 +963,96 @@ msgid ""
"products, they will be added to your existing cart."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Resume checkout"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Redeem a voucher"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Redeem"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Voucher code"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Close"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Close checkout"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "You cannot cancel this operation. Please wait for loading to finish."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Continue"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Show variants"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Hide variants"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Choose a different event"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Choose a different date"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Back"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Next month"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Previous month"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Next week"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Previous week"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Open seat selection"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid ""
@@ -1117,138 +1061,111 @@ msgid ""
"again."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Load more"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Mo"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Tu"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "We"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Th"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Fr"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Sa"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Su"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Monday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Tuesday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Wednesday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Thursday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Friday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Saturday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Sunday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "January"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "February"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "March"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "April"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "May"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "June"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "July"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "August"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "September"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "October"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "November"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "December"
msgstr ""
+346 -274
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -196,7 +196,7 @@ msgid "Allowed Post Logout URIs list, space separated"
msgstr ""
#: pretix/api/models.py pretix/base/models/customers.py
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#: pretix/plugins/paypal2/payment.py
msgid "Client ID"
msgstr ""
@@ -1716,6 +1716,18 @@ msgstr ""
msgid "Payment providers"
msgstr ""
#: pretix/base/exporters/invoices.py pretix/base/models/orders.py
msgid "Transmission type"
msgstr ""
#: pretix/base/exporters/invoices.py
msgid "Transmission status"
msgstr ""
#: pretix/base/exporters/invoices.py
msgid "Transmission date"
msgstr ""
#: pretix/base/exporters/invoices.py
msgid "Cancellation"
msgstr ""
@@ -2359,7 +2371,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html
#: pretix/plugins/banktransfer/refund_export.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
@@ -2383,7 +2394,6 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Quantity"
msgstr ""
@@ -2465,7 +2475,6 @@ msgstr ""
#: pretix/base/exporters/orderlist.py
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Payment ID"
msgstr ""
@@ -4223,6 +4232,10 @@ msgstr ""
msgid "Initialization date"
msgstr ""
#: pretix/base/models/devices.py
msgid "Security profile"
msgstr ""
#: pretix/base/models/discount.py
msgctxt "subevent"
msgid "Dates can be mixed without limitation"
@@ -5438,6 +5451,10 @@ msgstr ""
msgid "Maximum value"
msgstr ""
#: pretix/base/models/items.py
msgid "Minimum length"
msgstr ""
#: pretix/base/models/items.py
msgid "Maximum length"
msgstr ""
@@ -5504,6 +5521,10 @@ msgstr ""
msgid "The maximum value must not be lower than the minimum value."
msgstr ""
#: pretix/base/models/items.py
msgid "The maximum length must not be shorter than the minimum length."
msgstr ""
#: pretix/base/models/items.py
msgid ""
"The system already contains answers to this question that are not compatible "
@@ -5997,10 +6018,6 @@ msgstr ""
msgid "This reference will be printed on your invoice for your convenience."
msgstr ""
#: pretix/base/models/orders.py
msgid "Transmission type"
msgstr ""
#: pretix/base/models/orders.py
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_position_buttons.html
msgid "Badge"
@@ -9474,6 +9491,17 @@ msgid ""
"'Last date of payments' configured above."
msgstr ""
#: pretix/base/settings.py
msgid "Allow postponed payment choice for sales channels"
msgstr ""
#: pretix/base/settings.py
msgid ""
"If postponed payment is allowed on a sales channel, customers can complete "
"their order without selecting a payment method. This is useful whenever "
"orders are not created by the same person who is making the payment."
msgstr ""
#: pretix/base/settings.py
msgid "Show start date"
msgstr ""
@@ -9995,12 +10023,6 @@ msgstr ""
msgid "Hide all unavailable dates from calendar or list views"
msgstr ""
#: pretix/base/settings.py
msgid ""
"This option currently only affects the calendar of this event series, not "
"the organizer-wide calendar."
msgstr ""
#: pretix/base/settings.py
msgid "Hide all past dates from calendar"
msgstr ""
@@ -14689,6 +14711,11 @@ msgctxt "rrule"
msgid "third"
msgstr ""
#: pretix/control/forms/rrule.py
msgctxt "rrule"
msgid "fourth"
msgstr ""
#: pretix/control/forms/rrule.py
msgctxt "rrule"
msgid "last"
@@ -15672,6 +15699,22 @@ msgstr ""
msgid "Queued emails have been aborted."
msgstr ""
#: pretix/control/logdisplay.py
msgid "An organizer meta property has been created."
msgstr ""
#: pretix/control/logdisplay.py
msgid "An organizer meta property has been deleted."
msgstr ""
#: pretix/control/logdisplay.py
msgid "An organizer meta property has been changed."
msgstr ""
#: pretix/control/logdisplay.py
msgid "An organizer meta property has been reordered."
msgstr ""
#: pretix/control/logdisplay.py
msgid "Gift card acceptance for another organizer has been added."
msgstr ""
@@ -15874,6 +15917,10 @@ msgstr ""
msgid "A two-factor emergency code has been generated."
msgstr ""
#: pretix/control/logdisplay.py
msgid "Drift and throttle values for two-factor devices have been reset."
msgstr ""
#: pretix/control/logdisplay.py
#, python-brace-format
msgid ""
@@ -16706,6 +16753,87 @@ msgid ""
"Granting access to your pretix account happens at your own risk."
msgstr ""
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
#: pretix/control/templates/pretixcontrol/checkin/bulk_revert_confirm.html
#: pretix/control/templates/pretixcontrol/checkin/list_delete.html
#: pretix/control/templates/pretixcontrol/checkin/reset.html
#: pretix/control/templates/pretixcontrol/datasync/control_order_info.html
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
#: pretix/control/templates/pretixcontrol/event/tax_delete.html
#: pretix/control/templates/pretixcontrol/item/delete.html
#: pretix/control/templates/pretixcontrol/items/category_delete.html
#: pretix/control/templates/pretixcontrol/items/discount_delete.html
#: pretix/control/templates/pretixcontrol/items/question_delete.html
#: pretix/control/templates/pretixcontrol/items/quota_delete.html
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html
#: pretix/control/templates/pretixcontrol/oauth/app_delete.html
#: pretix/control/templates/pretixcontrol/oauth/app_rollkeys.html
#: pretix/control/templates/pretixcontrol/oauth/auth_revoke.html
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/templates/pretixcontrol/order/change_contact.html
#: pretix/control/templates/pretixcontrol/order/change_locale.html
#: pretix/control/templates/pretixcontrol/order/change_questions.html
#: pretix/control/templates/pretixcontrol/order/extend.html
#: pretix/control/templates/pretixcontrol/order/pay.html
#: pretix/control/templates/pretixcontrol/order/pay_complete.html
#: pretix/control/templates/pretixcontrol/order/reactivate.html
#: pretix/control/templates/pretixcontrol/order/refund_choose.html
#: pretix/control/templates/pretixcontrol/order/refund_done.html
#: pretix/control/templates/pretixcontrol/order/refund_process.html
#: pretix/control/templates/pretixcontrol/order/refund_start.html
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html
#: pretix/control/templates/pretixcontrol/orders/export_delete.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/channel_delete.html
#: pretix/control/templates/pretixcontrol/organizers/customer_anonymize.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership_delete.html
#: pretix/control/templates/pretixcontrol/organizers/device_revoke.html
#: pretix/control/templates/pretixcontrol/organizers/export_delete.html
#: pretix/control/templates/pretixcontrol/organizers/gate_delete.html
#: pretix/control/templates/pretixcontrol/organizers/membershiptype_delete.html
#: pretix/control/templates/pretixcontrol/organizers/property_delete.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_delete.html
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_delete.html
#: pretix/control/templates/pretixcontrol/organizers/team_delete.html
#: pretix/control/templates/pretixcontrol/pdf/index.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/delete.html
#: pretix/control/templates/pretixcontrol/subevents/delete_bulk.html
#: pretix/control/templates/pretixcontrol/subevents/edit.html
#: pretix/control/templates/pretixcontrol/user/2fa_delete.html
#: pretix/control/templates/pretixcontrol/user/2fa_disable.html
#: pretix/control/templates/pretixcontrol/user/2fa_enable.html
#: pretix/control/templates/pretixcontrol/user/2fa_leaveteams.html
#: pretix/control/templates/pretixcontrol/user/2fa_regenemergency.html
#: pretix/control/templates/pretixcontrol/user/change_email.html
#: pretix/control/templates/pretixcontrol/user/change_password.html
#: pretix/control/templates/pretixcontrol/user/confirmation_code_dialog.html
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/delete_bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/delete_carts.html
#: pretix/control/templates/pretixcontrol/waitinglist/delete.html
#: pretix/control/templates/pretixcontrol/waitinglist/delete_bulk.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/delete.html
#: pretix/plugins/badges/templates/pretixplugins/badges/delete.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pay.html
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_delete.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca.html
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/delete.html
#: pretix/presale/templates/pretixpresale/event/order_change.html
#: pretix/presale/templates/pretixpresale/event/order_modify.html
#: pretix/presale/templates/pretixpresale/event/order_pay.html
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html
#: pretix/presale/templates/pretixpresale/event/order_pay_confirm.html
#: pretix/presale/templates/pretixpresale/event/position_change.html
#: pretix/presale/templates/pretixpresale/event/position_modify.html
msgid "Cancel"
msgstr ""
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
msgid "Authorize"
msgstr ""
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
msgid "Error:"
msgstr ""
@@ -17067,82 +17195,6 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
#: pretix/control/templates/pretixcontrol/checkin/bulk_revert_confirm.html
#: pretix/control/templates/pretixcontrol/checkin/list_delete.html
#: pretix/control/templates/pretixcontrol/checkin/reset.html
#: pretix/control/templates/pretixcontrol/datasync/control_order_info.html
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
#: pretix/control/templates/pretixcontrol/event/tax_delete.html
#: pretix/control/templates/pretixcontrol/item/delete.html
#: pretix/control/templates/pretixcontrol/items/category_delete.html
#: pretix/control/templates/pretixcontrol/items/discount_delete.html
#: pretix/control/templates/pretixcontrol/items/question_delete.html
#: pretix/control/templates/pretixcontrol/items/quota_delete.html
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html
#: pretix/control/templates/pretixcontrol/oauth/app_delete.html
#: pretix/control/templates/pretixcontrol/oauth/app_rollkeys.html
#: pretix/control/templates/pretixcontrol/oauth/auth_revoke.html
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/templates/pretixcontrol/order/change_contact.html
#: pretix/control/templates/pretixcontrol/order/change_locale.html
#: pretix/control/templates/pretixcontrol/order/change_questions.html
#: pretix/control/templates/pretixcontrol/order/extend.html
#: pretix/control/templates/pretixcontrol/order/pay.html
#: pretix/control/templates/pretixcontrol/order/pay_complete.html
#: pretix/control/templates/pretixcontrol/order/reactivate.html
#: pretix/control/templates/pretixcontrol/order/refund_choose.html
#: pretix/control/templates/pretixcontrol/order/refund_done.html
#: pretix/control/templates/pretixcontrol/order/refund_process.html
#: pretix/control/templates/pretixcontrol/order/refund_start.html
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html
#: pretix/control/templates/pretixcontrol/orders/export_delete.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/channel_delete.html
#: pretix/control/templates/pretixcontrol/organizers/customer_anonymize.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership_delete.html
#: pretix/control/templates/pretixcontrol/organizers/device_revoke.html
#: pretix/control/templates/pretixcontrol/organizers/export_delete.html
#: pretix/control/templates/pretixcontrol/organizers/gate_delete.html
#: pretix/control/templates/pretixcontrol/organizers/membershiptype_delete.html
#: pretix/control/templates/pretixcontrol/organizers/property_delete.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_delete.html
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_delete.html
#: pretix/control/templates/pretixcontrol/organizers/team_delete.html
#: pretix/control/templates/pretixcontrol/pdf/index.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/delete.html
#: pretix/control/templates/pretixcontrol/subevents/delete_bulk.html
#: pretix/control/templates/pretixcontrol/subevents/edit.html
#: pretix/control/templates/pretixcontrol/user/2fa_delete.html
#: pretix/control/templates/pretixcontrol/user/2fa_disable.html
#: pretix/control/templates/pretixcontrol/user/2fa_enable.html
#: pretix/control/templates/pretixcontrol/user/2fa_leaveteams.html
#: pretix/control/templates/pretixcontrol/user/2fa_regenemergency.html
#: pretix/control/templates/pretixcontrol/user/change_email.html
#: pretix/control/templates/pretixcontrol/user/change_password.html
#: pretix/control/templates/pretixcontrol/user/confirmation_code_dialog.html
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/delete_bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/delete_carts.html
#: pretix/control/templates/pretixcontrol/waitinglist/delete.html
#: pretix/control/templates/pretixcontrol/waitinglist/delete_bulk.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/delete.html
#: pretix/plugins/badges/templates/pretixplugins/badges/delete.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pay.html
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_delete.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca.html
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/delete.html
#: pretix/presale/templates/pretixpresale/event/order_change.html
#: pretix/presale/templates/pretixpresale/event/order_modify.html
#: pretix/presale/templates/pretixpresale/event/order_pay.html
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html
#: pretix/presale/templates/pretixpresale/event/order_pay_confirm.html
#: pretix/presale/templates/pretixpresale/event/position_change.html
#: pretix/presale/templates/pretixpresale/event/position_modify.html
msgid "Cancel"
msgstr ""
#: pretix/control/templates/pretixcontrol/checkin/bulk_revert_confirm.html
#: pretix/control/templates/pretixcontrol/checkin/list_delete.html
#: pretix/control/templates/pretixcontrol/event/delete.html
@@ -19254,7 +19306,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/events/create_basics.html
msgid ""
"We strongly recommend against using short forms of more then 16 characters."
"We strongly recommend against using short forms of more than 16 characters."
msgstr ""
#: pretix/control/templates/pretixcontrol/events/create_basics.html
@@ -24194,6 +24246,7 @@ msgid "Add a new device"
msgstr ""
#: pretix/control/templates/pretixcontrol/user/2fa_main.html
#: pretix/control/templates/pretixcontrol/users/form.html
msgid "Emergency tokens"
msgstr ""
@@ -24438,6 +24491,26 @@ msgstr ""
msgid "Authentication backend"
msgstr ""
#: pretix/control/templates/pretixcontrol/users/form.html
msgid "Reset drift and throttle"
msgstr ""
#: pretix/control/templates/pretixcontrol/users/form.html
msgid "Available two-factor authentication methods"
msgstr ""
#: pretix/control/templates/pretixcontrol/users/form.html
#, python-format
msgid "1 failed attempt since %(date)s"
msgid_plural "%(cnt)s failed attempts since %(date)s"
msgstr[0] ""
msgstr[1] ""
#: pretix/control/templates/pretixcontrol/users/form.html
#, python-format
msgid "Currently locked until %(date)s"
msgstr ""
#: pretix/control/templates/pretixcontrol/users/form.html
msgid "Team memberships"
msgstr ""
@@ -24891,6 +24964,20 @@ msgstr ""
msgid "Invalid code, please try again."
msgstr ""
#: pretix/control/views/auth.py
#, python-brace-format
msgid "Invalid code. Please try again after waiting {value} minute."
msgid_plural "Invalid code. Please try again after waiting {value} minutes."
msgstr[0] ""
msgstr[1] ""
#: pretix/control/views/auth.py
#, python-brace-format
msgid "Invalid code. Please try again after waiting {value} second."
msgid_plural "Invalid code. Please try again after waiting {value} seconds."
msgstr[0] ""
msgstr[1] ""
#: pretix/control/views/checkin.py
msgid "The selected check-ins have been reverted."
msgstr ""
@@ -25643,6 +25730,10 @@ msgstr ""
msgid "You entered an order in an event with a different currency."
msgstr ""
#: pretix/control/views/orders.py
msgid "You entered an order in an event that you do not have access to."
msgstr ""
#: pretix/control/views/orders.py
msgid ""
"You can not refund more than the amount of a payment that is not yet "
@@ -25655,6 +25746,10 @@ msgid ""
"refunds."
msgstr ""
#: pretix/control/views/orders.py
msgid "The refunds you selected do not match the selected total refund amount."
msgstr ""
#: pretix/control/views/orders.py
#, python-brace-format
msgid ""
@@ -25696,10 +25791,6 @@ msgid ""
"Your {event} team"
msgstr ""
#: pretix/control/views/orders.py
msgid "The refunds you selected do not match the selected total refund amount."
msgstr ""
#: pretix/control/views/orders.py
msgid "The payment has been created successfully."
msgstr ""
@@ -26411,6 +26502,10 @@ msgid ""
"remain active."
msgstr ""
#: pretix/control/views/users.py
msgid "The drift values for TOTP devices have been reset."
msgstr ""
#: pretix/control/views/users.py
msgid "The new user has been created."
msgstr ""
@@ -26423,6 +26518,14 @@ msgstr ""
msgid "Bypass quota"
msgstr ""
#: pretix/control/views/vouchers.py
msgid "Budget"
msgstr ""
#: pretix/control/views/vouchers.py
msgid "Budget used"
msgstr ""
#: pretix/control/views/vouchers.py
msgid "The requested voucher does not exist."
msgstr ""
@@ -26575,12 +26678,11 @@ msgstr ""
#: pretix/plugins/autocheckin/apps.py pretix/plugins/badges/apps.py
#: pretix/plugins/banktransfer/apps.py pretix/plugins/checkinlists/apps.py
#: pretix/plugins/manualpayment/apps.py pretix/plugins/paypal/apps.py
#: pretix/plugins/paypal2/apps.py pretix/plugins/pretixdroid/apps.py
#: pretix/plugins/reports/apps.py pretix/plugins/returnurl/apps.py
#: pretix/plugins/sendmail/apps.py pretix/plugins/statistics/apps.py
#: pretix/plugins/stripe/apps.py pretix/plugins/ticketoutputpdf/apps.py
#: pretix/plugins/webcheckin/apps.py
#: pretix/plugins/manualpayment/apps.py pretix/plugins/paypal2/apps.py
#: pretix/plugins/pretixdroid/apps.py pretix/plugins/reports/apps.py
#: pretix/plugins/returnurl/apps.py pretix/plugins/sendmail/apps.py
#: pretix/plugins/statistics/apps.py pretix/plugins/stripe/apps.py
#: pretix/plugins/ticketoutputpdf/apps.py pretix/plugins/webcheckin/apps.py
msgid "the pretix team"
msgstr ""
@@ -27131,7 +27233,6 @@ msgstr ""
#: pretix/plugins/banktransfer/refund_export.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Payer"
@@ -27625,8 +27726,7 @@ msgstr ""
msgid "Automatically checked in"
msgstr ""
#: pretix/plugins/checkinlists/exporters.py pretix/plugins/paypal/payment.py
#: pretix/plugins/paypal2/payment.py
#: pretix/plugins/checkinlists/exporters.py pretix/plugins/paypal2/payment.py
msgid "Secret"
msgstr ""
@@ -27685,152 +27785,6 @@ msgstr ""
msgid "A fully customizable payment method for manual processing."
msgstr ""
#: pretix/plugins/paypal/apps.py pretix/plugins/paypal/payment.py
#: pretix/plugins/paypal2/apps.py pretix/plugins/paypal2/payment.py
#: pretix/plugins/stripe/payment.py
msgid "PayPal"
msgstr ""
#: pretix/plugins/paypal/apps.py
msgid ""
"Accept payments with your PayPal account. PayPal is one of the most popular "
"payment methods world-wide."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"The PayPal sandbox is being used, you can test without actually sending "
"money but you will need a PayPal sandbox user to log in."
msgstr ""
#: pretix/plugins/paypal/payment.py
msgid "PayPal account"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#: pretix/plugins/stripe/payment.py
msgid "Endpoint"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Reference prefix"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"Any value entered here will be added in front of the regular booking "
"reference containing the order number."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Reference postfix"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"Any value entered here will be added behind the regular booking reference "
"containing the order number."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Disconnect from PayPal"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "We had trouble communicating with PayPal"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"We were unable to process your payment. See below for details on how to "
"proceed."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid ""
"PayPal has not yet approved the payment. We will inform you as soon as the "
"payment completed."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "Refunding the amount via PayPal failed: {}"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "The payment for this invoice has already been received."
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "PayPal payment ID"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "PayPal sale ID"
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/checkout_payment_confirm.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_confirm.html
msgid ""
"The total amount listed above will be withdrawn from your PayPal account "
"after the confirmation of your purchase."
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/checkout_payment_form.html
msgid ""
"After you clicked continue, we will redirect you to PayPal to fill in your "
"payment details. You will then be redirected back here to review and confirm "
"your order."
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Sale ID"
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Last update"
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
msgid "Total value"
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/pending.html
msgid ""
"Our attempt to execute your Payment via PayPal has failed. Please try again "
"or contact us."
msgstr ""
#: pretix/plugins/paypal/templates/pretixplugins/paypal/pending.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pending.html
msgid ""
"We're waiting for an answer from PayPal regarding your payment. Please "
"contact us, if this takes more than a few hours."
msgstr ""
#: pretix/plugins/paypal/views.py pretix/plugins/paypal2/views.py
msgid "Invalid response from PayPal received."
msgstr ""
#: pretix/plugins/paypal/views.py pretix/plugins/paypal2/views.py
msgid "It looks like you canceled the PayPal payment"
msgstr ""
#: pretix/plugins/paypal/views.py pretix/plugins/paypal2/views.py
msgid "Your PayPal account has been disconnected."
msgstr ""
#: pretix/plugins/paypal2/apps.py
msgid ""
"Accept payments with your PayPal account. In addition to regular PayPal "
@@ -27840,10 +27794,23 @@ msgid ""
"methods world-wide."
msgstr ""
#: pretix/plugins/paypal2/apps.py pretix/plugins/paypal2/payment.py
#: pretix/plugins/stripe/payment.py
msgid "PayPal"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "PayPal Merchant ID"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
#: pretix/plugins/paypal2/payment.py pretix/plugins/stripe/payment.py
msgid "Endpoint"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Even if a customer chooses an Alternative Payment Method, they will always "
@@ -27912,6 +27879,26 @@ msgid ""
"while the last payment is still under investigation."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "Reference prefix"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Any value entered here will be added in front of the regular booking "
"reference containing the order number."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "Reference postfix"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Any value entered here will be added behind the regular booking reference "
"containing the order number."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "-- Automatic --"
msgstr ""
@@ -27932,6 +27919,10 @@ msgstr ""
msgid "Connect with {icon} PayPal"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "Disconnect from PayPal"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Please configure a PayPal Webhook to the following endpoint in order to "
@@ -27960,6 +27951,12 @@ msgstr ""
msgid "An error occurred during connecting with PayPal, please try again."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"The PayPal sandbox is being used, you can test without actually sending "
"money but you will need a PayPal sandbox user to log in."
msgstr ""
#: pretix/plugins/paypal2/payment.py
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pending.html
msgid ""
@@ -27968,6 +27965,10 @@ msgid ""
"attempt. You can also try other payment methods, if available."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "We had trouble communicating with PayPal"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Something went wrong when requesting the payment status. Please try again."
@@ -27977,12 +27978,41 @@ msgstr ""
msgid "You may need to enable JavaScript for PayPal payments."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"We were unable to process your payment. See below for details on how to "
"proceed."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"PayPal has not yet approved the payment. We will inform you as soon as the "
"payment completed."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid ""
"Refunding the amount via PayPal failed: The original payment does not "
"contain the required information to issue an automated refund."
msgstr ""
#: pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "Refunding the amount via PayPal failed: {}"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "The payment for this invoice has already been received."
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "PayPal payment ID"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "PayPal sale ID"
msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "PayPal APM"
msgstr ""
@@ -28070,6 +28100,12 @@ msgstr ""
msgid "We will then charge your PayPal account and finalize the order."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_confirm.html
msgid ""
"The total amount listed above will be withdrawn from your PayPal account "
"after the confirmation of your purchase."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_confirm.html
msgid ""
"After placing your order, you will be able to select your desired payment "
@@ -28101,6 +28137,11 @@ msgid ""
"different payment method, if you prefer."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Sale ID"
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
msgid "Capture status"
msgstr ""
@@ -28111,6 +28152,17 @@ msgid ""
"money will not be disbursed and the order remain in its pending state."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
msgid "Last update"
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control.html
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/control_legacy.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
msgid "Total value"
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pay.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca.html
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca_return.html
@@ -28144,6 +28196,12 @@ msgid ""
"to resolve this case."
msgstr ""
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/pending.html
msgid ""
"We're waiting for an answer from PayPal regarding your payment. Please "
"contact us, if this takes more than a few hours."
msgstr ""
#: pretix/plugins/paypal2/views.py
msgid ""
"An error occurred returning from PayPal: request parameters missing. Please "
@@ -28174,6 +28232,18 @@ msgid ""
"in detail below."
msgstr ""
#: pretix/plugins/paypal2/views.py
msgid "Invalid response from PayPal received."
msgstr ""
#: pretix/plugins/paypal2/views.py
msgid "It looks like you canceled the PayPal payment"
msgstr ""
#: pretix/plugins/paypal2/views.py
msgid "Your PayPal account has been disconnected."
msgstr ""
#: pretix/plugins/pretixdroid/apps.py
msgid "Old check-in device API"
msgstr ""
@@ -30193,7 +30263,6 @@ msgstr ""
#: pretix/presale/forms/order.py
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "plus taxes"
msgstr ""
@@ -30614,6 +30683,31 @@ msgid ""
"Please go to the payment settings and activate one or more payment providers."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
msgid ""
"Not sure yet? You can complete your order first and then select a payment "
"method later."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
msgid ""
"To do so, please first remove the payment methods you already selected above."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
#, python-format
msgid "Your payment needs to be completed within %(minutes)s minutes."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
#, python-format
msgid "Your payment needs to be completed by %(deadline)s."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html
msgid "Proceed without selection"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html
msgid "Before we continue, we need you to answer some questions."
msgstr ""
@@ -30680,14 +30774,12 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "minimum amount to order: %(num)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgctxt "price"
msgid "free"
msgstr ""
@@ -30716,66 +30808,56 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Original price:"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "New price:"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "Modify price for %(item)s, at least %(price)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "Modify price for %(item)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "incl. taxes"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "<strong>plus</strong> %(rate)s%% %(name)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "incl. %(rate)s%% %(name)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgctxt "checkbox"
msgid "Select"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Decrease quantity"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Increase quantity"
msgstr ""
@@ -31236,12 +31318,10 @@ msgid "Your order qualifies for a discount"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
msgid "Uncategorized items"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "Show full-size image of %(item)s"
msgstr ""
@@ -31254,19 +31334,16 @@ msgstr[0] ""
msgstr[1] ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "%(value)s incl. taxes"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "%(value)s without taxes"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "Set price in %(currency)s for %(item)s"
msgstr ""
@@ -31925,11 +32002,6 @@ msgid ""
"products at the specified price:"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "from %(minprice)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/voucher.html
#, python-format
msgid "You need to select at least %(number)s products."
+1 -84
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-06 15:52+0000\n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -766,194 +766,163 @@ msgstr ""
msgid "Google Pay"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Quantity"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Decrease quantity"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Increase quantity"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Filter events by"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Filter"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Price"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "Original price: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "New price: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Select"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "Select %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "Select variant %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Sold out"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Buy"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Register"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Reserved"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "FREE"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "from %(currency)s %(price)s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "Image of %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "incl. %(rate)s% %(taxname)s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "plus %(rate)s% %(taxname)s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "incl. taxes"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "plus taxes"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "currently available: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Only available with a voucher"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Not yet available"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Not available anymore"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Currently not available"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
#, javascript-format
msgctxt "widget"
msgid "minimum amount to order: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Close ticket shop"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "The ticket shop could not be loaded."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid ""
@@ -961,25 +930,21 @@ msgid ""
"in a new tab to continue."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Open ticket shop"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Checkout"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid ""
@@ -987,13 +952,11 @@ msgid ""
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Waiting list"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid ""
@@ -1001,115 +964,96 @@ msgid ""
"products, they will be added to your existing cart."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Resume checkout"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Redeem a voucher"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Redeem"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Voucher code"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Close"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Close checkout"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "You cannot cancel this operation. Please wait for loading to finish."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Continue"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Show variants"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Hide variants"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Choose a different event"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Choose a different date"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Back"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Next month"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Previous month"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Next week"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Previous week"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Open seat selection"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid ""
@@ -1118,138 +1062,111 @@ msgid ""
"again."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgctxt "widget"
msgid "Load more"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Mo"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Tu"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "We"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Th"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Fr"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Sa"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Su"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Monday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Tuesday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Wednesday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Thursday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Friday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Saturday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "Sunday"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "January"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "February"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "March"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "April"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "May"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "June"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "July"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "August"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "September"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "October"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "November"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "December"
msgstr ""
File diff suppressed because it is too large Load Diff

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