Compare commits

...
Author SHA1 Message Date
Mira Weller eff815056b Fix test case 2024-11-29 14:37:37 +01:00
Mira Weller 45391b104a Require at least one of street, zipcode, city if invoice_address_required 2024-11-29 14:15:36 +01:00
Mira Weller 54d3d20d70 Fix typo, require at least one of the address parts 2024-11-29 14:12:22 +01:00
Mira Weller be1cb12286 Add test case for country without zip code 2024-11-29 13:49:47 +01:00
Mira Weller c65b7f711d Simplify value comparison (state multiselect is not supported anyway) 2024-11-29 13:34:06 +01:00
Mira Weller 8e3d5511e2 Make the test case happy 2024-11-29 13:18:09 +01:00
9acfcf2526 Apply suggestions from code review
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-11-27 14:42:21 +01:00
Mira Weller 32fd9f2f4d Distinguish between addon product address forms in pretixcontrol 2024-11-25 21:53:16 +01:00
Mira Weller 47ec03baac use street instead of city as indicator whether address is filled 2024-11-25 21:46:48 +01:00
Mira Weller 04884ce874 lint 2024-11-25 21:38:56 +01:00
Mira Weller 3337783d1f Optional zipcodes/cities for attendee addresses as well 2024-11-25 21:30:54 +01:00
Mira Weller 391bbc25f8 Keep selected state on load 2024-11-25 21:30:15 +01:00
Mira Weller 68017cc8cb Always mark street address as required 2024-11-25 21:00:54 +01:00
Mira Weller 5fee16035f Distinguish between addon product address forms 2024-11-25 21:00:25 +01:00
Mira Weller 6d32aa5220 Load address format information when selecting country 2024-11-25 20:52:10 +01:00
Mira Weller 3461c59cfe Move countries-with-vat-id logic to addressform.js 2024-11-25 16:08:20 +01:00
Mira Weller 21b4dacee9 Correctly apply "required" attribute to address state field
(we try to use it from the country field, which is a <select> tag with default value and therefore never has a "required" attribute - we need to take the information from the .required class of the container)
2024-11-25 13:37:54 +01:00
Mira Weller 2663e9ff32 Move country-dependent state field JS logic to separate file
(avoids code duplication for presale and control)
2024-11-25 13:35:00 +01:00
a601c75923 CheckIns: Display a source_type icon (barcode/nfc) where known (#4628)
Co-authored-by: Raphael Michel <michel@rami.io>
2024-11-18 17:50:43 +01:00
Raphael MichelandRaphael Michel f94227f00f Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5779 of 5779 strings)

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

* Conflicts and word list

* Add MobilePay to wordlist

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

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

* navigate in typeahead with tab, add esc to close

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Update src/pretix/api/signals.py

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

* REmove dead class

---------

