Compare commits

..

141 Commits

Author SHA1 Message Date
Raphael Michel
362566eeb6 Dockerfile: Fix pretix version not known to pip 2021-07-02 10:10:08 +02:00
Raphael Michel
9894954233 Add static files to check-manifest ignore list 2021-06-30 16:14:27 +02:00
Raphael Michel
6e7505abd5 Add tests/plugins/sendmail to check-manifest ignore 2021-06-30 15:38:31 +02:00
Raphael Michel
9df381ec4c Do not require requirements.txt in GitLab CI 2021-06-30 15:26:53 +02:00
Raphael Michel
be5e2d8c33 Bump to 4.1.0 2021-06-30 15:14:46 +02:00
Raphael Michel
9da7321a19 Translated on translate.pretix.eu (German (informal) (de_Informal))
Currently translated at 97.6% (166 of 170 strings)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

@@ -57,7 +57,8 @@ jobs:
- name: Install system dependencies
run: sudo apt update && sudo apt install gettext mysql-client
- name: Install Python dependencies
run: pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt mysqlclient psycopg2-binary
run: pip3 install -e ".[dev]" mysqlclient psycopg2-binary
working-directory: ./src
- name: Run checks
run: python manage.py check
working-directory: ./src

View File

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

View File

@@ -41,17 +41,14 @@ ENV LC_ALL=C.UTF-8 \
DJANGO_SETTINGS_MODULE=production_settings
# To copy only the requirements files needed to install from PIP
COPY src/requirements /pretix/src/requirements
COPY src/requirements.txt /pretix/src
COPY src/setup.py /pretix/src/setup.py
RUN pip3 install -U \
pip \
setuptools \
wheel && \
cd /pretix/src && \
pip3 install \
-r requirements.txt \
-r requirements/memcached.txt \
-r requirements/mysql.txt \
PRETIX_DOCKER_BUILD=TRUE pip3 install \
-e ".[memcached,mysql]" \
gunicorn django-extensions ipython && \
rm -rf ~/.cache/pip
@@ -63,7 +60,7 @@ COPY deployment/docker/nginx.conf /etc/nginx/nginx.conf
COPY deployment/docker/production_settings.py /pretix/src/production_settings.py
COPY src /pretix/src
RUN cd /pretix/src && pip3 install .
RUN cd /pretix/src && python setup.py install
RUN chmod +x /usr/local/bin/pretix && \
rm /etc/nginx/sites-enabled/default && \

View File

@@ -297,6 +297,12 @@ to speed up various operations::
[redis]
location=redis://127.0.0.1:6379/1
sessions=false
sentinels=[
["sentinel_host_1", 26379],
["sentinel_host_2", 26379],
["sentinel_host_3", 26379]
]
password=password
``location``
The location of redis, as a URL of the form ``redis://[:password]@localhost:6379/0``
@@ -305,13 +311,34 @@ to speed up various operations::
``session``
When this is set to ``True``, redis will be used as the session storage.
``sentinels``
Configures redis sentinels to use.
If you don't want to use redis sentinels, you should omit this option.
If this is set, redis via sentinels will be used instead of plain redis.
In this case the location should be of the form ``redis://my_master/0``.
The ``sentinels`` variable should be a json serialized list of sentinels,
each being a list with the two elements hostname and port.
You cannot provide a password within the location when using sentinels.
Note that the configuration format requires you to either place the entire
value on one line or make sure all values are indented by at least one space.
``password``
If your redis setup doesn't require a password or you already specified it in the location you can omit this option.
If this is set it will be passed to redis as the connection option PASSWORD.
If redis is not configured, pretix will store sessions and locks in the database. If memcached
is configured, memcached will be used for caching instead of redis.
Translations
------------
pretix comes with a number of translations. Some of them are marked as "incubating", which means
pretix comes with a number of translations. All languages are enabled by default. If you want to limit
the languages available in your installation, you can enable a set of languages like this::
[languages]
enabled=en,de
Some of the languages them are marked as "incubating", which means
they can usually only be selected in development mode. If you want to use them nevertheless, you
can activate them like this::
@@ -337,11 +364,22 @@ an AMQP server (e.g. RabbitMQ) as a broker and redis or your database as a resul
[celery]
broker=amqp://guest:guest@localhost:5672//
backend=redis://localhost/0
broker_transport_options="{}"
backend_transport_options="{}"
RabbitMQ might be the better choice if you have a complex, multi-server, high-performance setup,
but as you already should have a redis instance ready for session and lock storage, we recommend
redis for convenience. See the `Celery documentation`_ for more details.
The two ``transport_options`` entries can be omitted in most cases.
If they are present they need to be a valid JSON dictionary.
For possible entries in that dictionary see the `Celery documentation`_.
To use redis with sentinels set the broker or backend to ``sentinel://sentinel_host_1:26379;sentinal_host_2:26379/0``
and the respective transport_options to ``{"master_name":"mymaster"}``.
If your redis instances behind the sentinel have a password use ``sentinel://:my_password@sentinel_host_1:26379;sentinal_host_2:26379/0``.
If your redis sentinels themselves have a password set the transport_options to ``{"master_name":"mymaster","sentinel_kwargs":{"password":"my_password"}}``.
Sentry
------

View File

@@ -58,7 +58,12 @@ Database
--------
Next, we need a database and a database user. We can create these with any kind of database managing tool or directly on
our database's shell. For PostgreSQL, we would do::
our database's shell. Please make sure that UTF8 is used as encoding for the best compatibility. You can check this with
the following command::
# sudo -u postgres psql -c 'SHOW SERVER_ENCODING'
For PostgreSQL database creation, we would do::
# sudo -u postgres createuser -P pretix
# sudo -u postgres createdb -O pretix pretix

View File

@@ -51,7 +51,12 @@ Database
--------
Having the database server installed, we still need a database and a database user. We can create these with any kind
of database managing tool or directly on our database's shell. For PostgreSQL, we would do::
of database managing tool or directly on our database's shell. Please make sure that UTF8 is used as encoding for the
best compatibility. You can check this with the following command::
# sudo -u postgres psql -c 'SHOW SERVER_ENCODING'
For PostgreSQL database creation, we would do::
# sudo -u postgres createuser pretix
# sudo -u postgres createdb -O pretix pretix
@@ -129,10 +134,13 @@ python installation::
$ source /var/pretix/venv/bin/activate
(venv)$ pip3 install -U pip setuptools wheel
We now install pretix, its direct dependencies and gunicorn. Replace ``postgres`` with ``mysql`` in the following
command if you're running MySQL::
We now install pretix, its direct dependencies and gunicorn::
(venv)$ pip3 install "pretix[postgres]" gunicorn
(venv)$ pip3 install pretix gunicorn
If you're running MySQL, also install the client library::
(venv)$ pip3 install mysqlclient
Note that you need Python 3.6 or newer. You can find out your Python version using ``python -V``.
@@ -251,14 +259,14 @@ The following snippet is an example on how to configure a nginx proxy for pretix
}
location /static/ {
alias /var/pretix/venv/lib/python3.5/site-packages/pretix/static.dist/;
alias /var/pretix/venv/lib/python3.7/site-packages/pretix/static.dist/;
access_log off;
expires 365d;
add_header Cache-Control "public";
}
}
.. note:: Remember to replace the ``python3.5`` in the ``/static/`` path in the config
.. note:: Remember to replace the ``python3.7`` in the ``/static/`` path in the config
above with your python version.
We recommend reading about setting `strong encryption settings`_ for your web server.
@@ -281,7 +289,7 @@ To upgrade to a new pretix release, pull the latest code changes and run the fol
``postgres`` with ``mysql`` if necessary)::
$ source /var/pretix/venv/bin/activate
(venv)$ pip3 install -U pretix[postgres] gunicorn
(venv)$ pip3 install -U --upgrade-strategy eager pretix gunicorn
(venv)$ python -m pretix migrate
(venv)$ python -m pretix rebuild
(venv)$ python -m pretix updatestyles

View File

@@ -362,6 +362,42 @@ Endpoints
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/failed_checkins/
Stores a failed check-in. Only necessary for statistical purposes if you perform scan validation offline.
:<json boolean error_reason: One of ``canceled``, ``invalid``, ``unpaid``, ``product``, ``rules``, ``revoked``,
``incomplete``, ``already_redeemed``, or ``error``. Required.
:<json raw_barcode: The raw barcode you scanned. Required.
:<json datetime: Date and time of the scan. Optional.
:<json type: Type of scan, defaults to ``"entry"``.
:<json position: Internal ID of an order position you matched. Optional.
:<json raw_item: Internal ID of an item you matched. Optional.
:<json raw_variation: Internal ID of an item variation you matched. Optional.
:<json raw_subevent: Internal ID of an event series date you matched. Optional.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/checkinlists/1/failed_checkins/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
{
"raw_barcode": "Pvrk50vUzQd0DhdpNRL4I4OcXsvg70uA",
"error_reason": "canceled"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param list: The ID of the check-in list to save for
:statuscode 201: no error
:statuscode 400: Invalid request
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested order position or check-in list does not exist.
Order position endpoints
------------------------
@@ -424,6 +460,9 @@ Order position endpoints
"checkins": [
{
"list": 1,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": true
}
@@ -535,6 +574,9 @@ Order position endpoints
{
"list": 1,
"datetime": "2017-12-25T12:45:23Z",
"type": "entry",
"gate": null,
"device": 2,
"auto_checked_in": true
}
],

View File

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

View File

@@ -58,6 +58,15 @@ lines list of objects The actual invo
created before this field was introduced as well as for
all lines not created by a product (e.g. a shipping or
cancellation fee).
├ fee_type string Fee type, e.g. ``shipping``, ``service``, ``payment``,
``cancellation``, ``giftcard``, or ``other. Can be ``null`` for
all invoice lines
created before this field was introduced as well as for
all lines not created by a fee (e.g. a product).
├ fee_internal_type string Additional fee type, e.g. type of payment provider. Can be ``null``
for all invoice lines
created before this field was introduced as well as for
all lines not created by a fee (e.g. a product).
├ event_date_from datetime Start date of the (sub)event this line was created for as it
was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in
@@ -97,6 +106,10 @@ internal_reference string Customer's refe
``lines.event_date_to``, and ``lines.attendee_name`` have been added.
``refers`` now returns an invoice number including the prefix.
.. versionchanged:: 4.1
The attributes ``fee_type`` and ``fee_internal_type`` have been added.
Endpoints
---------
@@ -162,6 +175,8 @@ Endpoints
"description": "Budget Ticket",
"item": 1234,
"variation": 245,
"fee_type": null,
"fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null,
"attendee_name": null,
@@ -248,6 +263,8 @@ Endpoints
"description": "Budget Ticket",
"item": 1234,
"variation": 245,
"fee_type": null,
"fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null,
"attendee_name": null,

View File

@@ -41,6 +41,7 @@ payment_date date **DEPRECATED AN
payment_provider string **DEPRECATED AND INACCURATE** Payment provider used for this order
total money (string) Total value of this order
comment string Internal comment on this order
custom_followup_at date Internal date for a custom follow-up action
checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if a ticket
of this order is scanned.
@@ -123,6 +124,10 @@ last_modified datetime Last modificati
The ``customer`` attribute has been added.
.. versionchanged:: 4.1
The ``custom_followup_at`` attribute has been added.
.. _order-position-resource:
@@ -160,11 +165,13 @@ secret string Secret code pri
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
subevent integer ID of the date inside an event series this position belongs to (or ``null``).
pseudonymization_id string A random ID, e.g. for use in lead scanning apps
checkins list of objects List of check-ins with this ticket
checkins list of objects List of **successful** check-ins with this ticket
├ id integer Internal ID of the check-in event
├ list integer Internal ID of the check-in list
├ datetime datetime Time of check-in
├ type string Type of scan (defaults to ``entry``)
├ gate integer Internal ID of the gate. Can be ``null``.
├ device integer Internal ID of the device. Can be ``null``.
└ auto_checked_in boolean Indicates if this check-in been performed automatically by the system
downloads list of objects List of ticket download options
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
@@ -305,6 +312,7 @@ List of all orders
"fees": [],
"total": "23.00",
"comment": "",
"custom_followup_at": null,
"checkin_attention": false,
"require_approval": false,
"invoice_address": {
@@ -355,6 +363,8 @@ List of all orders
{
"list": 44,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
@@ -474,6 +484,7 @@ Fetching individual orders
"fees": [],
"total": "23.00",
"comment": "",
"custom_followup_at": null,
"checkin_attention": false,
"require_approval": false,
"invoice_address": {
@@ -524,6 +535,8 @@ Fetching individual orders
{
"list": 44,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
@@ -638,6 +651,8 @@ Updating order fields
* ``comment``
* ``custom_followup_at``
* ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address)
**Example request**:
@@ -811,6 +826,7 @@ Creating orders
charge will be created), this is just informative in case you *handled the payment already*.
* ``payment_date`` (optional) Date and time of the completion of the payment.
* ``comment`` (optional)
* ``custom_followup_at`` (optional)
* ``checkin_attention`` (optional)
* ``invoice_address`` (optional)
@@ -1421,6 +1437,8 @@ List of all order positions
{
"list": 44,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
@@ -1527,6 +1545,8 @@ Fetching individual positions
{
"list": 44,
"type": "entry",
"gate": null,
"device": 2,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}

View File

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

View File

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

View File

@@ -54,7 +54,7 @@ Working with the code
The first thing you need are all the main application's dependencies::
cd src/
pip3 install -r requirements.txt -r requirements/dev.txt
pip3 install -e ".[dev]"
Next, you need to copy the SCSS files from the source folder to the STATIC_ROOT directory::

View File

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

View File

@@ -21,6 +21,7 @@ cancelled
casted
Ceph
checkbox
checkins
checksum
config
contenttypes

2
src/.gitignore vendored
View File

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

View File

@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "4.0.0"
__version__ = "4.1.0"

View File

@@ -59,6 +59,7 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:badgeitem-list'),
('GET', 'api-v1:checkinlist-list'),
('GET', 'api-v1:checkinlist-status'),
('POST', 'api-v1:checkinlist-failed_checkins'),
('GET', 'api-v1:checkinlistpos-list'),
('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'),
@@ -89,6 +90,7 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:badgeitem-list'),
('GET', 'api-v1:checkinlist-list'),
('GET', 'api-v1:checkinlist-status'),
('POST', 'api-v1:checkinlist-failed_checkins'),
('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'),

View File

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

View File

@@ -42,6 +42,7 @@ from django.utils.functional import cached_property
from django.utils.translation import gettext as _
from django_countries.serializers import CountryFieldMixin
from pytz import common_timezones
from rest_framework import serializers
from rest_framework.fields import ChoiceField, Field
from rest_framework.relations import SlugRelatedField
@@ -93,9 +94,12 @@ class MetaPropertyField(Field):
class SeatCategoryMappingField(Field):
def to_representation(self, value):
qs = value.seat_category_mappings.all()
if isinstance(value, Event):
qs = qs.filter(subevent=None)
if hasattr(value, '_seat_category_mappings'):
qs = value._seat_category_mappings
else:
qs = value.seat_category_mappings.all()
if isinstance(value, Event):
qs = qs.filter(subevent=None)
return {
v.layout_category: v.product_id for v in qs
}
@@ -156,6 +160,7 @@ class EventSerializer(I18nAwareModelSerializer):
seat_category_mapping = SeatCategoryMappingField(source='*', required=False)
timezone = TimeZoneField(required=False, choices=[(a, a) for a in common_timezones])
valid_keys = ValidKeysField(source='*', read_only=True)
best_availability_state = serializers.IntegerField(allow_null=True, read_only=True)
class Meta:
model = Event
@@ -163,12 +168,14 @@ class EventSerializer(I18nAwareModelSerializer):
'date_to', 'date_admission', 'is_public', 'presale_start',
'presale_end', 'location', 'geo_lat', 'geo_lon', 'has_subevents', 'meta_data', 'seating_plan',
'plugins', 'seat_category_mapping', 'timezone', 'item_meta_properties', 'valid_keys',
'sales_channels')
'sales_channels', 'best_availability_state')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not hasattr(self.context['request'], 'event'):
self.fields.pop('valid_keys')
if not self.context.get('request') or 'with_availability_for' not in self.context['request'].GET:
self.fields.pop('best_availability_state')
def validate(self, data):
data = super().validate(data)
@@ -441,13 +448,19 @@ class SubEventSerializer(I18nAwareModelSerializer):
seat_category_mapping = SeatCategoryMappingField(source='*', required=False)
event = SlugRelatedField(slug_field='slug', read_only=True)
meta_data = MetaDataField(source='*')
best_availability_state = serializers.IntegerField(allow_null=True, read_only=True)
class Meta:
model = SubEvent
fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission',
'presale_start', 'presale_end', 'location', 'geo_lat', 'geo_lon', 'event', 'is_public',
'frontpage_text', 'seating_plan', 'item_price_overrides', 'variation_price_overrides',
'meta_data', 'seat_category_mapping', 'last_modified')
'meta_data', 'seat_category_mapping', 'last_modified', 'best_availability_state')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.context.get('request') or 'with_availability_for' not in self.context['request'].GET:
self.fields.pop('best_availability_state')
def validate(self, data):
data = super().validate(data)

View File

@@ -408,10 +408,19 @@ class QuestionSerializer(I18nAwareModelSerializer):
class QuotaSerializer(I18nAwareModelSerializer):
available = serializers.BooleanField(read_only=True)
available_number = serializers.IntegerField(read_only=True)
class Meta:
model = Quota
fields = ('id', 'name', 'size', 'items', 'variations', 'subevent', 'closed', 'close_when_sold_out', 'release_after_exit')
fields = ('id', 'name', 'size', 'items', 'variations', 'subevent', 'closed', 'close_when_sold_out',
'release_after_exit', 'available', 'available_number')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'request' not in self.context or self.context['request'].GET.get('with_availability') != 'true':
del self.fields['available']
del self.fields['available_number']
def validate(self, data):
data = super().validate(data)

View File

@@ -199,7 +199,9 @@ class AnswerSerializer(I18nAwareModelSerializer):
return data
def validate(self, data):
if data.get('question').type == Question.TYPE_FILE:
if not data.get('question'):
raise ValidationError('Question not specified.')
elif data.get('question').type == Question.TYPE_FILE:
return self._handle_file_upload(data)
elif data.get('question').type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE):
if not data.get('options'):
@@ -250,7 +252,30 @@ class AnswerSerializer(I18nAwareModelSerializer):
class CheckinSerializer(I18nAwareModelSerializer):
class Meta:
model = Checkin
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'type')
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'type')
class FailedCheckinSerializer(I18nAwareModelSerializer):
error_reason = serializers.ChoiceField(choices=Checkin.REASONS, required=True, allow_null=False)
raw_barcode = serializers.CharField(required=True, allow_null=False)
position = serializers.PrimaryKeyRelatedField(queryset=OrderPosition.all.none(), required=False, allow_null=True)
raw_item = serializers.PrimaryKeyRelatedField(queryset=Item.objects.none(), required=False, allow_null=True)
raw_variation = serializers.PrimaryKeyRelatedField(queryset=ItemVariation.objects.none(), required=False, allow_null=True)
raw_subevent = serializers.PrimaryKeyRelatedField(queryset=SubEvent.objects.none(), required=False, allow_null=True)
class Meta:
model = Checkin
fields = ('error_reason', 'error_explanation', 'raw_barcode', 'raw_item', 'raw_variation',
'raw_subevent', 'datetime', 'type', 'position')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
event = self.context['event']
self.fields['raw_item'].queryset = event.items.all()
self.fields['raw_variation'].queryset = ItemVariation.objects.filter(item__event=event)
self.fields['position'].queryset = OrderPosition.all.filter(order__event=event)
if event.has_subevents:
self.fields['raw_subevent'].queryset = event.subevents.all()
class OrderDownloadsField(serializers.Field):
@@ -631,7 +656,7 @@ class OrderSerializer(I18nAwareModelSerializer):
model = Order
fields = (
'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer'
)
@@ -662,7 +687,7 @@ class OrderSerializer(I18nAwareModelSerializer):
def update(self, instance, validated_data):
# Even though all fields that shouldn't be edited are marked as read_only in the serializer
# (hopefully), we'll be extra careful here and be explicit about the model fields we update.
update_fields = ['comment', 'checkin_attention', 'email', 'locale', 'phone']
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'email', 'locale', 'phone']
if 'invoice_address' in validated_data:
iadata = validated_data.pop('invoice_address')
@@ -902,6 +927,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
min_length=5
)
comment = serializers.CharField(required=False, allow_blank=True)
custom_followup_at = serializers.DateField(required=False, allow_null=True)
payment_provider = serializers.CharField(required=False, allow_null=True)
payment_info = CompatibleJSONField(required=False)
consume_carts = serializers.ListField(child=serializers.CharField(), required=False)
@@ -920,7 +946,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
model = Order
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts',
'force', 'send_email', 'simulate', 'customer')
'force', 'send_email', 'simulate', 'customer', 'custom_followup_at')
def validate_payment_provider(self, pp):
if pp is None:
@@ -1269,7 +1295,13 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
if pos.voucher:
Voucher.objects.filter(pk=pos.voucher.pk).update(redeemed=F('redeemed') + 1)
pos.save()
seen_answers = set()
for answ_data in answers_data:
# Workaround for a pretixPOS bug :-(
if answ_data.get('question') in seen_answers:
continue
seen_answers.add(answ_data.get('question'))
options = answ_data.pop('options', [])
if isinstance(answ_data['answer'], File):
@@ -1393,7 +1425,8 @@ class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
class Meta:
model = InvoiceLine
fields = ('position', 'description', 'item', 'variation', 'attendee_name', 'event_date_from',
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_name')
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_name', 'fee_type',
'fee_internal_type')
class InvoiceSerializer(I18nAwareModelSerializer):

View File

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

View File

@@ -21,6 +21,7 @@
#
import django_filters
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import (
Count, Exists, F, Max, OrderBy, OuterRef, Prefetch, Q, Subquery,
)
@@ -34,16 +35,20 @@ from django_scopes import scopes_disabled
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.fields import DateTimeField
from rest_framework.permissions import SAFE_METHODS
from rest_framework.response import Response
from pretix.api.serializers.checkin import CheckinListSerializer
from pretix.api.serializers.item import QuestionSerializer
from pretix.api.serializers.order import CheckinListOrderPositionSerializer
from pretix.api.serializers.order import (
CheckinListOrderPositionSerializer, FailedCheckinSerializer,
)
from pretix.api.views import RichOrderingFilter
from pretix.api.views.order import OrderPositionFilter
from pretix.base.i18n import language
from pretix.base.models import (
CachedFile, Checkin, CheckinList, Event, Order, OrderPosition, Question,
CachedFile, Checkin, CheckinList, Device, Event, Order, OrderPosition,
Question,
)
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
@@ -79,8 +84,14 @@ class CheckinListViewSet(viewsets.ModelViewSet):
queryset = CheckinList.objects.none()
filter_backends = (DjangoFilterBackend,)
filterset_class = CheckinListFilter
permission = ('can_view_orders', 'can_checkin_orders',)
write_permission = 'can_change_event_settings'
def _get_permission_name(self, request):
if request.path.endswith('/failed_checkins/'):
return 'can_checkin_orders', 'can_change_orders'
elif request.method in SAFE_METHODS:
return 'can_view_orders', 'can_checkin_orders',
else:
return 'can_change_event_settings'
def get_queryset(self):
qs = self.request.event.checkin_lists.prefetch_related(
@@ -125,6 +136,49 @@ class CheckinListViewSet(viewsets.ModelViewSet):
)
super().perform_destroy(instance)
@action(detail=True, methods=['POST'], url_name='failed_checkins')
@transaction.atomic()
def failed_checkins(self, *args, **kwargs):
serializer = FailedCheckinSerializer(
data=self.request.data,
context={'event': self.request.event}
)
serializer.is_valid(raise_exception=True)
kwargs = {}
if not serializer.validated_data.get('position'):
kwargs['position'] = OrderPosition.all.filter(
secret=serializer.validated_data['raw_barcode']
).first()
c = serializer.save(
list=self.get_object(),
successful=False,
forced=True,
device=self.request.auth if isinstance(self.request.auth, Device) else None,
gate=self.request.auth.gate if isinstance(self.request.auth, Device) else None,
**kwargs,
)
if c.position:
c.position.order.log_action('pretix.event.checkin.denied', data={
'position': c.position.id,
'positionid': c.position.positionid,
'errorcode': c.error_reason,
'reason_explanation': c.error_explanation,
'datetime': c.datetime,
'type': c.type,
'list': c.list.pk
}, user=self.request.user, auth=self.request.auth)
else:
self.request.event.log_action('pretix.event.checkin.unknown', data={
'datetime': c.datetime,
'type': c.type,
'list': c.list.pk,
'barcode': c.raw_barcode
}, user=self.request.user, auth=self.request.auth)
return Response(serializer.data, status=201)
@action(detail=True, methods=['GET'])
def status(self, *args, **kwargs):
with language(self.request.event.settings.locale):
@@ -294,7 +348,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
lookup='checkins',
queryset=Checkin.objects.filter(list_id=self.checkinlist.pk)
),
'checkins', 'answers', 'answers__options', 'answers__question',
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
@@ -304,7 +358,8 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
Prefetch(
'positions',
OrderPosition.objects.prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
))
@@ -356,6 +411,17 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
else:
dt = now()
common_checkin_args = dict(
raw_barcode=self.kwargs['pk'],
type=type,
list=self.checkinlist,
datetime=dt,
device=self.request.auth if isinstance(self.request.auth, Device) else None,
gate=self.request.auth.gate if isinstance(self.request.auth, Device) else None,
nonce=nonce,
forced=force,
)
try:
queryset = self.get_queryset(ignore_status=True, ignore_products=True)
if self.kwargs['pk'].isnumeric():
@@ -364,22 +430,53 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
op = queryset.get(secret=self.kwargs['pk'])
except OrderPosition.DoesNotExist:
revoked_matches = list(self.request.event.revoked_secrets.filter(secret=self.kwargs['pk']))
if len(revoked_matches) == 0 or not force:
if len(revoked_matches) == 0:
self.request.event.log_action('pretix.event.checkin.unknown', data={
'datetime': dt,
'type': type,
'list': self.checkinlist.pk,
'barcode': self.kwargs['pk']
}, user=self.request.user, auth=self.request.auth)
raise Http404()
op = revoked_matches[0].position
op.order.log_action('pretix.event.checkin.revoked', data={
'datetime': dt,
'type': type,
'list': self.checkinlist.pk,
'barcode': self.kwargs['pk']
}, user=self.request.user, auth=self.request.auth)
for k, s in self.request.event.ticket_secret_generators.items():
try:
parsed = s.parse_secret(self.kwargs['pk'])
common_checkin_args.update({
'raw_item': parsed.item,
'raw_variation': parsed.variation,
'raw_subevent': parsed.subevent,
})
except:
pass
Checkin.objects.create(
position=None,
successful=False,
error_reason=Checkin.REASON_INVALID,
**common_checkin_args,
)
raise Http404()
else:
op = revoked_matches[0].position
op.order.log_action('pretix.event.checkin.revoked', data={
'datetime': dt,
'type': type,
'list': self.checkinlist.pk,
'barcode': self.kwargs['pk']
}, user=self.request.user, auth=self.request.auth)
Checkin.objects.create(
position=op,
successful=False,
error_reason=Checkin.REASON_REVOKED,
**common_checkin_args
)
return Response({
'status': 'error',
'reason': Checkin.REASON_REVOKED,
'reason_explanation': None,
'require_attention': False,
'position': CheckinListOrderPositionSerializer(op, context=self.get_serializer_context()).data
}, status=400)
given_answers = {}
if 'answers' in self.request.data:
@@ -409,6 +506,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
user=self.request.user,
auth=self.request.auth,
type=type,
raw_barcode=None,
)
except RequiredQuestionsError as e:
return Response({
@@ -424,11 +522,19 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
'position': op.id,
'positionid': op.positionid,
'errorcode': e.code,
'reason_explanation': e.reason,
'force': force,
'datetime': dt,
'type': type,
'list': self.checkinlist.pk
}, user=self.request.user, auth=self.request.auth)
Checkin.objects.create(
position=op,
successful=False,
error_reason=e.code,
error_explanation=e.reason,
**common_checkin_args,
)
return Response({
'status': 'error',
'reason': e.code,

View File

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

View File

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

View File

@@ -55,9 +55,9 @@ from pretix.api.serializers.order import (
)
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Device, Event, Invoice, InvoiceAddress,
Order, OrderFee, OrderPayment, OrderPosition, OrderRefund, Quota, SubEvent,
TaxRule, TeamAPIToken, generate_secret,
CachedCombinedTicket, CachedTicket, Checkin, Device, Event, Invoice,
InvoiceAddress, Order, OrderFee, OrderPayment, OrderPosition, OrderRefund,
Quota, SubEvent, TaxRule, TeamAPIToken, generate_secret,
)
from pretix.base.models.orders import QuestionAnswer, RevokedTicketSecret
from pretix.base.payment import PaymentException
@@ -201,7 +201,8 @@ class OrderViewSet(viewsets.ModelViewSet):
Prefetch(
'positions',
opq.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
'item', 'variation', 'answers', 'answers__options', 'answers__question',
'item__category', 'addon_to', 'seat',
Prefetch('addons', opq.select_related('item', 'variation', 'seat'))
)
@@ -212,7 +213,8 @@ class OrderViewSet(viewsets.ModelViewSet):
Prefetch(
'positions',
opq.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question', 'seat',
Prefetch('checkins', queryset=Checkin.objects.all()),
'item', 'variation', 'answers', 'answers__options', 'answers__question', 'seat',
)
)
)
@@ -690,6 +692,16 @@ class OrderViewSet(viewsets.ModelViewSet):
}
)
if 'custom_followup_at' in self.request.data and serializer.instance.custom_followup_at != self.request.data.get('custom_followup_at'):
serializer.instance.log_action(
'pretix.event.order.custom_followup_at',
user=self.request.user,
auth=self.request.auth,
data={
'new_custom_followup_at': self.request.data.get('custom_followup_at')
}
)
if 'checkin_attention' in self.request.data and serializer.instance.checkin_attention != self.request.data.get('checkin_attention'):
serializer.instance.log_action(
'pretix.event.order.checkin_attention',
@@ -781,7 +793,7 @@ with scopes_disabled():
)
def has_checkin_qs(self, queryset, name, value):
return queryset.filter(checkins__isnull=not value)
return queryset.alias(ce=Exists(Checkin.objects.filter(position=OuterRef('pk')))).filter(ce=value)
def attendee_name_qs(self, queryset, name, value):
return queryset.filter(Q(attendee_name_cached__iexact=value) | Q(addon_to__attendee_name_cached__iexact=value))
@@ -835,7 +847,8 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, vi
qs = qs.filter(order__event=self.request.event)
if self.request.query_params.get('pdf_data', 'false') == 'true':
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', qs.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
@@ -845,7 +858,8 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, vi
Prefetch(
'positions',
qs.prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
'item', 'variation', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
)
)
))
@@ -854,7 +868,8 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, vi
)
else:
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question',
Prefetch('checkins', queryset=Checkin.objects.all()),
'answers', 'answers__options', 'answers__question',
).select_related(
'item', 'order', 'order__event', 'order__event__organizer', 'seat'
)

View File

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

View File