Co-authored-by: robbi5 <richt@rami.io>
2024-11-11 17:13:53 +01:00
MiraGitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3170744c56 Bleach 6 update (#4610)
* Update bleach requirement from ==5.0.* to ==6.2.*

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

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

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

* Update bleach parameter types

* Fix tests

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Trying to build on Fedora 41 result in:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

* Fix widget tests

* Update src/tests/presale/test_widget.py

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

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-10-29 17:31:51 +01:00
Richard SchreiberandGitHub f9502a3212 Fix testutils redis mock (#4586) 2024-10-29 16:37:06 +01:00
Richard SchreiberandGitHub a31f624417 Dev: add support for asyncio_default_fixture_loop_scope in pytest-asyncio (#4589) 2024-10-29 16:36:43 +01:00
227 changed files with 35686 additions and 34902 deletions
+6 -23
View File
@@ -31,8 +31,6 @@ subevent integer ID of the date
position_count integer Number of tickets that match this list (read-only). position_count integer Number of tickets that match this list (read-only).
checkin_count integer Number of check-ins performed on this list (read-only). checkin_count integer Number of check-ins performed on this list (read-only).
include_pending boolean If ``true``, the check-in list also contains tickets from orders in pending state. include_pending boolean If ``true``, the check-in list also contains tickets from orders in pending state.
auto_checkin_sales_channels list of strings All items on the check-in list will be automatically marked as checked-in when purchased through any of the listed sales channels.
**Deprecated, will be removed in pretix 2024.10.** Use :ref:`rest-autocheckinrules`: instead.
allow_multiple_entries boolean If ``true``, subsequent scans of a ticket on this list should not show a warning but instead be stored as an additional check-in. allow_multiple_entries boolean If ``true``, subsequent scans of a ticket on this list should not show a warning but instead be stored as an additional check-in.
allow_entry_after_exit boolean If ``true``, subsequent scans of a ticket on this list are valid if the last scan of the ticket was an exit scan. allow_entry_after_exit boolean If ``true``, subsequent scans of a ticket on this list are valid if the last scan of the ticket was an exit scan.
rules object Custom check-in logic. The contents of this field are currently not considered a stable API and modifications through the API are highly discouraged. rules object Custom check-in logic. The contents of this field are currently not considered a stable API and modifications through the API are highly discouraged.
@@ -91,10 +89,7 @@ Endpoints
"allow_entry_after_exit": true, "allow_entry_after_exit": true,
"exit_all_at": null, "exit_all_at": null,
"rules": {}, "rules": {},
"addon_match": false, "addon_match": false
"auto_checkin_sales_channels": [
"pretixpos"
]
} }
] ]
} }
@@ -146,10 +141,7 @@ Endpoints
"allow_entry_after_exit": true, "allow_entry_after_exit": true,
"exit_all_at": null, "exit_all_at": null,
"rules": {}, "rules": {},
"addon_match": false, "addon_match": false
"auto_checkin_sales_channels": [
"pretixpos"
]
} }
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch
@@ -246,10 +238,7 @@ Endpoints
"subevent": null, "subevent": null,
"allow_multiple_entries": false, "allow_multiple_entries": false,
"allow_entry_after_exit": true, "allow_entry_after_exit": true,
"addon_match": false, "addon_match": false
"auto_checkin_sales_channels": [
"pretixpos"
]
} }
**Example response**: **Example response**:
@@ -271,10 +260,7 @@ Endpoints
"subevent": null, "subevent": null,
"allow_multiple_entries": false, "allow_multiple_entries": false,
"allow_entry_after_exit": true, "allow_entry_after_exit": true,
"addon_match": false, "addon_match": false
"auto_checkin_sales_channels": [
"pretixpos"
]
} }
:param organizer: The ``slug`` field of the organizer of the event/item to create a list for :param organizer: The ``slug`` field of the organizer of the event/item to create a list for
@@ -326,10 +312,7 @@ Endpoints
"subevent": null, "subevent": null,
"allow_multiple_entries": false, "allow_multiple_entries": false,
"allow_entry_after_exit": true, "allow_entry_after_exit": true,
"addon_match": false, "addon_match": false
"auto_checkin_sales_channels": [
"pretixpos"
]
} }
:param organizer: The ``slug`` field of the organizer to modify :param organizer: The ``slug`` field of the organizer to modify
@@ -342,7 +325,7 @@ Endpoints
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/checkinlist/(id)/ .. http:delete:: /api/v1/organizers/(organizer)/events/(event)/checkinlist/(id)/
Delete a check-in list. Note that this also deletes the information on all check-ins performed via this list. Delete a check-in list. **Note that this also deletes the information on all check-ins performed via this list.**
**Example request**: **Example request**:
+7 -7
View File
@@ -352,12 +352,12 @@ Fetching individual invoices
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch :param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice to fetch :param number: The ``number`` field of the invoice to fetch
:statuscode 200: no error :statuscode 200: no error
:statuscode 401: Authentication failure :statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/download/ .. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/download/
Download an invoice in PDF format. Download an invoice in PDF format.
@@ -384,7 +384,7 @@ Fetching individual invoices
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch :param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice to fetch :param number: The ``number`` field of the invoice to fetch
:statuscode 200: no error :statuscode 200: no error
:statuscode 401: Authentication failure :statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
@@ -397,7 +397,7 @@ Modifying invoices
Invoices cannot be edited directly, but the following actions can be triggered: Invoices cannot be edited directly, but the following actions can be triggered:
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/reissue/ .. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/reissue/
Cancels the invoice and creates a new one. Cancels the invoice and creates a new one.
@@ -419,13 +419,13 @@ Invoices cannot be edited directly, but the following actions can be triggered:
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch :param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice to reissue :param number: The ``number`` field of the invoice to reissue
:statuscode 200: no error :statuscode 200: no error
:statuscode 400: The invoice has already been canceled :statuscode 400: The invoice has already been canceled
:statuscode 401: Authentication failure :statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource. :statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/regenerate/ .. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/regenerate/
Re-generates the invoice from order data. Re-generates the invoice from order data.
@@ -447,7 +447,7 @@ Invoices cannot be edited directly, but the following actions can be triggered:
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch :param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice to regenerate :param number: The ``number`` field of the invoice to regenerate
:statuscode 200: no error :statuscode 200: no error
:statuscode 400: The invoice has already been canceled :statuscode 400: The invoice has already been canceled
:statuscode 401: Authentication failure :statuscode 401: Authentication failure
+12 -3
View File
@@ -104,6 +104,10 @@ url string The full URL to
payments list of objects List of payment processes (see below) payments list of objects List of payment processes (see below)
refunds list of objects List of refund processes (see below) refunds list of objects List of refund processes (see below)
last_modified datetime Last modification of this object last_modified datetime Last modification of this object
cancellation_date datetime Time of order cancellation (or ``null``). **Note**:
Will not be set for partial cancellations and is not
reliable for orders that have been cancelled,
reactivated and cancelled again.
===================================== ========================== ======================================================= ===================================== ========================== =======================================================
@@ -151,6 +155,9 @@ last_modified datetime Last modificati
The ``expires`` attribute can now be passed during order creation. The ``expires`` attribute can now be passed during order creation.
.. versionchanged:: 2024.11
The ``cancellation_date`` attribute has been added and can also be used as an ordering key.
.. _order-position-resource: .. _order-position-resource:
@@ -464,14 +471,15 @@ List of all orders
"provider": "banktransfer" "provider": "banktransfer"
} }
], ],
"refunds": [] "refunds": [],
"cancellation_date": null
} }
] ]
} }
:query integer page: The page number in case of a multi-page result set, default is 1 :query integer page: The page number in case of a multi-page result set, default is 1
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``code``, :query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``code``,
``last_modified``, and ``status``. Default: ``datetime`` ``last_modified``, ``status`` and ``cancellation_date``. Default: ``datetime``
:query string code: Only return orders that match the given order code :query string code: Only return orders that match the given order code
:query string status: Only return orders in the given order status (see above) :query string status: Only return orders in the given order status (see above)
:query string search: Only return orders matching a given search query (matching for names, email addresses, and company names) :query string search: Only return orders matching a given search query (matching for names, email addresses, and company names)
@@ -703,7 +711,8 @@ Fetching individual orders
"provider": "banktransfer" "provider": "banktransfer"
} }
], ],
"refunds": [] "refunds": [],
"cancellation_date": null
} }
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch
+4
View File
@@ -100,3 +100,7 @@ API
.. automodule:: pretix.base.signals .. automodule:: pretix.base.signals
:no-index: :no-index:
:members: validate_event_settings, api_event_settings_fields :members: validate_event_settings, api_event_settings_fields
.. automodule:: pretix.api.signals
:no-index:
:members: register_device_security_profile
+3 -4
View File
@@ -29,7 +29,7 @@ dependencies = [
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab "arabic-reshaper==3.0.0", # Support for Arabic in reportlab
"babel", "babel",
"BeautifulSoup4==4.12.*", "BeautifulSoup4==4.12.*",
"bleach==5.0.*", "bleach==6.2.*",
"celery==5.4.*", "celery==5.4.*",
"chardet==5.2.*", "chardet==5.2.*",
"cryptography>=3.4.2", "cryptography>=3.4.2",
@@ -91,9 +91,8 @@ dependencies = [
"redis==5.2.*", "redis==5.2.*",
"reportlab==4.2.*", "reportlab==4.2.*",
"requests==2.31.*", "requests==2.31.*",
"sentry-sdk==2.17.*", "sentry-sdk==2.18.*",
"sepaxml==2.6.*", "sepaxml==2.6.*",
"slimit",
"stripe==7.9.*", "stripe==7.9.*",
"text-unidecode==1.*", "text-unidecode==1.*",
"tlds>=2020041600", "tlds>=2020041600",
@@ -117,7 +116,7 @@ dev = [
"isort==5.13.*", "isort==5.13.*",
"pep8-naming==0.14.*", "pep8-naming==0.14.*",
"potypo", "potypo",
"pytest-asyncio", "pytest-asyncio>=0.24",
"pytest-cache", "pytest-cache",
"pytest-cov", "pytest-cov",
"pytest-django==4.*", "pytest-django==4.*",
+3 -2
View File
@@ -27,7 +27,7 @@ from rest_framework import exceptions
from rest_framework.authentication import TokenAuthentication from rest_framework.authentication import TokenAuthentication
from pretix.api.auth.devicesecurity import ( from pretix.api.auth.devicesecurity import (
DEVICE_SECURITY_PROFILES, FullAccessSecurityProfile, FullAccessSecurityProfile, get_all_security_profiles,
) )
from pretix.base.models import Device from pretix.base.models import Device
@@ -58,7 +58,8 @@ class DeviceTokenAuthentication(TokenAuthentication):
def authenticate(self, request): def authenticate(self, request):
r = super().authenticate(request) r = super().authenticate(request)
if r and isinstance(r[1], Device): if r and isinstance(r[1], Device):
profile = DEVICE_SECURITY_PROFILES.get(r[1].security_profile, FullAccessSecurityProfile) profiles = get_all_security_profiles()
profile = profiles.get(r[1].security_profile, FullAccessSecurityProfile())
if not profile.is_allowed(request): if not profile.is_allowed(request):
raise exceptions.PermissionDenied('Request denied by device security profile.') raise exceptions.PermissionDenied('Request denied by device security profile.')
return r return r
+51 -84
View File
@@ -20,13 +20,40 @@
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
import logging import logging
from collections import OrderedDict
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from pretix.api.signals import register_device_security_profile
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_ALL_PROFILES = None
class FullAccessSecurityProfile: class BaseSecurityProfile:
@property
def identifier(self) -> str:
"""
Unique identifier for this profile.
"""
raise NotImplementedError()
@property
def verbose_name(self) -> str:
"""
Human-readable name (can be a ``gettext_lazy`` object).
"""
raise NotImplementedError()
def is_allowed(self, request) -> bool:
"""
Return whether a given request should be allowed.
"""
raise NotImplementedError()
class FullAccessSecurityProfile(BaseSecurityProfile):
identifier = 'full' identifier = 'full'
verbose_name = _('Full device access (reading and changing orders and gift cards, reading of products and settings)') verbose_name = _('Full device access (reading and changing orders and gift cards, reading of products and settings)')
@@ -34,7 +61,7 @@ class FullAccessSecurityProfile:
return True return True
class AllowListSecurityProfile: class AllowListSecurityProfile(BaseSecurityProfile):
allowlist = () allowlist = ()
def is_allowed(self, request): def is_allowed(self, request):
@@ -157,88 +184,28 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
) )
class PretixPosSecurityProfile(AllowListSecurityProfile): def get_all_security_profiles():
identifier = 'pretixpos' global _ALL_PROFILES
verbose_name = _('pretixPOS')
allowlist = ( if _ALL_PROFILES:
('GET', 'api-v1:version'), return _ALL_PROFILES
('GET', 'api-v1:device.eventselection'),
('GET', 'api-v1:idempotency.query'), types = OrderedDict()
('GET', 'api-v1:device.info'), for recv, ret in register_device_security_profile.send(None):
('POST', 'api-v1:device.update'), if isinstance(ret, (list, tuple)):
('POST', 'api-v1:device.revoke'), for r in ret:
('POST', 'api-v1:device.roll'), types[r.identifier] = r
('GET', 'api-v1:event-list'), else:
('GET', 'api-v1:event-detail'), types[ret.identifier] = ret
('GET', 'api-v1:subevent-list'), _ALL_PROFILES = types
('GET', 'api-v1:subevent-detail'), return types
('GET', 'api-v1:itemcategory-list'),
('GET', 'api-v1:item-list'),
('GET', 'api-v1:question-list'),
('GET', 'api-v1:quota-list'),
('GET', 'api-v1:taxrule-list'),
('GET', 'api-v1:ticketlayout-list'),
('GET', 'api-v1:ticketlayoutitem-list'),
('GET', 'api-v1:badgelayout-list'),
('GET', 'api-v1:badgeitem-list'),
('GET', 'api-v1:voucher-list'),
('GET', 'api-v1:voucher-detail'),
('GET', 'api-v1:order-list'),
('POST', 'api-v1:order-list'),
('GET', 'api-v1:order-detail'),
('DELETE', 'api-v1:orderposition-detail'),
('PATCH', 'api-v1:orderposition-detail'),
('GET', 'api-v1:orderposition-list'),
('GET', 'api-v1:orderposition-answer'),
('GET', 'api-v1:orderposition-pdf_image'),
('POST', 'api-v1:orderposition-printlog'),
('POST', 'api-v1:order-mark-canceled'),
('POST', 'api-v1:orderpayment-list'),
('POST', 'api-v1:orderrefund-list'),
('POST', 'api-v1:orderrefund-done'),
('POST', 'api-v1:cartposition-list'),
('POST', 'api-v1:cartposition-bulk-create'),
('GET', 'api-v1:checkinlist-list'),
('POST', 'api-v1:checkinlistpos-redeem'),
('POST', 'plugins:pretix_posbackend:order.posprintlog'),
('POST', 'plugins:pretix_posbackend:order.poslock'),
('DELETE', 'plugins:pretix_posbackend:order.poslock'),
('DELETE', 'api-v1:cartposition-detail'),
('GET', 'api-v1:giftcard-list'),
('POST', 'api-v1:giftcard-transact'),
('PATCH', 'api-v1:giftcard-detail'),
('GET', 'plugins:pretix_posbackend:posclosing-list'),
('POST', 'plugins:pretix_posbackend:posreceipt-list'),
('POST', 'plugins:pretix_posbackend:posclosing-list'),
('POST', 'plugins:pretix_posbackend:posdebugdump-list'),
('POST', 'plugins:pretix_posbackend:posdebuglogentry-list'),
('POST', 'plugins:pretix_posbackend:posdebuglogentry-bulk-create'),
('GET', 'plugins:pretix_posbackend:poscashier-list'),
('POST', 'plugins:pretix_posbackend:stripeterminal.token'),
('POST', 'plugins:pretix_posbackend:stripeterminal.paymentintent'),
('PUT', 'plugins:pretix_posbackend:file.upload'),
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:event.settings'),
('GET', 'plugins:pretix_seating:event.event'),
('GET', 'plugins:pretix_seating:event.event.subevent'),
('GET', 'plugins:pretix_seating:event.plan'),
('GET', 'plugins:pretix_seating:selection.simple'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
('GET', 'api-v1:checkinrpc.search'),
('POST', 'api-v1:reusablemedium-lookup'),
('GET', 'api-v1:reusablemedium-list'),
('POST', 'api-v1:reusablemedium-list'),
)
DEVICE_SECURITY_PROFILES = { @receiver(register_device_security_profile, dispatch_uid="base_register_default_security_profiles")
k.identifier: k() for k in ( def register_default_webhook_events(sender, **kwargs):
FullAccessSecurityProfile, return (
PretixScanSecurityProfile, FullAccessSecurityProfile(),
PretixScanNoSyncSecurityProfile, PretixScanSecurityProfile(),
PretixScanNoSyncNoSearchSecurityProfile, PretixScanNoSyncSecurityProfile(),
PretixPosSecurityProfile, PretixScanNoSyncNoSearchSecurityProfile(),
) )
}
+1 -1
View File
@@ -235,7 +235,7 @@ class CartPositionCreateSerializer(BaseCartPositionCreateSerializer):
return cid return cid
def create(self, validated_data): def create(self, validated_data):
validated_data.pop('sales_channel') validated_data.pop('sales_channel', None)
addons_data = validated_data.pop('addons', None) addons_data = validated_data.pop('addons', None)
bundled_data = validated_data.pop('bundled', None) bundled_data = validated_data.pop('bundled', None)
+2 -11
View File
@@ -26,31 +26,22 @@ from rest_framework.exceptions import ValidationError
from pretix.api.serializers.event import SubEventSerializer from pretix.api.serializers.event import SubEventSerializer
from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.media import MEDIA_TYPES from pretix.base.media import MEDIA_TYPES
from pretix.base.models import Checkin, CheckinList, SalesChannel from pretix.base.models import Checkin, CheckinList
class CheckinListSerializer(I18nAwareModelSerializer): class CheckinListSerializer(I18nAwareModelSerializer):
checkin_count = serializers.IntegerField(read_only=True) checkin_count = serializers.IntegerField(read_only=True)
position_count = serializers.IntegerField(read_only=True) position_count = serializers.IntegerField(read_only=True)
auto_checkin_sales_channels = serializers.SlugRelatedField(
slug_field="identifier",
queryset=SalesChannel.objects.none(),
required=False,
allow_empty=True,
many=True,
)
class Meta: class Meta:
model = CheckinList model = CheckinList
fields = ('id', 'name', 'all_products', 'limit_products', 'subevent', 'checkin_count', 'position_count', fields = ('id', 'name', 'all_products', 'limit_products', 'subevent', 'checkin_count', 'position_count',
'include_pending', 'auto_checkin_sales_channels', 'allow_multiple_entries', 'allow_entry_after_exit', 'include_pending', 'allow_multiple_entries', 'allow_entry_after_exit',
'rules', 'exit_all_at', 'addon_match', 'ignore_in_statistics', 'consider_tickets_used') 'rules', 'exit_all_at', 'addon_match', 'ignore_in_statistics', 'consider_tickets_used')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['auto_checkin_sales_channels'].child_relation.queryset = self.context['event'].organizer.sales_channels.all()
if 'subevent' in self.context['request'].query_params.getlist('expand'): if 'subevent' in self.context['request'].query_params.getlist('expand'):
self.fields['subevent'] = SubEventSerializer(read_only=True) self.fields['subevent'] = SubEventSerializer(read_only=True)
+2 -2
View File
@@ -753,12 +753,12 @@ class OrderSerializer(I18nAwareModelSerializer):
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date', 'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads', 'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer', 'valid_if_pending', 'api_meta' 'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date'
) )
read_only_fields = ( read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date', 'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer', 'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer',
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel' 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date'
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
+6
View File
@@ -29,6 +29,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from pretix.api.auth.devicesecurity import get_all_security_profiles
from pretix.api.serializers import AsymmetricField from pretix.api.serializers import AsymmetricField
from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import CompatibleJSONField from pretix.api.serializers.order import CompatibleJSONField
@@ -297,6 +298,7 @@ class DeviceSerializer(serializers.ModelSerializer):
revoked = serializers.BooleanField(read_only=True) revoked = serializers.BooleanField(read_only=True)
initialized = serializers.DateTimeField(read_only=True) initialized = serializers.DateTimeField(read_only=True)
initialization_token = serializers.DateTimeField(read_only=True) initialization_token = serializers.DateTimeField(read_only=True)
security_profile = serializers.ChoiceField(choices=[], required=False, default="full")
class Meta: class Meta:
model = Device model = Device
@@ -306,6 +308,10 @@ class DeviceSerializer(serializers.ModelSerializer):
'os_name', 'os_version', 'software_brand', 'software_version', 'security_profile' 'os_name', 'os_version', 'software_brand', 'software_version', 'security_profile'
) )
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['security_profile'].choices = [(k, v.verbose_name) for k, v in get_all_security_profiles().items()]
class TeamInviteSerializer(serializers.ModelSerializer): class TeamInviteSerializer(serializers.ModelSerializer):
class Meta: class Meta:
+8 -1
View File
@@ -32,10 +32,17 @@ from pretix.helpers.periodic import minimum_interval
register_webhook_events = Signal() register_webhook_events = Signal()
""" """
This signal is sent out to get all known webhook events. Receivers should return an This signal is sent out to get all known webhook events. Receivers should return an
instance of a subclass of pretix.api.webhooks.WebhookEvent or a list of such instance of a subclass of ``pretix.api.webhooks.WebhookEvent`` or a list of such
instances. instances.
""" """
register_device_security_profile = Signal()
"""
This signal is sent out to get all known device security_profiles. Receivers should
return an instance of a subclass of ``pretix.api.auth.devicesecurity.BaseSecurityProfile``
or a list of such instances.
"""
@receiver(periodic_task) @receiver(periodic_task)
@scopes_disabled() @scopes_disabled()
+3 -1
View File
@@ -116,7 +116,7 @@ class CheckinListViewSet(viewsets.ModelViewSet):
if 'subevent' in self.request.query_params.getlist('expand'): if 'subevent' in self.request.query_params.getlist('expand'):
qs = qs.prefetch_related( qs = qs.prefetch_related(
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set', 'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
'subevent__seat_category_mappings', 'subevent__meta_values', 'auto_checkin_sales_channels' 'subevent__seat_category_mappings', 'subevent__meta_values',
) )
return qs return qs
@@ -143,7 +143,9 @@ class CheckinListViewSet(viewsets.ModelViewSet):
data=self.request.data data=self.request.data
) )
@transaction.atomic
def perform_destroy(self, instance): def perform_destroy(self, instance):
instance.checkins.all().delete()
instance.log_action( instance.log_action(
'pretix.event.checkinlist.deleted', 'pretix.event.checkinlist.deleted',
user=self.request.user, user=self.request.user,
+1 -1
View File
@@ -215,7 +215,7 @@ class OrderViewSetMixin:
queryset = Order.objects.none() queryset = Order.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('datetime',) ordering = ('datetime',)
ordering_fields = ('datetime', 'code', 'status', 'last_modified') ordering_fields = ('datetime', 'code', 'status', 'last_modified', 'cancellation_date')
filterset_class = OrderFilter filterset_class = OrderFilter
lookup_field = 'code' lookup_field = 'code'
+1 -1
View File
@@ -152,7 +152,7 @@ class NativeAuthBackend(BaseAuthBackend):
to log in. to log in.
""" """
d = OrderedDict([ d = OrderedDict([
('email', forms.EmailField(label=_("E-mail"), max_length=254, ('email', forms.EmailField(label=_("Email"), max_length=254,
widget=forms.EmailInput(attrs={'autofocus': 'autofocus'}))), widget=forms.EmailInput(attrs={'autofocus': 'autofocus'}))),
('password', forms.CharField(label=_("Password"), widget=forms.PasswordInput, ('password', forms.CharField(label=_("Password"), widget=forms.PasswordInput,
max_length=4096)), max_length=4096)),
+1 -1
View File
@@ -68,7 +68,7 @@ def test_custom_smtp_backend(backend: T, from_addr: str) -> None:
class BaseHTMLMailRenderer: class BaseHTMLMailRenderer:
""" """
This is the base class for all HTML e-mail renderers. This is the base class for all HTML email renderers.
""" """
def __init__(self, event: Event, organizer=None): def __init__(self, event: Event, organizer=None):
+1 -1
View File
@@ -64,7 +64,7 @@ class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
_('Customer ID'), _('Customer ID'),
_('SSO provider'), _('SSO provider'),
_('External identifier'), _('External identifier'),
_('E-mail'), _('Email'),
_('Phone number'), _('Phone number'),
_('Full name'), _('Full name'),
] ]
+2 -2
View File
@@ -199,7 +199,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
_('Invoice number'), _('Invoice number'),
_('Date'), _('Date'),
_('Order code'), _('Order code'),
_('E-mail address'), _('Email address'),
_('Invoice type'), _('Invoice type'),
_('Cancellation of'), _('Cancellation of'),
_('Language'), _('Language'),
@@ -326,7 +326,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
_('Event start date'), _('Event start date'),
_('Date'), _('Date'),
_('Order code'), _('Order code'),
_('E-mail address'), _('Email address'),
_('Invoice type'), _('Invoice type'),
_('Cancellation of'), _('Cancellation of'),
_('Invoice sender:') + ' ' + _('Name'), _('Invoice sender:') + ' ' + _('Name'),
+2 -2
View File
@@ -284,7 +284,7 @@ class OrderListExporter(MultiSheetListExporter):
headers.append(_('Comment')) headers.append(_('Comment'))
headers.append(_('Follow-up date')) headers.append(_('Follow-up date'))
headers.append(_('Positions')) headers.append(_('Positions'))
headers.append(_('E-mail address verified')) headers.append(_('Email address verified'))
headers.append(_('External customer ID')) headers.append(_('External customer ID'))
headers.append(_('Payment providers')) headers.append(_('Payment providers'))
if form_data.get('include_payment_amounts'): if form_data.get('include_payment_amounts'):
@@ -655,7 +655,7 @@ class OrderListExporter(MultiSheetListExporter):
headers += [ headers += [
_('Sales channel'), _('Sales channel'),
_('Order locale'), _('Order locale'),
_('E-mail address verified'), _('Email address verified'),
_('External customer ID'), _('External customer ID'),
_('Check-in lists'), _('Check-in lists'),
_('Payment providers'), _('Payment providers'),
+1 -1
View File
@@ -254,7 +254,7 @@ class PasswordRecoverForm(forms.Form):
class PasswordForgotForm(forms.Form): class PasswordForgotForm(forms.Form):
email = forms.EmailField( email = forms.EmailField(
label=_('E-mail'), label=_('Email'),
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
+20 -10
View File
@@ -54,6 +54,7 @@ from django.core.validators import (
from django.db.models import QuerySet from django.db.models import QuerySet
from django.forms import Select, widgets from django.forms import Select, widgets
from django.forms.widgets import FILE_INPUT_CONTRADICTION from django.forms.widgets import FILE_INPUT_CONTRADICTION
from django.urls import reverse
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.html import escape from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@@ -77,7 +78,7 @@ from pretix.base.i18n import (
get_babel_locale, get_language_without_region, language, get_babel_locale, get_language_without_region, language,
) )
from pretix.base.models import InvoiceAddress, Item, Question, QuestionOption from pretix.base.models import InvoiceAddress, Item, Question, QuestionOption
from pretix.base.models.tax import VAT_ID_COUNTRIES, ask_for_vat_id from pretix.base.models.tax import ask_for_vat_id
from pretix.base.services.tax import ( from pretix.base.services.tax import (
VATIDFinalError, VATIDTemporaryError, validate_vat_id, VATIDFinalError, VATIDTemporaryError, validate_vat_id,
) )
@@ -602,6 +603,7 @@ class BaseQuestionsForm(forms.Form):
questions = pos.item.questions_to_ask questions = pos.item.questions_to_ask
event = kwargs.pop('event') event = kwargs.pop('event')
self.all_optional = kwargs.pop('all_optional', False) self.all_optional = kwargs.pop('all_optional', False)
self.attendee_addresses_required = event.settings.attendee_addresses_required and not self.all_optional
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -676,7 +678,7 @@ class BaseQuestionsForm(forms.Form):
if item.ask_attendee_data and event.settings.attendee_addresses_asked: if item.ask_attendee_data and event.settings.attendee_addresses_asked:
add_fields['street'] = forms.CharField( add_fields['street'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional, required=self.attendee_addresses_required,
label=_('Address'), label=_('Address'),
widget=forms.Textarea(attrs={ widget=forms.Textarea(attrs={
'rows': 2, 'rows': 2,
@@ -686,7 +688,7 @@ class BaseQuestionsForm(forms.Form):
initial=(cartpos.street if cartpos else orderpos.street), initial=(cartpos.street if cartpos else orderpos.street),
) )
add_fields['zipcode'] = forms.CharField( add_fields['zipcode'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional, required=False,
max_length=30, max_length=30,
label=_('ZIP code'), label=_('ZIP code'),
initial=(cartpos.zipcode if cartpos else orderpos.zipcode), initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
@@ -695,7 +697,7 @@ class BaseQuestionsForm(forms.Form):
}), }),
) )
add_fields['city'] = forms.CharField( add_fields['city'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional, required=False,
label=_('City'), label=_('City'),
max_length=255, max_length=255,
initial=(cartpos.city if cartpos else orderpos.city), initial=(cartpos.city if cartpos else orderpos.city),
@@ -707,11 +709,12 @@ class BaseQuestionsForm(forms.Form):
add_fields['country'] = CountryField( add_fields['country'] = CountryField(
countries=CachedCountries countries=CachedCountries
).formfield( ).formfield(
required=event.settings.attendee_addresses_required and not self.all_optional, required=self.attendee_addresses_required,
label=_('Country'), label=_('Country'),
initial=country, initial=country,
widget=forms.Select(attrs={ widget=forms.Select(attrs={
'autocomplete': 'country', 'autocomplete': 'country',
'data-country-information-url': reverse('js_helpers.states'),
}), }),
) )
c = [('', pgettext_lazy('address', 'Select state'))] c = [('', pgettext_lazy('address', 'Select state'))]
@@ -946,9 +949,9 @@ class BaseQuestionsForm(forms.Form):
d = super().clean() d = super().clean()
if self.address_validation: if self.address_validation:
self.cleaned_data = d = validate_address(d, True) self.cleaned_data = d = validate_address(d, all_optional=not self.attendee_addresses_required)
if d.get('city') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS: if d.get('street') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
if not d.get('state'): if not d.get('state'):
self.add_error('state', _('This field is required.')) self.add_error('state', _('This field is required.'))
@@ -1005,7 +1008,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
'street': forms.Textarea(attrs={ 'street': forms.Textarea(attrs={
'rows': 2, 'rows': 2,
'placeholder': _('Street and Number'), 'placeholder': _('Street and Number'),
'autocomplete': 'street-address' 'autocomplete': 'street-address',
}), }),
'beneficiary': forms.Textarea(attrs={'rows': 3}), 'beneficiary': forms.Textarea(attrs={'rows': 3}),
'country': forms.Select(attrs={ 'country': forms.Select(attrs={
@@ -1021,7 +1024,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
'data-display-dependency': '#id_is_business_1', 'data-display-dependency': '#id_is_business_1',
'autocomplete': 'organization', 'autocomplete': 'organization',
}), }),
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-with-vat-id': ','.join(VAT_ID_COUNTRIES)}), 'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
'internal_reference': forms.TextInput, 'internal_reference': forms.TextInput,
} }
labels = { labels = {
@@ -1055,6 +1058,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
]) ])
self.fields['country'].choices = CachedCountries() self.fields['country'].choices = CachedCountries()
self.fields['country'].widget.attrs['data-country-information-url'] = reverse('js_helpers.states')
c = [('', pgettext_lazy('address', 'Select state'))] c = [('', pgettext_lazy('address', 'Select state'))]
fprefix = self.prefix + '-' if self.prefix else '' fprefix = self.prefix + '-' if self.prefix else ''
@@ -1083,6 +1087,10 @@ class BaseInvoiceAddressForm(forms.ModelForm):
) )
self.fields['state'].widget.is_required = True self.fields['state'].widget.is_required = True
self.fields['street'].required = False
self.fields['zipcode'].required = False
self.fields['city'].required = False
# Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected. # Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected.
if cc and not ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data: if cc and not ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data:
self.data = self.data.copy() self.data = self.data.copy()
@@ -1142,9 +1150,11 @@ class BaseInvoiceAddressForm(forms.ModelForm):
data['vat_id'] = '' data['vat_id'] = ''
if self.event.settings.invoice_address_required: if self.event.settings.invoice_address_required:
if data.get('is_business') and not data.get('company'): if data.get('is_business') and not data.get('company'):
raise ValidationError(_('You need to provide a company name.')) raise ValidationError({"company": _('You need to provide a company name.')})
if not data.get('is_business') and not data.get('name_parts'): if not data.get('is_business') and not data.get('name_parts'):
raise ValidationError(_('You need to provide your name.')) raise ValidationError(_('You need to provide your name.'))
if not data.get('street') and not data.get('zipcode') and not data.get('city'):
raise ValidationError({"street": _('This field is required.')})
if 'vat_id' in self.changed_data or not data.get('vat_id'): if 'vat_id' in self.changed_data or not data.get('vat_id'):
self.instance.vat_id_validated = False self.instance.vat_id_validated = False
+3 -3
View File
@@ -48,10 +48,10 @@ from pretix.control.forms import SingleLanguageWidget
class UserSettingsForm(forms.ModelForm): class UserSettingsForm(forms.ModelForm):
error_messages = { error_messages = {
'duplicate_identifier': _("There already is an account associated with this e-mail address. " 'duplicate_identifier': _("There already is an account associated with this email address. "
"Please choose a different one."), "Please choose a different one."),
'pw_current': _("Please enter your current password if you want to change your e-mail " 'pw_current': _("Please enter your current password if you want to change your email address "
"address or password."), "or password."),
'pw_current_wrong': _("The current password you entered was not correct."), 'pw_current_wrong': _("The current password you entered was not correct."),
'pw_mismatch': _("Please enter the same password twice"), 'pw_mismatch': _("Please enter the same password twice"),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."), 'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
+2 -2
View File
@@ -289,7 +289,7 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
def _clean_text(self, text, tags=None): def _clean_text(self, text, tags=None):
return self._normalize(bleach.clean( return self._normalize(bleach.clean(
text, text,
tags=tags or [] tags=set(tags) if tags else set()
).strip().replace('<br>', '<br />').replace('\n', '<br />\n')) ).strip().replace('<br>', '<br />').replace('\n', '<br />\n'))
@@ -461,7 +461,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
def _draw_event(self, canvas): def _draw_event(self, canvas):
def shorten(txt): def shorten(txt):
txt = str(txt) txt = str(txt)
txt = bleach.clean(txt, tags=[]).strip() txt = bleach.clean(txt, tags=set()).strip()
p = Paragraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal']) p = Paragraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
p_size = p.wrap(self.event_width, self.event_height) p_size = p.wrap(self.event_width, self.event_height)
+13
View File
@@ -37,6 +37,16 @@ class BaseMediaType:
def verbose_name(self): def verbose_name(self):
raise NotImplementedError() raise NotImplementedError()
@property
def icon(self):
"""
This can be:
- The name of a Font Awesome icon to represent this channel type.
- The name of a SVG icon file that is resolvable through the static file system. We recommend to design for a size of 18x14 pixels.
"""
return "circle"
def generate_identifier(self, organizer): def generate_identifier(self, organizer):
if self.medium_created_by_server: if self.medium_created_by_server:
raise NotImplementedError() raise NotImplementedError()
@@ -59,6 +69,7 @@ class BaseMediaType:
class BarcodePlainMediaType(BaseMediaType): class BarcodePlainMediaType(BaseMediaType):
identifier = 'barcode' identifier = 'barcode'
verbose_name = _('Barcode / QR-Code') verbose_name = _('Barcode / QR-Code')
icon = 'qrcode'
medium_created_by_server = True medium_created_by_server = True
supports_giftcard = False supports_giftcard = False
supports_orderposition = True supports_orderposition = True
@@ -75,6 +86,7 @@ class BarcodePlainMediaType(BaseMediaType):
class NfcUidMediaType(BaseMediaType): class NfcUidMediaType(BaseMediaType):
identifier = 'nfc_uid' identifier = 'nfc_uid'
verbose_name = _('NFC UID-based') verbose_name = _('NFC UID-based')
icon = 'pretixbase/img/media/nfc_uid.svg'
medium_created_by_server = False medium_created_by_server = False
supports_giftcard = True supports_giftcard = True
supports_orderposition = False supports_orderposition = False
@@ -114,6 +126,7 @@ class NfcUidMediaType(BaseMediaType):
class NfcMf0aesMediaType(BaseMediaType): class NfcMf0aesMediaType(BaseMediaType):
identifier = 'nfc_mf0aes' identifier = 'nfc_mf0aes'
verbose_name = 'NFC Mifare Ultralight AES' verbose_name = 'NFC Mifare Ultralight AES'
icon = 'pretixbase/img/media/nfc_secure.svg'
medium_created_by_server = False medium_created_by_server = False
supports_giftcard = True supports_giftcard = True
supports_orderposition = False supports_orderposition = False
+1 -1
View File
@@ -29,7 +29,7 @@ class Migration(migrations.Migration):
('password', models.CharField(verbose_name='password', max_length=128)), ('password', models.CharField(verbose_name='password', max_length=128)),
('last_login', models.DateTimeField(verbose_name='last login', blank=True, null=True)), ('last_login', models.DateTimeField(verbose_name='last login', blank=True, null=True)),
('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')), ('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')),
('email', models.EmailField(max_length=191, blank=True, unique=True, verbose_name='E-mail', null=True, ('email', models.EmailField(max_length=191, blank=True, unique=True, verbose_name='Email', null=True,
db_index=True)), db_index=True)),
('givenname', models.CharField(verbose_name='Given name', max_length=255, blank=True, null=True)), ('givenname', models.CharField(verbose_name='Given name', max_length=255, blank=True, null=True)),
('familyname', models.CharField(verbose_name='Family name', max_length=255, blank=True, null=True)), ('familyname', models.CharField(verbose_name='Family name', max_length=255, blank=True, null=True)),
@@ -48,7 +48,7 @@ class Migration(migrations.Migration):
('password', models.CharField(max_length=128, verbose_name='password')), ('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, unique=True, verbose_name='E-mail')), ('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, unique=True, verbose_name='Email')),
('givenname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Given name')), ('givenname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Given name')),
('familyname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Family name')), ('familyname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Family name')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')), ('is_active', models.BooleanField(default=True, verbose_name='Is active')),
@@ -232,7 +232,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=16, verbose_name='Order code')), ('code', models.CharField(max_length=16, verbose_name='Order code')),
('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')), ('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-mail')), ('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')), ('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')),
('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)), ('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)),
('datetime', models.DateTimeField(verbose_name='Date')), ('datetime', models.DateTimeField(verbose_name='Date')),
@@ -187,7 +187,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=16, verbose_name='Order code')), ('code', models.CharField(max_length=16, verbose_name='Order code')),
('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')), ('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-mail')), ('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')), ('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')),
('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)), ('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)),
('datetime', models.DateTimeField(verbose_name='Date')), ('datetime', models.DateTimeField(verbose_name='Date')),
@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')),
('email', models.EmailField(max_length=254, verbose_name='E-mail address')), ('email', models.EmailField(max_length=254, verbose_name='Email address')),
('locale', models.CharField(default='en', max_length=190)), ('locale', models.CharField(default='en', max_length=190)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')), ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')),
('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')), ('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')),
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')),
('email', models.EmailField(max_length=254, verbose_name='E-mail address')), ('email', models.EmailField(max_length=254, verbose_name='Email address')),
('locale', models.CharField(default='en', max_length=190)), ('locale', models.CharField(default='en', max_length=190)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')), ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')),
('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')), ('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')),
@@ -163,7 +163,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('action_type', models.CharField(max_length=255)), ('action_type', models.CharField(max_length=255)),
('method', models.CharField(choices=[('mail', 'E-mail')], max_length=255)), ('method', models.CharField(choices=[('mail', 'Email')], max_length=255)),
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, ('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
to='pretixbase.Event')), to='pretixbase.Event')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
@@ -21,7 +21,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('action_type', models.CharField(max_length=255)), ('action_type', models.CharField(max_length=255)),
('method', models.CharField(choices=[('mail', 'E-mail')], max_length=255)), ('method', models.CharField(choices=[('mail', 'Email')], max_length=255)),
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event')), ('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('enabled', models.BooleanField(default=True)), ('enabled', models.BooleanField(default=True)),
@@ -0,0 +1,48 @@
# Generated by Django 4.2.16 on 2024-10-29 15:03
from django.db import migrations
def migrate_autocheckin(apps, schema_editor):
CheckinList = apps.get_model("pretixbase", "CheckinList")
AutoCheckinRule = apps.get_model("autocheckin", "AutoCheckinRule")
for cl in CheckinList.objects.filter(auto_checkin_sales_channels__isnull=False).select_related("event", "event__organizer"):
sales_channels = cl.auto_checkin_sales_channels.all()
all_sales_channels = cl.event.organizer.sales_channels.all()
if "pretix.plugins.autocheckin" not in cl.event.plugins:
cl.event.plugins = cl.event.plugins + ",pretix.plugins.autocheckin"
cl.event.save()
r = AutoCheckinRule.objects.get_or_create(
list=cl,
event=cl.event,
all_products=True,
all_payment_methods=True,
defaults=dict(
mode="placed",
all_sales_channels=len(sales_channels) == len(all_sales_channels),
)
)[0]
if len(sales_channels) != len(all_sales_channels):
r.limit_sales_channels.set(sales_channels)
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0272_printlog"),
("autocheckin", "0001_initial"),
]
operations = [
migrations.RunPython(
migrate_autocheckin,
migrations.RunPython.noop,
),
migrations.RemoveField(
model_name="checkinlist",
name="auto_checkin_sales_channels",
),
]
+3
View File
@@ -256,6 +256,9 @@ class SubeventColumnMixin:
] ]
def clean(self, value, previous_values): def clean(self, value, previous_values):
if not value:
return None
if value in self._subevent_cache: if value in self._subevent_cache:
return self._subevent_cache[value] return self._subevent_cache[value]
+2 -2
View File
@@ -56,7 +56,7 @@ from pretix.base.signals import order_import_columns
class EmailColumn(ImportColumn): class EmailColumn(ImportColumn):
identifier = 'email' identifier = 'email'
verbose_name = gettext_lazy('E-mail address') verbose_name = gettext_lazy('Email address')
def clean(self, value, previous_values): def clean(self, value, previous_values):
if value: if value:
@@ -322,7 +322,7 @@ class AttendeeNamePart(ImportColumn):
class AttendeeEmail(ImportColumn): class AttendeeEmail(ImportColumn):
identifier = 'attendee_email' identifier = 'attendee_email'
verbose_name = gettext_lazy('Attendee e-mail address') verbose_name = gettext_lazy('Attendee email address')
def clean(self, value, previous_values): def clean(self, value, previous_values):
if value: if value:
+1 -1
View File
@@ -241,7 +241,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
REQUIRED_FIELDS = [] REQUIRED_FIELDS = []
email = models.EmailField(unique=True, db_index=True, null=True, blank=True, email = models.EmailField(unique=True, db_index=True, null=True, blank=True,
verbose_name=_('E-mail'), max_length=190) verbose_name=_('Email'), max_length=190)
fullname = models.CharField(max_length=255, blank=True, null=True, fullname = models.CharField(max_length=255, blank=True, null=True,
verbose_name=_('Full name')) verbose_name=_('Full name'))
is_active = models.BooleanField(default=True, is_active = models.BooleanField(default=True,
+7 -11
View File
@@ -99,14 +99,6 @@ class CheckinList(LoggedModel):
verbose_name=_('Automatically check out everyone at'), verbose_name=_('Automatically check out everyone at'),
null=True, blank=True null=True, blank=True
) )
auto_checkin_sales_channels = models.ManyToManyField(
"SalesChannel",
verbose_name=_('Sales channels to automatically check in'),
help_text=_('This option is deprecated and will be removed in the next months. As a replacement, our new plugin '
'"Auto check-in" can be used. When we remove this option, we will automatically migrate your event '
'to use the new plugin.'),
blank=True,
)
rules = models.JSONField(default=dict, blank=True) rules = models.JSONField(default=dict, blank=True)
objects = ScopedManager(organizer='event__organizer') objects = ScopedManager(organizer='event__organizer')
@@ -141,7 +133,7 @@ class CheckinList(LoggedModel):
return self.positions_query(ignore_status=False) return self.positions_query(ignore_status=False)
@scopes_disabled() @scopes_disabled()
def positions_inside_query(self, ignore_status=False, at_time=None): def _filter_positions_inside(self, qs, at_time=None):
if at_time is None: if at_time is None:
c_q = [] c_q = []
else: else:
@@ -149,7 +141,7 @@ class CheckinList(LoggedModel):
if "postgresql" not in settings.DATABASES["default"]["ENGINE"]: if "postgresql" not in settings.DATABASES["default"]["ENGINE"]:
# Use a simple approach that works on all databases # Use a simple approach that works on all databases
qs = self.positions_query(ignore_status=ignore_status).annotate( qs = qs.annotate(
last_entry=Subquery( last_entry=Subquery(
Checkin.objects.filter( Checkin.objects.filter(
*c_q, *c_q,
@@ -202,7 +194,7 @@ class CheckinList(LoggedModel):
.values("position_id", "type", "datetime", "cnt_exists_after") .values("position_id", "type", "datetime", "cnt_exists_after")
.query.sql_with_params() .query.sql_with_params()
) )
return self.positions_query(ignore_status=ignore_status).filter( return qs.filter(
pk__in=RawSQL( pk__in=RawSQL(
f""" f"""
SELECT "position_id" SELECT "position_id"
@@ -214,6 +206,10 @@ class CheckinList(LoggedModel):
) )
) )
@scopes_disabled()
def positions_inside_query(self, ignore_status=False, at_time=None):
return self._filter_positions_inside(self.positions_query(ignore_status=ignore_status), at_time=at_time)
@property @property
def positions_inside(self): def positions_inside(self):
return self.positions_inside_query(None) return self.positions_inside_query(None)
+2 -2
View File
@@ -91,7 +91,7 @@ class Customer(LoggedModel):
), ),
], ],
) )
email = models.EmailField(db_index=True, null=True, blank=False, verbose_name=_('E-mail'), max_length=190) email = models.EmailField(db_index=True, null=True, blank=False, verbose_name=_('Email'), max_length=190)
phone = PhoneNumberField(null=True, blank=True, verbose_name=_('Phone number')) phone = PhoneNumberField(null=True, blank=True, verbose_name=_('Phone number'))
password = models.CharField(verbose_name=_('Password'), max_length=128) password = models.CharField(verbose_name=_('Password'), max_length=128)
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True) name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
@@ -392,7 +392,7 @@ class CustomerSSOClient(LoggedModel):
SCOPE_CHOICES = ( SCOPE_CHOICES = (
('openid', _('OpenID Connect access (required)')), ('openid', _('OpenID Connect access (required)')),
('profile', _('Profile data (name, addresses)')), ('profile', _('Profile data (name, addresses)')),
('email', _('E-mail address')), ('email', _('Email address')),
('phone', _('Phone number')), ('phone', _('Phone number')),
) )
-2
View File
@@ -28,7 +28,6 @@ from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled from django_scopes import ScopedManager, scopes_disabled
from pretix.api.auth.devicesecurity import DEVICE_SECURITY_PROFILES
from pretix.base.models import LoggedModel from pretix.base.models import LoggedModel
@@ -161,7 +160,6 @@ class Device(LoggedModel):
) )
security_profile = models.CharField( security_profile = models.CharField(
max_length=190, max_length=190,
choices=[(k, v.verbose_name) for k, v in DEVICE_SECURITY_PROFILES.items()],
default='full', default='full',
null=True, null=True,
blank=False blank=False
+1 -4
View File
@@ -1024,10 +1024,9 @@ class Event(EventMixin, LoggedModel):
checkin_list_map = {} checkin_list_map = {}
for cl in other.checkin_lists.filter(subevent__isnull=True).prefetch_related( for cl in other.checkin_lists.filter(subevent__isnull=True).prefetch_related(
'limit_products', 'auto_checkin_sales_channels' 'limit_products'
): ):
items = list(cl.limit_products.all()) items = list(cl.limit_products.all())
auto_checkin_sales_channels = list(cl.auto_checkin_sales_channels.all())
checkin_list_map[cl.pk] = cl checkin_list_map[cl.pk] = cl
cl.pk = None cl.pk = None
cl._prefetched_objects_cache = {} cl._prefetched_objects_cache = {}
@@ -1039,8 +1038,6 @@ class Event(EventMixin, LoggedModel):
cl.log_action('pretix.object.cloned') cl.log_action('pretix.object.cloned')
for i in items: for i in items:
cl.limit_products.add(item_map[i.pk]) cl.limit_products.add(item_map[i.pk])
if auto_checkin_sales_channels:
cl.auto_checkin_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in auto_checkin_sales_channels]))
if other.seating_plan: if other.seating_plan:
if other.seating_plan.organizer_id == self.organizer_id: if other.seating_plan.organizer_id == self.organizer_id:
+1 -1
View File
@@ -43,7 +43,7 @@ class NotificationSetting(models.Model):
:type enabled: bool :type enabled: bool
""" """
CHANNELS = ( CHANNELS = (
('mail', _('E-mail')), ('mail', _('Email')),
) )
user = models.ForeignKey('User', on_delete=models.CASCADE, user = models.ForeignKey('User', on_delete=models.CASCADE,
related_name='notification_settings') related_name='notification_settings')
+5 -5
View File
@@ -242,7 +242,7 @@ class Order(LockModel, LoggedModel):
) )
email = models.EmailField( email = models.EmailField(
null=True, blank=True, null=True, blank=True,
verbose_name=_('E-mail') verbose_name=_('Email')
) )
phone = PhoneNumberField( phone = PhoneNumberField(
null=True, blank=True, null=True, blank=True,
@@ -317,7 +317,7 @@ class Order(LockModel, LoggedModel):
) )
email_known_to_work = models.BooleanField( email_known_to_work = models.BooleanField(
default=False, default=False,
verbose_name=_('E-mail address verified') verbose_name=_('Email address verified')
) )
invoice_dirty = models.BooleanField( invoice_dirty = models.BooleanField(
# Invoice needs to be re-issued when the order is paid again # Invoice needs to be re-issued when the order is paid again
@@ -3204,9 +3204,9 @@ class InvoiceAddress(models.Model):
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name')) company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'))
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True) name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
name_parts = models.JSONField(default=dict) name_parts = models.JSONField(default=dict)
street = models.TextField(verbose_name=_('Address'), blank=False) street = models.TextField(verbose_name=_('Address'), blank=True)
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False) zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=True)
city = models.CharField(max_length=255, verbose_name=_('City'), blank=False) city = models.CharField(max_length=255, verbose_name=_('City'), blank=True)
country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False) country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False)
country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'), country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
countries=CachedCountries) countries=CachedCountries)
+1 -1
View File
@@ -73,7 +73,7 @@ class WaitingListEntry(LoggedModel):
blank=True, default=dict blank=True, default=dict
) )
email = models.EmailField( email = models.EmailField(
verbose_name=_("E-mail address") verbose_name=_("Email address")
) )
phone = PhoneNumberField( phone = PhoneNumberField(
null=True, blank=True, null=True, blank=True,
+3
View File
@@ -343,11 +343,13 @@ class CartManager:
err = error_messages['some_subevent_not_started'] err = error_messages['some_subevent_not_started']
cp.addons.all().delete() cp.addons.all().delete()
cp.delete() cp.delete()
continue
if cp.subevent and cp.subevent.presale_end and time_machine_now(self.real_now_dt) > cp.subevent.presale_end: if cp.subevent and cp.subevent.presale_end and time_machine_now(self.real_now_dt) > cp.subevent.presale_end:
err = error_messages['some_subevent_ended'] err = error_messages['some_subevent_ended']
cp.addons.all().delete() cp.addons.all().delete()
cp.delete() cp.delete()
continue
if cp.subevent: if cp.subevent:
tlv = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper) tlv = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
@@ -360,6 +362,7 @@ class CartManager:
err = error_messages['some_subevent_ended'] err = error_messages['some_subevent_ended']
cp.addons.all().delete() cp.addons.all().delete()
cp.delete() cp.delete()
continue
return err return err
def _update_subevents_cache(self, se_ids: List[int]): def _update_subevents_cache(self, se_ids: List[int]):
+1 -18
View File
@@ -57,7 +57,7 @@ from pretix.base.models import (
Checkin, CheckinList, Device, Event, Gate, Item, ItemVariation, Order, Checkin, CheckinList, Device, Event, Gate, Item, ItemVariation, Order,
OrderPosition, QuestionOption, OrderPosition, QuestionOption,
) )
from pretix.base.signals import checkin_created, order_placed, periodic_task from pretix.base.signals import checkin_created, periodic_task
from pretix.helpers import OF_SELF from pretix.helpers import OF_SELF
from pretix.helpers.jsonlogic import Logic from pretix.helpers.jsonlogic import Logic
from pretix.helpers.jsonlogic_boolalg import convert_to_dnf from pretix.helpers.jsonlogic_boolalg import convert_to_dnf
@@ -1154,23 +1154,6 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
) )
@receiver(order_placed, dispatch_uid="legacy_autocheckin_order_placed")
def order_placed(sender, **kwargs):
order = kwargs['order']
event = sender
cls = list(event.checkin_lists.filter(auto_checkin_sales_channels=order.sales_channel).prefetch_related(
'limit_products'))
if not cls:
return
for op in order.positions.all():
for cl in cls:
if cl.all_products or op.item_id in {i.pk for i in cl.limit_products.all()}:
if not cl.subevent_id or cl.subevent_id == op.subevent_id:
ci = Checkin.objects.create(position=op, list=cl, auto_checked_in=True, type=Checkin.TYPE_ENTRY)
checkin_created.send(event, checkin=ci)
@receiver(periodic_task, dispatch_uid="autocheckout_exit_all") @receiver(periodic_task, dispatch_uid="autocheckout_exit_all")
@scopes_disabled() @scopes_disabled()
def process_exit_all(sender, **kwargs): def process_exit_all(sender, **kwargs):
+1 -1
View File
@@ -315,7 +315,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
body_html = renderer.render(content_plain, signature, raw_subject, order, position) body_html = renderer.render(content_plain, signature, raw_subject, order, position)
else: else:
# Backwards compatibility # Backwards compatibility
warnings.warn('E-mail renderer called without position argument because position argument is not ' warnings.warn('Email renderer called without position argument because position argument is not '
'supported.', 'supported.',
DeprecationWarning) DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order) body_html = renderer.render(content_plain, signature, raw_subject, order)
+1 -1
View File
@@ -550,7 +550,7 @@ DEFAULTS = {
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'type': bool, 'type': bool,
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Require a business addresses"), label=_("Require a business address"),
help_text=_('This will require users to enter a company name.'), help_text=_('This will require users to enter a company name.'),
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_required'}), widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_required'}),
) )
+2 -2
View File
@@ -287,9 +287,9 @@ class PhoneNumberShredder(BaseDataShredder):
class EmailAddressShredder(BaseDataShredder): class EmailAddressShredder(BaseDataShredder):
verbose_name = _('E-mails') verbose_name = _('Emails')
identifier = 'order_emails' identifier = 'order_emails'
description = _('This will remove all e-mail addresses from orders and attendees, as well as logged email ' description = _('This will remove all email addresses from orders and attendees, as well as logged email '
'contents. This will also remove the association to customer accounts.') 'contents. This will also remove the association to customer accounts.')
def generate_files(self) -> List[Tuple[str, str, str]]: def generate_files(self) -> List[Tuple[str, str, str]]:
+8 -8
View File
@@ -54,7 +54,7 @@ from tlds import tld_set
register = template.Library() register = template.Library()
ALLOWED_TAGS_SNIPPET = [ ALLOWED_TAGS_SNIPPET = {
'a', 'a',
'abbr', 'abbr',
'acronym', 'acronym',
@@ -68,8 +68,8 @@ ALLOWED_TAGS_SNIPPET = [
'strike', 'strike',
's', 's',
# Update doc/user/markdown.rst if you change this! # Update doc/user/markdown.rst if you change this!
] }
ALLOWED_TAGS = ALLOWED_TAGS_SNIPPET + [ ALLOWED_TAGS = ALLOWED_TAGS_SNIPPET | {
'blockquote', 'blockquote',
'li', 'li',
'ol', 'ol',
@@ -91,7 +91,7 @@ ALLOWED_TAGS = ALLOWED_TAGS_SNIPPET + [
'h6', 'h6',
'pre', 'pre',
# Update doc/user/markdown.rst if you change this! # Update doc/user/markdown.rst if you change this!
] }
ALLOWED_ATTRIBUTES = { ALLOWED_ATTRIBUTES = {
'a': ['href', 'title', 'class'], 'a': ['href', 'title', 'class'],
@@ -106,7 +106,7 @@ ALLOWED_ATTRIBUTES = {
# Update doc/user/markdown.rst if you change this! # Update doc/user/markdown.rst if you change this!
} }
ALLOWED_PROTOCOLS = ['http', 'https', 'mailto', 'tel'] ALLOWED_PROTOCOLS = {'http', 'https', 'mailto', 'tel'}
URL_RE = SimpleLazyObject(lambda: build_url_re(tlds=sorted(tld_set, key=len, reverse=True))) URL_RE = SimpleLazyObject(lambda: build_url_re(tlds=sorted(tld_set, key=len, reverse=True)))
@@ -211,9 +211,9 @@ class CleanPostprocessor(Postprocessor):
def run(self, text): def run(self, text):
return bleach.clean( return bleach.clean(
text, text,
tags=self.tags, tags=set(self.tags),
attributes=self.attributes, attributes=self.attributes,
protocols=self.protocols, protocols=set(self.protocols),
strip=self.strip strip=self.strip
) )
@@ -308,7 +308,7 @@ def markdown_compile_email(source, allowed_tags=ALLOWED_TAGS, allowed_attributes
EmailNl2BrExtension(), EmailNl2BrExtension(),
LinkifyAndCleanExtension( LinkifyAndCleanExtension(
linker, linker,
tags=allowed_tags, tags=set(allowed_tags),
attributes=allowed_attributes, attributes=allowed_attributes,
protocols=ALLOWED_PROTOCOLS, protocols=ALLOWED_PROTOCOLS,
strip=False, strip=False,
+19 -5
View File
@@ -22,16 +22,30 @@
import pycountry import pycountry
from django.http import JsonResponse from django.http import JsonResponse
from pretix.base.addressvalidation import (
COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED,
)
from pretix.base.models.tax import VAT_ID_COUNTRIES
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
def states(request): def states(request):
cc = request.GET.get("country", "DE") cc = request.GET.get("country", "DE")
info = {
'street': {'required': True},
'zipcode': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED},
'city': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED},
'state': {'visible': cc in COUNTRIES_WITH_STATE_IN_ADDRESS, 'required': cc in COUNTRIES_WITH_STATE_IN_ADDRESS},
'vat_id': {'visible': cc in VAT_ID_COUNTRIES, 'required': False},
}
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS: if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
return JsonResponse({'data': []}) return JsonResponse({'data': [], **info, })
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc] types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types] statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
return JsonResponse({'data': [ return JsonResponse({
{'name': s.name, 'code': s.code[3:]} 'data': [
for s in sorted(statelist, key=lambda s: s.name) {'name': s.name, 'code': s.code[3:]}
]}) for s in sorted(statelist, key=lambda s: s.name)
],
**info,
})
+1 -9
View File
@@ -33,9 +33,7 @@ from django_scopes.forms import (
from pretix.base.forms.widgets import SplitDateTimePickerWidget from pretix.base.forms.widgets import SplitDateTimePickerWidget
from pretix.base.models import Gate from pretix.base.models import Gate
from pretix.base.models.checkin import Checkin, CheckinList from pretix.base.models.checkin import Checkin, CheckinList
from pretix.control.forms import ( from pretix.control.forms import ItemMultipleChoiceField
ItemMultipleChoiceField, SalesChannelCheckboxSelectMultiple,
)
from pretix.control.forms.widgets import Select2 from pretix.control.forms.widgets import Select2
@@ -67,10 +65,6 @@ class CheckinListForm(forms.ModelForm):
kwargs.pop('locales', None) kwargs.pop('locales', None)
super().__init__(**kwargs) super().__init__(**kwargs)
self.fields['limit_products'].queryset = self.event.items.all() self.fields['limit_products'].queryset = self.event.items.all()
self.fields['auto_checkin_sales_channels'].queryset = self.event.organizer.sales_channels.all()
self.fields['auto_checkin_sales_channels'].widget = SalesChannelCheckboxSelectMultiple(
self.event, choices=self.fields['auto_checkin_sales_channels'].widget.choices
)
if not self.event.organizer.gates.exists(): if not self.event.organizer.gates.exists():
del self.fields['gates'] del self.fields['gates']
@@ -102,7 +96,6 @@ class CheckinListForm(forms.ModelForm):
'limit_products', 'limit_products',
'subevent', 'subevent',
'include_pending', 'include_pending',
'auto_checkin_sales_channels',
'allow_multiple_entries', 'allow_multiple_entries',
'allow_entry_after_exit', 'allow_entry_after_exit',
'rules', 'rules',
@@ -125,7 +118,6 @@ class CheckinListForm(forms.ModelForm):
'limit_products': ItemMultipleChoiceField, 'limit_products': ItemMultipleChoiceField,
'gates': SafeModelMultipleChoiceField, 'gates': SafeModelMultipleChoiceField,
'subevent': SafeModelChoiceField, 'subevent': SafeModelChoiceField,
'auto_checkin_sales_channels': SafeModelMultipleChoiceField,
'exit_all_at': NextTimeField, 'exit_all_at': NextTimeField,
} }
+4 -4
View File
@@ -549,7 +549,7 @@ class EventOrderExpertFilterForm(EventOrderFilterForm):
) )
email = forms.CharField( email = forms.CharField(
required=False, required=False,
label=_('E-mail address') label=_('Email address')
) )
comment = forms.CharField( comment = forms.CharField(
required=False, required=False,
@@ -563,7 +563,7 @@ class EventOrderExpertFilterForm(EventOrderFilterForm):
email_known_to_work = forms.NullBooleanField( email_known_to_work = forms.NullBooleanField(
required=False, required=False,
widget=FilterNullBooleanSelect, widget=FilterNullBooleanSelect,
label=_('E-mail address verified'), label=_('Email address verified'),
) )
total = forms.DecimalField( total = forms.DecimalField(
localize=True, localize=True,
@@ -648,7 +648,7 @@ class EventOrderExpertFilterForm(EventOrderFilterForm):
) )
self.fields['attendee_email'] = forms.CharField( self.fields['attendee_email'] = forms.CharField(
required=False, required=False,
label=_('Attendee e-mail address') label=_('Attendee email address')
) )
self.fields['attendee_address_company'] = forms.CharField( self.fields['attendee_address_company'] = forms.CharField(
required=False, required=False,
@@ -1967,7 +1967,7 @@ class CheckinListAttendeeFilterForm(FilterForm):
if s == '1': if s == '1':
qs = qs.filter(last_entry__isnull=False) qs = qs.filter(last_entry__isnull=False)
elif s == '2': elif s == '2':
qs = qs.filter(pk__in=self.list.positions_inside.values_list('pk')) qs = self.list._filter_positions_inside(qs)
elif s == '3': elif s == '3':
qs = qs.filter(last_entry__isnull=False).filter( qs = qs.filter(last_entry__isnull=False).filter(
Q(last_exit__isnull=False) & Q(last_exit__gte=F('last_entry')) Q(last_exit__isnull=False) & Q(last_exit__gte=F('last_entry'))
+1 -1
View File
@@ -128,7 +128,7 @@ class UpdateSettingsForm(SettingsForm):
) )
update_check_email = forms.EmailField( update_check_email = forms.EmailField(
required=False, required=False,
label=_("E-mail notifications"), label=_("Email notifications"),
help_text=_("We will notify you at this address if we detect that a new update is available. This " help_text=_("We will notify you at this address if we detect that a new update is available. This "
"address will not be transmitted to pretix.eu, the emails will be sent by this server " "address will not be transmitted to pretix.eu, the emails will be sent by this server "
"locally.") "locally.")
+43
View File
@@ -609,6 +609,49 @@ class OrderFeeChangeForm(forms.Form):
change_decimal_field(self.fields['value'], instance.order.event.currency) change_decimal_field(self.fields['value'], instance.order.event.currency)
class OrderFeeAddForm(forms.Form):
fee_type = forms.ChoiceField(choices=OrderFee.FEE_TYPES)
value = forms.DecimalField(
max_digits=13, decimal_places=2,
localize=True,
label=_('Price'),
help_text=_("including all taxes"),
)
tax_rule = forms.ModelChoiceField(
TaxRule.objects.none(),
required=False,
)
description = forms.CharField(required=False)
def __init__(self, *args, **kwargs):
order = kwargs.pop('order')
super().__init__(*args, **kwargs)
self.fields['tax_rule'].queryset = order.event.tax_rules.all()
change_decimal_field(self.fields['value'], order.event.currency)
class OrderFeeAddFormset(forms.BaseFormSet):
def __init__(self, *args, **kwargs):
self.order = kwargs.pop('order', None)
super().__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['order'] = self.order
return super()._construct_form(i, **kwargs)
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
use_required_attribute=False,
order=self.order,
)
self.add_fields(form, None)
return form
class OrderContactForm(forms.ModelForm): class OrderContactForm(forms.ModelForm):
regenerate_secrets = forms.BooleanField(required=False, label=_('Invalidate secrets'), regenerate_secrets = forms.BooleanField(required=False, label=_('Invalidate secrets'),
help_text=_('Regenerates the order and ticket secrets. You will ' help_text=_('Regenerates the order and ticket secrets. You will '
+11
View File
@@ -54,6 +54,7 @@ from i18nfield.strings import LazyI18nString
from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.formfields import PhoneNumberField
from pytz import common_timezones from pytz import common_timezones
from pretix.api.auth.devicesecurity import get_all_security_profiles
from pretix.api.models import WebHook from pretix.api.models import WebHook
from pretix.api.webhooks import get_all_webhook_events from pretix.api.webhooks import get_all_webhook_events
from pretix.base.customersso.oidc import oidc_validate_and_complete_config from pretix.base.customersso.oidc import oidc_validate_and_complete_config
@@ -311,6 +312,11 @@ class DeviceForm(forms.ModelForm):
'-has_subevents', '-date_from' '-has_subevents', '-date_from'
) )
self.fields['gate'].queryset = organizer.gates.all() self.fields['gate'].queryset = organizer.gates.all()
self.fields['security_profile'] = forms.ChoiceField(
label=self.fields['security_profile'].label,
help_text=self.fields['security_profile'].help_text,
choices=[(k, v.verbose_name) for k, v in get_all_security_profiles().items()],
)
def clean(self): def clean(self):
d = super().clean() d = super().clean()
@@ -344,6 +350,11 @@ class DeviceBulkEditForm(forms.ModelForm):
'-has_subevents', '-date_from' '-has_subevents', '-date_from'
) )
self.fields['gate'].queryset = organizer.gates.all() self.fields['gate'].queryset = organizer.gates.all()
self.fields['security_profile'] = forms.ChoiceField(
label=self.fields['security_profile'].label,
help_text=self.fields['security_profile'].help_text,
choices=[(k, v.verbose_name) for k, v in get_all_security_profiles().items()],
)
def clean(self): def clean(self):
d = super().clean() d = super().clean()
+1 -1
View File
@@ -40,7 +40,7 @@ class StaffSessionForm(forms.ModelForm):
class UserEditForm(forms.ModelForm): class UserEditForm(forms.ModelForm):
error_messages = { error_messages = {
'duplicate_identifier': _("There already is an account associated with this e-mail address. " 'duplicate_identifier': _("There already is an account associated with this email address. "
"Please choose a different one."), "Please choose a different one."),
'pw_mismatch': _("Please enter the same password twice"), 'pw_mismatch': _("Please enter the same password twice"),
} }
+1 -1
View File
@@ -613,7 +613,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
if logentry.action_type == 'pretix.event.order.consent': if logentry.action_type == 'pretix.event.order.consent':
return _('The user confirmed the following message: "{}"').format( return _('The user confirmed the following message: "{}"').format(
bleach.clean(logentry.parsed_data.get('msg'), tags=[], strip=True) bleach.clean(logentry.parsed_data.get('msg'), tags=set(), strip=True)
) )
if logentry.action_type == 'pretix.event.order.canceled': if logentry.action_type == 'pretix.event.order.canceled':
+14 -12
View File
@@ -78,7 +78,7 @@ def get_event_navigation(request: HttpRequest):
'active': url.url_name == 'event.settings.tickets', 'active': url.url_name == 'event.settings.tickets',
}, },
{ {
'label': _('E-mail'), 'label': _('Email'),
'url': reverse('control:event.settings.mail', kwargs={ 'url': reverse('control:event.settings.mail', kwargs={
'event': request.event.slug, 'event': request.event.slug,
'organizer': request.event.organizer.slug, 'organizer': request.event.organizer.slug,
@@ -132,16 +132,6 @@ def get_event_navigation(request: HttpRequest):
'icon': 'wrench', 'icon': 'wrench',
'children': event_settings 'children': event_settings
}) })
if request.event.has_subevents:
nav.append({
'label': pgettext_lazy('subevent', 'Dates'),
'url': reverse('control:event.subevents', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': ('event.subevent' in url.url_name),
'icon': 'calendar',
})
if 'can_change_items' in request.eventpermset: if 'can_change_items' in request.eventpermset:
nav.append({ nav.append({
@@ -197,6 +187,18 @@ def get_event_navigation(request: HttpRequest):
] ]
}) })
if 'can_change_event_settings' in request.eventpermset:
if request.event.has_subevents:
nav.append({
'label': pgettext_lazy('subevent', 'Dates'),
'url': reverse('control:event.subevents', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': ('event.subevent' in url.url_name),
'icon': 'calendar',
})
if 'can_view_orders' in request.eventpermset: if 'can_view_orders' in request.eventpermset:
children = [ children = [
{ {
@@ -496,7 +498,7 @@ def get_organizer_navigation(request):
'active': url.url_name.startswith('organizer.propert'), 'active': url.url_name.startswith('organizer.propert'),
}, },
{ {
'label': _('E-mail'), 'label': _('Email'),
'url': reverse('control:organizer.settings.mail', kwargs={ 'url': reverse('control:organizer.settings.mail', kwargs={
'organizer': request.organizer.slug, 'organizer': request.organizer.slug,
}), }),
@@ -61,6 +61,7 @@
<script type="text/javascript" src="{% static "fileupload/jquery.fileupload.js" %}"></script> <script type="text/javascript" src="{% static "fileupload/jquery.fileupload.js" %}"></script>
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script> <script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
<script type="text/javascript" src="{% static "are-you-sure/jquery.are-you-sure.js" %}"></script> <script type="text/javascript" src="{% static "are-you-sure/jquery.are-you-sure.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
{% endcompress %} {% endcompress %}
{{ html_head|safe }} {{ html_head|safe }}
@@ -127,6 +127,7 @@
<strong> <strong>
<a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=c.position.order.code %}">{{ c.position.order.code }}</a>-{{ c.position.positionid }} <a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=c.position.order.code %}">{{ c.position.order.code }}</a>-{{ c.position.positionid }}
</strong> </strong>
{% include "pretixcontrol/checkin/fragment_checkin_source_type.html" with source_type=c.raw_source_type %}
{% if c.position.attendee_name %} {% if c.position.attendee_name %}
<br> <br>
<small> <small>
@@ -143,7 +144,7 @@
</small> </small>
{% endif %} {% endif %}
{% else %} {% else %}
<span class="fa fa-qrcode fa-fw"></span> {% include "pretixcontrol/checkin/fragment_checkin_source_type.html" with source_type=c.raw_source_type %}
<span title="{{ c.raw_barcode }}"> <span title="{{ c.raw_barcode }}">
{{ c.raw_barcode|slice:":16" }}{% if c.raw_barcode|length > 16 %}…{% endif %} {{ c.raw_barcode|slice:":16" }}{% if c.raw_barcode|length > 16 %}…{% endif %}
<button type="button" class="btn btn-xs btn-link btn-clipboard" data-clipboard-text="{{ c.raw_barcode }}"> <button type="button" class="btn btn-xs btn-link btn-clipboard" data-clipboard-text="{{ c.raw_barcode }}">
@@ -0,0 +1,15 @@
{% load i18n %}
{% load static %}
{% load getitem %}
{% if source_type %}
{% with media_types|getitem:source_type as media_type %}
{% if "." in media_type.icon %}
<img src="{% static media_type.icon %}" class="fa-like-image"
data-toggle="tooltip" title="{{ media_type.verbose_name }}">
{% else %}
<span class="fa fa-fw fa-{{ media_type.icon }} text-muted"
data-toggle="tooltip" title="{{ media_type.verbose_name }}"></span>
{% endif %}
{% endwith %}
{% endif %}
@@ -185,6 +185,7 @@
<span class="fa fa-magic text-muted" <span class="fa fa-magic text-muted"
data-toggle="tooltip" title="{% trans "Checked in automatically" %}"></span> data-toggle="tooltip" title="{% trans "Checked in automatically" %}"></span>
{% endif %} {% endif %}
{% include "pretixcontrol/checkin/fragment_checkin_source_type.html" with source_type=e.last_entry_source_type %}
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
@@ -67,7 +67,6 @@
{% bootstrap_field form.allow_entry_after_exit layout="control" %} {% bootstrap_field form.allow_entry_after_exit layout="control" %}
{% bootstrap_field form.addon_match layout="control" %} {% bootstrap_field form.addon_match layout="control" %}
{% bootstrap_field form.exit_all_at layout="control" %} {% bootstrap_field form.exit_all_at layout="control" %}
{% bootstrap_field form.auto_checkin_sales_channels layout="control" %}
{% if form.gates %} {% if form.gates %}
{% bootstrap_field form.gates layout="control" %} {% bootstrap_field form.gates layout="control" %}
{% endif %} {% endif %}
@@ -101,7 +101,6 @@
<a href="?{% url_replace request 'ordering' 'subevent' %}"><i class="fa fa-caret-up"></i></a> <a href="?{% url_replace request 'ordering' 'subevent' %}"><i class="fa fa-caret-up"></i></a>
</th> </th>
{% endif %} {% endif %}
<th class="iconcol">{% trans "Automated check-in" %}</th>
<th>{% trans "Products" %}</th> <th>{% trans "Products" %}</th>
<th class="action-col-2"></th> <th class="action-col-2"></th>
</tr> </tr>
@@ -137,17 +136,6 @@
</td> </td>
{% endif %} {% endif %}
{% endif %} {% endif %}
<td>
{% for channel in cl.auto_checkin_sales_channels.all %}
{% if "." in channel.icon %}
<img src="{% static channel.icon %}" class="fa-like-image"
data-toggle="tooltip" title="{{ channel.label }}">
{% else %}
<span class="fa fa-{{ channel.icon }} text-muted"
data-toggle="tooltip" title="{{ channel.label }}"></span>
{% endif %}
{% endfor %}
</td>
<td> <td>
{% if cl.all_products %} {% if cl.all_products %}
<em>{% trans "All" %}</em> <em>{% trans "All" %}</em>
@@ -5,7 +5,7 @@
{% load static %} {% load static %}
{% block title %}{% trans "Organizer" %}{% endblock %} {% block title %}{% trans "Organizer" %}{% endblock %}
{% block content %} {% block content %}
<h1>{% trans "E-mail sending" %}</h1> <h1>{% trans "Email sending" %}</h1>
<form action="" method="post" class="form-horizontal"> <form action="" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
<div class="panel-group" id="email"> <div class="panel-group" id="email">
@@ -27,7 +27,7 @@
<div class="panel-body form-horizontal"> <div class="panel-body form-horizontal">
<p> <p>
{% blocktrans trimmed %} {% blocktrans trimmed %}
E-mails will be sent through the system's default server. They will show the following Emails will be sent through the system's default server. They will show the following
sender information: sender information:
{% endblocktrans %} {% endblocktrans %}
</p> </p>
@@ -62,7 +62,7 @@
<div class="panel-body form-horizontal"> <div class="panel-body form-horizontal">
<p> <p>
{% blocktrans trimmed %} {% blocktrans trimmed %}
E-mails will be sent through the system's default server but with your own sender Emails will be sent through the system's default server but with your own sender
address. address.
This will make your emails look more personalized and coming directly from you, but it This will make your emails look more personalized and coming directly from you, but it
also might require some extra steps to ensure good deliverability. also might require some extra steps to ensure good deliverability.
@@ -5,7 +5,7 @@
{% load static %} {% load static %}
{% block title %}{% trans "Organizer" %}{% endblock %} {% block title %}{% trans "Organizer" %}{% endblock %}
{% block content %} {% block content %}
<h1>{% trans "E-mail sending" %}</h1> <h1>{% trans "Email sending" %}</h1>
<form action="" method="post" class="form-horizontal"> <form action="" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{% for k, v in request.POST.items %} {% for k, v in request.POST.items %}
@@ -5,7 +5,7 @@
{% load static %} {% load static %}
{% block title %}{% trans "Organizer" %}{% endblock %} {% block title %}{% trans "Organizer" %}{% endblock %}
{% block content %} {% block content %}
<h1>{% trans "E-mail sending" %}</h1> <h1>{% trans "Email sending" %}</h1>
<form action="" method="post" class="form-horizontal"> <form action="" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{% for k, v in request.POST.items %} {% for k, v in request.POST.items %}
@@ -4,7 +4,7 @@
{% load hierarkey_form %} {% load hierarkey_form %}
{% load static %} {% load static %}
{% block inside %} {% block inside %}
<h1>{% trans "E-mail settings" %}</h1> <h1>{% trans "Email settings" %}</h1>
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data" <form action="" method="post" class="form-horizontal" enctype="multipart/form-data"
mail-preview-url="{% url "control:event.settings.mail.preview" event=request.event.slug organizer=request.event.organizer.slug %}"> mail-preview-url="{% url "control:event.settings.mail.preview" event=request.event.slug organizer=request.event.organizer.slug %}">
{% csrf_token %} {% csrf_token %}
@@ -63,7 +63,7 @@
{% bootstrap_field form.mail_attach_ical_description layout="control" %} {% bootstrap_field form.mail_attach_ical_description layout="control" %}
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "E-mail design" %}</legend> <legend>{% trans "Email design" %}</legend>
<div class="row"> <div class="row">
{% for r in renderers.values %} {% for r in renderers.values %}
<div class="col-md-3"> <div class="col-md-3">
@@ -84,7 +84,7 @@
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "E-mail content" %}</legend> <legend>{% trans "Email content" %}</legend>
<h4>{% trans "Text" %}</h4> <h4>{% trans "Text" %}</h4>
<div class="panel-group" id="questions_group"> <div class="panel-group" id="questions_group">
{% blocktrans asvar title_placed_order %}Placed order{% endblocktrans %} {% blocktrans asvar title_placed_order %}Placed order{% endblocktrans %}
@@ -67,7 +67,7 @@
<h4>{% trans "Customer data (once per order)" %}</h4> <h4>{% trans "Customer data (once per order)" %}</h4>
<div class="form-group"> <div class="form-group">
<label class="control-label col-md-3"> <label class="control-label col-md-3">
{% trans "E-mail" %} {% trans "Email" %}
</label> </label>
<div class="col-md-9"> <div class="col-md-9">
<div class="checkbox"> <div class="checkbox">
@@ -16,8 +16,18 @@
{% bootstrap_field form.internal_name layout="control" %} {% bootstrap_field form.internal_name layout="control" %}
</div> </div>
{% bootstrap_field form.description layout="control" %} {% bootstrap_field form.description layout="control" %}
{% bootstrap_field form.category_type layout="control" horizontal_field_class="big-radio-wrapper col-lg-9" %} {% bootstrap_field form.category_type layout="control" horizontal_field_class="big-radio-wrapper col-md-9" %}
{% bootstrap_field form.cross_selling_condition layout="control" horizontal_field_class="col-lg-9" %} <div class="row" data-display-dependency="#id_category_type_2">
<div class="col-md-offset-3 col-md-9">
<div class="alert alert-info">
{% blocktrans trimmed %}
Please note that cross-selling categories are intended as a marketing feature and are not
suitable for strictly ensuring that products are only available in certain combinations.
{% endblocktrans %}
</div>
</div>
</div>
{% bootstrap_field form.cross_selling_condition layout="control" horizontal_field_class="col-md-9" %}
{% bootstrap_field form.cross_selling_match_products layout="control" %} {% bootstrap_field form.cross_selling_match_products layout="control" %}
</fieldset> </fieldset>
</div> </div>
@@ -296,11 +296,11 @@
{% endfor %} {% endfor %}
<div class="formset" data-formset data-formset-prefix="{{ add_formset.prefix }}"> <div class="formset" data-formset data-formset-prefix="{{ add_position_formset.prefix }}">
{{ add_formset.management_form }} {{ add_position_formset.management_form }}
{% bootstrap_formset_errors add_formset %} {% bootstrap_formset_errors add_position_formset %}
<div data-formset-body> <div data-formset-body>
{% for add_form in add_formset %} {% for add_form in add_position_formset %}
<div class="panel panel-default items" data-formset-form data-subevent="0"> <div class="panel panel-default items" data-formset-form data-subevent="0">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title"> <h3 class="panel-title">
@@ -351,25 +351,25 @@
</button> </button>
{% trans "Add product" %} {% trans "Add product" %}
<div class="sr-only"> <div class="sr-only">
{{ add_formset.empty_form.id }} {{ add_position_formset.empty_form.id }}
{% bootstrap_field add_formset.empty_form.DELETE form_group_class="" layout="inline" %} {% bootstrap_field add_position_formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div> </div>
</h3> </h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="form-horizontal"> <div class="form-horizontal">
{% bootstrap_field add_formset.empty_form.itemvar layout="control" %} {% bootstrap_field add_position_formset.empty_form.itemvar layout="control" %}
{% bootstrap_field add_formset.empty_form.price addon_after=request.event.currency layout="control" %} {% bootstrap_field add_position_formset.empty_form.price addon_after=request.event.currency layout="control" %}
{% if add_formset.empty_form.addon_to %} {% if add_position_formset.empty_form.addon_to %}
{% bootstrap_field add_formset.empty_form.addon_to layout="control" %} {% bootstrap_field add_position_formset.empty_form.addon_to layout="control" %}
{% endif %} {% endif %}
{% if add_formset.empty_form.subevent %} {% if add_position_formset.empty_form.subevent %}
{% bootstrap_field add_formset.empty_form.subevent layout="control" %} {% bootstrap_field add_position_formset.empty_form.subevent layout="control" %}
{% endif %} {% endif %}
{% if add_formset.empty_form.used_membership %} {% if add_position_formset.empty_form.used_membership %}
{% bootstrap_field add_formset.empty_form.used_membership layout="control" %} {% bootstrap_field add_position_formset.empty_form.used_membership layout="control" %}
{% endif %} {% endif %}
{% bootstrap_field add_formset.empty_form.seat layout="control" %} {% bootstrap_field add_position_formset.empty_form.seat layout="control" %}
</div> </div>
</div> </div>
</div> </div>
@@ -431,13 +431,77 @@
{% bootstrap_field fee.form.operation_cancel layout='inline' %} {% bootstrap_field fee.form.operation_cancel layout='inline' %}
{% if fee.fee_type == "payment" %} {% if fee.fee_type == "payment" %}
<em class="text-danger"> <em class="text-danger">
{% trans "Manually modifying payment fees is discouraged since they might automatically be on subsequent order changes or when choosing a different payment method." %} {% trans "Manually modifying payment fees is discouraged since they might automatically be updated on subsequent order changes or when choosing a different payment method." %}
</em> </em>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<div class="formset" data-formset data-formset-prefix="{{ add_fee_formset.prefix }}">
{{ add_fee_formset.management_form }}
{% bootstrap_formset_errors add_fee_formset %}
<div data-formset-body>
{% for add_form in add_fee_formset %}
<div class="panel panel-default items" data-formset-form data-subevent="0">
<div class="panel-heading">
<h3 class="panel-title">
<button type="button" class="btn btn-danger btn-xs pull-right flip"
data-formset-delete-button>
<i class="fa fa-trash"></i>
</button>
{% trans "Add fee" %}
<div class="sr-only">
{{ add_form.id }}
{% bootstrap_field add_form.DELETE form_group_class="" layout="inline" %}
</div>
</h3>
</div>
<div class="panel-body">
<div class="form-horizontal">
{% bootstrap_field add_form.fee_type layout='control' %}
{% bootstrap_field add_form.value addon_after=request.event.currency layout='control' %}
{% bootstrap_field add_form.tax_rule layout='control' %}
{% bootstrap_field add_form.description layout='control' %}
</div>
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="panel panel-default items" data-formset-form data-subevent="0">
<div class="panel-heading">
<h3 class="panel-title">
<button type="button" class="btn btn-danger btn-xs pull-right flip"
data-formset-delete-button>
<i class="fa fa-trash"></i>
</button>
{% trans "Add fee" %}
<div class="sr-only">
{{ add_fee_formset.empty_form.id }}
{% bootstrap_field add_fee_formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
</h3>
</div>
<div class="panel-body">
<div class="form-horizontal">
{% bootstrap_field add_fee_formset.empty_form.fee_type layout='control' %}
{% bootstrap_field add_fee_formset.empty_form.value addon_after=request.event.currency layout='control' %}
{% bootstrap_field add_fee_formset.empty_form.tax_rule layout='control' %}
{% bootstrap_field add_fee_formset.empty_form.description layout='control' %}
</div>
</div>
</div>
{% endescapescript %}
</script>
<p>
<button type="button" class="btn btn-primary" data-formset-add>
<i class="fa fa-plus"></i> {% trans "Add fee" %}</button>
</p>
</div>
<div class="panel panel-default items"> <div class="panel panel-default items">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title"> <h3 class="panel-title">
@@ -46,11 +46,13 @@
<div id="cp{{ pos.id }}"> <div id="cp{{ pos.id }}">
<div class="panel-body"> <div class="panel-body">
{% for form in forms %} {% for form in forms %}
{% if form.pos.item != pos.item %} <div class="profile-scope">
{# Add-Ons #} {% if form.pos.item != pos.item %}
<legend>+ {{ form.pos.item }}</legend> {# Add-Ons #}
{% endif %} <legend>+ {{ form.pos.item }}</legend>
{% bootstrap_form form layout="control" %} {% endif %}
{% bootstrap_form form layout="control" %}
</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@@ -198,7 +198,7 @@
</a> </a>
{% endif %} {% endif %}
</dd> </dd>
{% if order.status == "n" %} {% if order.status == "n" and not order.require_approval %}
<dt>{% trans "Expiry date" %}</dt> <dt>{% trans "Expiry date" %}</dt>
<dd> <dd>
{{ order.expires|date:"SHORT_DATETIME_FORMAT" }} {{ order.expires|date:"SHORT_DATETIME_FORMAT" }}
@@ -24,7 +24,7 @@
{% endif %} {% endif %}
{% if request.method == "POST" %} {% if request.method == "POST" %}
<fieldset> <fieldset>
<legend>{% trans "E-mail preview" %}</legend> <legend>{% trans "Email preview" %}</legend>
<div class="tab-pane mail-preview-group"> <div class="tab-pane mail-preview-group">
<div lang="{{ order.locale }}" class="mail-preview"> <div lang="{{ order.locale }}" class="mail-preview">
<strong>{{ preview_output.subject }}</strong><br><br> <strong>{{ preview_output.subject }}</strong><br><br>
@@ -46,7 +46,7 @@
{% trans "active" %} {% trans "active" %}
{% endif %} {% endif %}
</dd> </dd>
<dt>{% trans "E-mail" %}</dt> <dt>{% trans "Email" %}</dt>
<dd> <dd>
{{ customer.email|default_if_none:"" }} {{ customer.email|default_if_none:"" }}
{% if customer.email and not customer.provider %} {% if customer.email and not customer.provider %}
@@ -8,7 +8,7 @@
{% endblock %} {% endblock %}
{% block title %}{% trans "Organizer" %}{% endblock %} {% block title %}{% trans "Organizer" %}{% endblock %}
{% block content %} {% block content %}
<h1>{% trans "E-mail settings" %}</h1> <h1>{% trans "Email settings" %}</h1>
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data" <form action="" method="post" class="form-horizontal" enctype="multipart/form-data"
mail-preview-url="{% url "control:organizer.settings.mail.preview" organizer=request.organizer.slug %}"> mail-preview-url="{% url "control:organizer.settings.mail.preview" organizer=request.organizer.slug %}">
@@ -55,7 +55,7 @@
{% bootstrap_field form.mail_bcc layout="control" %} {% bootstrap_field form.mail_bcc layout="control" %}
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "E-mail content" %}</legend> <legend>{% trans "Email content" %}</legend>
<div class="panel-group" id="questions_group"> <div class="panel-group" id="questions_group">
{% blocktrans asvar title_customer_registration %}Customer account registration{% endblocktrans %} {% blocktrans asvar title_customer_registration %}Customer account registration{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="customer_registration" title=title_customer_registration items="mail_subject_customer_registration,mail_text_customer_registration" %} {% include "pretixcontrol/event/mail_settings_fragment.html" with pid="customer_registration" title=title_customer_registration items="mail_subject_customer_registration,mail_text_customer_registration" %}
@@ -54,7 +54,7 @@
<thead> <thead>
<tr> <tr>
<th>{% trans "Notification type" %}</th> <th>{% trans "Notification type" %}</th>
<th class="text-center">{% trans "E-Mail notification" %}</th> <th class="text-center">{% trans "Email notification" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -39,7 +39,7 @@
<thead> <thead>
<tr> <tr>
<th> <th>
{% trans "E-mail address" %} {% trans "Email address" %}
<a href="?{% url_replace request 'ordering' '-email' %}"><i class="fa fa-caret-down"></i></a> <a href="?{% url_replace request 'ordering' '-email' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'email' %}"><i class="fa fa-caret-up"></i></a> <a href="?{% url_replace request 'ordering' 'email' %}"><i class="fa fa-caret-up"></i></a>
</th> </th>
+3 -3
View File
@@ -343,7 +343,7 @@ class Forgot(TemplateView):
logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.') logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.')
except SendMailException: except SendMailException:
logger.exception('Sending password reset e-mail to \"' + email + '\" failed.') logger.exception('Sending password reset email to \"' + email + '\" failed.')
except RepeatedResetDenied: except RepeatedResetDenied:
pass pass
@@ -354,10 +354,10 @@ class Forgot(TemplateView):
finally: finally:
if has_redis: if has_redis:
messages.info(request, _('If the address is registered to valid account, then we have sent you an e-mail containing further instructions. ' messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions. '
'Please note that we will send at most one email every 24 hours.')) 'Please note that we will send at most one email every 24 hours.'))
else: else:
messages.info(request, _('If the address is registered to valid account, then we have sent you an e-mail containing further instructions.')) messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions.'))
return redirect('control:auth.forgot') return redirect('control:auth.forgot')
else: else:
+8 -6
View File
@@ -49,6 +49,7 @@ from django.views.generic import FormView, ListView, TemplateView
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
from pretix.api.views.checkin import _redeem_process from pretix.api.views.checkin import _redeem_process
from pretix.base.media import MEDIA_TYPES
from pretix.base.models import Checkin, Order, OrderPosition from pretix.base.models import Checkin, Order, OrderPosition
from pretix.base.models.checkin import CheckinList from pretix.base.models.checkin import CheckinList
from pretix.base.services.checkin import ( from pretix.base.services.checkin import (
@@ -81,9 +82,7 @@ class CheckInListQueryMixin:
position_id=OuterRef('pk'), position_id=OuterRef('pk'),
list_id=self.list.pk, list_id=self.list.pk,
type=Checkin.TYPE_ENTRY type=Checkin.TYPE_ENTRY
).order_by().values('position_id').annotate( ).order_by('-datetime').values('position_id')
m=Max('datetime')
).values('m')
cqs_exit = Checkin.objects.filter( cqs_exit = Checkin.objects.filter(
position_id=OuterRef('pk'), position_id=OuterRef('pk'),
list_id=self.list.pk, list_id=self.list.pk,
@@ -103,7 +102,7 @@ class CheckInListQueryMixin:
status_q, status_q,
order__event=self.request.event, order__event=self.request.event,
).annotate( ).annotate(
last_entry=Subquery(cqs), last_entry=Subquery(cqs[:1].values('datetime')),
last_exit=Subquery(cqs_exit), last_exit=Subquery(cqs_exit),
auto_checked_in=Exists( auto_checked_in=Exists(
Checkin.objects.filter( Checkin.objects.filter(
@@ -112,7 +111,8 @@ class CheckInListQueryMixin:
list_id=self.list.pk, list_id=self.list.pk,
auto_checked_in=True auto_checked_in=True
) )
) ),
last_entry_source_type=Subquery(cqs[:1].values('raw_source_type'))
).select_related( ).select_related(
'item', 'variation', 'order', 'addon_to' 'item', 'variation', 'order', 'addon_to'
).prefetch_related( ).prefetch_related(
@@ -157,6 +157,7 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, CheckInList
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['media_types'] = MEDIA_TYPES
ctx['checkinlist'] = self.list ctx['checkinlist'] = self.list
if self.request.event.has_subevents: if self.request.event.has_subevents:
ctx['seats'] = ( ctx['seats'] = (
@@ -299,7 +300,7 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
def get_queryset(self): def get_queryset(self):
qs = self.request.event.checkin_lists.select_related('subevent').prefetch_related( qs = self.request.event.checkin_lists.select_related('subevent').prefetch_related(
"limit_products", "auto_checkin_sales_channels" "limit_products",
) )
if self.filter_form.is_valid(): if self.filter_form.is_valid():
@@ -497,6 +498,7 @@ class CheckinListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data() ctx = super().get_context_data()
ctx['filter_form'] = self.filter_form ctx['filter_form'] = self.filter_form
ctx['media_types'] = MEDIA_TYPES
return ctx return ctx
+1 -1
View File
@@ -807,7 +807,7 @@ class MailSettingsRendererPreview(MailSettingsPreview):
r._csp_ignore = True r._csp_ignore = True
return r return r
else: else:
raise Http404(_('Unknown e-mail renderer.')) raise Http404(_('Unknown email renderer.'))
class TicketSettingsPreview(EventPermissionRequiredMixin, View): class TicketSettingsPreview(EventPermissionRequiredMixin, View):
+1 -1
View File
@@ -257,7 +257,7 @@ class CategoryUpdate(EventPermissionRequiredMixin, UpdateView):
messages.success(self.request, _('Your changes have been saved.')) messages.success(self.request, _('Your changes have been saved.'))
if form.has_changed(): if form.has_changed():
self.object.log_action( self.object.log_action(
'pretix.event.category.reordered', user=self.request.user, data={ 'pretix.event.category.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data k: form.cleaned_data.get(k) for k in form.changed_data
} }
) )
+55 -14
View File
@@ -122,10 +122,11 @@ from pretix.control.forms.filter import (
) )
from pretix.control.forms.orders import ( from pretix.control.forms.orders import (
CancelForm, CommentForm, DenyForm, EventCancelForm, ExporterForm, CancelForm, CommentForm, DenyForm, EventCancelForm, ExporterForm,
ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeChangeForm, ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeAddForm,
OrderLocaleForm, OrderMailForm, OrderPositionAddForm, OrderFeeAddFormset, OrderFeeChangeForm, OrderLocaleForm, OrderMailForm,
OrderPositionAddFormset, OrderPositionChangeForm, OrderPositionMailForm, OrderPositionAddForm, OrderPositionAddFormset, OrderPositionChangeForm,
OrderRefundForm, OtherOperationsForm, ReactivateOrderForm, OrderPositionMailForm, OrderRefundForm, OtherOperationsForm,
ReactivateOrderForm,
) )
from pretix.control.forms.rrule import RRuleForm from pretix.control.forms.rrule import RRuleForm
from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.permissions import EventPermissionRequiredMixin
@@ -1874,18 +1875,30 @@ class OrderChange(OrderView):
data=self.request.POST if self.request.method == "POST" else None) data=self.request.POST if self.request.method == "POST" else None)
@cached_property @cached_property
def add_formset(self): def add_position_formset(self):
ff = formset_factory( ff = formset_factory(
OrderPositionAddForm, formset=OrderPositionAddFormset, OrderPositionAddForm, formset=OrderPositionAddFormset,
can_order=False, can_delete=True, extra=0 can_order=False, can_delete=True, extra=0
) )
return ff( return ff(
prefix='add', prefix='add_position',
order=self.order, order=self.order,
items=self.items, items=self.items,
data=self.request.POST if self.request.method == "POST" else None data=self.request.POST if self.request.method == "POST" else None
) )
@cached_property
def add_fee_formset(self):
ff = formset_factory(
OrderFeeAddForm, formset=OrderFeeAddFormset,
can_order=False, can_delete=True, extra=0
)
return ff(
prefix='add_fee',
order=self.order,
data=self.request.POST if self.request.method == "POST" else None
)
@cached_property @cached_property
def items(self): def items(self):
return self.request.event.items.prefetch_related('variations', 'tax_rule').all() return self.request.event.items.prefetch_related('variations', 'tax_rule').all()
@@ -1914,7 +1927,8 @@ class OrderChange(OrderView):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['positions'] = self.positions ctx['positions'] = self.positions
ctx['fees'] = self.fees ctx['fees'] = self.fees
ctx['add_formset'] = self.add_formset ctx['add_position_formset'] = self.add_position_formset
ctx['add_fee_formset'] = self.add_fee_formset
ctx['other_form'] = self.other_form ctx['other_form'] = self.other_form
ctx['use_revocation_list'] = self.request.event.ticket_secret_generator.use_revocation_list ctx['use_revocation_list'] = self.request.event.ticket_secret_generator.use_revocation_list
return ctx return ctx
@@ -1929,12 +1943,35 @@ class OrderChange(OrderView):
) )
return True return True
def _process_add(self, ocm): def _process_add_fees(self, ocm):
if not self.add_formset.is_valid(): if not self.add_fee_formset.is_valid():
return False return False
else: else:
for f in self.add_formset.forms: for f in self.add_fee_formset.forms:
if f in self.add_formset.deleted_forms or not f.has_changed(): if f in self.add_fee_formset.deleted_forms or not f.has_changed():
continue
f = OrderFee(
fee_type=f.cleaned_data['fee_type'],
value=f.cleaned_data['value'],
order=ocm.order,
tax_rule=f.cleaned_data['tax_rule'],
description=f.cleaned_data['description'],
)
f._calculate_tax()
try:
ocm.add_fee(f)
except OrderError as e:
f.custom_error = str(e)
return False
return True
def _process_add_positions(self, ocm):
if not self.add_position_formset.is_valid():
return False
else:
for f in self.add_position_formset.forms:
if f in self.add_position_formset.deleted_forms or not f.has_changed():
continue continue
if '-' in f.cleaned_data['itemvar']: if '-' in f.cleaned_data['itemvar']:
@@ -1959,7 +1996,7 @@ class OrderChange(OrderView):
return False return False
return True return True
def _process_fees(self, ocm): def _process_change_fees(self, ocm):
for f in self.fees: for f in self.fees:
if not f.form.is_valid(): if not f.form.is_valid():
return False return False
@@ -1980,7 +2017,7 @@ class OrderChange(OrderView):
return False return False
return True return True
def _process_change(self, ocm): def _process_change_positions(self, ocm):
for p in self.positions: for p in self.positions:
if not p.form.is_valid(): if not p.form.is_valid():
return False return False
@@ -2061,7 +2098,11 @@ class OrderChange(OrderView):
notify=notify, notify=notify,
reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True
) )
form_valid = self._process_add(ocm) and self._process_fees(ocm) and self._process_change(ocm) and self._process_other(ocm) form_valid = (self._process_add_fees(ocm) and
self._process_add_positions(ocm) and
self._process_change_fees(ocm) and
self._process_change_positions(ocm) and
self._process_other(ocm))
if not form_valid: if not form_valid:
messages.error(self.request, _('An error occurred. Please see the details below.')) messages.error(self.request, _('An error occurred. Please see the details below.'))
+1 -1
View File
@@ -145,7 +145,7 @@ class UserResetView(AdministratorPermissionRequiredMixin, RecentAuthenticationRe
self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent', self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent',
user=request.user) user=request.user)
messages.success(request, _('We sent out an e-mail containing further instructions.')) messages.success(request, _('We sent out an email containing further instructions.'))
return redirect(self.get_success_url()) return redirect(self.get_success_url())
def get_success_url(self): def get_success_url(self):
+1 -1
View File
@@ -304,7 +304,7 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=",") writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=",")
headers = [ headers = [
_('Name'), _('E-mail address'), _('Phone number'), _('Product'), _('On list since'), _('Status'), _('Voucher code'), _('Name'), _('Email address'), _('Phone number'), _('Product'), _('On list since'), _('Status'), _('Voucher code'),
_('Language'), _('Priority') _('Language'), _('Priority')
] ]
if self.request.event.has_subevents: if self.request.event.has_subevents:
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:06+0000\n" "POT-Creation-Date: 2024-11-08 13:45+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -622,40 +622,40 @@ msgid ""
"darker shade." "darker shade."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:495 #: pretix/static/pretixcontrol/js/ui/main.js:496
#: pretix/static/pretixcontrol/js/ui/main.js:515 #: pretix/static/pretixcontrol/js/ui/main.js:516
msgid "Search query" msgid "Search query"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:513 #: pretix/static/pretixcontrol/js/ui/main.js:514
msgid "All" msgid "All"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:514 #: pretix/static/pretixcontrol/js/ui/main.js:515
msgid "None" msgid "None"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:518 #: pretix/static/pretixcontrol/js/ui/main.js:519
msgid "Selected only" msgid "Selected only"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:861 #: pretix/static/pretixcontrol/js/ui/main.js:862
msgid "Enter page number between 1 and %(max)s." msgid "Enter page number between 1 and %(max)s."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:864 #: pretix/static/pretixcontrol/js/ui/main.js:865
msgid "Invalid page number." msgid "Invalid page number."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1022 #: pretix/static/pretixcontrol/js/ui/main.js:1023
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1062 #: pretix/static/pretixcontrol/js/ui/main.js:1063
msgid "Click to close" msgid "Click to close"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1137 #: pretix/static/pretixcontrol/js/ui/main.js:1138
msgid "You have unsaved changes!" msgid "You have unsaved changes!"
msgstr "" msgstr ""
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:06+0000\n" "POT-Creation-Date: 2024-11-08 13:45+0000\n"
"PO-Revision-Date: 2021-09-15 11:22+0000\n" "PO-Revision-Date: 2021-09-15 11:22+0000\n"
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n" "Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -656,40 +656,40 @@ msgid ""
"darker shade." "darker shade."
msgstr "تباين اللون سيئ للخلفية البيضاء، الرجاء اختيار لون غامق." msgstr "تباين اللون سيئ للخلفية البيضاء، الرجاء اختيار لون غامق."
#: pretix/static/pretixcontrol/js/ui/main.js:495 #: pretix/static/pretixcontrol/js/ui/main.js:496
#: pretix/static/pretixcontrol/js/ui/main.js:515 #: pretix/static/pretixcontrol/js/ui/main.js:516
msgid "Search query" msgid "Search query"
msgstr "البحث في الاستفسارات" msgstr "البحث في الاستفسارات"
#: pretix/static/pretixcontrol/js/ui/main.js:513 #: pretix/static/pretixcontrol/js/ui/main.js:514
msgid "All" msgid "All"
msgstr "الكل" msgstr "الكل"
#: pretix/static/pretixcontrol/js/ui/main.js:514 #: pretix/static/pretixcontrol/js/ui/main.js:515
msgid "None" msgid "None"
msgstr "لا شيء" msgstr "لا شيء"
#: pretix/static/pretixcontrol/js/ui/main.js:518 #: pretix/static/pretixcontrol/js/ui/main.js:519
msgid "Selected only" msgid "Selected only"
msgstr "المختارة فقط" msgstr "المختارة فقط"
#: pretix/static/pretixcontrol/js/ui/main.js:861 #: pretix/static/pretixcontrol/js/ui/main.js:862
msgid "Enter page number between 1 and %(max)s." msgid "Enter page number between 1 and %(max)s."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:864 #: pretix/static/pretixcontrol/js/ui/main.js:865
msgid "Invalid page number." msgid "Invalid page number."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1022 #: pretix/static/pretixcontrol/js/ui/main.js:1023
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "قم باستخدم اسم مختلف داخليا" msgstr "قم باستخدم اسم مختلف داخليا"
#: pretix/static/pretixcontrol/js/ui/main.js:1062 #: pretix/static/pretixcontrol/js/ui/main.js:1063
msgid "Click to close" msgid "Click to close"
msgstr "اضغط لاغلاق الصفحة" msgstr "اضغط لاغلاق الصفحة"
#: pretix/static/pretixcontrol/js/ui/main.js:1137 #: pretix/static/pretixcontrol/js/ui/main.js:1138
msgid "You have unsaved changes!" msgid "You have unsaved changes!"
msgstr "لم تقم بحفظ التعديلات!" msgstr "لم تقم بحفظ التعديلات!"
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:06+0000\n" "POT-Creation-Date: 2024-11-08 13:45+0000\n"
"PO-Revision-Date: 2020-12-19 07:00+0000\n" "PO-Revision-Date: 2020-12-19 07:00+0000\n"
"Last-Translator: albert <albert.serra.monner@gmail.com>\n" "Last-Translator: albert <albert.serra.monner@gmail.com>\n"
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -623,40 +623,40 @@ msgid ""
"darker shade." "darker shade."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:495 #: pretix/static/pretixcontrol/js/ui/main.js:496
#: pretix/static/pretixcontrol/js/ui/main.js:515 #: pretix/static/pretixcontrol/js/ui/main.js:516
msgid "Search query" msgid "Search query"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:513 #: pretix/static/pretixcontrol/js/ui/main.js:514
msgid "All" msgid "All"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:514 #: pretix/static/pretixcontrol/js/ui/main.js:515
msgid "None" msgid "None"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:518 #: pretix/static/pretixcontrol/js/ui/main.js:519
msgid "Selected only" msgid "Selected only"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:861 #: pretix/static/pretixcontrol/js/ui/main.js:862
msgid "Enter page number between 1 and %(max)s." msgid "Enter page number between 1 and %(max)s."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:864 #: pretix/static/pretixcontrol/js/ui/main.js:865
msgid "Invalid page number." msgid "Invalid page number."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1022 #: pretix/static/pretixcontrol/js/ui/main.js:1023
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1062 #: pretix/static/pretixcontrol/js/ui/main.js:1063
msgid "Click to close" msgid "Click to close"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1137 #: pretix/static/pretixcontrol/js/ui/main.js:1138
msgid "You have unsaved changes!" msgid "You have unsaved changes!"
msgstr "" msgstr ""
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:06+0000\n" "POT-Creation-Date: 2024-11-08 13:45+0000\n"
"PO-Revision-Date: 2023-09-15 06:00+0000\n" "PO-Revision-Date: 2023-09-15 06:00+0000\n"
"Last-Translator: Michael <michael.happl@gmx.at>\n" "Last-Translator: Michael <michael.happl@gmx.at>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -650,40 +650,40 @@ msgstr ""
"Tato barva je pro text na bílém pozadí špatně kontrastní, zvolte prosím " "Tato barva je pro text na bílém pozadí špatně kontrastní, zvolte prosím "
"tmavší odstín." "tmavší odstín."
#: pretix/static/pretixcontrol/js/ui/main.js:495 #: pretix/static/pretixcontrol/js/ui/main.js:496
#: pretix/static/pretixcontrol/js/ui/main.js:515 #: pretix/static/pretixcontrol/js/ui/main.js:516
msgid "Search query" msgid "Search query"
msgstr "Hledaný výraz" msgstr "Hledaný výraz"
#: pretix/static/pretixcontrol/js/ui/main.js:513 #: pretix/static/pretixcontrol/js/ui/main.js:514
msgid "All" msgid "All"
msgstr "Všechny" msgstr "Všechny"
#: pretix/static/pretixcontrol/js/ui/main.js:514 #: pretix/static/pretixcontrol/js/ui/main.js:515
msgid "None" msgid "None"
msgstr "Žádný" msgstr "Žádný"
#: pretix/static/pretixcontrol/js/ui/main.js:518 #: pretix/static/pretixcontrol/js/ui/main.js:519
msgid "Selected only" msgid "Selected only"
msgstr "Pouze vybrané" msgstr "Pouze vybrané"
#: pretix/static/pretixcontrol/js/ui/main.js:861 #: pretix/static/pretixcontrol/js/ui/main.js:862
msgid "Enter page number between 1 and %(max)s." msgid "Enter page number between 1 and %(max)s."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:864 #: pretix/static/pretixcontrol/js/ui/main.js:865
msgid "Invalid page number." msgid "Invalid page number."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1022 #: pretix/static/pretixcontrol/js/ui/main.js:1023
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "Interně používat jiný název" msgstr "Interně používat jiný název"
#: pretix/static/pretixcontrol/js/ui/main.js:1062 #: pretix/static/pretixcontrol/js/ui/main.js:1063
msgid "Click to close" msgid "Click to close"
msgstr "Kliknutím zavřete" msgstr "Kliknutím zavřete"
#: pretix/static/pretixcontrol/js/ui/main.js:1137 #: pretix/static/pretixcontrol/js/ui/main.js:1138
msgid "You have unsaved changes!" msgid "You have unsaved changes!"
msgstr "Máte neuložené změny!" msgstr "Máte neuložené změny!"
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:06+0000\n" "POT-Creation-Date: 2024-11-08 13:45+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -623,40 +623,40 @@ msgid ""
"darker shade." "darker shade."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:495 #: pretix/static/pretixcontrol/js/ui/main.js:496
#: pretix/static/pretixcontrol/js/ui/main.js:515 #: pretix/static/pretixcontrol/js/ui/main.js:516
msgid "Search query" msgid "Search query"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:513 #: pretix/static/pretixcontrol/js/ui/main.js:514
msgid "All" msgid "All"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:514 #: pretix/static/pretixcontrol/js/ui/main.js:515
msgid "None" msgid "None"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:518 #: pretix/static/pretixcontrol/js/ui/main.js:519
msgid "Selected only" msgid "Selected only"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:861 #: pretix/static/pretixcontrol/js/ui/main.js:862
msgid "Enter page number between 1 and %(max)s." msgid "Enter page number between 1 and %(max)s."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:864 #: pretix/static/pretixcontrol/js/ui/main.js:865
msgid "Invalid page number." msgid "Invalid page number."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1022 #: pretix/static/pretixcontrol/js/ui/main.js:1023
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1062 #: pretix/static/pretixcontrol/js/ui/main.js:1063
msgid "Click to close" msgid "Click to close"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1137 #: pretix/static/pretixcontrol/js/ui/main.js:1138
msgid "You have unsaved changes!" msgid "You have unsaved changes!"
msgstr "" msgstr ""
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:06+0000\n" "POT-Creation-Date: 2024-11-08 13:45+0000\n"
"PO-Revision-Date: 2024-07-10 15:00+0000\n" "PO-Revision-Date: 2024-07-10 15:00+0000\n"
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n" "Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -670,40 +670,40 @@ msgid ""
"darker shade." "darker shade."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:495 #: pretix/static/pretixcontrol/js/ui/main.js:496
#: pretix/static/pretixcontrol/js/ui/main.js:515 #: pretix/static/pretixcontrol/js/ui/main.js:516
msgid "Search query" msgid "Search query"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:513 #: pretix/static/pretixcontrol/js/ui/main.js:514
msgid "All" msgid "All"
msgstr "Alle" msgstr "Alle"
#: pretix/static/pretixcontrol/js/ui/main.js:514 #: pretix/static/pretixcontrol/js/ui/main.js:515
msgid "None" msgid "None"
msgstr "Ingen" msgstr "Ingen"
#: pretix/static/pretixcontrol/js/ui/main.js:518 #: pretix/static/pretixcontrol/js/ui/main.js:519
msgid "Selected only" msgid "Selected only"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:861 #: pretix/static/pretixcontrol/js/ui/main.js:862
msgid "Enter page number between 1 and %(max)s." msgid "Enter page number between 1 and %(max)s."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:864 #: pretix/static/pretixcontrol/js/ui/main.js:865
msgid "Invalid page number." msgid "Invalid page number."
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1022 #: pretix/static/pretixcontrol/js/ui/main.js:1023
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "" msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1062 #: pretix/static/pretixcontrol/js/ui/main.js:1063
msgid "Click to close" msgid "Click to close"
msgstr "Klik for at lukke" msgstr "Klik for at lukke"
#: pretix/static/pretixcontrol/js/ui/main.js:1137 #: pretix/static/pretixcontrol/js/ui/main.js:1138
msgid "You have unsaved changes!" msgid "You have unsaved changes!"
msgstr "Du har ændringer, der ikke er gemt!" msgstr "Du har ændringer, der ikke er gemt!"
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:06+0000\n" "POT-Creation-Date: 2024-11-08 13:45+0000\n"
"PO-Revision-Date: 2024-09-10 07:17+0000\n" "PO-Revision-Date: 2024-09-10 07:17+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n" "Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -644,40 +644,40 @@ msgstr ""
"Diese Farbe hat einen schlechten Kontrast für Text auf einem weißen " "Diese Farbe hat einen schlechten Kontrast für Text auf einem weißen "
"Hintergrund. Bitte wählen Sie eine dunklere Farbe." "Hintergrund. Bitte wählen Sie eine dunklere Farbe."
#: pretix/static/pretixcontrol/js/ui/main.js:495 #: pretix/static/pretixcontrol/js/ui/main.js:496
#: pretix/static/pretixcontrol/js/ui/main.js:515 #: pretix/static/pretixcontrol/js/ui/main.js:516
msgid "Search query" msgid "Search query"
msgstr "Suchbegriff" msgstr "Suchbegriff"
#: pretix/static/pretixcontrol/js/ui/main.js:513 #: pretix/static/pretixcontrol/js/ui/main.js:514
msgid "All" msgid "All"
msgstr "Alle" msgstr "Alle"
#: pretix/static/pretixcontrol/js/ui/main.js:514 #: pretix/static/pretixcontrol/js/ui/main.js:515
msgid "None" msgid "None"
msgstr "Keine" msgstr "Keine"
#: pretix/static/pretixcontrol/js/ui/main.js:518 #: pretix/static/pretixcontrol/js/ui/main.js:519
msgid "Selected only" msgid "Selected only"
msgstr "Nur ausgewählte" msgstr "Nur ausgewählte"
#: pretix/static/pretixcontrol/js/ui/main.js:861 #: pretix/static/pretixcontrol/js/ui/main.js:862
msgid "Enter page number between 1 and %(max)s." msgid "Enter page number between 1 and %(max)s."
msgstr "Geben Sie eine Seitenzahl zwischen 1 und %(max)s ein." msgstr "Geben Sie eine Seitenzahl zwischen 1 und %(max)s ein."
#: pretix/static/pretixcontrol/js/ui/main.js:864 #: pretix/static/pretixcontrol/js/ui/main.js:865
msgid "Invalid page number." msgid "Invalid page number."
msgstr "Ungültige Seitenzahl." msgstr "Ungültige Seitenzahl."
#: pretix/static/pretixcontrol/js/ui/main.js:1022 #: pretix/static/pretixcontrol/js/ui/main.js:1023
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "Intern einen anderen Namen verwenden" msgstr "Intern einen anderen Namen verwenden"
#: pretix/static/pretixcontrol/js/ui/main.js:1062 #: pretix/static/pretixcontrol/js/ui/main.js:1063
msgid "Click to close" msgid "Click to close"
msgstr "Klicken zum Schließen" msgstr "Klicken zum Schließen"
#: pretix/static/pretixcontrol/js/ui/main.js:1137 #: pretix/static/pretixcontrol/js/ui/main.js:1138
msgid "You have unsaved changes!" msgid "You have unsaved changes!"
msgstr "Sie haben ungespeicherte Änderungen!" msgstr "Sie haben ungespeicherte Änderungen!"
+1
View File
@@ -198,6 +198,7 @@ Mitgliedschafts
Mitgliedschaftsdauer Mitgliedschaftsdauer
min min
Mo Mo
MobilePay
MOTO MOTO
Multibanco Multibanco
MwSt MwSt
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