@@ -26,6 +26,7 @@ from decimal import Decimal
from itertools import groupby
from smtplib import SMTPResponseException
import css_inline
from django.conf import settings
from django.core.mail.backends.smtp import EmailBackend
from django.db.models import Count
@@ -35,7 +36,6 @@ from django.utils.timezone import now
from django.utils.translation import (
get_language, gettext_lazy as _, pgettext_lazy,
)
from inlinestyler.utils import inline_css
from pretix.base.i18n import (
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
@@ -174,7 +174,11 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
htmlctx['ev'] = position.subevent or self.event
tpl = get_template(self.template_name)
body_html = inline_css(tpl.render(htmlctx))
body_html = tpl.render(htmlctx)
inliner = css_inline.CSSInliner(remove_style_tags=True)
body_html = inliner.inline(body_html)
return body_html
@@ -448,6 +452,16 @@ def base_placeholders(sender, **kwargs):
}
),
),
SimpleFunctionalMailTextPlaceholder(
'subevent', ['waiting_list_entry', 'event'],
lambda waiting_list_entry, event: str(waiting_list_entry.subevent or event),
lambda event: str(event if not event.has_subevents or not event.subevents.exists() else event.subevents.first())
),
SimpleFunctionalMailTextPlaceholder(
'subevent_date_from', ['waiting_list_entry', 'event'],
lambda waiting_list_entry, event: (waiting_list_entry.subevent or event).get_date_from_display(),
lambda event: (event if not event.has_subevents or not event.subevents.exists() else event.subevents.first()).get_date_from_display()
),
SimpleFunctionalMailTextPlaceholder(
'url', ['waiting_list_entry', 'event'],
lambda waiting_list_entry, event: build_absolute_uri(

View File

@@ -288,6 +288,7 @@ class OrderListExporter(MultiSheetListExporter):
headers.append(_('Sales channel'))
headers.append(_('Requires special attention'))
headers.append(_('Comment'))
headers.append(_('Follow-up date'))
headers.append(_('Positions'))
headers.append(_('E-mail address verified'))
headers.append(_('Payment providers'))
@@ -393,6 +394,7 @@ class OrderListExporter(MultiSheetListExporter):
row.append(order.sales_channel)
row.append(_('Yes') if order.checkin_attention else _('No'))
row.append(order.comment or "")
row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "")
row.append(order.pcnt)
row.append(_('Yes') if order.email_known_to_work else _('No'))
row.append(', '.join([
@@ -574,6 +576,7 @@ class OrderListExporter(MultiSheetListExporter):
_('Seat row'),
_('Seat number'),
_('Order comment'),
_('Follow-up date'),
]
questions = list(Question.objects.filter(event__in=self.events))
@@ -677,6 +680,7 @@ class OrderListExporter(MultiSheetListExporter):
row += ['', '', '', '', '']
row.append(order.comment)
row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "")
acache = {}
for a in op.answers.all():
# We do not want to localize Date, Time and Datetime question answers, as those can lead
@@ -721,7 +725,7 @@ class OrderListExporter(MultiSheetListExporter):
row += [
order.sales_channel,
order.locale,
row.append(_('Yes') if order.email_known_to_work else _('No'))
_('Yes') if order.email_known_to_work else _('No')
]
row.append(', '.join([
str(self.providers.get(p, p)) for p in sorted(set((op.payment_providers or '').split(',')))
@@ -780,7 +784,7 @@ class PaymentListExporter(ListExporter):
headers = [
_('Event slug'), _('Order'), _('Payment ID'), _('Creation date'), _('Completion date'), _('Status'),
_('Status code'), _('Amount'), _('Payment method'), _('Comment')
_('Status code'), _('Amount'), _('Payment method'), _('Comment'),
]
yield headers

View File

@@ -214,7 +214,7 @@ def _render_csp(h):
def _merge_csp(a, b):
for k, v in a.items():
if k in b:
a[k] += b[k]
a[k] += [i for i in b[k] if i not in a[k]]
for k, v in b.items():
if k not in a:

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.3 on 2021-06-17 10:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0193_auto_20210611_1355'),
]
operations = [
migrations.AddField(
model_name='membership',
name='canceled',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 3.2.3 on 2021-06-22 14:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0194_membership_canceled'),
]
operations = [
migrations.AddField(
model_name='invoiceline',
name='fee_internal_type',
field=models.CharField(max_length=190, null=True),
),
migrations.AddField(
model_name='invoiceline',
name='fee_type',
field=models.CharField(max_length=190, null=True),
),
]

View File

@@ -31,6 +31,7 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
from datetime import timedelta
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -231,9 +232,14 @@ class CheckinList(LoggedModel):
return rules
class SuccessfulCheckinManager(ScopedManager(organizer='list__event__organizer').__class__):
def get_queryset(self):
return super().get_queryset().filter(successful=True)
class Checkin(models.Model):
"""
A check-in object is created when a person enters or exits the event.
A check-in object is created when a ticket is scanned with our scanning apps.
"""
TYPE_ENTRY = 'entry'
TYPE_EXIT = 'exit'
@@ -241,13 +247,83 @@ class Checkin(models.Model):
(TYPE_ENTRY, _('Entry')),
(TYPE_EXIT, _('Exit')),
)
position = models.ForeignKey('pretixbase.OrderPosition', related_name='checkins', on_delete=models.CASCADE)
REASON_CANCELED = 'canceled'
REASON_INVALID = 'invalid'
REASON_UNPAID = 'unpaid'
REASON_PRODUCT = 'product'
REASON_RULES = 'rules'
REASON_REVOKED = 'revoked'
REASON_INCOMPLETE = 'incomplete'
REASON_ALREADY_REDEEMED = 'already_redeemed'
REASON_ERROR = 'error'
REASONS = (
(REASON_CANCELED, _('Order canceled')),
(REASON_INVALID, _('Unknown ticket')),
(REASON_UNPAID, _('Ticket not paid')),
(REASON_RULES, _('Forbidden by custom rule')),
(REASON_REVOKED, _('Ticket code revoked/changed')),
(REASON_INCOMPLETE, _('Information required')),
(REASON_ALREADY_REDEEMED, _('Ticket already used')),
(REASON_PRODUCT, _('Ticket type not allowed here')),
(REASON_ERROR, _('Server error')),
)
successful = models.BooleanField(
default=True,
)
error_reason = models.CharField(
max_length=100,
choices=REASONS,
null=True,
blank=True,
)
error_explanation = models.TextField(
null=True,
blank=True,
)
position = models.ForeignKey(
'pretixbase.OrderPosition',
related_name='all_checkins',
on_delete=models.CASCADE,
null=True, blank=True,
)
# For "raw" scans where we do not know which position they belong to (e.g. scan of signed
# barcode that is not in database).
raw_barcode = models.TextField(null=True, blank=True)
raw_item = models.ForeignKey(
'pretixbase.Item',
related_name='checkins',
on_delete=models.SET_NULL,
null=True, blank=True,
)
raw_variation = models.ForeignKey(
'pretixbase.ItemVariation',
related_name='checkins',
on_delete=models.SET_NULL,
null=True, blank=True,
)
raw_subevent = models.ForeignKey(
'pretixbase.SubEvent',
related_name='checkins',
on_delete=models.SET_NULL,
null=True, blank=True,
)
# Datetime of checkin, might be different from created if past scans are uploaded
datetime = models.DateTimeField(default=now)
nonce = models.CharField(max_length=190, null=True, blank=True)
# Datetime of creation on server
created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
list = models.ForeignKey(
'pretixbase.CheckinList', related_name='checkins', on_delete=models.PROTECT,
)
type = models.CharField(max_length=100, choices=CHECKIN_TYPES, default=TYPE_ENTRY)
nonce = models.CharField(max_length=190, null=True, blank=True)
forced = models.BooleanField(default=False)
device = models.ForeignKey(
'pretixbase.Device', related_name='checkins', on_delete=models.PROTECT, null=True, blank=True
@@ -257,7 +333,8 @@ class Checkin(models.Model):
)
auto_checked_in = models.BooleanField(default=False)
objects = ScopedManager(organizer='position__order__event__organizer')
all = ScopedManager(organizer='list__event__organizer')
objects = SuccessfulCheckinManager()
class Meta:
ordering = (('-datetime'),)
@@ -269,7 +346,8 @@ class Checkin(models.Model):
def save(self, **kwargs):
super().save(**kwargs)
self.position.order.touch()
if self.position:
self.position.order.touch()
self.list.event.cache.delete('checkin_count')
self.list.touch()
@@ -277,3 +355,7 @@ class Checkin(models.Model):
super().delete(**kwargs)
self.position.order.touch()
self.list.touch()
@property
def is_late_upload(self):
return self.created and abs(self.created - self.datetime) > timedelta(minutes=2)

View File

@@ -75,7 +75,6 @@ from .organizer import Organizer, Team
class EventMixin:
def clean(self):
if self.presale_start and self.presale_end and self.presale_start > self.presale_end:
raise ValidationError({'presale_end': _('The end of the presale period has to be later than its start.')})
@@ -494,11 +493,17 @@ class Event(EventMixin, LoggedModel):
)
seating_plan = models.ForeignKey('SeatingPlan', on_delete=models.PROTECT, null=True, blank=True,
related_name='events')
last_modified = models.DateTimeField(
auto_now=True, db_index=True
)
sales_channels = MultiStringField(
verbose_name=_('Restrict to specific sales channels'),
help_text=_('Only sell tickets for this event on the following sales channels.'),
default=default_sales_channels,
)
objects = ScopedManager(organizer='organizer')
class Meta:
@@ -1255,13 +1260,14 @@ class SubEvent(EventMixin, LoggedModel):
)
seating_plan = models.ForeignKey('SeatingPlan', on_delete=models.PROTECT, null=True, blank=True,
related_name='subevents')
last_modified = models.DateTimeField(
auto_now=True, db_index=True
)
items = models.ManyToManyField('Item', through='SubEventItem')
variations = models.ManyToManyField('ItemVariation', through='SubEventItemVariation')
last_modified = models.DateTimeField(
auto_now=True, db_index=True
)
objects = ScopedManager(organizer='event__organizer')
class Meta:

View File

@@ -343,6 +343,8 @@ class InvoiceLine(models.Model):
item = models.ForeignKey('Item', null=True, blank=True, on_delete=models.PROTECT)
variation = models.ForeignKey('ItemVariation', null=True, blank=True, on_delete=models.PROTECT)
attendee_name = models.TextField(null=True, blank=True)
fee_type = models.CharField(max_length=190, null=True, blank=True)
fee_internal_type = models.CharField(max_length=190, null=True, blank=True)
@property
def net_value(self):

View File

@@ -118,6 +118,10 @@ class Membership(models.Model):
verbose_name=_('Test mode'),
default=False
)
canceled = models.BooleanField(
verbose_name=_('Canceled'),
default=False
)
customer = models.ForeignKey(
Customer,
related_name='memberships',

View File

@@ -60,7 +60,7 @@ from django.utils.crypto import get_random_string
from django.utils.encoding import escape_uri_path
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.timezone import make_aware, now
from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_countries.fields import Country
from django_scopes import ScopedManager, scopes_disabled
@@ -217,6 +217,11 @@ class Order(LockModel, LoggedModel):
help_text=_("The text entered in this field will not be visible to the user and is available for your "
"convenience.")
)
custom_followup_at = models.DateField(
verbose_name=_("Follow-up date"),
help_text=_('We\'ll show you this order to be due for a follow-up on this day.'),
null=True, blank=True
)
checkin_attention = models.BooleanField(
verbose_name=_('Requires special attention'),
default=False,
@@ -300,6 +305,10 @@ class Order(LockModel, LoggedModel):
"""
return self.all_fees(manager='objects')
@property
def custom_followup_due(self):
return self.custom_followup_at and self.custom_followup_at <= now().astimezone(get_current_timezone()).date()
@cached_property
@scopes_disabled()
def count_positions(self):
@@ -590,6 +599,8 @@ class Order(LockModel, LoggedModel):
for gc in op.issued_gift_cards.all():
if gc.value != op.price:
return False
if op.granted_memberships.with_usages().filter(usages__gt=0):
return False
if self.user_cancel_deadline and now() > self.user_cancel_deadline:
return False
if self.status == Order.STATUS_PENDING:
@@ -899,7 +910,7 @@ class Order(LockModel, LoggedModel):
return str(e)
return True
def send_mail(self, subject: str, template: Union[str, LazyI18nString],
def send_mail(self, subject: Union[str, LazyI18nString], template: Union[str, LazyI18nString],
context: Dict[str, Any]=None, log_entry_type: str='pretix.event.order.email.sent',
user: User=None, headers: dict=None, sender: str=None, invoices: list=None,
auth=None, attach_tickets=False, position: 'OrderPosition'=None, auto_email=True,
@@ -942,7 +953,7 @@ class Order(LockModel, LoggedModel):
try:
email_content = render_mail(template, context)
subject = subject.format_map(TolerantDict(context))
subject = str(subject).format_map(TolerantDict(context))
mail(
recipient, subject, template, context,
self.event, self.locale, self, headers=headers, sender=sender,
@@ -1689,7 +1700,7 @@ class OrderPayment(models.Model):
})
if self.order.pending_sum + r.amount == Decimal('0.00'):
self.refund.done()
r.done()
return r
@@ -2054,6 +2065,14 @@ class OrderPosition(AbstractPosition):
def sort_key(self):
return self.addon_to.positionid if self.addon_to else self.positionid, self.addon_to_id or 0
@property
def checkins(self):
"""
Related manager for all successful checkins. Use ``all_checkins`` instead if you want
canceled positions as well.
"""
return self.all_checkins(manager='objects')
@property
def generate_ticket(self):
if self.item.generate_tickets is not None:
@@ -2168,7 +2187,7 @@ class OrderPosition(AbstractPosition):
def send_mail(self, subject: str, template: Union[str, LazyI18nString],
context: Dict[str, Any]=None, log_entry_type: str='pretix.event.order.email.sent',
user: User=None, headers: dict=None, sender: str=None, invoices: list=None,
auth=None, attach_tickets=False):
auth=None, attach_tickets=False, attach_ical=False):
"""
Sends an email to the attendee. Basically, this method does two things:
@@ -2185,6 +2204,7 @@ class OrderPosition(AbstractPosition):
:param headers: Dictionary with additional mail headers
:param sender: Custom email sender.
:param attach_tickets: Attach tickets of this order, if they are existing and ready to download
:param attach_ical: Attach relevant ICS files
"""
from pretix.base.services.mail import (
SendMailException, mail, render_mail,
@@ -2204,7 +2224,9 @@ class OrderPosition(AbstractPosition):
recipient, subject, template, context,
self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
position=self,
invoices=invoices, attach_tickets=attach_tickets
invoices=invoices,
attach_tickets=attach_tickets,
attach_ical=attach_ical,
)
except SendMailException:
raise
@@ -2219,6 +2241,7 @@ class OrderPosition(AbstractPosition):
'recipient': recipient,
'invoices': [i.pk for i in invoices] if invoices else [],
'attach_tickets': attach_tickets,
'attach_ical': attach_ical,
}
)

View File

@@ -19,6 +19,7 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import datetime
import re
from collections import defaultdict
from decimal import Decimal, DecimalException
@@ -40,7 +41,7 @@ from pretix.base.channels import get_all_sales_channels
from pretix.base.forms.questions import guess_country
from pretix.base.models import (
ItemVariation, OrderPosition, Question, QuestionAnswer, QuestionOption,
Seat,
Seat, SubEvent,
)
from pretix.base.services.pricing import get_price
from pretix.base.settings import (
@@ -160,6 +161,10 @@ class SubeventColumn(ImportColumn):
verbose_name = pgettext_lazy('subevents', 'Date')
default_value = None
def __init__(self, *args, **kwargs):
self._subevent_cache = {}
super().__init__(*args, **kwargs)
@cached_property
def subevents(self):
return list(self.event.subevents.filter(active=True).order_by('date_from'))
@@ -172,6 +177,30 @@ class SubeventColumn(ImportColumn):
def clean(self, value, previous_values):
if not value:
raise ValidationError(pgettext("subevent", "You need to select a date."))
if value in self._subevent_cache:
return self._subevent_cache[value]
input_formats = formats.get_format('DATETIME_INPUT_FORMATS', use_l10n=True)
for format in input_formats:
try:
d = datetime.datetime.strptime(value, format)
d = self.event.timezone.localize(d)
try:
se = self.event.subevents.get(
active=True,
date_from__gt=d - datetime.timedelta(seconds=1),
date_from__lt=d + datetime.timedelta(seconds=1),
)
self._subevent_cache[value] = se
return se
except SubEvent.DoesNotExist:
raise ValidationError(pgettext("subevent", "No matching date was found."))
except SubEvent.MultipleObjectsReturned:
raise ValidationError(pgettext("subevent", "Multiple matching dates were found."))
except (ValueError, TypeError):
continue
matches = [
p for p in self.subevents
if str(p.pk) == value or any(
@@ -181,6 +210,8 @@ class SubeventColumn(ImportColumn):
raise ValidationError(pgettext("subevent", "No matching date was found."))
if len(matches) > 1:
raise ValidationError(pgettext("subevent", "Multiple matching dates were found."))
self._subevent_cache[value] = matches[0]
return matches[0]
def assign(self, value, order, position, invoice_address, **kwargs):

View File

@@ -50,6 +50,7 @@ from django.conf import settings
from django.contrib.staticfiles import finders
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.functional import SimpleLazyObject
from django.utils.html import conditional_escape
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -551,6 +552,12 @@ def get_first_scan(op: OrderPosition):
return ""
reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={
'delete_harakat': True,
'support_ligatures': False,
}))
class Renderer:
def __init__(self, event, layout, background_file):
@@ -568,6 +575,8 @@ class Renderer:
@classmethod
def _register_fonts(cls):
if hasattr(cls, '_fonts_registered'):
return
pdfmetrics.registerFont(TTFont('Open Sans', finders.find('fonts/OpenSans-Regular.ttf')))
pdfmetrics.registerFont(TTFont('Open Sans I', finders.find('fonts/OpenSans-Italic.ttf')))
pdfmetrics.registerFont(TTFont('Open Sans B', finders.find('fonts/OpenSans-Bold.ttf')))
@@ -582,6 +591,8 @@ class Renderer:
if 'bolditalic' in styles:
pdfmetrics.registerFont(TTFont(family + ' B I', finders.find(styles['bolditalic']['truetype'])))
cls._fonts_registered = True
def _draw_poweredby(self, canvas: Canvas, op: OrderPosition, o: dict):
content = o.get('content', 'dark')
if content not in ('dark', 'white'):
@@ -716,11 +727,6 @@ class Renderer:
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
# to resolve all ligatures and python-bidi to switch RTL texts.
configuration = {
'delete_harakat': True,
'support_ligatures': False,
}
reshaper = ArabicReshaper(configuration=configuration)
try:
text = "<br/>".join(get_display(reshaper.reshape(l)) for l in text.split("<br/>"))
except:

View File

@@ -22,6 +22,8 @@
import base64
import inspect
import struct
from collections import namedtuple
from typing import Optional
from cryptography.hazmat.backends.openssl.backend import Backend
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
@@ -37,6 +39,8 @@ from pretix.base.models import Item, ItemVariation, SubEvent
from pretix.base.secretgenerators import pretix_sig1_pb2
from pretix.base.signals import register_ticket_secret_generators
ParsedSecret = namedtuple('AnalyzedSecret', 'item variation subevent attendee_name opaque_id')
class BaseTicketSecretGenerator:
"""
@@ -72,6 +76,14 @@ class BaseTicketSecretGenerator:
"""
return False
def parse_secret(self, secret: str) -> Optional[ParsedSecret]:
"""
Given a ``secret``, return an ``ParsedSecret`` with the information decoded from the secret, if possible.
Any value of ``ParsedSecret`` may be ``None``, and if parsing is not possible at all, you can ``None`` (as
the default implementation does).
"""
return None
def generate_secret(self, item: Item, variation: ItemVariation = None, subevent: SubEvent = None,
attendee_name: str = None, current_secret: str = None, force_invalidate=False) -> str:
"""
@@ -181,6 +193,15 @@ class Sig1TicketSecretGenerator(BaseTicketSecretGenerator):
except:
return None
def parse_secret(self, secret: str) -> Optional[ParsedSecret]:
ticket = self._parse(secret)
if ticket:
item = self.event.items.filter(pk=ticket.item).first() if ticket.item else None
subevent = self.event.subevents.filter(pk=ticket.subevent).first() if ticket.subevent else None
variation = item.variations.filter(pk=ticket.variation).first() if item and ticket.subevent else None
opaque_id = ticket.seed
return self.ParsedSecret(item=item, subevent=subevent, variation=variation, opaque_id=opaque_id, attendee_name=None)
def generate_secret(self, item: Item, variation: ItemVariation = None, subevent: SubEvent = None,
current_secret: str = None, force_invalidate=False):
if current_secret and not force_invalidate:

View File

@@ -227,7 +227,7 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
if not self.request.called_directly and counter % max(10, total // 100) == 0:
self.update_state(
state='PROGRESS',
meta={'value': round(counter / total * 100, 2)}
meta={'value': round(counter / total * 100 if total else 0, 2)}
)
except LockTimeoutException:
logger.exception("Could not cancel order")
@@ -285,7 +285,7 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
if not self.request.called_directly and counter % max(10, total // 100) == 0:
self.update_state(
state='PROGRESS',
meta={'value': round(counter / total * 100, 2)}
meta={'value': round(counter / total * 100 if total else 0, 2)}
)
if send_waitinglist:
@@ -296,6 +296,6 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
if not self.request.called_directly and counter % max(10, total // 100) == 0:
self.update_state(
state='PROGRESS',
meta={'value': round(counter / total * 100, 2)}
meta={'value': round(counter / total * 100 if total else 0, 2)}
)
return failed

View File

@@ -566,7 +566,8 @@ def _save_answers(op, answers, given_answers):
def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict, force=False,
ignore_unpaid=False, nonce=None, datetime=None, questions_supported=True,
user=None, auth=None, canceled_supported=False, type=Checkin.TYPE_ENTRY):
user=None, auth=None, canceled_supported=False, type=Checkin.TYPE_ENTRY,
raw_barcode=None):
"""
Create a checkin for this particular order position and check-in list. Fails with CheckInError if the check in is
not valid at this time.
@@ -623,12 +624,6 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
_('This order is not marked as paid.'),
'unpaid'
)
elif require_answers and not force and questions_supported:
raise RequiredQuestionsError(
_('You need to answer questions to complete this check-in.'),
'incomplete',
require_answers
)
if type == Checkin.TYPE_ENTRY and clist.rules and not force:
rule_data = LazyRuleVars(op, clist, dt)
@@ -643,6 +638,13 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
reason=reason
)
if require_answers and not force and questions_supported:
raise RequiredQuestionsError(
_('You need to answer questions to complete this check-in.'),
'incomplete',
require_answers
)
device = None
if isinstance(auth, Device):
device = auth
@@ -668,6 +670,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
gate=device.gate if device else None,
nonce=nonce,
forced=force and not entry_allowed,
raw_barcode=raw_barcode,
)
op.order.log_action('pretix.event.checkin', data={
'position': op.id,
@@ -676,6 +679,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
'forced': force or op.order.status != Order.STATUS_PAID,
'datetime': dt,
'type': type,
'answers': {k.pk: str(v) for k, v in given_answers.items()},
'list': clist.pk
}, user=user, auth=auth)
checkin_created.send(op.order.event, checkin=ci)

View File

@@ -244,7 +244,9 @@ def build_invoice(invoice: Invoice) -> Invoice:
event_date_to=None if invoice.event.has_subevents else invoice.event.date_to,
tax_value=fee.tax_value,
tax_rate=fee.tax_rate,
tax_name=fee.tax_rule.name if fee.tax_rule else ''
tax_name=fee.tax_rule.name if fee.tax_rule else '',
fee_type=fee.fee_type,
fee_internal_type=fee.internal_type or None,
)
if fee.tax_rule and fee.tax_rule.is_reverse_charge(ia) and fee.value and not fee.tax_value:

View File

@@ -45,7 +45,6 @@ from email.utils import formataddr
from typing import Any, Dict, List, Sequence, Union
from urllib.parse import urljoin, urlparse
import cssutils
import pytz
import requests
from bs4 import BeautifulSoup
@@ -79,7 +78,6 @@ from pretix.presale.ical import get_ical
logger = logging.getLogger('pretix.base.mail')
INVALID_ADDRESS = 'invalid-pretix-mail-address'
cssutils.log.setLevel(logging.CRITICAL)
class TolerantDict(dict):
@@ -176,8 +174,8 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
subject = str(subject).format_map(TolerantDict(context))
sender = (
sender or
(event.settings.get('mail_from') if event else settings.MAIL_FROM) or
(organizer.settings.get('mail_from') if organizer else settings.MAIL_FROM) or
(event.settings.get('mail_from') if event else None) or
(organizer.settings.get('mail_from') if organizer else None) or
settings.MAIL_FROM
)
if event:
@@ -628,6 +626,7 @@ def encoder_linelength(msg):
def convert_image_to_cid(image_src, cid_id, verify_ssl=True):
image_src = image_src.strip()
try:
if image_src.startswith('data:image/'):
image_type, image_content = image_src.split(',', 1)

View File

@@ -141,6 +141,11 @@ def validate_memberships_in_order(customer: Customer, positions: List[AbstractPo
_('You selected a membership that is connected to a different customer account.')
)
if m.canceled:
raise ValidationError(
_('You selected membership that has been canceled.')
)
if m.testmode != testmode:
raise ValidationError(
_('You can only use a test mode membership for test mode tickets.')

View File

@@ -19,11 +19,11 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import css_inline
from django.conf import settings
from django.template.loader import get_template
from django.utils.timezone import override
from django_scopes import scope, scopes_disabled
from inlinestyler.utils import inline_css
from pretix.base.i18n import language
from pretix.base.models import LogEntry, NotificationSetting, User
@@ -131,7 +131,11 @@ def send_notification_mail(notification: Notification, user: User):
}
tpl_html = get_template('pretixbase/email/notification.html')
body_html = inline_css(tpl_html.render(ctx))
body_html = tpl_html.render(ctx)
inliner = css_inline.CSSInliner(remove_style_tags=True)
body_html = inliner.inline(body_html)
tpl_plain = get_template('pretixbase/email/notification.txt')
body_plain = tpl_plain.render(ctx)

View File

@@ -149,8 +149,8 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
raise OrderError('The order was not canceled.')
with order.event.lock() as now_dt:
is_available = force or order._is_still_available(now_dt, count_waitinglist=False, check_voucher_usage=True,
check_memberships=True)
is_available = order._is_still_available(now_dt, count_waitinglist=False, check_voucher_usage=True,
check_memberships=True, force=force)
if is_available is True:
if order.payment_refund_sum >= order.total:
order.status = Order.STATUS_PAID
@@ -177,6 +177,10 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
gc = GiftCard.objects.select_for_update().get(pk=gc.pk)
gc.transactions.create(value=position.price, order=order)
break
for m in position.granted_memberships.all():
m.canceled = False
m.save()
else:
raise OrderError(is_available)
@@ -222,8 +226,8 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, user: User
change(was_expired=False)
else:
with order.event.lock() as now_dt:
is_available = order._is_still_available(now_dt, count_waitinglist=False)
if is_available is True or force is True:
is_available = order._is_still_available(now_dt, count_waitinglist=False, force=force)
if is_available is True:
change(was_expired=True)
else:
raise OrderError(is_available)
@@ -410,6 +414,10 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
else:
gc.transactions.create(value=-position.price, order=order)
for m in position.granted_memberships.all():
m.canceled = True
m.save()
if cancellation_fee:
with order.event.lock():
for position in order.positions.all():
@@ -1768,7 +1776,26 @@ class OrderChangeManager:
else:
gc.transactions.create(value=-op.position.price, order=self.order)
for m in op.position.granted_memberships.with_usages().all():
m.canceled = True
m.save()
for opa in op.position.addons.all():
for gc in opa.issued_gift_cards.all():
gc = GiftCard.objects.select_for_update().get(pk=gc.pk)
if gc.value < opa.position.price:
raise OrderError(_(
'A position can not be canceled since the gift card {card} purchased in this order has '
'already been redeemed.').format(
card=gc.secret
))
else:
gc.transactions.create(value=-opa.position.price, order=self.order)
for m in opa.granted_memberships.with_usages().all():
m.canceled = True
m.save()
self.order.log_action('pretix.event.order.changed.cancel', user=self.user, auth=self.auth, data={
'position': opa.pk,
'positionid': opa.positionid,

View File

@@ -454,7 +454,9 @@ Arguments: ``checkin``
This signal is sent out every time a check-in is created (i.e. an order position is marked as
checked in). It is not send if the position was already checked in and is force-checked-in a second time.
The check-in object is given as the first argument
The check-in object is given as the first argument.
For backwards compatibility reasons, this signal is only sent when a **successful** scan is saved.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""

View File

@@ -22,6 +22,5 @@
<a id='goback' href='#'>{% trans "Take a step back" %}</a>
&middot; <a id='reload' href='#'>{% trans "Try again" %}</a>
</p>
<img src="{% static "pretixbase/img/pretix-logo.svg" %}" class="logo"/>
</div>
{% endblock %}

View File

@@ -11,19 +11,23 @@
background-position: top;
background-repeat: repeat-x;
font-family: "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
font-size: 16px;
line-height: 22px;
color: #333;
margin: 0;
padding-top: 20px;
}
table.layout > tr > td {
table.layout > tr > td,
table.layout > tbody > tr > td,
table.layout > thead > tr > td {
background-color: white;
padding: 0;
}
table.layout > tr > td.header {
table.layout > tr > td.header,
table.layout > tbody > tr > td.header,
table.layout > thead > tr > td.header {
padding: 0 20px;
text-align: center;
}
@@ -151,10 +155,14 @@
font-size: 12px;
}
.cart-table > tr > td:first-child {
.cart-table > tr > td:first-child,
.cart-table > tbody > tr > td:first-child,
.cart-table > thead > tr > td:first-child {
width: 40px;
}
.order-details > tr > td:first-child {
.order-details > tr > td:first-child,
.order-details > tbody > tr > td:first-child,
.order-details > thead > tr > td:first-child {
width: 20%;
}
.order-details td {

View File

@@ -13,23 +13,29 @@
background-position: top;
background-repeat: repeat-x;
font-family: "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
font-size: 16px;
line-height: 1.4;
color: #333;
margin: 0;
padding-top: 20px;
}
table.layout > tr > td {
table.layout > tr > td,
table.layout > tbody > tr > td,
table.layout > thead > tr > td {
background-color: white;
padding: 0;
}
table.layout > tr > td.logo {
table.layout > tr > td.logo,
table.layout > tbody > tr > td.logo,
table.layout > thead > tr > td.logo {
padding: 20px 0 0 0;
}
table.layout > tr > td.header {
table.layout > tr > td.header,
table.layout > tbody > tr > td.header,
table.layout > thead > tr > td.header {
padding: 0 20px;
text-align: center;
}
@@ -130,7 +136,9 @@
vertical-align: top;
text-align: left;
}
table.layout > tr > td.containertd {
table.layout > tr > td.containertd,
table.layout > tbody > tr > td.containertd,
table.layout > thead > tr > td.containertd {
padding: 15px 0;
}
@@ -162,10 +170,14 @@
font-size: 12px;
}
.cart-table > tr > td:first-child {
.cart-table > tr > td:first-child,
.cart-table > tbody > tr > td:first-child,
.cart-table > thead > tr > td:first-child {
width: 40px;
}
.order-details > tr > td:first-child {
.order-details > tr > td:first-child,
.order-details > tbody > tr > td:first-child,
.order-details > thead > tr > td:first-child {
width: 20%;
}
.order-details td {

View File

@@ -0,0 +1,56 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError, Variable
from django.templatetags.cache import CacheNode
register = Library()
class DummyNode(Node):
def __init__(self, nodelist, *args):
self.nodelist = nodelist
def render(self, context):
value = self.nodelist.render(context)
return value
@register.tag('cache_large')
def do_cache(parser, token):
nodelist = parser.parse(('endcache_large',))
parser.delete_first_token()
tokens = token.split_contents()
if len(tokens) < 3:
raise TemplateSyntaxError("'%r' tag requires at least 2 arguments." % tokens[0])
if not settings.CACHE_LARGE_VALUES_ALLOWED:
return DummyNode(
nodelist,
)
return CacheNode(
nodelist, parser.compile_filter(tokens[1]),
tokens[2], # fragment_name can't be a variable.
[parser.compile_filter(t) for t in tokens[3:]],
Variable(repr(settings.CACHE_LARGE_VALUES_ALIAS)),
)

View File

@@ -43,6 +43,7 @@ from django import template
from django.conf import settings
from django.core import signing
from django.urls import reverse
from django.utils.functional import SimpleLazyObject
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.safestring import mark_safe
from tlds import tld_set
@@ -100,9 +101,9 @@ ALLOWED_ATTRIBUTES = {
ALLOWED_PROTOCOLS = ['http', 'https', 'mailto', 'tel']
URL_RE = 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)))
EMAIL_RE = build_email_re(tlds=sorted(tld_set, key=len, reverse=True))
EMAIL_RE = SimpleLazyObject(lambda: build_email_re(tlds=sorted(tld_set, key=len, reverse=True)))
def safelink_callback(attrs, new=False):

View File

@@ -106,7 +106,7 @@ class AsyncMixin:
elif res.state == 'PROGRESS':
data.update({
'started': True,
'percentage': res.result.get('value', 0)
'percentage': res.result.get('value', 0) if isinstance(res.result, dict) else 0
})
elif res.state == 'STARTED':
data.update({
@@ -205,11 +205,11 @@ class AsyncFormView(AsyncMixin, FormView):
known_errortypes = ['ValidationError']
def __init_subclass__(cls):
def async_execute(self, *, request_path, form_kwargs, locale, tz, organizer=None, event=None, user=None, session_key=None):
def async_execute(self, *, request_path, query_string, form_kwargs, locale, tz, organizer=None, event=None, user=None, session_key=None):
view_instance = cls()
form_kwargs['data'] = QueryDict(form_kwargs['data'])
req = RequestFactory().post(
request_path,
request_path + '?' + query_string,
data=form_kwargs['data'].urlencode(),
content_type='application/x-www-form-urlencoded'
)
@@ -271,6 +271,7 @@ class AsyncFormView(AsyncMixin, FormView):
form_kwargs.pop('event', None)
kwargs = {
'request_path': self.request.path,
'query_string': self.request.GET.urlencode(),
'form_kwargs': form_kwargs,
'locale': get_language(),
'tz': get_current_timezone().zone,

View File

@@ -110,7 +110,9 @@ class EventWizardFoundationForm(forms.Form):
self.fields['organizer'].widget.choices = self.fields['organizer'].choices
if len(self.fields['organizer'].choices) == 1:
self.fields['organizer'].initial = self.fields['organizer'].queryset.first()
organizer = self.fields['organizer'].queryset.first()
self.fields['organizer'].initial = organizer
self.fields['locales'].initial = organizer.settings.locales
class EventWizardBasicsForm(I18nModelForm):
@@ -176,7 +178,7 @@ class EventWizardBasicsForm(I18nModelForm):
self.locales = kwargs.get('locales')
self.has_subevents = kwargs.pop('has_subevents')
self.user = kwargs.pop('user')
kwargs.pop('session')
self.session = kwargs.pop('session')
super().__init__(*args, **kwargs)
if 'timezone' not in self.initial:
self.initial['timezone'] = get_current_timezone_name()
@@ -191,7 +193,7 @@ class EventWizardBasicsForm(I18nModelForm):
del self.fields['presale_end']
del self.fields['date_to']
if self.has_control_rights(self.user, self.organizer):
if self.has_control_rights(self.user, self.organizer, self.session):
del self.fields['team']
else:
self.fields['team'].queryset = self.user.teams.filter(organizer=self.organizer)
@@ -233,11 +235,11 @@ class EventWizardBasicsForm(I18nModelForm):
return slug
@staticmethod
def has_control_rights(user, organizer):
def has_control_rights(user, organizer, session):
return user.teams.filter(
organizer=organizer, all_events=True, can_change_event_settings=True, can_change_items=True,
can_change_orders=True, can_change_vouchers=True
).exists() or user.is_staff
).exists() or user.has_active_staff_session(session.session_key)
class EventChoiceMixin:

View File

@@ -48,15 +48,17 @@ from django.utils.formats import date_format, localize
from django.utils.functional import cached_property
from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext, gettext_lazy as _, pgettext_lazy
from django_scopes.forms import SafeModelChoiceField
from pretix.base.channels import get_all_sales_channels
from pretix.base.forms.widgets import (
DatePickerWidget, SplitDateTimePickerWidget,
)
from pretix.base.models import (
Checkin, Event, EventMetaProperty, EventMetaValue, Invoice, InvoiceAddress,
Item, Order, OrderPayment, OrderPosition, OrderRefund, Organizer, Question,
QuestionAnswer, SubEvent,
Checkin, CheckinList, Device, Event, EventMetaProperty, EventMetaValue,
Gate, Invoice, InvoiceAddress, Item, Order, OrderPayment, OrderPosition,
OrderRefund, Organizer, Question, QuestionAnswer, SubEvent, Team,
TeamAPIToken, TeamInvite,
)
from pretix.base.signals import register_payment_providers
from pretix.control.forms.widgets import Select2
@@ -204,6 +206,10 @@ class OrderFilterForm(FilterForm):
('na', _('Approved, payment pending')),
('pa', _('Approval pending')),
)),
(_('Follow-up date'), (
('custom_followup_at', _('Follow-up configured')),
('custom_followup_due', _('Follow-up due')),
)),
('testmode', _('Test mode')),
),
required=False,
@@ -323,6 +329,14 @@ class OrderFilterForm(FilterForm):
status=Order.STATUS_PENDING,
require_approval=False
)
elif s == 'custom_followup_at':
qs = qs.filter(
custom_followup_at__isnull=False
)
elif s == 'custom_followup_due':
qs = qs.filter(
custom_followup_at__lte=now().astimezone(get_current_timezone()).date()
)
elif s == 'testmode':
qs = qs.filter(
testmode=True
@@ -1036,6 +1050,26 @@ class CustomerFilterForm(FilterForm):
}),
required=False
)
status = forms.ChoiceField(
label=_('Status'),
required=False,
choices=(
('', _('All')),
('active', _('active')),
('disabled', _('disabled')),
('unverified', _('not yet activated')),
)
)
memberships = forms.ChoiceField(
label=_('Memberships'),
required=False,
choices=(
('', _('All')),
('no', _('Has no memberships')),
('any', _('Has any membership')),
('valid', _('Has valid membership')),
)
)
def __init__(self, *args, **kwargs):
kwargs.pop('request')
@@ -1052,12 +1086,26 @@ class CustomerFilterForm(FilterForm):
| Q(identifier__istartswith=query)
)
if fdata.get('ordering'):
qs = qs.order_by(self.get_order_by())
else:
qs = qs.order_by('-email')
if fdata.get('status') == 'active':
qs = qs.filter(is_active=True, is_verified=True)
elif fdata.get('status') == 'disabled':
qs = qs.filter(is_active=False)
elif fdata.get('status') == 'unverified':
qs = qs.filter(is_verified=False)
return qs
if fdata.get('memberships') == 'no':
qs = qs.filter(memberships__isnull=True)
elif fdata.get('memberships') == 'any':
qs = qs.filter(memberships__isnull=False)
elif fdata.get('memberships') == 'valid':
qs = qs.filter(memberships__date_start__lt=now(), memberships__date_end__gt=now(), memberships__canceled=False)
if fdata.get('ordering'):
qs = qs.order_by(self.get_order_by())
else:
qs = qs.order_by('-email')
return qs.distinct()
class TeamFilterForm(FilterForm):
@@ -1083,11 +1131,25 @@ class TeamFilterForm(FilterForm):
if fdata.get('query'):
query = fdata.get('query')
qs = qs.filter(
Q(name__icontains=query)
| Q(members__email__icontains=query)
| Q(members__fullname__icontains=query)
| Q(invites__email__icontains=query)
| Q(tokens__name__icontains=query)
Q(Exists(
Team.members.through.objects.filter(
Q(user__email__icontains=query) | Q(user__fullname__icontains=query),
team_id=OuterRef('pk'),
)
))
| Q(Exists(
TeamInvite.objects.filter(
email__icontains=query,
team_id=OuterRef('pk'),
)
))
| Q(Exists(
TeamAPIToken.objects.filter(
name__icontains=query,
team_id=OuterRef('pk'),
)
))
| Q(name__icontains=query)
)
if fdata.get('ordering'):
@@ -1736,3 +1798,129 @@ class OverviewFilterForm(FilterForm):
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
elif 'subevent':
del self.fields['subevent']
class CheckinFilterForm(FilterForm):
status = forms.ChoiceField(
label=_('Status'),
choices=[
('', _('All check-ins')),
('successful', _('Successful check-ins')),
('unsuccessful', _('Unsuccessful check-ins')),
] + list(Checkin.REASONS),
required=False
)
type = forms.ChoiceField(
label=_('Scan type'),
choices=[
('', _('All directions')),
] + list(Checkin.CHECKIN_TYPES),
required=False
)
itemvar = forms.ChoiceField(
label=_("Product"),
required=False
)
device = SafeModelChoiceField(
label=_('Device'),
empty_label=_('All devices'),
queryset=Device.objects.none(),
required=False
)
gate = SafeModelChoiceField(
label=_('Gate'),
empty_label=_('All gates'),
queryset=Gate.objects.none(),
required=False
)
checkin_list = SafeModelChoiceField(queryset=CheckinList.objects.none(), required=False) # overridden later
datetime_from = forms.SplitDateTimeField(
widget=SplitDateTimePickerWidget(attrs={
}),
label=pgettext_lazy('filter', 'Start date'),
required=False,
)
datetime_until = forms.SplitDateTimeField(
widget=SplitDateTimePickerWidget(attrs={
}),
label=pgettext_lazy('filter', 'End date'),
required=False,
)
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
super().__init__(*args, **kwargs)
self.fields['device'].queryset = self.event.organizer.devices.all()
self.fields['gate'].queryset = self.event.organizer.gates.all()
self.fields['checkin_list'].queryset = self.event.checkin_lists.all()
self.fields['checkin_list'].widget = Select2(
attrs={
'data-model-select2': 'generic',
'data-select2-url': reverse('control:event.orders.checkinlists.select2', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
}),
'data-placeholder': _('Check-in list'),
}
)
self.fields['checkin_list'].widget.choices = self.fields['checkin_list'].choices
self.fields['checkin_list'].label = _('Check-in list')
choices = [('', _('All products'))]
for i in self.event.items.prefetch_related('variations').all():
variations = list(i.variations.all())
if variations:
choices.append((str(i.pk), _('{product} Any variation').format(product=i.name)))
for v in variations:
choices.append(('%d-%d' % (i.pk, v.pk), '%s %s' % (i.name, v.value)))
else:
choices.append((str(i.pk), i.name))
self.fields['itemvar'].choices = choices
def filter_qs(self, qs):
fdata = self.cleaned_data
if fdata.get('status'):
s = fdata.get('status')
if s == 'successful':
qs = qs.filter(successful=True)
elif s == 'unsuccessful':
qs = qs.filter(successful=False)
elif s:
qs = qs.filter(successful=False, error_reason=s)
if fdata.get('type'):
qs = qs.filter(type=fdata.get('type'))
if fdata.get('itemvar'):
if '-' in fdata.get('itemvar'):
qs = qs.alias(
item_id=Coalesce('raw_item_id', 'position__item_id'),
variation_id=Coalesce('raw_variation_id', 'position__variation_id'),
).filter(
item_id=fdata.get('itemvar').split('-')[0],
variation_id=fdata.get('itemvar').split('-')[1]
)
else:
qs = qs.alias(
item_id=Coalesce('raw_item_id', 'position__item_id'),
).filter(item_id=fdata.get('itemvar'))
if fdata.get('device'):
qs = qs.filter(device_id=fdata.get('device').pk)
if fdata.get('gate'):
qs = qs.filter(gate_id=fdata.get('gate').pk)
if fdata.get('checkin_list'):
qs = qs.filter(list_id=fdata.get('checkin_list').pk)
if fdata.get('datetime_from'):
qs = qs.filter(datetime__gte=fdata.get('datetime_from'))
if fdata.get('datetime_until'):
qs = qs.filter(datetime__lte=fdata.get('datetime_until'))
return qs

View File

@@ -230,12 +230,13 @@ class ExporterForm(forms.Form):
class CommentForm(I18nModelForm):
class Meta:
model = Order
fields = ['comment', 'checkin_attention']
fields = ['comment', 'checkin_attention', 'custom_followup_at']
widgets = {
'comment': forms.Textarea(attrs={
'rows': 3,
'class': 'helper-width-100',
}),
'custom_followup_at': DatePickerWidget(),
}

View File

@@ -298,6 +298,7 @@ class OrganizerSettingsForm(SettingsForm):
'giftcard_expiry_years',
'locales',
'region',
'meta_noindex',
'event_team_provisioning',
'primary_color',
'theme_color_success',
@@ -560,7 +561,7 @@ class MembershipUpdateForm(forms.ModelForm):
class Meta:
model = Membership
fields = ['testmode', 'membership_type', 'date_start', 'date_end', 'attendee_name_parts']
fields = ['testmode', 'membership_type', 'date_start', 'date_end', 'attendee_name_parts', 'canceled']
field_classes = {
'date_start': SplitDateTimeField,
'date_end': SplitDateTimeField,

View File

@@ -360,6 +360,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'),
'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'),
'pretix.event.order.comment': _('The order\'s internal comment has been updated.'),
'pretix.event.order.custom_followup_at': _('The order\'s follow-up date has been updated.'),
'pretix.event.order.checkin_attention': _('The order\'s flag to require attention at check-in has been '
'toggled.'),
'pretix.event.order.payment.changed': _('A new payment {local_id} has been started instead of the previous one.'),

View File

@@ -297,7 +297,15 @@ def get_event_navigation(request: HttpRequest):
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.checkin' in url.url_name,
'active': 'event.orders.checkinlists' in url.url_name,
},
{
'label': _('Check-in history'),
'url': reverse('control:event.orders.checkins', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.checkins' in url.url_name,
},
]
})

View File

@@ -0,0 +1,170 @@
{% extends "pretixcontrol/items/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Check-in history" %}{% endblock %}
{% block inside %}
<h1>{% trans "Check-in history" %}</h1>
<form class="" action="" method="get">
<div class="row filter-form">
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.checkin_list layout='inline' %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.status layout='inline' %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.type layout='inline' %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.device layout='inline' %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.datetime_from layout='inline' %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.datetime_until layout='inline' %}
</div>
<div class="col-md-2 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.gate layout='inline' %}
</div>
<div class="col-md-2 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.itemvar layout='inline' %}
</div>
<div class="col-md-2 col-xs-6">
<button class="btn btn-block btn-primary" type="submit">
<span class="fa fa-filter"></span>
<span class="hidden-md">{% trans "Filter" %}</span>
</button>
</div>
</div>
</form>
{% if checkins|length == 0 %}
<div class="empty-collection">
<p>
{% if request.GET %}
{% trans "Your search did not match any check-ins." %}
{% else %}
{% blocktrans trimmed %}
You haven't scanned any tickets yet.
{% endblocktrans %}
{% endif %}
</p>
</div>
{% else %}
<div class="table-responsive">
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Time of scan" %}</th>
<th>{% trans "Scan type" %}<br>{% trans "Check-in list" %}</th>
<th>{% trans "Result" %}</th>
<th>{% trans "Ticket" %}<br>{% trans "Product" %}</th>
<th>{% trans "Device" %}<br>{% trans "Gate" %}</th>
</tr>
</thead>
<tbody>
{% for c in checkins %}
<tr>
<td>
{{ c.datetime|date:"SHORT_DATETIME_FORMAT" }}
{% if c.type == "exit" %}
{% if c.auto_checked_in %}
<span class="fa fa-fw fa-hourglass-end" data-toggle="tooltip_html"
title="{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically marked not present: {{ date }}{% endblocktrans %}"></span>
{% endif %}
{% elif c.forced and c.successful %}
<span class="fa fa-fw fa-warning" data-toggle="tooltip_html"
title="{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Additional entry scan: {{ date }}{% endblocktrans %}"></span>
{% elif c.forced and not c.successful %}
<br>
<small class="text-muted">{% trans "Failed in offline mode" %}</small>
{% elif c.auto_checked_in %}
<span class="fa fa-fw fa-magic" data-toggle="tooltip_html"
title="{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}"></span>
{% endif %}
</td>
<td>
{% if c.type == "exit" %}<span class="fa fa-fw fa-sign-out"></span>{% endif %}
{% if c.type == "entry" %}<span class="fa fa-fw fa-sign-in"></span>{% endif %}
{{ c.get_type_display }}
<br>
<small>
<a href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=c.list.id %}">{{ c.list }}</a>
</small>
</td>
<td>
{% if c.successful %}
<span class="label label-success">
<span class="fa fa-fw fa-check"></span> {% trans "Successful" context "checkin_result" %}
</span>
{% else %}
<span class="label label-danger">
<span class="fa fa-fw fa-exclamation-triangle"></span>
{% trans "Denied" context "checkin_result" %}
</span>
<br>
<small>
{{ c.get_error_reason_display }}
{% if c.error_explanation %}
<br>
{{ c.error_explanation }}
{% endif %}
</small>
{% endif %}
</td>
<td>
{% if c.position %}
<span class="fa fa-user fa-fw"></span>
<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 }}
</strong>
{% if c.position.attendee_name %}
<br>
<small>
{{ c.position.attendee_name }}
</small>
{% endif %}
{% if c.position.item %}
<br>
<small>
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=c.position.id %}">
{{ c.position.item }}{% if c.position.variation %}
{{ c.position.variation }}{% endif %}
</a>
</small>
{% endif %}
{% else %}
<span class="fa fa-qrcode fa-fw"></span>
<span title="{{ c.raw_barcode }}">
{{ c.raw_barcode|slice:":16" }}{% if c.raw_barcode|length > 16 %}…{% endif %}
</span>
{% if c.raw_item %}
<br>
<small>
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=c.raw_item.id %}">
{{ c.raw_item }}{% if c.raw_variation %} {{ c.raw_variation }}{% endif %}
</a>
</small>
{% endif %}
{% if c.raw_subevent %}
<br>
<small>
{{ c.raw_subevent }}{% if c.raw_variation %} {{ c.raw_variation }}{% endif %}
</small>
{% endif %}
{% endif %}
</td>
<td>
{{ c.device|default:"" }}
{% if c.gate %}
<br><small>{{ c.gate }}</small>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% include "pretixcontrol/pagination.html" %}
{% endblock %}

View File

@@ -231,7 +231,10 @@
{% bootstrap_field sform.display_net_prices layout="control" %}
{% bootstrap_field sform.show_variations_expanded layout="control" %}
{% bootstrap_field sform.hide_sold_out layout="control" %}
{% bootstrap_field sform.meta_noindex layout="control" %}
{% url "control:organizer.edit" organizer=request.organizer.slug as org_url %}
{% propagated request.event org_url "meta_noindex" %}
{% bootstrap_field sform.meta_noindex layout="control" %}
{% endpropagated %}
{% if sform.frontpage_subevent_ordering %}
{% bootstrap_field sform.frontpage_subevent_ordering layout="control" %}
{% endif %}
@@ -250,6 +253,12 @@
</fieldset>
<fieldset>
<legend>{% trans "Waiting list" %}</legend>
<div class="alert alert-info">
{% blocktrans trimmed %}
The waiting list currently is not compatible with some advanced features of pretix such as
seating plans, add-on products or product bundles.
{% endblocktrans %}
</div>
{% bootstrap_field sform.waiting_list_enabled layout="control" %}
{% bootstrap_field sform.waiting_list_auto layout="control" %}
{% bootstrap_field sform.waiting_list_hours layout="control" %}

View File

@@ -44,18 +44,18 @@
<tr>
<th>{% trans "Quota name" %}
<a href="?{% url_replace request 'ordering' '-name' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'name' %}"><i class="fa fa-caret-up"></i></a></th>
<a href="?{% url_replace request 'ordering' 'name' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>{% trans "Products" %}</th>
{% if request.event.has_subevents %}
<th>{% trans "Date" context "subevent" %}
<a href="?{% url_replace request 'ordering' '-date' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'date' %}"><i class="fa fa-caret-up"></i></a></th>
<a href="?{% url_replace request 'ordering' 'date' %}"><i class="fa fa-caret-up"></i></a>
</th>
{% endif %}
<th>{% trans "Total capacity" %}
<a href="?{% url_replace request 'ordering' '-size' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'size' %}"><i class="fa fa-caret-up"></i></a></th>
<a href="?{% url_replace request 'ordering' 'size' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>{% trans "Capacity left" %}</th>
<th class="action-col-2"></th>

View File

@@ -324,19 +324,21 @@
{{ line.variation }}
{% endif %}
{% if line.checkins.all %}
{% for c in line.checkins.all %}
{% if c.type == "exit" %}
{% for c in line.all_checkins.all %}
{% if not c.successful %}
<span class="fa fa-fw fa-exclamation-circle text-danger" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Denied scan: {{ date }}{% endblocktrans %}<br>{{ c.get_error_reason_display }}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% elif c.type == "exit" %}
{% if c.auto_checked_in %}
<span class="fa fa-fw fa-hourglass-end" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically marked not present: {{ date }}{% endblocktrans %}"></span>
<span class="fa fa-fw text-success fa-hourglass-end" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically marked not present: {{ date }}{% endblocktrans %}"></span>
{% else %}
<span class="fa fa-fw fa-sign-out" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Exit scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
<span class="fa fa-fw text-success fa-sign-out" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Exit scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% endif %}
{% elif c.forced %}
<span class="fa fa-fw fa-warning" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Additional entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
<span class="fa fa-fw fa-warning text-warning" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Additional entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% elif c.auto_checked_in %}
<span class="fa fa-fw fa-magic" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
<span class="fa fa-fw fa-magic text-success" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% else %}
<span class="fa fa-fw fa-check" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
<span class="fa fa-fw fa-check text-success" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% endif %}
{% endfor %}
{% endif %}
@@ -888,10 +890,9 @@
<form class="form" method="post"
action="{% url "control:event.order.comment" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
{% csrf_token %}
<div class="row">
{% bootstrap_field comment_form.comment layout="horizontal" show_help=True show_label=False horizontal_field_class="col-md-12" %}
{% bootstrap_field comment_form.checkin_attention layout="horizontal" show_help=True show_label=False horizontal_field_class="col-md-12" %}
</div>
{% bootstrap_field comment_form.comment show_help=True show_label=False %}
{% bootstrap_field comment_form.custom_followup_at %}
{% bootstrap_field comment_form.checkin_attention show_help=True show_label=False %}
<button class="btn btn-default">
{% trans "Update comment" %}
</button>

View File

@@ -134,6 +134,11 @@
{% if o.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
{% if o.custom_followup_due %}
<span class="label label-danger">{% blocktrans with date=o.custom_followup_at|date:"SHORT_DATE_FORMAT" context "followup" %}TODO {{ date }}{% endblocktrans %}</span>
{% elif o.custom_followup_at %}
<span class="label label-default">{% blocktrans with date=o.custom_followup_at|date:"SHORT_DATE_FORMAT" context "followup" %}TODO {{ date }}{% endblocktrans %}</span>
{% endif %}
</td>
<td>
{{ o.email|default_if_none:"" }}

View File

@@ -22,31 +22,41 @@
</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{% trans "Customer ID" %}</dt>
<dd>#{{ customer.identifier }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>
{% if not customer.is_active %}
{% trans "disabled" %}
<form action="" method="post">
{% csrf_token %}
<dl class="dl-horizontal">
<dt>{% trans "Customer ID" %}</dt>
<dd>#{{ customer.identifier }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>
{% if not customer.is_active %}
{% trans "disabled" %}
{% elif not customer.is_verified %}
{% trans "not yet activated" %}
{% else %}
{% trans "active" %}
{% endif %}
</dd>
<dt>{% trans "E-mail" %}</dt>
<dd>{{ customer.email|default_if_none:"" }}</dd>
<dt>{% trans "Name" %}</dt>
<dd>{{ customer.name }}</dd>
<dt>{% trans "Locale" %}</dt>
<dd>{{ display_locale }}</dd>
<dt>{% trans "Registration date" %}</dt>
<dd>{{ customer.date_joined|date:"SHORT_DATETIME_FORMAT" }}</dd>
<dt>{% trans "Last login" %}</dt>
<dd>{% if customer.last_login %}{{ customer.last_login|date:"SHORT_DATETIME_FORMAT" }}{% else %}
{% endif %}</dd>
</dl>
{% trans "not yet activated" %}
{% else %}
{% trans "active" %}
{% endif %}
</dd>
<dt>{% trans "E-mail" %}</dt>
<dd>
{{ customer.email|default_if_none:"" }}
{% if customer.email %}
<button type="submit" name="action" value="pwreset" class="btn btn-xs btn-default">
{% trans "Send password reset link" %}
</button>
{% endif %}
</dd>
<dt>{% trans "Name" %}</dt>
<dd>{{ customer.name }}</dd>
<dt>{% trans "Locale" %}</dt>
<dd>{{ display_locale }}</dd>
<dt>{% trans "Registration date" %}</dt>
<dd>{{ customer.date_joined|date:"SHORT_DATETIME_FORMAT" }}</dd>
<dt>{% trans "Last login" %}</dt>
<dd>{% if customer.last_login %}{{ customer.last_login|date:"SHORT_DATETIME_FORMAT" }}{% else %}
{% endif %}</dd>
</dl>
</form>
<div class="text-right">
<a href="{% url "control:organizer.customer.edit" organizer=request.organizer.slug customer=customer.identifier %}"
class="btn btn-default">
@@ -81,7 +91,10 @@
{% for m in memberships %}
<tr>
<td>
{% if m.canceled %}
<del>{% endif %}
{{ m.membership_type.name }}
{% if m.canceled %}</del>{% endif %}
{% if m.testmode %}<span class="label label-warning">{% trans "TEST MODE" %}</span>{% endif %}
</td>
<td>
@@ -131,17 +144,17 @@
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="7">
<a href="{% url "control:organizer.customer.membership.add" organizer=request.organizer.slug customer=customer.identifier %}"
class="btn btn-default">
<i class="fa fa-plus"></i>
{% trans "Add membership" %}
</a>
</td>
</tr>
</tfoot>
<tfoot>
<tr>
<td colspan="7">
<a href="{% url "control:organizer.customer.membership.add" organizer=request.organizer.slug customer=customer.identifier %}"
class="btn btn-default">
<i class="fa fa-plus"></i>
{% trans "Add membership" %}
</a>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="panel panel-default items">
@@ -179,7 +192,7 @@
</td>
<td>
<span class="fa fa-{{ o.sales_channel_obj.icon }} text-muted"
data-toggle="tooltip" title="{% trans o.sales_channel_obj.verbose_name %}"></span>
data-toggle="tooltip" title="{% trans o.sales_channel_obj.verbose_name %}"></span>
{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}
{% if o.customer_id != customer.pk %}
<span class="fa fa-link text-muted"
@@ -194,14 +207,14 @@
{% endif %}
{% if o.has_external_refund or o.has_pending_refund %}
<span class="label label-danger">{% trans "REFUND PENDING" %}</span>
{% elif o.has_pending_refund %}
{% elif o.has_pending_refund %}
<span class="label label-warning">{% trans "REFUND PENDING" %}</span>
{% endif %}
{% if o.is_overpaid %}
<span class="label label-warning">{% trans "OVERPAID" %}</span>
{% elif o.is_underpaid %}
{% elif o.is_underpaid %}
<span class="label label-danger">{% trans "UNDERPAID" %}</span>
{% elif o.is_pending_with_full_payment %}
{% elif o.is_pending_with_full_payment %}
<span class="label label-danger">{% trans "FULLY PAID" %}</span>
{% endif %}
{% if o.computed_payment_refund_sum == o.total or o.computed_payment_refund_sum == 0 %}

View File

@@ -18,9 +18,15 @@
</div>
{% else %}
<form class="row filter-form" action="" method="get">
<div class="col-md-10 col-sm-6 col-xs-12">
<div class="col-md-4 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.query layout='inline' %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.status layout='inline' %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.memberships layout='inline' %}
</div>
<div class="col-md-2 col-sm-6 col-xs-12">
<button class="btn btn-primary btn-block" type="submit">
<span class="fa fa-filter"></span>

View File

@@ -45,6 +45,7 @@
{% bootstrap_field sform.event_list_type layout="control" %}
{% bootstrap_field sform.event_list_availability layout="control" %}
{% bootstrap_field sform.organizer_link_back layout="control" %}
{% bootstrap_field sform.meta_noindex layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "Localization" %}</legend>

View File

@@ -70,4 +70,5 @@
{% endfor %}
</tbody>
</table>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}

View File

@@ -69,6 +69,11 @@
{% if o.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
{% if o.custom_followup_due %}
<span class="label label-danger">{% blocktrans with date=o.custom_followup_at|date:"SHORT_DATE_FORMAT" context "followup" %}TODO {{ date }}{% endblocktrans %}</span>
{% elif o.custom_followup_at %}
<span class="label label-default">{% blocktrans with date=o.custom_followup_at|date:"SHORT_DATE_FORMAT" context "followup" %}TODO {{ date }}{% endblocktrans %}</span>
{% endif %}
</td>
<td>{{ o.event.name }}</td>
<td>

View File

@@ -174,7 +174,7 @@
<tr>
<td>
{% if "can_change_orders" in request.eventpermset %}
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label"><input type="checkbox" name="entry" class="batch-select-checkbox" value="{{ s.pk }}"/></label>
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label"><input type="checkbox" name="entry" class="batch-select-checkbox" value="{{ e.pk }}"/></label>
{% endif %}
</td>
{% if request.event.settings.waiting_list_names_asked %}

View File

@@ -364,6 +364,7 @@ urlpatterns = [
re_path(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'),
re_path(r'^waitinglist/(?P<entry>\d+)/delete$', waitinglist.EntryDelete.as_view(),
name='event.orders.waitinglist.delete'),
re_path(r'^checkins/$', checkin.CheckinListView.as_view(), name='event.orders.checkins'),
re_path(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'),
re_path(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'),
re_path(r'^checkinlists/select2$', typeahead.checkinlist_select2, name='event.orders.checkinlists.select2'),

View File

@@ -50,7 +50,7 @@ from pretix.base.models import Checkin, Order, OrderPosition
from pretix.base.models.checkin import CheckinList
from pretix.base.signals import checkin_created
from pretix.control.forms.checkin import CheckinListForm
from pretix.control.forms.filter import CheckInFilterForm
from pretix.control.forms.filter import CheckInFilterForm, CheckinFilterForm
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.views import CreateView, PaginationMixin, UpdateView
from pretix.helpers.models import modelcopy
@@ -371,3 +371,31 @@ class CheckinListDelete(EventPermissionRequiredMixin, DeleteView):
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
})
class CheckinListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
model = Checkin
context_object_name = 'checkins'
permission = 'can_view_orders'
template_name = 'pretixcontrol/checkin/checkins.html'
def get_queryset(self):
qs = Checkin.all.filter(
list__event=self.request.event,
).select_related(
'position', 'position', 'position__item', 'position__variation', 'position__subevent'
).prefetch_related(
'list', 'gate'
)
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
return qs
@cached_property
def filter_form(self):
return CheckinFilterForm(data=self.request.GET, event=self.request.event)
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
ctx['filter_form'] = self.filter_form
return ctx

View File

@@ -716,6 +716,13 @@ class MailSettingsRendererPreview(MailSettingsPreview):
def post(self, request, *args, **kwargs):
return HttpResponse(status=405)
# get all supported placeholders with dummy values
def placeholders(self, item):
ctx = {}
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item]).values():
ctx[p.identifier] = str(p.render_sample(self.request.event))
return ctx
def get(self, request, *args, **kwargs):
v = str(request.event.settings.mail_text_order_placed)
v = v.format_map(self.placeholders('mail_text_order_placed'))

View File

@@ -31,6 +31,7 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import re
from django.conf import settings
from django.contrib import messages
@@ -160,8 +161,16 @@ class EventWizard(SafeSessionWizardView):
initial['locales'] = self.clone_from.settings.locales
initial['has_subevents'] = self.clone_from.has_subevents
elif step == 'basics':
initial['request'] = self.request
initial['name'] = self.clone_from.name
initial['slug'] = self.clone_from.slug + '-2'
if re.match('^.*-[0-9]+$', self.clone_from.slug):
# slug-2 -> slug-3
initial['slug'] = self.clone_from.slug.rsplit('-', 1)[0] + '-' + str(int(self.clone_from.slug.rsplit('-', 1)[1]) + 1)
else:
# slug -> slug-2
initial['slug'] = self.clone_from.slug + '-2'
initial['currency'] = self.clone_from.currency
initial['date_from'] = self.clone_from.date_from
initial['date_to'] = self.clone_from.date_to
@@ -182,7 +191,9 @@ class EventWizard(SafeSessionWizardView):
qs = qs.filter(
id__in=self.request.user.teams.filter(can_create_events=True).values_list('organizer', flat=True)
)
initial['organizer'] = qs.get(slug=self.request.GET.get('organizer'))
organizer = qs.get(slug=self.request.GET.get('organizer'))
initial['organizer'] = organizer
initial['locales'] = organizer.settings.locales
except Organizer.DoesNotExist:
pass
@@ -251,16 +262,16 @@ class EventWizard(SafeSessionWizardView):
with transaction.atomic(), language(basics_data['locale']):
event = form_dict['basics'].instance
event.organizer = foundation_data['organizer']
event.set_active_plugins(settings.PRETIX_PLUGINS_DEFAULT.split(","), allow_restricted=True)
event.has_subevents = foundation_data['has_subevents']
event.testmode = True
form_dict['basics'].save()
event.set_active_plugins(settings.PRETIX_PLUGINS_DEFAULT.split(","), allow_restricted=True)
event.log_action(
'pretix.event.added',
user=self.request.user,
)
if not EventWizardBasicsForm.has_control_rights(self.request.user, event.organizer):
if not EventWizardBasicsForm.has_control_rights(self.request.user, event.organizer, self.request.session):
if basics_data["team"] is not None:
t = basics_data["team"]
t.limit_events.add(event)

View File

@@ -303,6 +303,7 @@ class OrderDetail(OrderView):
ctx['invoices'] = list(self.order.invoices.all().select_related('event'))
ctx['comment_form'] = CommentForm(initial={
'comment': self.order.comment,
'custom_followup_at': self.order.custom_followup_at,
'checkin_attention': self.order.checkin_attention
})
ctx['display_locale'] = dict(settings.LANGUAGES)[self.object.locale or self.request.event.settings.locale]
@@ -340,7 +341,7 @@ class OrderDetail(OrderView):
).prefetch_related(
'item__questions', 'issued_gift_cards',
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options').select_related('question')),
Prefetch('checkins', queryset=Checkin.objects.select_related('list').order_by('datetime')),
Prefetch('all_checkins', queryset=Checkin.all.select_related('list').order_by('datetime')),
).order_by('positionid')
positions = []
@@ -487,12 +488,21 @@ class OrderComment(OrderView):
'new_comment': form.cleaned_data.get('comment')
})
if form.cleaned_data.get('custom_followup_at') != self.order.custom_followup_at:
self.order.custom_followup_at = form.cleaned_data.get('custom_followup_at')
self.order.log_action('pretix.event.order.custom_followup_at', user=self.request.user, data={
'new_custom_followup_at': form.cleaned_data.get('custom_followup_at')
})
if form.cleaned_data.get('checkin_attention') != self.order.checkin_attention:
self.order.checkin_attention = form.cleaned_data.get('checkin_attention')
self.order.log_action('pretix.event.order.checkin_attention', user=self.request.user, data={
'new_value': form.cleaned_data.get('checkin_attention')
})
self.order.save(update_fields=['checkin_attention', 'comment'])
print(self.order.custom_followup_at)
self.order.save(update_fields=['checkin_attention', 'comment', 'custom_followup_at'])
self.order.refresh_from_db()
print(self.order.custom_followup_at)
messages.success(self.request, _('The comment has been updated.'))
else:
messages.error(self.request, _('Could not update the comment.'))
@@ -2204,10 +2214,17 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, Templ
'organizer': self.request.event.organizer.slug
}) + '?identifier=' + self.exporter.identifier
def get_check_url(self, task_id, ajax):
return self.request.path + '?async_id=%s&exporter=%s' % (task_id, self.exporter.identifier) + ('&ajax=1' if ajax else '')
@cached_property
def exporter(self):
if self.request.method == "POST":
identifier = self.request.POST.get("exporter")
else:
identifier = self.request.GET.get("exporter")
for ex in self.exporters:
if ex.identifier == self.request.POST.get("exporter"):
if ex.identifier == identifier:
return ex
def get(self, request, *args, **kwargs):

View File

@@ -102,7 +102,9 @@ from pretix.control.permissions import (
from pretix.control.signals import nav_organizer
from pretix.control.views import PaginationMixin
from pretix.helpers.dicts import merge_dicts
from pretix.helpers.urls import build_absolute_uri
from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.presale.forms.customer import TokenGenerator
from pretix.presale.style import regenerate_organizer_css
@@ -668,7 +670,7 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
'user': self,
'organizer': self.request.organizer.name,
'team': instance.team.name,
'url': build_absolute_uri('control:auth.invite', kwargs={
'url': build_global_uri('control:auth.invite', kwargs={
'token': instance.token
})
},
@@ -1763,6 +1765,34 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
identifier=self.kwargs.get('customer')
)
def post(self, request, *args, **kwargs):
if request.POST.get('action') == 'pwreset':
self.customer.log_action('pretix.customer.password.resetrequested', {}, user=self.request.user)
ctx = self.customer.get_email_context()
token = TokenGenerator().make_token(self.customer)
ctx['url'] = build_absolute_uri(
self.request.organizer,
'presale:organizer.customer.recoverpw'
) + '?id=' + self.customer.identifier + '&token=' + token
mail(
self.customer.email,
_('Set a new password for your account at {organizer}').format(organizer=self.request.organizer.name),
self.request.organizer.settings.mail_text_customer_reset,
ctx,
locale=self.customer.locale,
customer=self.customer,
organizer=self.request.organizer,
)
messages.success(
self.request,
_('We\'ve sent the customer an email with further instructions on resetting your password.')
)
return redirect(reverse('control:organizer.customer', kwargs={
'organizer': self.request.organizer.slug,
'customer': self.customer.identifier,
}))
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['customer'] = self.customer
@@ -1891,7 +1921,6 @@ class MembershipDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequired
template_name = 'pretixcontrol/organizers/customer_membership_delete.html'
permission = 'can_manage_customers'
context_object_name = 'membership'
form_class = MembershipUpdateForm
def get_object(self, queryset=None):
return get_object_or_404(

View File

@@ -1298,7 +1298,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
for se in subevents:
q = list(se.quotas.all())[qidx]
for fname in ('size', 'name', 'release_after_exit'):
for fname in ('size', 'name', 'release_after_exit', 'ignore_for_event_availability'):
setattr(q, fname, f.cleaned_data.get(fname))
q.save(clear_cache=False)
if 'itemvars' in f.changed_data:

View File

@@ -312,9 +312,12 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
writer.writerow(row)
r = HttpResponse(output.getvalue().encode("utf-8"), content_type='text/csv')
r['Content-Disposition'] = 'attachment; filename="waitinglist.csv"'
r['Content-Disposition'] = 'attachment; filename="{}.csv"'.format(self.get_filename())
return r
def get_filename(self):
return '{}_waitinglist'.format(self.request.event.slug)
class EntryDelete(EventPermissionRequiredMixin, DeleteView):
model = WaitingListEntry

View File

@@ -85,12 +85,20 @@ def create_thumbnail(sourcename, size):
if crop:
image = image.crop(crop)
if source.name.endswith('.jpg') or source.name.endswith('.jpeg'):
# Yields better file sizes for photos
target_ext = 'jpeg'
quality = 95
else:
target_ext = 'png'
quality = None
checksum = hashlib.md5(image.tobytes()).hexdigest()
name = checksum + '.' + size.replace('^', 'c') + '.png'
name = checksum + '.' + size.replace('^', 'c') + '.' + target_ext
buffer = BytesIO()
if image.mode not in ("1", "L", "RGB", "RGBA"):
image = image.convert('RGB')
image.save(fp=buffer, format='PNG')
image.save(fp=buffer, format=target_ext.upper(), quality=quality)
imgfile = ContentFile(buffer.getvalue())
t = Thumbnail.objects.create(source=sourcename, size=size)

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-27 12:05+0000\n"
"PO-Revision-Date: 2021-05-31 11:26+0000\n"
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
"POT-Creation-Date: 2021-06-30 12:23+0000\n"
"PO-Revision-Date: 2021-06-14 18:45+0000\n"
"Last-Translator: Abdullah <abdullah.gumaijan@gmail.com>\n"
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
"ar/>\n"
"Language: ar\n"
@@ -124,7 +124,7 @@ msgstr "ملغاة"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
msgid "Redeemed"
msgstr "مسترد"
msgstr "مستخدم"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
msgid "Cancel"
@@ -164,12 +164,16 @@ msgid "Information required"
msgstr "معلومات مطلوبة"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
msgid "Invalid ticket"
msgstr "تذكرة غير سارية المفعول"
#, fuzzy
#| msgid "Unknown error."
msgid "Unknown ticket"
msgstr "خطأ غير معروف."
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
msgid "Invalid product"
msgstr "منتج غير ساري المفعول"
#, fuzzy
#| msgid "Entry not allowed"
msgid "Ticket type not allowed here"
msgstr "إدخال غير مسموح"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
msgid "Entry not allowed"
@@ -442,15 +446,15 @@ msgstr "البحث في الاستفسارات"
msgid "Selected only"
msgstr "المختارة فقط"
#: pretix/static/pretixcontrol/js/ui/main.js:832
#: pretix/static/pretixcontrol/js/ui/main.js:834
msgid "Use a different name internally"
msgstr "قم باستخدم اسم مختلف داخليا"
#: pretix/static/pretixcontrol/js/ui/main.js:867
#: pretix/static/pretixcontrol/js/ui/main.js:870
msgid "Click to close"
msgstr "اضغط لاغلاق الصفحة"
#: pretix/static/pretixcontrol/js/ui/main.js:908
#: pretix/static/pretixcontrol/js/ui/main.js:911
msgid "You have unsaved changes!"
msgstr "لم تقم بحفظ التعديلات!"
@@ -514,20 +518,20 @@ msgstr "ستسترد %(currency)%(amount)"
msgid "Please enter the amount the organizer can keep."
msgstr "الرجاء إدخال المبلغ الذي يمكن للمنظم الاحتفاظ به."
#: pretix/static/pretixpresale/js/ui/main.js:307
#: pretix/static/pretixpresale/js/ui/main.js:308
msgid "Please enter a quantity for one of the ticket types."
msgstr "الرجاء إدخال عدد لأحد أنواع التذاكر."
#: pretix/static/pretixpresale/js/ui/main.js:343
#: pretix/static/pretixpresale/js/ui/main.js:344
msgid "required"
msgstr "مطلوب"
#: pretix/static/pretixpresale/js/ui/main.js:440
#: pretix/static/pretixpresale/js/ui/main.js:458
#: pretix/static/pretixpresale/js/ui/main.js:441
#: pretix/static/pretixpresale/js/ui/main.js:459
msgid "Time zone:"
msgstr "المنطقة الزمنية:"
#: pretix/static/pretixpresale/js/ui/main.js:449
#: pretix/static/pretixpresale/js/ui/main.js:450
msgid "Your local time:"
msgstr "التوقيت المحلي:"
@@ -610,15 +614,37 @@ msgstr "لا يمكن تحميل متجر التذاكر."
#: pretix/static/pretixpresale/js/widget/widget.js:32
msgctxt "widget"
msgid ""
"There are currently a lot of users in this ticket shop. Please open the shop "
"in a new tab to continue."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:34
#, fuzzy
#| msgctxt "widget"
#| msgid "Close ticket shop"
msgctxt "widget"
msgid "Open ticket shop"
msgstr "إغلاق متجر التذاكر"
#: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr "لا يمكن إنشاء سلة التسوق. الرجاء المحاولة مرة أخرى في وقت لاحق"
#: pretix/static/pretixpresale/js/widget/widget.js:33
#: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget"
msgid ""
"We could not create your cart, since there are currently too many users in "
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:38
msgctxt "widget"
msgid "Waiting list"
msgstr "قائمة الإنتظار"
#: pretix/static/pretixpresale/js/widget/widget.js:34
#: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget"
msgid ""
"You currently have an active cart for this event. If you select more "
@@ -627,157 +653,163 @@ msgstr ""
"لديك الآن سلة تسوق مفعلة لهذا الحدث. إذا قمت باختيار منتجات أخرى سيتم "
"إضافتها إلى سلة التسوق الموجودة حالياً."
#: pretix/static/pretixpresale/js/widget/widget.js:36
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
msgid "Resume checkout"
msgstr "استئناف الدفع"
#: pretix/static/pretixpresale/js/widget/widget.js:37
#: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget"
msgid "Redeem a voucher"
msgstr "استبدال قسيمة"
#: pretix/static/pretixpresale/js/widget/widget.js:38
#: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget"
msgid "Redeem"
msgstr "استبدال"
#: pretix/static/pretixpresale/js/widget/widget.js:39
#: pretix/static/pretixpresale/js/widget/widget.js:44
msgctxt "widget"
msgid "Voucher code"
msgstr "رمز القسيمة"
#: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget"
msgid "Close"
msgstr "إغلاق"
#: pretix/static/pretixpresale/js/widget/widget.js:41
#: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget"
msgid "Continue"
msgstr "استمرار"
#: pretix/static/pretixpresale/js/widget/widget.js:42
#: pretix/static/pretixpresale/js/widget/widget.js:47
msgctxt "widget"
msgid "See variations"
msgstr "أنظر إلى الاختلافات"
#: pretix/static/pretixpresale/js/widget/widget.js:43
#: pretix/static/pretixpresale/js/widget/widget.js:48
msgctxt "widget"
msgid "Choose a different event"
msgstr "اختر فعالية أخرى"
#: pretix/static/pretixpresale/js/widget/widget.js:44
#: pretix/static/pretixpresale/js/widget/widget.js:49
msgctxt "widget"
msgid "Choose a different date"
msgstr "اختر تاريخا آخر"
#: pretix/static/pretixpresale/js/widget/widget.js:45
#: pretix/static/pretixpresale/js/widget/widget.js:50
msgctxt "widget"
msgid "Back"
msgstr "العودة"
#: pretix/static/pretixpresale/js/widget/widget.js:46
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
msgid "Next month"
msgstr "الشهر القادم"
#: pretix/static/pretixpresale/js/widget/widget.js:47
#: pretix/static/pretixpresale/js/widget/widget.js:52
msgctxt "widget"
msgid "Previous month"
msgstr "الشهر السابق"
#: pretix/static/pretixpresale/js/widget/widget.js:48
#: pretix/static/pretixpresale/js/widget/widget.js:53
msgctxt "widget"
msgid "Next week"
msgstr "الأسبوع القادم"
#: pretix/static/pretixpresale/js/widget/widget.js:49
#: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget"
msgid "Previous week"
msgstr "الأسبوع السابق"
#: pretix/static/pretixpresale/js/widget/widget.js:50
#: pretix/static/pretixpresale/js/widget/widget.js:55
msgctxt "widget"
msgid "Open seat selection"
msgstr "خيارات المقاعد"
#: pretix/static/pretixpresale/js/widget/widget.js:52
#: pretix/static/pretixpresale/js/widget/widget.js:57
msgid "Mo"
msgstr "الإثنين"
#: pretix/static/pretixpresale/js/widget/widget.js:53
#: pretix/static/pretixpresale/js/widget/widget.js:58
msgid "Tu"
msgstr "الثلاثاء"
#: pretix/static/pretixpresale/js/widget/widget.js:54
#: pretix/static/pretixpresale/js/widget/widget.js:59
msgid "We"
msgstr "الأربعاء"
#: pretix/static/pretixpresale/js/widget/widget.js:55
#: pretix/static/pretixpresale/js/widget/widget.js:60
msgid "Th"
msgstr "الخميس"
#: pretix/static/pretixpresale/js/widget/widget.js:56
#: pretix/static/pretixpresale/js/widget/widget.js:61
msgid "Fr"
msgstr "الجمعة"
#: pretix/static/pretixpresale/js/widget/widget.js:57
#: pretix/static/pretixpresale/js/widget/widget.js:62
msgid "Sa"
msgstr "السبت"
#: pretix/static/pretixpresale/js/widget/widget.js:58
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgid "Su"
msgstr "الأحد"
#: pretix/static/pretixpresale/js/widget/widget.js:61
#: pretix/static/pretixpresale/js/widget/widget.js:66
msgid "January"
msgstr "يناير"
#: pretix/static/pretixpresale/js/widget/widget.js:62
#: pretix/static/pretixpresale/js/widget/widget.js:67
msgid "February"
msgstr "فبراير"
#: pretix/static/pretixpresale/js/widget/widget.js:63
#: pretix/static/pretixpresale/js/widget/widget.js:68
msgid "March"
msgstr "مارس"
#: pretix/static/pretixpresale/js/widget/widget.js:64
#: pretix/static/pretixpresale/js/widget/widget.js:69
msgid "April"
msgstr "أبريل"
#: pretix/static/pretixpresale/js/widget/widget.js:65
#: pretix/static/pretixpresale/js/widget/widget.js:70
msgid "May"
msgstr "مايو"
#: pretix/static/pretixpresale/js/widget/widget.js:66
#: pretix/static/pretixpresale/js/widget/widget.js:71
msgid "June"
msgstr "يونيو"
#: pretix/static/pretixpresale/js/widget/widget.js:67
#: pretix/static/pretixpresale/js/widget/widget.js:72
msgid "July"
msgstr "يوليو"
#: pretix/static/pretixpresale/js/widget/widget.js:68
#: pretix/static/pretixpresale/js/widget/widget.js:73
msgid "August"
msgstr "أغسطس"
#: pretix/static/pretixpresale/js/widget/widget.js:69
#: pretix/static/pretixpresale/js/widget/widget.js:74
msgid "September"
msgstr "سبتمبر"
#: pretix/static/pretixpresale/js/widget/widget.js:70
#: pretix/static/pretixpresale/js/widget/widget.js:75
msgid "October"
msgstr "أكتوبر"
#: pretix/static/pretixpresale/js/widget/widget.js:71
#: pretix/static/pretixpresale/js/widget/widget.js:76
msgid "November"
msgstr "نوفمبر"
#: pretix/static/pretixpresale/js/widget/widget.js:72
#: pretix/static/pretixpresale/js/widget/widget.js:77
msgid "December"
msgstr "ديسمبر"
#~ msgid "Invalid product"
#~ msgstr "منتج غير ساري المفعول"
#~ msgid "Invalid ticket"
#~ msgstr "تذكرة غير سارية المفعول"
#~ msgid "Check-in result"
#~ msgstr "نتيجة الدخول"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-27 12:05+0000\n"
"POT-Creation-Date: 2021-06-30 12:23+0000\n"
"PO-Revision-Date: 2020-12-19 07:00+0000\n"
"Last-Translator: albert <albert.serra.monner@gmail.com>\n"
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -163,11 +163,11 @@ msgid "Information required"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
msgid "Invalid ticket"
msgid "Unknown ticket"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
msgid "Invalid product"
msgid "Ticket type not allowed here"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
@@ -434,15 +434,15 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:832
#: pretix/static/pretixcontrol/js/ui/main.js:834
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:867
#: pretix/static/pretixcontrol/js/ui/main.js:870
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:908
#: pretix/static/pretixcontrol/js/ui/main.js:911
msgid "You have unsaved changes!"
msgstr ""
@@ -498,22 +498,22 @@ msgstr ""
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:307
#: pretix/static/pretixpresale/js/ui/main.js:308
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:343
#: pretix/static/pretixpresale/js/ui/main.js:344
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Cistella expirada"
#: pretix/static/pretixpresale/js/ui/main.js:440
#: pretix/static/pretixpresale/js/ui/main.js:458
#: pretix/static/pretixpresale/js/ui/main.js:441
#: pretix/static/pretixpresale/js/ui/main.js:459
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:449
#: pretix/static/pretixpresale/js/ui/main.js:450
msgid "Your local time:"
msgstr ""
@@ -596,168 +596,187 @@ msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:32
msgctxt "widget"
msgid ""
"There are currently a lot of users in this ticket shop. Please open the shop "
"in a new tab to continue."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:34
msgctxt "widget"
msgid "Open ticket shop"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:33
#: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget"
msgid ""
"We could not create your cart, since there are currently too many users in "
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:38
msgctxt "widget"
msgid "Waiting list"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:34
#: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget"
msgid ""
"You currently have an active cart for this event. If you select more "
"products, they will be added to your existing cart."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:36
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
msgid "Resume checkout"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:37
#: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget"
msgid "Redeem a voucher"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:38
#: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget"
msgid "Redeem"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:39
#: pretix/static/pretixpresale/js/widget/widget.js:44
msgctxt "widget"
msgid "Voucher code"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget"
msgid "Close"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:41
#: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget"
msgid "Continue"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:42
#: pretix/static/pretixpresale/js/widget/widget.js:47
msgctxt "widget"
msgid "See variations"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:43
#: pretix/static/pretixpresale/js/widget/widget.js:48
msgctxt "widget"
msgid "Choose a different event"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:44
#: pretix/static/pretixpresale/js/widget/widget.js:49
msgctxt "widget"
msgid "Choose a different date"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:45
#: pretix/static/pretixpresale/js/widget/widget.js:50
msgctxt "widget"
msgid "Back"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:46
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
msgid "Next month"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:47
#: pretix/static/pretixpresale/js/widget/widget.js:52
msgctxt "widget"
msgid "Previous month"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:48
#: pretix/static/pretixpresale/js/widget/widget.js:53
msgctxt "widget"
msgid "Next week"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:49
#: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget"
msgid "Previous week"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:50
#: pretix/static/pretixpresale/js/widget/widget.js:55
msgctxt "widget"
msgid "Open seat selection"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:52
#: pretix/static/pretixpresale/js/widget/widget.js:57
msgid "Mo"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:53
#: pretix/static/pretixpresale/js/widget/widget.js:58
msgid "Tu"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:54
#: pretix/static/pretixpresale/js/widget/widget.js:59
msgid "We"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:55
#: pretix/static/pretixpresale/js/widget/widget.js:60
msgid "Th"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:56
#: pretix/static/pretixpresale/js/widget/widget.js:61
msgid "Fr"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:57
#: pretix/static/pretixpresale/js/widget/widget.js:62
msgid "Sa"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:58
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgid "Su"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:61
#: pretix/static/pretixpresale/js/widget/widget.js:66
msgid "January"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:62
#: pretix/static/pretixpresale/js/widget/widget.js:67
msgid "February"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:63
#: pretix/static/pretixpresale/js/widget/widget.js:68
msgid "March"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:64
#: pretix/static/pretixpresale/js/widget/widget.js:69
msgid "April"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:65
#: pretix/static/pretixpresale/js/widget/widget.js:70
msgid "May"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:66
#: pretix/static/pretixpresale/js/widget/widget.js:71
msgid "June"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:67
#: pretix/static/pretixpresale/js/widget/widget.js:72
msgid "July"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:68
#: pretix/static/pretixpresale/js/widget/widget.js:73
msgid "August"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:69
#: pretix/static/pretixpresale/js/widget/widget.js:74
msgid "September"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:70
#: pretix/static/pretixpresale/js/widget/widget.js:75
msgid "October"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:71
#: pretix/static/pretixpresale/js/widget/widget.js:76
msgid "November"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:72
#: pretix/static/pretixpresale/js/widget/widget.js:77
msgid "December"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-27 12:05+0000\n"
"POT-Creation-Date: 2021-06-30 12:23+0000\n"
"PO-Revision-Date: 2021-05-20 07:27+0000\n"
"Last-Translator: Smejkal Vojtěch <me@vsmejkal.eu>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -163,12 +163,14 @@ msgid "Information required"
msgstr "Informace vyžadována"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
msgid "Invalid ticket"
msgstr "Neplatná vstupenka"
msgid "Unknown ticket"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
msgid "Invalid product"
msgstr "Neplatný produkt"
#, fuzzy
#| msgid "Entry not allowed"
msgid "Ticket type not allowed here"
msgstr "Vstup není povolen"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
msgid "Entry not allowed"
@@ -446,15 +448,15 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:832
#: pretix/static/pretixcontrol/js/ui/main.js:834
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:867
#: pretix/static/pretixcontrol/js/ui/main.js:870
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:908
#: pretix/static/pretixcontrol/js/ui/main.js:911
msgid "You have unsaved changes!"
msgstr ""
@@ -512,20 +514,20 @@ msgstr ""
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:307
#: pretix/static/pretixpresale/js/ui/main.js:308
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:343
#: pretix/static/pretixpresale/js/ui/main.js:344
msgid "required"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:440
#: pretix/static/pretixpresale/js/ui/main.js:458
#: pretix/static/pretixpresale/js/ui/main.js:441
#: pretix/static/pretixpresale/js/ui/main.js:459
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:449
#: pretix/static/pretixpresale/js/ui/main.js:450
msgid "Your local time:"
msgstr ""
@@ -608,168 +610,193 @@ msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:32
msgctxt "widget"
msgid ""
"There are currently a lot of users in this ticket shop. Please open the shop "
"in a new tab to continue."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:34
msgctxt "widget"
msgid "Open ticket shop"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:33
#: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget"
msgid ""
"We could not create your cart, since there are currently too many users in "
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:38
msgctxt "widget"
msgid "Waiting list"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:34
#: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget"
msgid ""
"You currently have an active cart for this event. If you select more "
"products, they will be added to your existing cart."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:36
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
msgid "Resume checkout"
msgstr "Obnovit checkout"
#: pretix/static/pretixpresale/js/widget/widget.js:37
#: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget"
msgid "Redeem a voucher"
msgstr "Uplatnit poukázku"
#: pretix/static/pretixpresale/js/widget/widget.js:38
#: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget"
msgid "Redeem"
msgstr "Uplatnit"
#: pretix/static/pretixpresale/js/widget/widget.js:39
#: pretix/static/pretixpresale/js/widget/widget.js:44
msgctxt "widget"
msgid "Voucher code"
msgstr "Kód poukázky"
#: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget"
msgid "Close"
msgstr "Zavřít"
#: pretix/static/pretixpresale/js/widget/widget.js:41
#: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget"
msgid "Continue"
msgstr "Pokračovat"
#: pretix/static/pretixpresale/js/widget/widget.js:42
#: pretix/static/pretixpresale/js/widget/widget.js:47
msgctxt "widget"
msgid "See variations"
msgstr "Zobrazit možnosti"
#: pretix/static/pretixpresale/js/widget/widget.js:43
#: pretix/static/pretixpresale/js/widget/widget.js:48
msgctxt "widget"
msgid "Choose a different event"
msgstr "Vybrat jinou událost"
#: pretix/static/pretixpresale/js/widget/widget.js:44
#: pretix/static/pretixpresale/js/widget/widget.js:49
msgctxt "widget"
msgid "Choose a different date"
msgstr "Vybrat jiný datum"
#: pretix/static/pretixpresale/js/widget/widget.js:45
#: pretix/static/pretixpresale/js/widget/widget.js:50
msgctxt "widget"
msgid "Back"
msgstr "Zpět"
#: pretix/static/pretixpresale/js/widget/widget.js:46
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
msgid "Next month"
msgstr "Následující měsíc"
#: pretix/static/pretixpresale/js/widget/widget.js:47
#: pretix/static/pretixpresale/js/widget/widget.js:52
msgctxt "widget"
msgid "Previous month"
msgstr "Předchozí měsíc"
#: pretix/static/pretixpresale/js/widget/widget.js:48
#: pretix/static/pretixpresale/js/widget/widget.js:53
msgctxt "widget"
msgid "Next week"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:49
#: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget"
msgid "Previous week"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:50
#: pretix/static/pretixpresale/js/widget/widget.js:55
msgctxt "widget"
msgid "Open seat selection"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:52
#: pretix/static/pretixpresale/js/widget/widget.js:57
msgid "Mo"
msgstr "Po"
#: pretix/static/pretixpresale/js/widget/widget.js:53
#: pretix/static/pretixpresale/js/widget/widget.js:58
msgid "Tu"
msgstr "Út"
#: pretix/static/pretixpresale/js/widget/widget.js:54
#: pretix/static/pretixpresale/js/widget/widget.js:59
msgid "We"
msgstr "St"
#: pretix/static/pretixpresale/js/widget/widget.js:55
#: pretix/static/pretixpresale/js/widget/widget.js:60
msgid "Th"
msgstr "Čt"
#: pretix/static/pretixpresale/js/widget/widget.js:56
#: pretix/static/pretixpresale/js/widget/widget.js:61
msgid "Fr"
msgstr "Pá"
#: pretix/static/pretixpresale/js/widget/widget.js:57
#: pretix/static/pretixpresale/js/widget/widget.js:62
msgid "Sa"
msgstr "So"
#: pretix/static/pretixpresale/js/widget/widget.js:58
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgid "Su"
msgstr "Ne"
#: pretix/static/pretixpresale/js/widget/widget.js:61
#: pretix/static/pretixpresale/js/widget/widget.js:66
msgid "January"
msgstr "Leden"
#: pretix/static/pretixpresale/js/widget/widget.js:62
#: pretix/static/pretixpresale/js/widget/widget.js:67
msgid "February"
msgstr "Únor"
#: pretix/static/pretixpresale/js/widget/widget.js:63
#: pretix/static/pretixpresale/js/widget/widget.js:68
msgid "March"
msgstr "Březen"
#: pretix/static/pretixpresale/js/widget/widget.js:64
#: pretix/static/pretixpresale/js/widget/widget.js:69
msgid "April"
msgstr "Duben"
#: pretix/static/pretixpresale/js/widget/widget.js:65
#: pretix/static/pretixpresale/js/widget/widget.js:70
msgid "May"
msgstr "Květen"
#: pretix/static/pretixpresale/js/widget/widget.js:66
#: pretix/static/pretixpresale/js/widget/widget.js:71
msgid "June"
msgstr "červen"
#: pretix/static/pretixpresale/js/widget/widget.js:67
#: pretix/static/pretixpresale/js/widget/widget.js:72
msgid "July"
msgstr "Červenec"
#: pretix/static/pretixpresale/js/widget/widget.js:68
#: pretix/static/pretixpresale/js/widget/widget.js:73
msgid "August"
msgstr "Srpen"
#: pretix/static/pretixpresale/js/widget/widget.js:69
#: pretix/static/pretixpresale/js/widget/widget.js:74
msgid "September"
msgstr "Září"
#: pretix/static/pretixpresale/js/widget/widget.js:70
#: pretix/static/pretixpresale/js/widget/widget.js:75
msgid "October"
msgstr "Říjen"
#: pretix/static/pretixpresale/js/widget/widget.js:71
#: pretix/static/pretixpresale/js/widget/widget.js:76
msgid "November"
msgstr "Listopad"
#: pretix/static/pretixpresale/js/widget/widget.js:72
#: pretix/static/pretixpresale/js/widget/widget.js:77
msgid "December"
msgstr "Prosinec"
#~ msgid "Invalid product"
#~ msgstr "Neplatný produkt"
#~ msgid "Invalid ticket"
#~ msgstr "Neplatná vstupenka"

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-27 12:05+0000\n"
"POT-Creation-Date: 2021-06-30 12:23+0000\n"
"PO-Revision-Date: 2020-09-15 02:00+0000\n"
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -171,11 +171,13 @@ msgid "Information required"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
msgid "Invalid ticket"
msgstr ""
#, fuzzy
#| msgid "Unknown error."
msgid "Unknown ticket"
msgstr "Ukendt fejl."
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
msgid "Invalid product"
msgid "Ticket type not allowed here"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
@@ -472,15 +474,15 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:832
#: pretix/static/pretixcontrol/js/ui/main.js:834
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:867
#: pretix/static/pretixcontrol/js/ui/main.js:870
msgid "Click to close"
msgstr "Klik for at lukke"
#: pretix/static/pretixcontrol/js/ui/main.js:908
#: pretix/static/pretixcontrol/js/ui/main.js:911
msgid "You have unsaved changes!"
msgstr "Du har ændringer, der ikke er gemt!"
@@ -546,22 +548,22 @@ msgstr "fra %(currency)s %(price)s"
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:307
#: pretix/static/pretixpresale/js/ui/main.js:308
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:343
#: pretix/static/pretixpresale/js/ui/main.js:344
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Kurv udløbet"
#: pretix/static/pretixpresale/js/ui/main.js:440
#: pretix/static/pretixpresale/js/ui/main.js:458
#: pretix/static/pretixpresale/js/ui/main.js:441
#: pretix/static/pretixpresale/js/ui/main.js:459
msgid "Time zone:"
msgstr "Tidszone:"
#: pretix/static/pretixpresale/js/ui/main.js:449
#: pretix/static/pretixpresale/js/ui/main.js:450
msgid "Your local time:"
msgstr "Din lokaltid:"
@@ -644,15 +646,37 @@ msgstr "Billetbutikken kunne ikke hentes."
#: pretix/static/pretixpresale/js/widget/widget.js:32
msgctxt "widget"
msgid ""
"There are currently a lot of users in this ticket shop. Please open the shop "
"in a new tab to continue."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:34
#, fuzzy
#| msgctxt "widget"
#| msgid "Close ticket shop"
msgctxt "widget"
msgid "Open ticket shop"
msgstr "Luk billetbutik"
#: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr "Kurven kunne ikke oprettes. Prøv igen senere"
#: pretix/static/pretixpresale/js/widget/widget.js:33
#: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget"
msgid ""
"We could not create your cart, since there are currently too many users in "
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:38
msgctxt "widget"
msgid "Waiting list"
msgstr "Venteliste"
#: pretix/static/pretixpresale/js/widget/widget.js:34
#: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget"
msgid ""
"You currently have an active cart for this event. If you select more "
@@ -661,72 +685,72 @@ msgstr ""
"Du har allerede en aktiv booking i gang for dette arrangement. Hvis du "
"vælger flere produkter, så vil de blive tilføjet din eksisterende booking."
#: pretix/static/pretixpresale/js/widget/widget.js:36
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
msgid "Resume checkout"
msgstr "Fortsæt booking"
#: pretix/static/pretixpresale/js/widget/widget.js:37
#: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget"
msgid "Redeem a voucher"
msgstr "Indløs rabatkode"
#: pretix/static/pretixpresale/js/widget/widget.js:38
#: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget"
msgid "Redeem"
msgstr "Indløs"
#: pretix/static/pretixpresale/js/widget/widget.js:39
#: pretix/static/pretixpresale/js/widget/widget.js:44
msgctxt "widget"
msgid "Voucher code"
msgstr "Rabatkode"
#: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget"
msgid "Close"
msgstr "Luk"
#: pretix/static/pretixpresale/js/widget/widget.js:41
#: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget"
msgid "Continue"
msgstr "Fortsæt"
#: pretix/static/pretixpresale/js/widget/widget.js:42
#: pretix/static/pretixpresale/js/widget/widget.js:47
msgctxt "widget"
msgid "See variations"
msgstr "Vis varianter"
#: pretix/static/pretixpresale/js/widget/widget.js:43
#: pretix/static/pretixpresale/js/widget/widget.js:48
msgctxt "widget"
msgid "Choose a different event"
msgstr "Vælg et andet arrangement"
#: pretix/static/pretixpresale/js/widget/widget.js:44
#: pretix/static/pretixpresale/js/widget/widget.js:49
msgctxt "widget"
msgid "Choose a different date"
msgstr "Vælg en anden dato"
#: pretix/static/pretixpresale/js/widget/widget.js:45
#: pretix/static/pretixpresale/js/widget/widget.js:50
msgctxt "widget"
msgid "Back"
msgstr "Tilbage"
#: pretix/static/pretixpresale/js/widget/widget.js:46
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
msgid "Next month"
msgstr "Næste måned"
#: pretix/static/pretixpresale/js/widget/widget.js:47
#: pretix/static/pretixpresale/js/widget/widget.js:52
msgctxt "widget"
msgid "Previous month"
msgstr "Forrige måned"
#: pretix/static/pretixpresale/js/widget/widget.js:48
#: pretix/static/pretixpresale/js/widget/widget.js:53
msgctxt "widget"
msgid "Next week"
msgstr "Næste uge"
#: pretix/static/pretixpresale/js/widget/widget.js:49
#: pretix/static/pretixpresale/js/widget/widget.js:54
#, fuzzy
#| msgctxt "widget"
#| msgid "Previous month"
@@ -734,84 +758,84 @@ msgctxt "widget"
msgid "Previous week"
msgstr "Forrige måned"
#: pretix/static/pretixpresale/js/widget/widget.js:50
#: pretix/static/pretixpresale/js/widget/widget.js:55
msgctxt "widget"
msgid "Open seat selection"
msgstr "Åbn sædevalg"
#: pretix/static/pretixpresale/js/widget/widget.js:52
#: pretix/static/pretixpresale/js/widget/widget.js:57
msgid "Mo"
msgstr "Man"
#: pretix/static/pretixpresale/js/widget/widget.js:53
#: pretix/static/pretixpresale/js/widget/widget.js:58
msgid "Tu"
msgstr "Tirs"
#: pretix/static/pretixpresale/js/widget/widget.js:54
#: pretix/static/pretixpresale/js/widget/widget.js:59
msgid "We"
msgstr "Ons"
#: pretix/static/pretixpresale/js/widget/widget.js:55
#: pretix/static/pretixpresale/js/widget/widget.js:60
msgid "Th"
msgstr "Tors"
#: pretix/static/pretixpresale/js/widget/widget.js:56
#: pretix/static/pretixpresale/js/widget/widget.js:61
msgid "Fr"
msgstr "Fre"
#: pretix/static/pretixpresale/js/widget/widget.js:57
#: pretix/static/pretixpresale/js/widget/widget.js:62
msgid "Sa"
msgstr "Lør"
#: pretix/static/pretixpresale/js/widget/widget.js:58
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgid "Su"
msgstr "Søn"
#: pretix/static/pretixpresale/js/widget/widget.js:61
#: pretix/static/pretixpresale/js/widget/widget.js:66
msgid "January"
msgstr "Januar"
#: pretix/static/pretixpresale/js/widget/widget.js:62
#: pretix/static/pretixpresale/js/widget/widget.js:67
msgid "February"
msgstr "Februar"
#: pretix/static/pretixpresale/js/widget/widget.js:63
#: pretix/static/pretixpresale/js/widget/widget.js:68
msgid "March"
msgstr "Marts"
#: pretix/static/pretixpresale/js/widget/widget.js:64
#: pretix/static/pretixpresale/js/widget/widget.js:69
msgid "April"
msgstr "April"
#: pretix/static/pretixpresale/js/widget/widget.js:65
#: pretix/static/pretixpresale/js/widget/widget.js:70
msgid "May"
msgstr "Maj"
#: pretix/static/pretixpresale/js/widget/widget.js:66
#: pretix/static/pretixpresale/js/widget/widget.js:71
msgid "June"
msgstr "Juni"
#: pretix/static/pretixpresale/js/widget/widget.js:67
#: pretix/static/pretixpresale/js/widget/widget.js:72
msgid "July"
msgstr "Juli"
#: pretix/static/pretixpresale/js/widget/widget.js:68
#: pretix/static/pretixpresale/js/widget/widget.js:73
msgid "August"
msgstr "August"
#: pretix/static/pretixpresale/js/widget/widget.js:69
#: pretix/static/pretixpresale/js/widget/widget.js:74
msgid "September"
msgstr "September"
#: pretix/static/pretixpresale/js/widget/widget.js:70
#: pretix/static/pretixpresale/js/widget/widget.js:75
msgid "October"
msgstr "Oktober"
#: pretix/static/pretixpresale/js/widget/widget.js:71
#: pretix/static/pretixpresale/js/widget/widget.js:76
msgid "November"
msgstr "November"
#: pretix/static/pretixpresale/js/widget/widget.js:72
#: pretix/static/pretixpresale/js/widget/widget.js:77
msgid "December"
msgstr "December"

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-27 12:05+0000\n"
"PO-Revision-Date: 2021-05-13 10:15+0200\n"
"POT-Creation-Date: 2021-06-30 12:23+0000\n"
"PO-Revision-Date: 2021-06-30 13:06+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
"de/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Poedit 2.4.1\n"
"X-Generator: Weblate 4.6\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -156,19 +156,19 @@ msgstr "Ausgang gespeichert"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
msgid "Ticket already used"
msgstr "Ticket bereits benutzt"
msgstr "Ticket bereits eingelöst"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:55
msgid "Information required"
msgstr "Informationen benötigt"
msgstr "Infos benötigt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
msgid "Invalid ticket"
msgstr "Ungültiges Ticket"
msgid "Unknown ticket"
msgstr "Unbekanntes Ticket"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
msgid "Invalid product"
msgstr "Ungültiges Produkt"
msgid "Ticket type not allowed here"
msgstr "Ticketart hier nicht erlaubt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
msgid "Entry not allowed"
@@ -176,7 +176,7 @@ msgstr "Eingang nicht erlaubt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
msgid "Ticket code revoked/changed"
msgstr "Ticket gesperrt/geändert"
msgstr "Ticket-Code gesperrt/geändert"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
msgid "Order canceled"
@@ -426,9 +426,11 @@ msgid "Your color has great contrast and is very easy to read!"
msgstr "Diese Farbe hat einen sehr guten Kontrast und ist sehr gut zu lesen!"
#: pretix/static/pretixcontrol/js/ui/main.js:275
#, fuzzy
msgid "Your color has decent contrast and is probably good-enough to read!"
msgstr ""
"Diese Farbe hat einen ausreichenden Kontrast und wahrscheinlich gut zu lesen!"
"Diese Farbe hat einen ausreichenden Kontrast und ist wahrscheinlich gut zu "
"lesen!"
#: pretix/static/pretixcontrol/js/ui/main.js:279
msgid ""
@@ -454,15 +456,15 @@ msgstr "Suchbegriff"
msgid "Selected only"
msgstr "Nur ausgewählte"
#: pretix/static/pretixcontrol/js/ui/main.js:832
#: pretix/static/pretixcontrol/js/ui/main.js:834
msgid "Use a different name internally"
msgstr "Intern einen anderen Namen verwenden"
#: pretix/static/pretixcontrol/js/ui/main.js:867
#: pretix/static/pretixcontrol/js/ui/main.js:870
msgid "Click to close"
msgstr "Klicken zum Schließen"
#: pretix/static/pretixcontrol/js/ui/main.js:908
#: pretix/static/pretixcontrol/js/ui/main.js:911
msgid "You have unsaved changes!"
msgstr "Sie haben ungespeicherte Änderungen!"
@@ -520,20 +522,20 @@ msgstr "Sie erhalten %(currency)s %(amount)s zurück"
msgid "Please enter the amount the organizer can keep."
msgstr "Bitte geben Sie den Betrag ein, den der Veranstalter einbehalten darf."
#: pretix/static/pretixpresale/js/ui/main.js:307
#: pretix/static/pretixpresale/js/ui/main.js:308
msgid "Please enter a quantity for one of the ticket types."
msgstr "Bitte tragen Sie eine Menge für eines der Produkte ein."
#: pretix/static/pretixpresale/js/ui/main.js:343
#: pretix/static/pretixpresale/js/ui/main.js:344
msgid "required"
msgstr "verpflichtend"
#: pretix/static/pretixpresale/js/ui/main.js:440
#: pretix/static/pretixpresale/js/ui/main.js:458
#: pretix/static/pretixpresale/js/ui/main.js:441
#: pretix/static/pretixpresale/js/ui/main.js:459
msgid "Time zone:"
msgstr "Zeitzone:"
#: pretix/static/pretixpresale/js/ui/main.js:449
#: pretix/static/pretixpresale/js/ui/main.js:450
msgid "Your local time:"
msgstr "Deine lokale Zeit:"
@@ -616,15 +618,37 @@ msgstr "Der Ticket-Shop konnte nicht geladen werden."
#: pretix/static/pretixpresale/js/widget/widget.js:32
msgctxt "widget"
msgid ""
"There are currently a lot of users in this ticket shop. Please open the shop "
"in a new tab to continue."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:34
#, fuzzy
#| msgctxt "widget"
#| msgid "Close ticket shop"
msgctxt "widget"
msgid "Open ticket shop"
msgstr "Ticket-Shop schließen"
#: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr "Der Warenkorb konnte nicht erstellt werden. Bitte erneut versuchen."
#: pretix/static/pretixpresale/js/widget/widget.js:33
#: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget"
msgid ""
"We could not create your cart, since there are currently too many users in "
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:38
msgctxt "widget"
msgid "Waiting list"
msgstr "Warteliste"
#: pretix/static/pretixpresale/js/widget/widget.js:34
#: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget"
msgid ""
"You currently have an active cart for this event. If you select more "
@@ -633,157 +657,163 @@ msgstr ""
"Sie haben einen aktiven Warenkorb für diese Veranstaltung. Wenn Sie mehr "
"Produkte auswählen, werden diese zu Ihrem Warenkorb hinzugefügt."
#: pretix/static/pretixpresale/js/widget/widget.js:36
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
msgid "Resume checkout"
msgstr "Kauf fortsetzen"
#: pretix/static/pretixpresale/js/widget/widget.js:37
#: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget"
msgid "Redeem a voucher"
msgstr "Gutschein einlösen"
#: pretix/static/pretixpresale/js/widget/widget.js:38
#: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget"
msgid "Redeem"
msgstr "Einlösen"
#: pretix/static/pretixpresale/js/widget/widget.js:39
#: pretix/static/pretixpresale/js/widget/widget.js:44
msgctxt "widget"
msgid "Voucher code"
msgstr "Gutscheincode"
#: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget"
msgid "Close"
msgstr "Schließen"
#: pretix/static/pretixpresale/js/widget/widget.js:41
#: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget"
msgid "Continue"
msgstr "Fortfahren"
#: pretix/static/pretixpresale/js/widget/widget.js:42
#: pretix/static/pretixpresale/js/widget/widget.js:47
msgctxt "widget"
msgid "See variations"
msgstr "Varianten zeigen"
#: pretix/static/pretixpresale/js/widget/widget.js:43
#: pretix/static/pretixpresale/js/widget/widget.js:48
msgctxt "widget"
msgid "Choose a different event"
msgstr "Andere Veranstaltung auswählen"
#: pretix/static/pretixpresale/js/widget/widget.js:44
#: pretix/static/pretixpresale/js/widget/widget.js:49
msgctxt "widget"
msgid "Choose a different date"
msgstr "Anderen Termin auswählen"
#: pretix/static/pretixpresale/js/widget/widget.js:45
#: pretix/static/pretixpresale/js/widget/widget.js:50
msgctxt "widget"
msgid "Back"
msgstr "Zurück"
#: pretix/static/pretixpresale/js/widget/widget.js:46
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
msgid "Next month"
msgstr "Nächster Monat"
#: pretix/static/pretixpresale/js/widget/widget.js:47
#: pretix/static/pretixpresale/js/widget/widget.js:52
msgctxt "widget"
msgid "Previous month"
msgstr "Vorheriger Monat"
#: pretix/static/pretixpresale/js/widget/widget.js:48
#: pretix/static/pretixpresale/js/widget/widget.js:53
msgctxt "widget"
msgid "Next week"
msgstr "Nächste Woche"
#: pretix/static/pretixpresale/js/widget/widget.js:49
#: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget"
msgid "Previous week"
msgstr "Vorherige Woche"
#: pretix/static/pretixpresale/js/widget/widget.js:50
#: pretix/static/pretixpresale/js/widget/widget.js:55
msgctxt "widget"
msgid "Open seat selection"
msgstr "Saalplan öffnen"
#: pretix/static/pretixpresale/js/widget/widget.js:52
#: pretix/static/pretixpresale/js/widget/widget.js:57
msgid "Mo"
msgstr "Mo"
#: pretix/static/pretixpresale/js/widget/widget.js:53
#: pretix/static/pretixpresale/js/widget/widget.js:58
msgid "Tu"
msgstr "Di"
#: pretix/static/pretixpresale/js/widget/widget.js:54
#: pretix/static/pretixpresale/js/widget/widget.js:59
msgid "We"
msgstr "Mi"
#: pretix/static/pretixpresale/js/widget/widget.js:55
#: pretix/static/pretixpresale/js/widget/widget.js:60
msgid "Th"
msgstr "Do"
#: pretix/static/pretixpresale/js/widget/widget.js:56
#: pretix/static/pretixpresale/js/widget/widget.js:61
msgid "Fr"
msgstr "Fr"
#: pretix/static/pretixpresale/js/widget/widget.js:57
#: pretix/static/pretixpresale/js/widget/widget.js:62
msgid "Sa"
msgstr "Sa"
#: pretix/static/pretixpresale/js/widget/widget.js:58
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgid "Su"
msgstr "So"
#: pretix/static/pretixpresale/js/widget/widget.js:61
#: pretix/static/pretixpresale/js/widget/widget.js:66
msgid "January"
msgstr "Januar"
#: pretix/static/pretixpresale/js/widget/widget.js:62
#: pretix/static/pretixpresale/js/widget/widget.js:67
msgid "February"
msgstr "Februar"
#: pretix/static/pretixpresale/js/widget/widget.js:63
#: pretix/static/pretixpresale/js/widget/widget.js:68
msgid "March"
msgstr "März"
#: pretix/static/pretixpresale/js/widget/widget.js:64
#: pretix/static/pretixpresale/js/widget/widget.js:69
msgid "April"
msgstr "April"
#: pretix/static/pretixpresale/js/widget/widget.js:65
#: pretix/static/pretixpresale/js/widget/widget.js:70
msgid "May"
msgstr "Mai"
#: pretix/static/pretixpresale/js/widget/widget.js:66
#: pretix/static/pretixpresale/js/widget/widget.js:71
msgid "June"
msgstr "Juni"
#: pretix/static/pretixpresale/js/widget/widget.js:67
#: pretix/static/pretixpresale/js/widget/widget.js:72
msgid "July"
msgstr "Juli"
#: pretix/static/pretixpresale/js/widget/widget.js:68
#: pretix/static/pretixpresale/js/widget/widget.js:73
msgid "August"
msgstr "August"
#: pretix/static/pretixpresale/js/widget/widget.js:69
#: pretix/static/pretixpresale/js/widget/widget.js:74
msgid "September"
msgstr "September"
#: pretix/static/pretixpresale/js/widget/widget.js:70
#: pretix/static/pretixpresale/js/widget/widget.js:75
msgid "October"
msgstr "Oktober"
#: pretix/static/pretixpresale/js/widget/widget.js:71
#: pretix/static/pretixpresale/js/widget/widget.js:76
msgid "November"
msgstr "November"
#: pretix/static/pretixpresale/js/widget/widget.js:72
#: pretix/static/pretixpresale/js/widget/widget.js:77
msgid "December"
msgstr "Dezember"
#~ msgid "Invalid product"
#~ msgstr "Ungültiges Produkt"
#~ msgid "Invalid ticket"
#~ msgstr "Ungültiges Ticket"
#~ msgid "Check-in result"
#~ msgstr "Check-In Ergebnis"

View File

@@ -155,6 +155,7 @@ MwSt
name
Nr
number
OK
Open
OpenCage
OpenStreetMap
@@ -241,6 +242,7 @@ Telephone
Ticketing
Tokengenerator
Toolbar
TODO
TOTP
Trace
Turnover

File diff suppressed because it is too large Load Diff

View File

@@ -7,17 +7,17 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-27 12:05+0000\n"
"PO-Revision-Date: 2021-05-13 10:17+0200\n"
"POT-Creation-Date: 2021-06-30 12:23+0000\n"
"PO-Revision-Date: 2021-06-30 13:06+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix-js/de_Informal/>\n"
"Language: de\n"
"Language: de_Informal\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Poedit 2.4.1\n"
"X-Generator: Weblate 4.6\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -156,19 +156,19 @@ msgstr "Ausgang gespeichert"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
msgid "Ticket already used"
msgstr "Ticket bereits benutzt"
msgstr "Ticket bereits eingelöst"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:55
msgid "Information required"
msgstr "Informationen benötigt"
msgstr "Infos benötigt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
msgid "Invalid ticket"
msgstr "Ungültiges Ticket"
msgid "Unknown ticket"
msgstr "Unbekanntes Ticket"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
msgid "Invalid product"
msgstr "Ungültiges Produkt"
msgid "Ticket type not allowed here"
msgstr "Ticketart hier nicht erlaubt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
msgid "Entry not allowed"
@@ -176,7 +176,7 @@ msgstr "Eingang nicht erlaubt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
msgid "Ticket code revoked/changed"
msgstr "Ticket gesperrt/geändert"
msgstr "Ticket-Code gesperrt/geändert"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
msgid "Order canceled"
@@ -425,9 +425,11 @@ msgid "Your color has great contrast and is very easy to read!"
msgstr "Diese Farbe hat einen sehr guten Kontrast und ist sehr gut zu lesen!"
#: pretix/static/pretixcontrol/js/ui/main.js:275
#, fuzzy
msgid "Your color has decent contrast and is probably good-enough to read!"
msgstr ""
"Diese Farbe hat einen ausreichenden Kontrast und wahrscheinlich gut zu lesen!"
"Diese Farbe hat einen ausreichenden Kontrast und ist wahrscheinlich gut zu "
"lesen!"
#: pretix/static/pretixcontrol/js/ui/main.js:279
msgid ""
@@ -453,15 +455,15 @@ msgstr "Suchbegriff"
msgid "Selected only"
msgstr "Nur ausgewählte"
#: pretix/static/pretixcontrol/js/ui/main.js:832
#: pretix/static/pretixcontrol/js/ui/main.js:834
msgid "Use a different name internally"
msgstr "Intern einen anderen Namen verwenden"
#: pretix/static/pretixcontrol/js/ui/main.js:867
#: pretix/static/pretixcontrol/js/ui/main.js:870
msgid "Click to close"
msgstr "Klicken zum Schließen"
#: pretix/static/pretixcontrol/js/ui/main.js:908
#: pretix/static/pretixcontrol/js/ui/main.js:911
msgid "You have unsaved changes!"
msgstr "Du hast ungespeicherte Änderungen!"
@@ -519,20 +521,20 @@ msgstr "Du erhältst %(currency)s %(amount)s zurück"
msgid "Please enter the amount the organizer can keep."
msgstr "Bitte gib den Betrag ein, den der Veranstalter einbehalten darf."
#: pretix/static/pretixpresale/js/ui/main.js:307
#: pretix/static/pretixpresale/js/ui/main.js:308
msgid "Please enter a quantity for one of the ticket types."
msgstr "Bitte trage eine Menge für eines der Produkte ein."
#: pretix/static/pretixpresale/js/ui/main.js:343
#: pretix/static/pretixpresale/js/ui/main.js:344
msgid "required"
msgstr "verpflichtend"
#: pretix/static/pretixpresale/js/ui/main.js:440
#: pretix/static/pretixpresale/js/ui/main.js:458
#: pretix/static/pretixpresale/js/ui/main.js:441
#: pretix/static/pretixpresale/js/ui/main.js:459
msgid "Time zone:"
msgstr "Zeitzone:"
#: pretix/static/pretixpresale/js/ui/main.js:449
#: pretix/static/pretixpresale/js/ui/main.js:450
msgid "Your local time:"
msgstr "Deine lokale Zeit:"
@@ -615,15 +617,37 @@ msgstr "Der Ticket-Shop konnte nicht geladen werden."
#: pretix/static/pretixpresale/js/widget/widget.js:32
msgctxt "widget"
msgid ""
"There are currently a lot of users in this ticket shop. Please open the shop "
"in a new tab to continue."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:34
#, fuzzy
#| msgctxt "widget"
#| msgid "Close ticket shop"
msgctxt "widget"
msgid "Open ticket shop"
msgstr "Ticket-Shop schließen"
#: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr "Der Warenkorb konnte nicht erstellt werden. Bitte erneut versuchen."
#: pretix/static/pretixpresale/js/widget/widget.js:33
#: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget"
msgid ""
"We could not create your cart, since there are currently too many users in "
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:38
msgctxt "widget"
msgid "Waiting list"
msgstr "Warteliste"
#: pretix/static/pretixpresale/js/widget/widget.js:34
#: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget"
msgid ""
"You currently have an active cart for this event. If you select more "
@@ -632,157 +656,163 @@ msgstr ""
"Du hast einen aktiven Warenkorb für diese Veranstaltung. Wenn du mehr "
"Produkte auswählst, werden diese zu deinem Warenkorb hinzugefügt."
#: pretix/static/pretixpresale/js/widget/widget.js:36
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
msgid "Resume checkout"
msgstr "Kauf fortsetzen"
#: pretix/static/pretixpresale/js/widget/widget.js:37
#: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget"
msgid "Redeem a voucher"
msgstr "Gutschein einlösen"
#: pretix/static/pretixpresale/js/widget/widget.js:38
#: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget"
msgid "Redeem"
msgstr "Einlösen"
#: pretix/static/pretixpresale/js/widget/widget.js:39
#: pretix/static/pretixpresale/js/widget/widget.js:44
msgctxt "widget"
msgid "Voucher code"
msgstr "Gutscheincode"
#: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget"
msgid "Close"
msgstr "Schließen"
#: pretix/static/pretixpresale/js/widget/widget.js:41
#: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget"
msgid "Continue"
msgstr "Fortfahren"
#: pretix/static/pretixpresale/js/widget/widget.js:42
#: pretix/static/pretixpresale/js/widget/widget.js:47
msgctxt "widget"
msgid "See variations"
msgstr "Varianten zeigen"
#: pretix/static/pretixpresale/js/widget/widget.js:43
#: pretix/static/pretixpresale/js/widget/widget.js:48
msgctxt "widget"
msgid "Choose a different event"
msgstr "Andere Veranstaltung auswählen"
#: pretix/static/pretixpresale/js/widget/widget.js:44
#: pretix/static/pretixpresale/js/widget/widget.js:49
msgctxt "widget"
msgid "Choose a different date"
msgstr "Anderen Termin auswählen"
#: pretix/static/pretixpresale/js/widget/widget.js:45
#: pretix/static/pretixpresale/js/widget/widget.js:50
msgctxt "widget"
msgid "Back"
msgstr "Zurück"
#: pretix/static/pretixpresale/js/widget/widget.js:46
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
msgid "Next month"
msgstr "Nächster Monat"
#: pretix/static/pretixpresale/js/widget/widget.js:47
#: pretix/static/pretixpresale/js/widget/widget.js:52
msgctxt "widget"
msgid "Previous month"
msgstr "Vorheriger Monat"
#: pretix/static/pretixpresale/js/widget/widget.js:48
#: pretix/static/pretixpresale/js/widget/widget.js:53
msgctxt "widget"
msgid "Next week"
msgstr "Nächste Woche"
#: pretix/static/pretixpresale/js/widget/widget.js:49
#: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget"
msgid "Previous week"
msgstr "Vorherige Woche"
#: pretix/static/pretixpresale/js/widget/widget.js:50
#: pretix/static/pretixpresale/js/widget/widget.js:55
msgctxt "widget"
msgid "Open seat selection"
msgstr "Saalplan öffnen"
#: pretix/static/pretixpresale/js/widget/widget.js:52
#: pretix/static/pretixpresale/js/widget/widget.js:57
msgid "Mo"
msgstr "Mo"
#: pretix/static/pretixpresale/js/widget/widget.js:53
#: pretix/static/pretixpresale/js/widget/widget.js:58
msgid "Tu"
msgstr "Di"
#: pretix/static/pretixpresale/js/widget/widget.js:54
#: pretix/static/pretixpresale/js/widget/widget.js:59
msgid "We"
msgstr "Mi"
#: pretix/static/pretixpresale/js/widget/widget.js:55
#: pretix/static/pretixpresale/js/widget/widget.js:60
msgid "Th"
msgstr "Do"
#: pretix/static/pretixpresale/js/widget/widget.js:56
#: pretix/static/pretixpresale/js/widget/widget.js:61
msgid "Fr"
msgstr "Fr"
#: pretix/static/pretixpresale/js/widget/widget.js:57
#: pretix/static/pretixpresale/js/widget/widget.js:62
msgid "Sa"
msgstr "Sa"
#: pretix/static/pretixpresale/js/widget/widget.js:58
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgid "Su"
msgstr "So"
#: pretix/static/pretixpresale/js/widget/widget.js:61
#: pretix/static/pretixpresale/js/widget/widget.js:66
msgid "January"
msgstr "Januar"
#: pretix/static/pretixpresale/js/widget/widget.js:62
#: pretix/static/pretixpresale/js/widget/widget.js:67
msgid "February"
msgstr "Februar"
#: pretix/static/pretixpresale/js/widget/widget.js:63
#: pretix/static/pretixpresale/js/widget/widget.js:68
msgid "March"
msgstr "März"
#: pretix/static/pretixpresale/js/widget/widget.js:64
#: pretix/static/pretixpresale/js/widget/widget.js:69
msgid "April"
msgstr "April"
#: pretix/static/pretixpresale/js/widget/widget.js:65
#: pretix/static/pretixpresale/js/widget/widget.js:70
msgid "May"
msgstr "Mai"
#: pretix/static/pretixpresale/js/widget/widget.js:66
#: pretix/static/pretixpresale/js/widget/widget.js:71
msgid "June"
msgstr "Juni"
#: pretix/static/pretixpresale/js/widget/widget.js:67
#: pretix/static/pretixpresale/js/widget/widget.js:72
msgid "July"
msgstr "Juli"
#: pretix/static/pretixpresale/js/widget/widget.js:68
#: pretix/static/pretixpresale/js/widget/widget.js:73
msgid "August"
msgstr "August"
#: pretix/static/pretixpresale/js/widget/widget.js:69
#: pretix/static/pretixpresale/js/widget/widget.js:74
msgid "September"
msgstr "September"
#: pretix/static/pretixpresale/js/widget/widget.js:70
#: pretix/static/pretixpresale/js/widget/widget.js:75
msgid "October"
msgstr "Oktober"
#: pretix/static/pretixpresale/js/widget/widget.js:71
#: pretix/static/pretixpresale/js/widget/widget.js:76
msgid "November"
msgstr "November"
#: pretix/static/pretixpresale/js/widget/widget.js:72
#: pretix/static/pretixpresale/js/widget/widget.js:77
msgid "December"
msgstr "Dezember"
#~ msgid "Invalid product"
#~ msgstr "Ungültiges Produkt"
#~ msgid "Invalid ticket"
#~ msgstr "Ungültiges Ticket"
#~ msgid "Check-in result"
#~ msgstr "Check-In Ergebnis"

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