Compare commits

..

831 Commits

Author SHA1 Message Date
Raphael Michel
11f3057f76 Bump version to 3.7.0 2020-03-09 14:33:17 +01:00
Raphael Michel
ba164c16f6 Fix test failure that only happens in the 20 days before daylight saving time changes 2020-03-09 12:56:58 +01:00
Raphael Michel
7ef766ddfa Cancelling events: Only show email fields conditionally 2020-03-09 11:44:25 +01:00
Raphael Michel
bcafcc7dd8 Fix invalid translation file 2020-03-09 11:36:59 +01:00
Raphael Michel
e5b7102abc Merge pull request #1600 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-03-09 11:21:33 +01:00
Raphael Michel
3601dd6bee Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3516 of 3516 strings)

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

powered by weblate
2020-03-09 11:20:45 +01:00
Raphael Michel
a1d5854fbf Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3516 of 3516 strings)

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

powered by weblate
2020-03-09 11:20:44 +01:00
Raphael Michel
09544a688d Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2020-03-09 11:11:54 +01:00
Raphael Michel
58a5892cc0 Merge pull request #1598 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-03-09 11:11:12 +01:00
Maarten van den Berg
c9af76b46e Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 97.9% (3436 of 3510 strings)

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

powered by weblate
2020-03-09 01:00:13 +01:00
Maarten van den Berg
91753935cf Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3510 of 3510 strings)

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

powered by weblate
2020-03-09 01:00:12 +01:00
Raphael Michel
23a52eb12a Add lang attribute to <html> tag in presale 2020-03-08 15:23:47 +01:00
Raphael Michel
79ecb231f2 Fix placement of back link when a header is used 2020-03-08 15:07:59 +01:00
Raphael Michel
08de7f59a3 Move iframe.js into main JS file 2020-03-08 15:07:59 +01:00
Raphael Michel
0de3c33bab Remove unused bootstrap components from CSS 2020-03-08 15:07:59 +01:00
Raphael Michel
a4ae8b0e66 Include lightbox in main.scss file 2020-03-08 15:07:59 +01:00
Raphael Michel
be1bf81298 Add font-display: swap to custom shop fonts 2020-03-08 15:07:59 +01:00
Raphael Michel
b7528ae1cf Cancelling events: Fix incorrect refund amount in emails 2020-03-06 18:09:38 +01:00
Raphael Michel
4f6712ccbe OrderChangeManager: Prevent creation of 0 € invoices 2020-03-06 17:05:15 +01:00
Raphael Michel
939335f94b Widget: Fix incorrect computation of cache key 2020-03-06 17:00:47 +01:00
Raphael Michel
c849276a35 Widget: Allow to filter by product 2020-03-06 17:00:26 +01:00
Raphael Michel
8e9f0f07a1 Optimize performance of waiting list dashboard tile 2020-03-06 14:03:28 +01:00
Raphael Michel
389884d191 shell_scoped: Fall back to shell if shell_plus is not installed 2020-03-06 14:03:06 +01:00
Raphael Michel
d8c2c82da7 Update invoice language in build_invoice 2020-03-06 09:36:20 +01:00
Raphael Michel
c3ed3d4899 Adjust tests for last commit 2020-03-05 18:12:40 +01:00
Raphael Michel
e9235cd433 Bank transfer: ignore order status when manually assigning amount 2020-03-05 17:37:54 +01:00
Raphael Michel
975b6d800a Fix hardcoded PK in test 2020-03-05 17:09:33 +01:00
Raphael Michel
ee260c8231 API: Allow to simulate orders 2020-03-05 16:37:55 +01:00
Raphael Michel
f7fddc05dd Improve caching of widget.js 2020-03-05 13:09:42 +01:00
Raphael Michel
eaa61c7795 Add @gzip_page to widget views 2020-03-05 12:53:17 +01:00
Raphael Michel
d4994258e6 Avoid issues with duplicate ItemBundles 2020-03-05 12:53:02 +01:00
Raphael Michel
9b50ec2d74 Cancelling events: Allow to inform waiting list 2020-03-05 10:22:59 +01:00
Raphael Michel
447b6b7fee Waiting list: Do not send notifications for disabled items 2020-03-05 09:37:35 +01:00
Raphael Michel
40f763c999 Cancelling events: Create special log entry 2020-03-05 09:37:17 +01:00
Raphael Michel
6a3d05be9e Cancelling events: Fix invalid email logging 2020-03-05 09:23:07 +01:00
Raphael Michel
766447f021 Fix typo in form field name 2020-03-04 14:45:54 +01:00
Raphael Michel
5fbeb90f00 Order import: Fix crash on unknown delimiter
PRETIXEU-1Y0
2020-03-04 14:44:55 +01:00
Raphael Michel
c01cc85eda Merge pull request #1597 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-03-03 17:13:10 +01:00
Raphael Michel
4a054da6ee Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3510 of 3510 strings)

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

powered by weblate
2020-03-03 17:11:12 +01:00
Raphael Michel
583a2b6572 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3510 of 3510 strings)

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

powered by weblate
2020-03-03 17:11:11 +01:00
Raphael Michel
fbe025afb2 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3488 of 3488 strings)

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

powered by weblate
2020-03-03 16:56:06 +01:00
Raphael Michel
66e6191122 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3488 of 3488 strings)

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

powered by weblate
2020-03-03 16:56:05 +01:00
Raphael Michel
0f26f0787c Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2020-03-03 16:55:56 +01:00
Raphael Michel
62a86c9b4a Allow to cancel all orders in an event (#1596)
* Allow to cancel all orders in an event

* Add tests

* Actually add tests
2020-03-03 16:55:05 +01:00
Raphael Michel
07318be4c9 Fix incorrect total in invoice preview 2020-03-03 16:45:18 +01:00
Raphael Michel
3c8ef2c620 Fix incorrect result message 2020-03-03 16:43:36 +01:00
Raphael Michel
a858f47220 API: Fix crash when cancelling a redeemed gift card 2020-03-02 13:18:16 +01:00
Raphael Michel
381fa5e1cd Allow to add a text to gift card transactions 2020-03-02 12:47:39 +01:00
Raphael Michel
1539eea664 API: Allow devices to access gift cards 2020-03-02 12:19:19 +01:00
Raphael Michel
3d41d1331a API: Support for cross-organizer gift cards 2020-03-02 12:19:02 +01:00
Raphael Michel
00848b3339 Remove "default" flag from cloned layouts 2020-03-02 09:35:53 +01:00
Raphael Michel
d174b11c6a Reduce retention time of sass compiler cache 2020-02-28 16:21:34 +01:00
Raphael Michel
a501ce496a Fix broken sass compiler cache 2020-02-28 16:21:18 +01:00
Raphael Michel
de277cc959 Badges, PDF tickets: Allow to copy layouts 2020-02-28 12:50:31 +01:00
Raphael Michel
12b3ae81d6 Mark payment_term_days settings as required in the backend 2020-02-28 12:24:00 +01:00
Raphael Michel
fcdb40dda0 Refs #1593 -- Fix subsequent issue when clearing the cart 2020-02-27 16:28:07 +01:00
Raphael Michel
f65cf8e86a Fix checks for min_per_order and max_per_order in combination with variations 2020-02-27 15:08:35 +01:00
Raphael Michel
12540238b7 Ignore replica database in tests 2020-02-27 14:44:20 +01:00
Raphael Michel
398a30e33a Merge pull request #1592 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-02-27 11:05:27 +01:00
Raphael Michel
3410640618 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3488 of 3488 strings)

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

powered by weblate
2020-02-27 11:05:05 +01:00
Raphael Michel
7b45cfccc2 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3488 of 3488 strings)

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

powered by weblate
2020-02-27 11:05:04 +01:00
Raphael Michel
33f503aea1 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2020-02-27 10:55:11 +01:00
Raphael Michel
3fd650081b Allowing more options to style pretix shops (#1585)
* Fix caching issues in SASS compilation

* Allow to set a custom page background color

* Allow to disable round corners

* Support larger header pictures

* Allow to show title despite header

* Move language picker

* FIx widget styles
2020-02-27 10:54:00 +01:00
Martin Gross
b622854be6 Fix versionchanged for item_meta_data 2020-02-27 09:25:44 +01:00
Raphael Michel
6d00daa9ee Fix crash when sending mails with empty mail_from 2020-02-26 17:34:37 +01:00
Raphael Michel
f27148998a Support for itemmeta: objects in the API 2020-02-26 15:59:42 +01:00
Raphael Michel
4a0369cc37 REbase migration 2020-02-26 15:22:41 +01:00
Martin Gross
76aaf61e19 Add meta_data for items (#1576)
* PoC for ItemMetaProperties/Values

* Missing is_valid

* ItemMetaProperties/Values in editable via API, cloneable

* Tests

* Add Docs

* Fix import order

* Fix another import sorting...

* Typeahead for ItemMetaValues

* Test for editing event-objects

* Fix typeahead permission checks

* Further access restriction

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2020-02-26 15:06:24 +01:00
Raphael Michel
dd1e5fa929 Test suite: Allow to cancel expired orders 2020-02-26 11:56:23 +01:00
Raphael Michel
4a2516e303 Allow to cancel expired orders 2020-02-26 11:23:52 +01:00
Raphael Michel
cf06712eca Merge pull request #1590 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-02-26 08:51:30 +01:00
Maarten van den Berg
6185d675f0 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 97.5% (3388 of 3474 strings)

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

powered by weblate
2020-02-26 04:00:13 +01:00
David100mark
a53cd3abce Translated on translate.pretix.eu (French)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2020-02-26 04:00:12 +01:00
David100mark
ebe86a17fb Translated on translate.pretix.eu (French)
Currently translated at 66.9% (2325 of 3474 strings)

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

powered by weblate
2020-02-26 04:00:11 +01:00
David100mark
d189b16ee7 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3474 of 3474 strings)

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

powered by weblate
2020-02-26 04:00:11 +01:00
Raphael Michel
d70ce4491a Merge pull request #1584 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-02-20 16:12:22 +01:00
Maarten van den Berg
607ff48d70 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 97.4% (3382 of 3474 strings)

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

powered by weblate
2020-02-19 18:25:18 +01:00
Maarten van den Berg
4bab44ca85 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3474 of 3474 strings)

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

powered by weblate
2020-02-19 18:25:18 +01:00
Raphael Michel
a5cdb485d0 Fix faulty test cases 2020-02-19 18:25:05 +01:00
Raphael Michel
282ef792c4 Do not show "create new event" if there are no permissions to dos o 2020-02-19 18:24:47 +01:00
Raphael Michel
6cd888a1dc Fix date parsing issue in Danish locale 2020-02-19 18:00:02 +01:00
Raphael Michel
2e5b80c83c Fix missing fees in order overview 2020-02-19 16:09:40 +01:00
Raphael Michel
4511110069 Fix timezone of notifications 2020-02-19 14:28:06 +01:00
Raphael Michel
1af1d8c658 Add rich_text_snippet 2020-02-18 09:45:20 +01:00
Raphael Michel
9f6a3f9a6a Add custom field for invoice addresses 2020-02-18 09:21:00 +01:00
Raphael Michel
1c03d5d305 Invoices: Label tax ID as ABN in Australia 2020-02-18 09:05:09 +01:00
Raphael Michel
69a1fccd20 Fix #1582 - Add explicit requirement to packaging library 2020-02-17 21:00:44 +01:00
Raphael Michel
2a54aa2d83 Bump to 3.7.0.dev0 2020-02-17 16:39:27 +01:00
Raphael Michel
2269c8dee0 Bump version to 3.6.0 2020-02-17 16:38:53 +01:00
Raphael Michel
46295ea887 Merge pull request #1581 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-02-17 16:31:00 +01:00
Raphael Michel
e41863229b Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3474 of 3474 strings)

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

powered by weblate
2020-02-17 16:30:22 +01:00
Raphael Michel
ca5a6ddba1 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3474 of 3474 strings)

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

powered by weblate
2020-02-17 16:30:21 +01:00
Raphael Michel
4d4dafb5dd Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2020-02-17 16:22:55 +01:00
Raphael Michel
9c2af952b7 Fix another empty sequence error 2020-02-17 14:00:15 +01:00
Raphael Michel
dc6e425c2a Fix accidental string interpolation in a test case 2020-02-17 13:57:36 +01:00
Raphael Michel
5f65b9528f Fix a migration exception 2020-02-17 13:30:19 +01:00
Raphael Michel
8957c2f106 Seating: Support custom row and seat labels 2020-02-17 13:15:49 +01:00
Raphael Michel
2bbbc88a9c Allow duplicate ticket secrets in different organizers 2020-02-17 12:35:02 +01:00
Raphael Michel
162b7c1b52 Fixed subsequent issue with failed paypal payments 2020-02-14 14:22:05 +01:00
Raphael Michel
755f3d53b6 Fix issue with failed paypal payments 2020-02-14 13:24:18 +01:00
Raphael Michel
f6db62d6ce Add retry button to cookie failure page 2020-02-14 11:43:14 +01:00
Raphael Michel
aa1ffc402c Remove some unnecessary queries 2020-02-14 11:25:28 +01:00
Raphael Michel
2c9b96f0c5 Fix various issues with min() statements 2020-02-14 09:16:04 +01:00
Raphael Michel
16599e242d Partially revert last change 2020-02-13 17:39:17 +01:00
Raphael Michel
19c13d7f38 Improved order position secret generation 2020-02-13 17:04:50 +01:00
Raphael Michel
65db8cd583 Widget: Pass referer to cart session 2020-02-13 09:58:08 +01:00
Raphael Michel
d0794d7b94 Optionally allow to automatically reissue an invoice after a data change 2020-02-13 09:49:21 +01:00
Raphael Michel
a770f5a8e7 Add FAQ section on payment deadlines 2020-02-13 09:37:25 +01:00
Raphael Michel
80a3063799 Fix KeyError in sendmail history 2020-02-13 09:22:50 +01:00
Raphael Michel
34ec11ecfa Fix KeyError for form validation 2020-02-13 09:21:23 +01:00
Martin Gross
a1da2eafdc Reinstate MOTO-Flagging for Reseller Scheme-TX (#1570) 2020-02-12 09:49:33 +01:00
Raphael Michel
6bc2175ea9 More robust redis connections 2020-02-12 09:29:30 +01:00
Raphael Michel
21dcb4f43d Revert "Optional MOTO-Flagging for Reseller Scheme-TXs (#1570)"
This reverts commit 0a920ac21c.
2020-02-12 08:01:24 +01:00
Raphael Michel
e9722bcdbd Merge pull request #1578 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-02-11 17:22:04 +01:00
Raphael Michel
e7eb8e3111 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3472 of 3472 strings)

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

powered by weblate
2020-02-11 17:21:00 +01:00
Raphael Michel
a895d83764 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3472 of 3472 strings)

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

powered by weblate
2020-02-11 17:20:59 +01:00
Raphael Michel
b6697b838b Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2020-02-11 16:55:36 +01:00
Raphael Michel
0d8c4271a9 Show successful payments on the invoice 2020-02-11 16:54:02 +01:00
Raphael Michel
d226bbda5c Order data export: Add column with the number of positions 2020-02-11 16:25:48 +01:00
Raphael Michel
38d0198dea Do not show a flag for single language events 2020-02-11 13:19:46 +01:00
Martin Gross
0a920ac21c Optional MOTO-Flagging for Reseller Scheme-TXs (#1570)
* Optional MOTO-Flagging for Reseller Scheme-TXs

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

Co-Authored-By: Raphael Michel <michel@rami.io>

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

Co-Authored-By: Raphael Michel <michel@rami.io>

* Manually rebase again...

* Fix a single whitespace for style...

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2020-02-11 11:08:28 +01:00
Raphael Michel
7acee9458d Move bulk_create logic during bulk voucher creation 2020-02-09 12:17:48 +01:00
Raphael Michel
82e40ce664 Merge pull request #1574 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-02-08 13:45:16 +01:00
Raphael Michel
4632269ac3 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3463 of 3463 strings)

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

powered by weblate
2020-02-08 13:44:32 +01:00
Raphael Michel
6d7e1ef53d Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3463 of 3463 strings)

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

powered by weblate
2020-02-08 13:44:30 +01:00
Raphael Michel
3ea4cdc3b3 Small categorization adjustments 2020-02-08 13:14:19 +01:00
Raphael Michel
e4619eeca3 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2020-02-08 12:42:35 +01:00
Raphael Michel
bb5c7c5ad7 Re-introduce plugin categories 2020-02-08 12:38:43 +01:00
Raphael Michel
9984fe97ba Add OrderPayment.fail() to prevent race conditions (#1572) 2020-02-07 09:00:35 +01:00
Martin Gross
242dd24caa Fix hiding of payment methods 2020-02-06 10:53:02 +01:00
Raphael Michel
2482d9390a Fix incorrect settings field type 2020-02-05 21:29:22 +01:00
Raphael Michel
3b4923ccae Fix required field 2020-02-05 18:34:58 +01:00
Raphael Michel
8a2e4385ff Fix required field 2020-02-05 18:33:40 +01:00
Raphael Michel
e83b8ac218 Allow to hide payment methods behind a secret link 2020-02-05 18:09:27 +01:00
Raphael Michel
b387fba5f4 Hide time on list of events for event series 2020-02-05 17:30:15 +01:00
Raphael Michel
da68cb618e Improved logging and transaction handling around payment confirmations 2020-02-05 12:02:02 +01:00
Raphael Michel
eb11dac21e Fix single badge downloads 2020-02-05 10:31:11 +01:00
Raphael Michel
6e531ee067 Event list filters: Deal with events without presale start/end 2020-02-05 10:31:11 +01:00
Raphael Michel
c8e6daa7a1 Do not show disabled sub-methods in timeline 2020-02-05 10:31:11 +01:00
Martin Gross
b3e3d427cb Add more Stripe Payment Methods, simplify forms (#1571)
* Add more Stripe Payment Methods, simplify forms

* Revert accidential commit of boxoffice control renderer...

* Use existing QR-Code encoding in presale
2020-02-05 09:50:29 +01:00
pajowu
6e88054af7 Send signal on checkin (#1546)
* Send signal when orderposition is checked in

* Add position_checked_in signal to documentation

* Rename signal to checkin_created

* Update general.rst

* Update signals.py

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2020-02-04 18:26:35 +01:00
Raphael Michel
22dfa0e61d Use select2 input for category fields 2020-02-04 18:22:03 +01:00
Alexander Schwartz
833cd32578 Comparison for file extensions should be case insensitive (#1563) 2020-02-04 17:09:12 +01:00
Raphael Michel
fd1c964c92 Fix #1378 -- API: Allow to access and modify (some) event setti… (#1569)
* API: Allow to access event settings

* Convert most "general" settings

* Smaller fixes

* Add more settings

* Relative dates, nulling

* Fix a test failure

* Fix wrong attribute access
2020-02-04 17:06:23 +01:00
Raphael Michel
87b10ef055 Allow to print multiple badges on one page (#1380)
* Allow to print multiple badges on one page

* Fix test

* Add more sizes

* Add A4 sizes
2020-02-04 17:02:33 +01:00
Raphael Michel
734f65b10b Ask Google not to index the "Resend order links" site 2020-02-04 16:26:05 +01:00
Martin Gross
0f826a6f76 Show payment card information for Stripe PaymentIntents 2020-02-04 15:09:34 +01:00
Raphael Michel
35e521cc55 Merge pull request #1564 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2020-02-03 17:58:29 +01:00
Martin Gross
63c845574f Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3363 of 3363 strings)

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

powered by weblate
2020-02-03 15:39:33 +01:00
Raphael Michel
5a675cc75d Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3363 of 3363 strings)

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

powered by weblate
2020-02-03 15:39:33 +01:00
András Veres-Szentkirályi
994dc9bf76 Translated on translate.pretix.eu (Hungarian)
Currently translated at 2.8% (94 of 3363 strings)

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

powered by weblate
2020-02-03 15:39:33 +01:00
Raphael Michel
cc4a07e3b0 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3363 of 3363 strings)

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

powered by weblate
2020-02-03 15:39:33 +01:00
Raphael Michel
2ca88d5328 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3363 of 3363 strings)

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

powered by weblate
2020-02-03 15:39:33 +01:00
Raphael Michel
0bca9b9bf1 Box office: Support for Stripe Terminal 2020-02-03 15:39:19 +01:00
Raphael Michel
742d2f11be SCSS: Fix font generation error 2020-02-01 16:25:05 +01:00
Raphael Michel
5ea5b82994 Docs: Fix typo 2020-02-01 16:25:00 +01:00
Raphael Michel
81245cf125 Fix #1549 -- JS API to open pretix Button 2020-02-01 15:59:24 +01:00
Raphael Michel
c6bcd05404 Widget: Fix button behaviour without iframe 2020-02-01 15:39:03 +01:00
Raphael Michel
1999a25095 Widget: no reload on buttons 2020-02-01 14:19:39 +01:00
Raphael Michel
62f7c5ba0f Doc spelling: Add SQL to wordlist 2020-02-01 14:08:24 +01:00
Raphael Michel
d11b0e92f1 Reverse default order of subevents 2020-02-01 13:59:55 +01:00
Raphael Michel
662bdea45b Preload all samples 2020-02-01 13:48:56 +01:00
Raphael Michel
d37cc4f641 Font API: Support for script-specific samples 2020-02-01 13:46:50 +01:00
Raphael Michel
6b2bc71be9 Order list export: consistent decimals 2020-02-01 13:06:40 +01:00
Raphael Michel
f267940562 Update Sphinx 2020-02-01 12:57:19 +01:00
Raphael Michel
7140406f35 Order data export: Split multiple choice questions in multiple columns 2020-02-01 12:47:39 +01:00
Raphael Michel
e275e2e240 API: Add endpoint to expose pretix version 2020-01-31 11:11:24 +01:00
Raphael Michel
75c0920f5e API: Allow to mark order as pending when creating refunds 2020-01-31 10:48:32 +01:00
Raphael Michel
b6efe9ae1e Fix middleware name 2020-01-29 12:44:39 +01:00
Raphael Michel
a28378bac9 Fix further problems with middleware ordering 2020-01-29 11:50:09 +01:00
Raphael Michel
a940fa9eb7 Correct order of middlewares, thereby fix event list widget on custom domains
With the incorrect order, Django used the wrong URL config file to
determine whether this URL is valid and APPEND_SLASH kicked in too
often.
2020-01-28 23:38:09 +01:00
Raphael Michel
332fba6168 DOwngrade libsass 2020-01-28 22:46:43 +01:00
Raphael Michel
41655532e9 Upgrade bootstrap to 3.4.0 2020-01-28 22:46:43 +01:00
Martin Gross
6cc9801fe1 Only allow letters, numbers, dots and dashes for giftcard codes 2020-01-28 16:53:44 +01:00
Raphael Michel
29ff5b9416 Use correct timezone for events in event list view 2020-01-28 13:32:43 +01:00
Raphael Michel
889dd651ef Add BasePaymentProvider.matching_id() 2020-01-27 21:46:36 +01:00
Raphael Michel
8c7d7a3055 Add documentation on the campaign module api 2020-01-27 21:24:48 +01:00
Raphael Michel
ff67931c04 Fix a documentation error 2020-01-27 16:51:50 +01:00
Raphael Michel
faa6f0e0a3 Correctly copy seats when copying events 2020-01-27 10:52:25 +01:00
Raphael Michel
68ec37605f Fix another doc typo 2020-01-26 19:35:58 +01:00
Raphael Michel
2ef8b89da0 Add reseller to doc wordlist 2020-01-25 17:16:09 +01:00
Raphael Michel
dfc746ea7a Add documentation on VAR API 2020-01-25 15:26:32 +01:00
Raphael Michel
661546f130 Update from Weblate (#1560)
Update from Weblate
2020-01-25 15:23:29 +01:00
Raphael Michel
5e61342ff5 Add an API for teams (#1562)
* Add Team resource to API

* Add team memer endpoints

* Add team invites endpoint

* Add token endpoints
2020-01-25 15:22:50 +01:00
Prokaj Miklós
4eadfdeec2 Translated on translate.pretix.eu (Hungarian)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2020-01-25 10:07:47 +00:00
Prokaj Miklós
8284a9de44 Translated on translate.pretix.eu (Hungarian)
Currently translated at 1.0% (35 of 3363 strings)

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

powered by weblate
2020-01-25 10:07:47 +00:00
Prokaj Miklós
ae2e70245f Added translation on translate.pretix.eu (Hungarian) 2020-01-25 10:07:47 +00:00
Prokaj Miklós
a92e283a66 Added translation on translate.pretix.eu (Hungarian) 2020-01-25 10:07:47 +00:00
Mie Frydensbjerg
9e2c0d8152 Translated on translate.pretix.eu (Danish)
Currently translated at 47.7% (1603 of 3360 strings)

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

powered by weblate
2020-01-25 10:07:47 +00:00
Raphael Michel
57453a5b00 Fix missing known_errortypes attribute 2020-01-25 11:07:42 +01:00
gnomus
1ccf677ea2 Solve some Docker Setup scaling issues (#1561) 2020-01-24 14:14:36 +01:00
Raphael Michel
0a9daf0d3a API: Fix crash when passing an empty seat_category_mapping during event creation 2020-01-23 14:27:43 +01:00
Raphael Michel
934217ee4f Attach invoices to order changed/canceled emails 2020-01-23 14:25:20 +01:00
Raphael Michel
deff282a63 Do not allow slugs to start with a non-alphanumeric character 2020-01-23 09:39:39 +01:00
Raphael Michel
bcd687764c API: Allow to create payments directly 2020-01-22 17:15:40 +01:00
Raphael Michel
8d7224fecc Force-escape all PDF values on the renderer level (#1556)
* Force-escape all PDF values

* Do not concatenate empty strings to name combinations
2020-01-22 14:56:25 +01:00
Martin Gross
3fff3378c0 Handle media_url of embedded images in simple mail renderer better for instances with dedicated media-hosts 2020-01-21 10:36:46 +01:00
Raphael Michel
91ae89d463 Add cache to sass compiler 2020-01-20 12:04:58 +01:00
Martin Gross
5c0d112def Add simple e-mail-renderer with logo (#1552)
* Add unembellished eMail-renderer

* Fix layout issues with very wide images

* Use prettier padding

* Rename to "simple with logo"

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2020-01-20 11:26:53 +01:00
Raphael Michel
f7ae90811e Merge pull request #1548 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2020-01-20 10:50:34 +01:00
Luka
6ec8c33ecc Translated on translate.pretix.eu (Slovenian)
Currently translated at 20.8% (698 of 3360 strings)

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

powered by weblate
2020-01-20 08:46:31 +00:00
Mie Frydensbjerg
f991d5434f Translated on translate.pretix.eu (Danish)
Currently translated at 47.7% (1602 of 3360 strings)

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

powered by weblate
2020-01-20 08:46:31 +00:00
Raphael Michel
7cf1688de5 PDF: Allow to use combinations of attendee name parts 2020-01-20 09:46:09 +01:00
Raphael Michel
298b3c3660 Consistently put original price in its own line 2020-01-17 17:04:13 +01:00
Raphael Michel
5ea1c96e19 Bump to 3.6.0.dev0 2020-01-12 14:44:25 +01:00
Raphael Michel
d29b8eba01 Bump to 3.5.0 2020-01-12 14:42:57 +01:00
Raphael Michel
f566b353f2 Retry more email failures 2020-01-11 14:11:20 +01:00
Raphael Michel
c2221fad32 Update from Weblate (#1544)
Update from Weblate
2020-01-11 14:00:18 +01:00
Maarten van den Berg
06a8a804f4 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3360 of 3360 strings)

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

powered by weblate
2020-01-11 12:54:54 +00:00
Raphael Michel
5832429540 Fix unrecognized user agents seen in the wild 2020-01-11 13:52:37 +01:00
Raphael Michel
7913de971c Fix timezone assumptions in test fixtures 2020-01-11 13:45:49 +01:00
Raphael Michel
9b91e3e4f6 Fix doc typos 2020-01-11 13:34:11 +01:00
Raphael Michel
402730df43 API: Add timezone attribute to events
Note: I still believe the issues described in https://github.com/pretix/pretix/issues/1378
are a problem, and I'm still not keen on adding settings properties to
the API until we have a proper design for it. However, I'm making an
exception here since the list of events can't be used in a very useful
way with not access to the timezone.
2020-01-11 13:24:19 +01:00
Raphael Michel
c1d6d9bf1a Downgrade openpyxl since we still support Python 3.5 2020-01-11 13:15:58 +01:00
Raphael Michel
ddbe27f351 API: Allow to return canceled positions and fees 2020-01-11 12:57:43 +01:00
Raphael Michel
35f2b10069 Compatibility with new openpyxl 2020-01-11 12:12:53 +01:00
Raphael Michel
0d2a534982 Bank transfer: Show date of last import 2020-01-09 21:41:08 +01:00
Raphael Michel
a1ad00a30c Add anchors for products/categories 2020-01-09 10:19:44 +01:00
Raphael Michel
07d2463960 Correctly set timezone when cloning events 2020-01-09 09:19:11 +01:00
Raphael Michel
08de722525 Prevent showing internal product name in question step 2020-01-09 09:19:11 +01:00
Raphael Michel
891e740ede Update from Weblate (#1541)
Update from Weblate
2020-01-08 16:52:20 +01:00
pajowu
87645a0b1f Limit Payment Term Days Field (#1542) 2020-01-08 16:52:02 +01:00
oocf
d7a575683e Translated on translate.pretix.eu (Spanish)
Currently translated at 91.6% (3077 of 3360 strings)

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

powered by weblate
2020-01-08 12:17:00 +00:00
Raphael Michel
ca83a44489 PDF Editor: Fix compatibility problems with older text objects 2020-01-08 13:16:42 +01:00
Raphael Michel
40da03f979 Improve text rendering options in PDF editor (#1540)
Improve text rendering options in PDF editor
2020-01-07 12:23:37 +01:00
Raphael Michel
fdd45a85f0 Do not send download reminders if order is placed after download date 2020-01-07 11:54:06 +01:00
Raphael Michel
47579d0517 Fix #505 -- Allow to let text flow downwards 2020-01-07 11:48:04 +01:00
Raphael Michel
8704a7f3dd Fix #1053 -- Rotation support in reportlab renderer 2020-01-07 11:26:28 +01:00
Raphael Michel
244e0695b1 PDF editor: Do not catch keyboard events inside source view 2020-01-03 18:30:33 +01:00
Raphael Michel
8e2821b398 Add a maximum budget to vouchers (#1526)
* Data model changes

* Fix test failures

* Adjustments

* Some tests and API support

* Check when extending orders

* Make things more deterministic, fix style

* Do not apply negative discounts

* Update price_before_voucher on item/subevent changes

* Add tests for price_before_voucher in combination with free price

* Fix InvoiceAddress.DoesNotExist
2020-01-03 16:15:17 +01:00
Raphael Michel
b738e3bd9d Do not show canceled payment to user 2020-01-03 10:48:57 +01:00
Raphael Michel
fa224fd17e Allow to use invoice address city in PDFs 2020-01-03 10:47:53 +01:00
Raphael Michel
76359c859f Improve logging of gift card changes and show logs 2020-01-02 17:40:11 +01:00
Katharina Bogad
ff98ae3200 Update oauthlib to ==3.1.* (#1538)
* Bumped oauthlib to ==3.1.*

* Fix tests for oauthlib 3.1

In some cases, oauthlib now returns 400 instead of 401.
2020-01-02 13:15:24 +01:00
Raphael Michel
2ffb4edee9 Import: Do not set country to NULL
PRETIXEU-1QJ
2020-01-02 10:53:01 +01:00
Raphael Michel
902f589ee6 Update from Weblate (#1536)
Update from Weblate
2020-01-02 10:11:49 +01:00
Raphael Michel
2a6dc22d7b Allow to use datetime components in invoice prefixes (#1529) 2020-01-02 09:46:07 +01:00
Abdullah
4d9ec7c8e4 Translated on translate.pretix.eu (Arabic)
Currently translated at 97.1% (100 of 103 strings)

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

powered by weblate
2019-12-25 19:33:08 +00:00
Abdullah
b84a0e93ae Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3310 of 3359 strings)

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

powered by weblate
2019-12-25 19:33:08 +00:00
Abdullah
534f09bdc6 Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3310 of 3359 strings)

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

powered by weblate
2019-12-25 19:33:08 +00:00
Patrick Arminio
370aa63ead Translated on translate.pretix.eu (Italian)
Currently translated at 47.6% (49 of 103 strings)

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

powered by weblate
2019-12-25 19:33:08 +00:00
Raphael Michel
fb7e859e72 Ticket download: Fix error message representation 2019-12-25 20:32:22 +01:00
Raphael Michel
80a7c45e05 Fix missing checks in 38a19bb58 2019-12-25 20:26:47 +01:00
Raphael Michel
38a19bb58b Allow to download tickets through GET requests 2019-12-23 22:07:32 +01:00
Patrick Arminio
19873e2a09 Expose help texts on questions' API (#1534)
* Expose help texts on questions' API

* Update questions docs to show help_text

* Update questions.rst

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2019-12-22 19:30:17 +01:00
Raphael Michel
eb7e938af6 Allow to explicitly set ticket language 2019-12-20 18:17:00 +01:00
Raphael Michel
614c40596f Move revoked devices to the bottom in the list of devices 2019-12-19 09:22:42 +01:00
Raphael Michel
4db56e218e Update from Weblate (#1530)
Update from Weblate
2019-12-19 08:56:49 +01:00
Abdullah
ed9b96a41c Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3310 of 3359 strings)

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

powered by weblate
2019-12-19 02:00:10 +00:00
Raphael Michel
e839dbc7d4 Emulate secure request in samesite tests 2019-12-18 19:37:43 +01:00
Raphael Michel
982fb0149d Never set SameSite=None without HTTPS 2019-12-18 19:03:17 +01:00
Raphael Michel
4597cb9849 Drop typeahead for seats 2019-12-18 13:48:15 +01:00
Raphael Michel
9f629fc1c9 Do not show subject prefix in HTML email headline 2019-12-17 11:09:05 +01:00
Raphael Michel
387e1b4998 Fix issue that blocks seat sold in canceled position 2019-12-16 14:00:08 +01:00
Raphael Michel
84415864e5 Note cloning of objects to log 2019-12-16 14:00:08 +01:00
Maico Timmerman
82feca6e38 Fix #1521 -- External authenticated users cannot delete events (#1523)
* Remove check password for event deletion, instead require recent login.

* Reauthenticate for backends using authentication_url.

* Require recent login for data shredder and prompt slug instead of password.

* Fix tests for recent login required on event delete and data shred.

* Pull request remarks for recent login required for event delete and data shred.

* Remove unused imported check_password.
2019-12-16 10:45:01 +01:00
Maico Timmerman
28242e52aa Fix #1522 -- Login button redirects to authentication url with… (#1525) 2019-12-16 10:42:51 +01:00
Raphael Michel
488ee19b11 Set direction:rtl in emails 2019-12-16 10:35:54 +01:00
Raphael Michel
ba4f00cfc0 Update from Weblate (#1520)
Update from Weblate
2019-12-16 10:33:56 +01:00
Maarten van den Berg
eb392ebf20 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-12-16 08:54:34 +00:00
Maarten van den Berg
3cef690779 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3360 of 3360 strings)

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

powered by weblate
2019-12-16 08:54:34 +00:00
Abdullah
294fc4735a Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3310 of 3359 strings)

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

powered by weblate
2019-12-16 08:54:34 +00:00
Abdullah
6d17cad529 Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3310 of 3359 strings)

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

powered by weblate
2019-12-16 08:54:34 +00:00
Abdullah
668380cc3f Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3310 of 3359 strings)

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

powered by weblate
2019-12-16 08:54:34 +00:00
Abdullah
3bf8de39a0 Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3310 of 3359 strings)

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

powered by weblate
2019-12-16 08:54:34 +00:00
saad91
1986cdf4b9 Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3310 of 3359 strings)

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

powered by weblate
2019-12-16 08:54:34 +00:00
Raphael Michel
577729a271 Add customer data to OrderTaxListReport 2019-12-16 09:54:16 +01:00
Raphael Michel
9033e5b6f7 Propose a gift card code when creating new cards 2019-12-16 09:45:36 +01:00
Raphael Michel
c1fa0d1559 Fix #1524 -- Bug in CartManager's max_per_item validation 2019-12-16 09:45:36 +01:00
Raphael Michel
e1a4dd6e43 Revert "Data model changes"
This reverts commit 089a468a5d.
2019-12-15 19:00:02 +01:00
Raphael Michel
089a468a5d Data model changes 2019-12-15 18:28:51 +01:00
Raphael Michel
018d345008 Add trust-x-forwarded-proto settings 2019-12-14 13:37:44 +01:00
Raphael Michel
529e2a0286 Re-set redeemed property when copying vouchers 2019-12-13 15:52:27 +01:00
Raphael Michel
1d36ef3c24 Allow to ignore plugin conflicts via environment 2019-12-13 12:57:25 +01:00
Raphael Michel
282ad2c869 Do not check compatibiliy while upgrading 2019-12-13 12:55:33 +01:00
Raphael Michel
e67ff83378 Do not allow to create negative gift cards through the API 2019-12-12 14:18:47 +01:00
Raphael Michel
21be22e489 Add typeahead when filling event meta parameters 2019-12-12 12:21:53 +01:00
Raphael Michel
3da79ad36b Remove positivity constraint in apply_voucher 2019-12-12 12:02:12 +01:00
Raphael Michel
8a17fedaa6 Clarify show_vouchers constraint with subevents 2019-12-12 11:56:36 +01:00
Raphael Michel
7d6b3e7140 Set sales channel on all cart operations 2019-12-12 10:06:00 +01:00
Raphael Michel
f80ba365a5 Fix test for voucher csv 2019-12-11 18:13:38 +01:00
Raphael Michel
8156cdd539 Add seat to voucher CSV export 2019-12-11 17:30:53 +01:00
Raphael Michel
cd55146867 Add request and subevent parameters to front page widgets 2019-12-11 17:05:05 +01:00
Raphael Michel
3cb7482bae Add signal pretix.presale.signals.front_page_bottom_widget 2019-12-11 17:04:59 +01:00
Raphael Michel
99f3db04a9 Allow to redeem a voucher for an existing cart (#1517)
* Allow to redeem a voucher for an existing cart

* Bundle behaviour
2019-12-11 15:58:22 +01:00
Raphael Michel
352942b7d6 Allow sale of blocked seats on specific channels (#1518)
* Allow sale of blocked seats on specific channels

* Add docs
2019-12-11 15:56:20 +01:00
Raphael Michel
6d3ccc0182 More useful titles in select2 widget for seats 2019-12-11 13:05:04 +01:00
Raphael Michel
49b73fc096 Fix redemption of all-product vouchers with seating plans 2019-12-11 12:06:23 +01:00
Raphael Michel
24b931e1c3 Allow to import orders (#1516)
* Allow to import orders

* seats, subevents

* Plugin support

* Add docs

* Warn about lack of quota handling

* Control interface test

* Test skeleton

* First tests for the impotr columns

* Add tests for all columns

* Fix question validation
2019-12-11 11:44:06 +01:00
Raphael Michel
1c99e01af9 Fix test failure introduced in last commit 2019-12-11 11:22:52 +01:00
Raphael Michel
66183e805e Check-in list export: Filter by checkin_attention 2019-12-11 09:16:58 +01:00
Raphael Michel
d33c9332c6 Show event time in list of events on organizer page 2019-12-11 09:11:54 +01:00
Raphael Michel
2284def607 Fix package name of dependency 2019-12-10 18:10:29 +01:00
Raphael Michel
15c25a5a0d PDF renderer: Support for arabic 2019-12-10 17:57:48 +01:00
Martin Gross
cf5ac6af4b Organizer-level override for giftcard code length 2019-12-09 13:55:26 +01:00
Raphael Michel
2a929200b5 Vouchers: Fix CSV export for all-product vouchers 2019-12-09 10:23:50 +01:00
Raphael Michel
3f77d34026 Use the arab league flag to represent Arabic, for now
https://www.quora.com/Which-flag-represents-the-Arabic-language
http://www.flagsarenotlanguages.com/blog/the-arab-league-flag-for-arabic-language/
2019-12-07 15:30:14 +01:00
Raphael Michel
a395b24b80 Add arabic, re-order languages 2019-12-07 15:08:57 +01:00
Raphael Michel
984ef60099 Fix issues in translation 2019-12-07 15:06:15 +01:00
saad91
5b6f0df963 Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3309 of 3359 strings)

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

powered by weblate
2019-12-07 13:56:30 +00:00
saad91
509c7d98cc Translated on translate.pretix.eu (Arabic)
Currently translated at 97.4% (3271 of 3359 strings)

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

powered by weblate
2019-12-07 13:20:50 +00:00
saad91
3bd4959efe Translated on translate.pretix.eu (Arabic)
Currently translated at 97.4% (3271 of 3359 strings)

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

powered by weblate
2019-12-07 13:20:50 +00:00
Maarten van den Berg
4faaa8e521 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-12-07 13:20:49 +00:00
Maarten van den Berg
0e8832fd54 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3360 of 3360 strings)

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

powered by weblate
2019-12-07 13:20:49 +00:00
Raphael Michel
4faa76d9c7 Translated on translate.pretix.eu (Arabic)
Currently translated at 98.5% (3274 of 3323 strings)

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

powered by weblate
2019-12-07 13:20:49 +00:00
Raphael Michel
8d1f9bf0f3 Fix TypeError during widget loading 2019-12-07 14:18:04 +01:00
Raphael Michel
4afef62cbd Fix typo 2019-12-07 13:24:35 +01:00
Raphael Michel
3d5cfdd9c7 Fix conflict between are-you-sure.js and async tasks
Thanks @luto for reporting and helping to debug
2019-12-07 13:23:33 +01:00
Raphael Michel
b3b1d09690 Use "Tax Invoice" as the invoice headline in Australia 2019-12-07 12:12:20 +01:00
Raphael Michel
381ecd6d75 Update German translation 2019-12-06 20:47:51 +01:00
Raphael Michel
a12fea71e5 Include expire date on invoices 2019-12-06 20:43:01 +01:00
Raphael Michel
a6dd6ac537 Fix AttributeError in e275677a0 2019-12-06 20:31:17 +01:00
Raphael Michel
c3041aa8c4 Fix ItemBundle.MultipleObjectsReturned error when extending cart lifetimes 2019-12-06 20:30:35 +01:00
Raphael Michel
e275677a0a Default to modern invoice renderer for new events 2019-12-06 20:25:05 +01:00
Raphael Michel
fff14c31ba Add Event.set_defaults 2019-12-06 20:24:34 +01:00
Raphael Michel
a74bde60eb Show invoice address form once again before generating a new invoice 2019-12-06 20:03:22 +01:00
Raphael Michel
12b9d23efb Hide "Generate invoice" button if no payment method is selected 2019-12-06 20:03:11 +01:00
Raphael Michel
afec39ce57 Fix exception when submitting cart positions with invalid subevent IDs 2019-12-06 15:58:13 +01:00
Raphael Michel
4ae22c4a1e Bump to 3.5.0.dev0 2019-12-06 15:36:27 +01:00
Raphael Michel
a928d8c781 Bump version to 3.4.0 2019-12-06 15:35:44 +01:00
Raphael Michel
27b7a756fb Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-12-06 15:07:44 +01:00
Raphael Michel
7252167cc7 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-12-06 15:07:44 +01:00
Raphael Michel
d6ac337bde Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3360 of 3360 strings)

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

powered by weblate
2019-12-06 15:07:44 +01:00
Raphael Michel
aa9d3ab614 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3360 of 3360 strings)

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

powered by weblate
2019-12-06 15:07:44 +01:00
Martin Gross
186fb851e6 Translated on translate.pretix.eu (Russian)
Currently translated at 33.5% (1127 of 3360 strings)

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

powered by weblate
2019-12-06 13:07:26 +01:00
Martin Gross
97f7507dad Translated on translate.pretix.eu (Latvian)
Currently translated at 33.5% (1124 of 3360 strings)

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

powered by weblate
2019-12-06 13:07:19 +01:00
Martin Gross
e4582dd9ed Add itemcount (Items, excluding AddOns) to fragment_cart (#1514)
* Add itemcount (Items, excluding AddOns) to fragment_cart

* Simplify itemcount
2019-12-05 16:45:41 +01:00
Raphael Michel
090d5bfe94 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-12-05 14:34:15 +01:00
Martin Gross
b4e98e21a1 Translated on translate.pretix.eu (Russian)
Currently translated at 33.8% (1124 of 3323 strings)

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

powered by weblate
2019-12-05 14:20:29 +01:00
Carolina Fernández
4571abe496 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-12-05 14:20:20 +01:00
Carolina Fernández
f86520af37 Translated on translate.pretix.eu (Spanish)
Currently translated at 92.5% (3074 of 3323 strings)

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

powered by weblate
2019-12-05 14:20:15 +01:00
Jaroslav Lichtblau
1a5b8a2e07 Translated on translate.pretix.eu (Czech)
Currently translated at 0.2% (8 of 3323 strings)

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

powered by weblate
2019-12-05 14:20:07 +01:00
Maico Timmerman
dddf91d3bf Feature/optional team provisioning (#1487)
* Event creation: Add option to select existing team instead of provisioning.

* Event creation: disallow event team provisioning on organizer level.

* Event team provisioning: update default setting location and display strings.

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

Co-Authored-By: Raphael Michel <mail@raphaelmichel.de>
2019-12-05 12:20:40 +01:00
Martin Gross
f88a09a78c Update reportlab requirement 2019-12-05 10:43:38 +01:00
Raphael Michel
adebcc31d4 Call order_paid when creating paid orders through the API 2019-12-05 10:02:46 +01:00
Raphael Michel
5645a07cdc Email design: Make height of separator more reliable 2019-12-05 10:02:27 +01:00
Martin Gross
1d732e9675 Update pillow requirement from 5.* to 6.* 2019-12-04 18:47:18 +01:00
Martin Gross
61de010bbb Put customer VAT ID in the same line as the descriptor 2019-12-04 17:55:13 +01:00
Martin Gross
22eb9d3493 Add "Are you sure?"-dialog to dirty forms (#1506)
* Add "Are you sure?"-dialog to dirty forms

* Better form detection
2019-12-04 13:59:08 +01:00
Raphael Michel
4e8bda0c96 Improve email contents (#1510)
Improve email contents
2019-12-04 13:58:20 +01:00
Raphael Michel
0c83e5a50c Use a narrower separator PNG to reduce wrong-looking area in dark mode 2019-12-04 12:23:45 +01:00
Raphael Michel
745d7f74a1 Fix a display issue in GMail 2019-12-04 11:56:14 +01:00
Raphael Michel
8c86169d3f Fix #1415 -- Proper MIME structure 2019-12-04 11:56:02 +01:00
Raphael Michel
3c87272fdc Bank transfer import: Fix crash when no date column is selected 2019-12-04 10:29:26 +01:00
Raphael Michel
f1142560f6 Fix crash with specific chromium user agents 2019-12-04 10:29:18 +01:00
Raphael Michel
d46278f04f Set cookies with SameSite=None if possible (#1509) 2019-12-03 14:50:18 +01:00
Raphael Michel
098b7363e6 Waren about products with hidden_if_available 2019-12-01 12:34:47 +01:00
Raphael Michel
11d0c37415 Try to discourage accidentally setting Item.hidden_if_available 2019-12-01 12:31:09 +01:00
Raphael Michel
99607aa74a Fix AttributeError in sendmail form
PRETIXEU-1NQ
2019-12-01 11:17:30 +01:00
Raphael Michel
d44b75388e RelativeDate: Fix parsing of invalid strings
PRETIXEU-1NH
2019-11-29 10:29:53 +01:00
Raphael Michel
531c8aedc2 Remove logging statement 2019-11-28 16:32:02 +01:00
Martin Gross
0474651070 Add unlimited_items_per_order-flag to Sales Channels (#1508)
* Add unlimited_items_per_order-flag to Sales Channels

* Test for unlimited_items_per_order Sales Channels-flag

* Fix test
2019-11-28 16:31:38 +01:00
Raphael Michel
fd7ad3cb16 Fix docs typo 2019-11-28 15:10:26 +01:00
Raphael Michel
d5b932e0d9 Add data-fix option to widget 2019-11-28 14:26:14 +01:00
Raphael Michel
5462e256ac Allow to create a new gift card when refunding 2019-11-28 13:50:10 +01:00
Raphael Michel
46d4d97c13 Add BasePaymentProvider.refund_control_render 2019-11-28 13:50:10 +01:00
Raphael Michel
8b5241d520 Improve error messages when changing seating plans 2019-11-28 12:12:12 +01:00
Raphael Michel
ee06844b0d Allow to sort the list of vouchers 2019-11-28 11:29:33 +01:00
Raphael Michel
d4a491fc1b Add new signal pretix.control.signals.order_search_filter_q 2019-11-28 11:18:09 +01:00
Raphael Michel
bb3348022f Allow to configure help text of email field 2019-11-28 11:11:55 +01:00
Raphael Michel
a10082a46f CSV exports: Subevent name and date in separate columns 2019-11-28 10:24:12 +01:00
Raphael Michel
ef555eaff1 Fix new tests on real databases 2019-11-27 17:40:42 +01:00
Raphael Michel
31adc37599 API: Add convenience methods to make it easier to create fees 2019-11-27 17:14:44 +01:00
Raphael Michel
1d02764be2 Do not show "other fee" text on invoices 2019-11-27 16:55:53 +01:00
Raphael Michel
f80f2e9bf9 Show "choose other event date" button even in iframes 2019-11-27 16:09:46 +01:00
Raphael Michel
f5e7e0e309 Fix crash if Bundle.designated_price is set to null 2019-11-27 16:07:22 +01:00
Raphael Michel
bbc70447a2 Allow to create vouchers for *all* products (#1504) 2019-11-27 14:57:09 +01:00
Raphael Michel
530632d624 Add invoice_line_text to documentation 2019-11-26 17:15:13 +01:00
Raphael Michel
370130f047 Add signal invoice_line_text 2019-11-26 16:41:41 +01:00
Raphael Michel
14575693b8 Add signal item_description 2019-11-26 16:36:25 +01:00
Raphael Michel
436dcc68f2 Show link for other dates in widget checkout 2019-11-25 16:44:42 +01:00
Raphael Michel
b49e02d988 Voucher redemption: Disable add-on-cart button if no products are
selected
2019-11-25 15:54:11 +01:00
Raphael Michel
037644421b Question statistics: Do not include canceled orders 2019-11-25 15:42:37 +01:00
Raphael Michel
654faa5a10 Order list: Filter by answers should be case-sensitive 2019-11-25 15:42:37 +01:00
Raphael Michel
53a22e0e88 Expiry warnings: Pre-fetch expiry column 2019-11-24 16:27:47 +01:00
Raphael Michel
f8a080d180 Refs #1289 -- Download reminders for subevents and download reminder performance 2019-11-24 16:05:03 +01:00
Raphael Michel
b013737d70 Fix AttributeError introduced in last commit 2019-11-24 15:40:14 +01:00
Raphael Michel
24d6816dac Improve performance of send_expire_warnings and expire_orders 2019-11-24 15:28:03 +01:00
Maico Timmerman
2eb92bef9b Send widget_data when opening waiting list (#1502) 2019-11-24 11:33:56 +01:00
Raphael Michel
8d5b4d190c Fix TypeError introduced in 50dabb5b2 2019-11-22 16:10:58 +01:00
Raphael Michel
50dabb5b26 Show seat in web and PDF check-in list 2019-11-22 13:32:39 +01:00
Raphael Michel
84fb25e4d9 Voucher creation: Auto-select products based on seat 2019-11-22 13:25:20 +01:00
Raphael Michel
ee4f75c2fb Introduce Seat.sorting_rank (#1499)
* Introduce Seat.sorting_rank

* Fix comments

* Comments, for real
2019-11-22 13:25:08 +01:00
Raphael Michel
1e63eb743c Merge pull request #1498 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-11-22 13:24:21 +01:00
Mie Frydensbjerg
09477e1431 Translated on translate.pretix.eu (Danish)
Currently translated at 48.1% (1600 of 3323 strings)

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

powered by weblate
2019-11-21 17:00:11 +00:00
Raphael Michel
2be49d8cab Bank transfer: Do not cause duplicate payments 2019-11-21 17:22:11 +01:00
Raphael Michel
808ccfee75 Bank import: useful matching of negative transactions
Negative transactions are never matched automatically, which does not
change. However, when matching them manually, a negative payment was
created, which does not make much sense. Now, if a negative payment is
manually matched, the system checks whether:

a) There is a manual refund in pending state. In this case, the refund
   will be marked as done.

b) There is a bank transfer payment of the same amount, in which case
   this is handled like a credit card chargeback (i.e. notification on
   the dashboard, ...)

c) Otherwise, an error will be returned asking the user to create a
   refund object manually.
2019-11-20 18:35:15 +01:00
Raphael Michel
485766e247 Sendmail plugins: Fix issues around history of checkin-filtered emails 2019-11-20 18:08:22 +01:00
Martin Gross
bab27f5ab6 Respect payment-saleschannel restrictions also on order change 2019-11-20 17:18:51 +01:00
Martin Gross
26141da1b3 Update from Weblate (#1497)
Currently translated at 70.9% (73 of 103 strings)

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

powered by weblate

Co-authored-by: Mie Frydensbjerg <mif@aarhus.dk>
2019-11-20 15:42:27 +01:00
Mie Frydensbjerg
4c6cf63d68 Translated on translate.pretix.eu (Danish)
Currently translated at 70.9% (73 of 103 strings)

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

powered by weblate
2019-11-20 14:41:46 +00:00
Martin Gross
24fa251b05 Update from Weblate (#1496)
Update from Weblate

Co-authored-by: Mie Frydensbjerg <mif@aarhus.dk>
2019-11-20 15:01:16 +01:00
Mie Frydensbjerg
d65eaf7b95 Translated on translate.pretix.eu (Danish)
Currently translated at 48.1% (1598 of 3323 strings)

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

powered by weblate
2019-11-20 13:59:25 +00:00
Mie Frydensbjerg
e24a7f3887 Translated on translate.pretix.eu (Danish)
Currently translated at 69.9% (72 of 103 strings)

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

powered by weblate
2019-11-20 13:54:08 +00:00
Martin Gross
ec66b20798 Fix ToC for docs 2019-11-20 12:55:19 +01:00
Martin Gross
2c3c9a4ba1 Merge pull request #1495 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-11-20 10:04:41 +01:00
Martin Gross
4c8e1cef7f Translated on translate.pretix.eu (Russian)
Currently translated at 33.8% (1123 of 3323 strings)

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

powered by weblate
2019-11-20 09:02:33 +00:00
Raphael Michel
180963c787 Widget: Auto-open seating plan in some situations 2019-11-19 22:48:47 +01:00
Raphael Michel
ee96fb2ca1 Fix widget test cases 2019-11-19 22:42:51 +01:00
Raphael Michel
5563259b11 Fix gettext import 2019-11-19 21:44:34 +01:00
Raphael Michel
e7b86e0deb Refactor attribution note in the widget 2019-11-19 21:16:04 +01:00
Raphael Michel
067c3b1abc Update from Weblate (#1494)
Update from Weblate
2019-11-19 17:00:48 +01:00
Maarten van den Berg
3fff04bcda Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3323 of 3323 strings)

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

powered by weblate
2019-11-19 15:55:59 +00:00
Maarten van den Berg
150ebc7ddb Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3323 of 3323 strings)

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

powered by weblate
2019-11-19 15:55:56 +00:00
Raphael Michel
2f906bae5c Translated on translate.pretix.eu (Latvian)
Currently translated at 33.7% (1120 of 3323 strings)

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

powered by weblate
2019-11-19 15:55:01 +00:00
Raphael Michel
82d1927fe7 Fix date range shown for one-day event 2019-11-19 16:16:08 +01:00
Raphael Michel
cde87b437a Update from Weblate (#1493)
Update from Weblate
2019-11-18 16:01:00 +01:00
Zane Smite
9cbf3490cf Translated on translate.pretix.eu (Latvian)
Currently translated at 33.7% (1120 of 3323 strings)

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

powered by weblate
2019-11-18 15:00:25 +00:00
Raphael Michel
56cfdfc5b3 Stripe: Only use alphanumeric characters in statement descriptors 2019-11-18 10:47:43 +01:00
Raphael Michel
8564d20183 Update from Weblate (#1492)
Update from Weblate
2019-11-18 10:46:11 +01:00
Raphael Michel
c3155ad16d Translated on translate.pretix.eu (Arabic)
Currently translated at 98.1% (101 of 103 strings)

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

powered by weblate
2019-11-18 09:44:59 +00:00
Raphael Michel
7fce062c2a Translated on translate.pretix.eu (Arabic)
Currently translated at 98.1% (101 of 103 strings)

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

powered by weblate
2019-11-18 09:41:05 +00:00
Raphael Michel
05311fda54 Update from Weblate (#1490)
Update from Weblate
2019-11-17 19:40:39 +01:00
Raphael Michel
0396c851ed Translated on translate.pretix.eu (Arabic)
Currently translated at 98.1% (101 of 103 strings)

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

powered by weblate
2019-11-17 18:35:20 +00:00
Zane Smite
1d2094f5ea Translated on translate.pretix.eu (Latvian)
Currently translated at 33.7% (1120 of 3323 strings)

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

powered by weblate
2019-11-17 18:35:20 +00:00
Maarten van den Berg
44b8531614 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 97.1% (3225 of 3323 strings)

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

powered by weblate
2019-11-17 18:35:20 +00:00
Raphael Michel
5e9610eecf Fix typo in function call 2019-11-17 19:35:06 +01:00
Raphael Michel
1c40351b27 Bulk voucher sending: Correctly strip lines of whitespace 2019-11-17 19:31:41 +01:00
Martin Gross
3a4196c087 Remove seating-icon filler to stop Chrome complaining about the CSP for inline-styling 2019-11-15 20:53:20 +01:00
Martin Gross
080b5f02cb Fix FAQ on linear barcodes spelling even more, ffs... 2019-11-15 19:21:30 +01:00
Martin Gross
21c052918c Fix FAQ on linear barcodes spelling 2019-11-15 18:53:58 +01:00
Raphael Michel
9515249098 Bulk voucher creation: Fix spaces in seat IDs 2019-11-15 18:44:47 +01:00
Martin Gross
a0dd495cc3 FAQ on linear barcodes 2019-11-15 16:27:07 +01:00
Martin Gross
46ce9904cd Fix documentation for override_layout signal 2019-11-15 15:54:50 +01:00
Martin Gross
600310da0f Fix override_layout signal 2019-11-15 12:12:13 +01:00
Martin Gross
3b306de1bb New signal: ticketoutput_override_layout (#1483)
* Add signal to override ticketoutput layout

* Change Signal collection to send_chained

* Move ticketoutput_override_layout to ticketoutputpdf-plugin
2019-11-15 11:18:52 +01:00
Raphael Michel
a2c1c69d7e Seat-specific vouchers (#1486)
* Basic functionality

* API

* Do not delete seats with vouchers

* Show seat in list of seats

* Validate availability of seats

* Fix invalid logic in Seat.is_available

* Show voucher name in edit form
2019-11-15 10:56:34 +01:00
Raphael Michel
f79df47b78 Add specific social media image 2019-11-15 10:33:55 +01:00
Raphael Michel
1703f3d636 Update from Weblate (#1489)
Update from Weblate
2019-11-15 09:47:48 +01:00
Raphael Michel
580ae7a2e9 Translated on translate.pretix.eu (Latvian)
Currently translated at 33.5% (1113 of 3323 strings)

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

powered by weblate
2019-11-15 08:42:30 +00:00
Raphael Michel
34703cf6de Translated on translate.pretix.eu (Russian)
Currently translated at 33.8% (1123 of 3323 strings)

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

powered by weblate
2019-11-15 08:32:53 +00:00
Raphael Michel
1999495638 Translated on translate.pretix.eu (Russian)
Currently translated at 33.8% (1123 of 3323 strings)

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

powered by weblate
2019-11-15 08:30:51 +00:00
Raphael Michel
00798d9207 Payment providers: Add priority flag 2019-11-15 09:29:38 +01:00
Raphael Michel
ac2ff6c2aa Phone number fields: Do not crash on invalid default value 2019-11-15 08:48:14 +01:00
Raphael Michel
35ac2f0d2b Merge pull request #1482 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-11-14 12:38:43 +01:00
Raphael Michel
3432bb089a Translated on translate.pretix.eu (Latvian)
Currently translated at 33.4% (1110 of 3323 strings)

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

powered by weblate
2019-11-14 11:37:41 +00:00
Zane Smite
69b03b24a1 Translated on translate.pretix.eu (Latvian)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-11-14 11:12:30 +00:00
Raphael Michel
520dd3e254 Refunds: Do not try to cancel canceled order 2019-11-14 12:12:15 +01:00
Raphael Michel
d98fce3594 Fix cancelling payments through the API 2019-11-14 12:10:45 +01:00
Raphael Michel
aa5ae8b2bd Do not crash because of invalid phone numbers in the database 2019-11-14 11:36:31 +01:00
Raphael Michel
b148182240 Cancel payments when cancelling order 2019-11-14 10:42:33 +01:00
Raphael Michel
339d7f06ed Payment provider API: Add method cancel_payment 2019-11-14 10:39:54 +01:00
Raphael Michel
b876293453 Fix problem when cancelling an order with invoices with different prefixes 2019-11-13 14:14:14 +01:00
Martin Gross
54091b9721 Add question type: phone number (#1462)
* Add Phonenumber-Field as to Questions

* Add setup requirements

* Add list of ask-during-checkin restricted question types and enforce it

* Fix requirements

* Fix crash using custom locales

* Re-format phone numbers when outputting to humans

* Initialize country code field with a guess for the customer's country

* Document TEL type in API docs
2019-11-13 12:52:07 +01:00
Raphael Michel
06a744ea2d Refactor cart management into new helper function cached_invoice_address 2019-11-13 11:42:03 +01:00
Raphael Michel
c24ab642ce Fix logic bug introduced in 0e9d2cfc1 during form validation 2019-11-13 10:30:29 +01:00
Raphael Michel
2ab3e1d420 Explicitly write Latvian with L 2019-11-13 10:08:51 +01:00
Raphael Michel
0e9d2cfc10 Require certain parts of names if names are required 2019-11-12 18:15:25 +01:00
Raphael Michel
63a46d0156 Fix russian translation 2019-11-12 17:28:49 +01:00
Martin Gross
6896682dd1 Restrict Payment Providers to Sales Channels (#1481)
* Allow to restrict payment providers to specific sales channels

* Fix test

* Add `payment_restrictions_supported`-property to SalesChannels
2019-11-12 17:11:43 +01:00
Raphael Michel
384e7f8fc1 Update from Weblate (#1479)
Update from Weblate
2019-11-12 17:11:29 +01:00
Mie Frydensbjerg
d403b3d433 Translated on translate.pretix.eu (Danish)
Currently translated at 48.0% (1595 of 3323 strings)

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

powered by weblate
2019-11-12 10:11:34 +00:00
Zane Smite
552e523229 Translated on translate.pretix.eu (Latvian)
Currently translated at 98.1% (101 of 103 strings)

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

powered by weblate
2019-11-12 10:11:34 +00:00
Maarten van den Berg
95ca6da74a Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3323 of 3323 strings)

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

powered by weblate
2019-11-12 10:11:34 +00:00
Zane Smite
bce8879804 Translated on translate.pretix.eu (Latvian)
Currently translated at 49.5% (51 of 103 strings)

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

powered by weblate
2019-11-12 10:11:34 +00:00
Raphael Michel
a05cce8af7 Translated on translate.pretix.eu (Russian)
Currently translated at 97.1% (100 of 103 strings)

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

powered by weblate
2019-11-12 10:11:34 +00:00
Zane Smite
dd6f1ee70f Translated on translate.pretix.eu (Latvian)
Currently translated at 10.7% (11 of 103 strings)

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

powered by weblate
2019-11-12 10:11:34 +00:00
Raphael Michel
ad91ed4a30 Correctly deal with tax rules that used to have custom rules 2019-11-12 11:11:21 +01:00
Raphael Michel
e3c3154469 Fix invoice duplication when importing payments for expired orders 2019-11-11 18:59:33 +01:00
Raphael Michel
54cd38bbaa Always render product names bold 2019-11-11 17:13:54 +01:00
Raphael Michel
8dbba1cfd3 Order details: Do not show download note when disabled on product level 2019-11-11 12:38:33 +01:00
Raphael Michel
0bf3ee1a6f Refactor into method in ClassicInvoiceRenderer 2019-11-10 13:07:06 +01:00
Raphael Michel
da7e1dee3e Offset refunds: Catch exceptions in the right place 2019-11-10 12:59:10 +01:00
Raphael Michel
df3cc1499f Fix #1480 -- Remove apparently obsolete code 2019-11-08 16:13:23 +01:00
Raphael Michel
2d066e3b5e Add CSS required for font preview to organizer settings 2019-11-08 09:19:27 +01:00
Raphael Michel
25cfa8973c API: Add invoice line position to invoice object 2019-11-07 15:43:22 +01:00
Raphael Michel
3f7f04ed21 Enable Russian and Latvian languages 2019-11-07 13:50:41 +01:00
Raphael Michel
88b2f4738a Ticket preview: use an existing subevent 2019-11-07 13:06:01 +01:00
Raphael Michel
ea71b1f04e Fix typo 2019-11-06 15:42:20 +01:00
Raphael Michel
03a06b6997 Fix wrong attribute name 2019-11-06 15:42:12 +01:00
Raphael Michel
1cd319357d Add "Invoice" to the filename of attached invoices 2019-11-06 14:10:42 +01:00
Raphael Michel
4c653aa4f1 Bump to 3.4.0.dev0 2019-11-06 14:05:54 +01:00
Raphael Michel
5b4e27c47b Bump version to 3.3.0 2019-11-06 13:10:40 +01:00
Raphael Michel
919afb94c4 Fix outdated API documentation 2019-11-05 19:00:09 +01:00
Raphael Michel
8f112f8d9a Pass cart positions to fee_calculation_for_cart 2019-11-04 11:00:48 +01:00
Raphael Michel
d15e37d93e Do not show availability for currently-selected add-on 2019-11-04 09:55:00 +01:00
Raphael Michel
47cf019079 Make voucher send task transaction-aware 2019-11-04 09:18:23 +01:00
Raphael Michel
f51521143b PDF: Fix incorrect type of position id 2019-11-02 12:33:29 +01:00
Raphael Michel
a0ca76f0ec Fix incorrect offset payments when changing and splitting orders at the same time 2019-11-02 11:15:26 +01:00
Raphael Michel
d7f71948c1 Fix typo in docstring of a signal 2019-11-01 14:30:51 +01:00
Raphael Michel
7d72611851 Allow to add order position number to PDFs 2019-11-01 14:30:51 +01:00
Raphael Michel
2e9e5a8994 Fix untranslated email subject 2019-10-31 16:00:44 +01:00
Raphael Michel
c8289877bb Update from Weblate (#1476)
Update from Weblate
2019-10-31 15:40:39 +01:00
Raphael Michel
bb0ad6fb11 Translated on translate.pretix.eu (Turkish)
Currently translated at 77.9% (2587 of 3323 strings)

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

powered by weblate
2019-10-31 14:39:48 +00:00
Raphael Michel
6dcdb87725 Translated on translate.pretix.eu (Spanish)
Currently translated at 90.2% (2997 of 3323 strings)

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

powered by weblate
2019-10-31 14:39:47 +00:00
Raphael Michel
c2c8f36724 Translated on translate.pretix.eu (Greek)
Currently translated at 91.8% (3051 of 3323 strings)

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

powered by weblate
2019-10-31 14:39:46 +00:00
Raphael Michel
11c0a38665 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3323 of 3323 strings)

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

powered by weblate
2019-10-31 14:39:45 +00:00
Raphael Michel
c1c5388923 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3323 of 3323 strings)

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

powered by weblate
2019-10-31 14:39:43 +00:00
Raphael Michel
c7054fe72d Translated on translate.pretix.eu (French)
Currently translated at 69.7% (2317 of 3323 strings)

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

powered by weblate
2019-10-31 14:39:42 +00:00
Raphael Michel
13be3b7899 Translated on translate.pretix.eu (Danish)
Currently translated at 47.5% (1580 of 3323 strings)

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

powered by weblate
2019-10-31 14:39:41 +00:00
Raphael Michel
d9fe3e51da Translated on translate.pretix.eu (Catalan)
Currently translated at 31.4% (1044 of 3323 strings)

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

powered by weblate
2019-10-31 14:39:40 +00:00
telegnom
e381e50fbf missing dot in docker command (#1474) 2019-10-30 18:00:33 +01:00
Raphael Michel
73a323528b Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-10-30 17:23:53 +01:00
Raphael Michel
3a0aaa3f92 Stripe: Relax same-session requirement for iDEAL payments
This security mechanism caused problems with banking apps who open the
return view in a separate browser session inside the banking app.
2019-10-30 17:23:11 +01:00
Martin Gross
2a9c105e51 PayPal: Show payment ID on paid invoices (#1471)
* Always call render_invoice_text - even if order is already paid

* Print PayPal payment ID on invoice if available and invoice is paid

* Also display Sale ID on invoice

* try/except for paymentId/SaleId
2019-10-30 10:48:15 +01:00
Raphael Michel
038533ad63 Allow to change fees in existing orders (#1472)
* Allow to change fees in existing orders

* Add tests

* Add special case for payment options

* Fix PK reference in tests
2019-10-29 22:04:42 +01:00
Raphael Michel
fcf9f0054e Fix #1328 -- Show internal name in list of categories 2019-10-29 20:01:35 +01:00
Raphael Michel
621b0c8c95 Fix #1390 -- Fix string on frontpage being evaluated before translations are loaded 2019-10-29 20:00:00 +01:00
Raphael Michel
4a40312a32 Fix #1377 -- Copy has_subevents when cloning events through the API 2019-10-29 19:54:45 +01:00
Raphael Michel
b156efaae8 Fix #1468 -- Fix sorting by fallback name parts in check-in lists 2019-10-29 19:53:52 +01:00
Felix Schäfer
f473439f77 PDF renderer: Properly escape HTML answer fields (#1473) 2019-10-29 17:58:10 +01:00
Raphael Michel
9ed49fb379 PDF renderer: Properly escape HTML in some fields 2019-10-29 12:10:30 +01:00
Martin Gross
70e95b42fa Add manual order-matching field for duplicate bankimport transactions (#1461)
* Add manual order-matching field for duplicate bankimport transactions

* Only show matching-field for already matched transactions - not for errored ones.
2019-10-29 11:54:36 +01:00
Raphael Michel
1e0e8184c8 Fix #312 -- Bulk-send vouchers via email (#1469)
* Allow to directly bulk-send vouchers via email

* Send mails

* Log messages

* Fix test failures

* Add new test cases
2019-10-29 11:53:59 +01:00
Raphael Michel
161f4a8132 Update from Weblate (#1467)
Update from Weblate
2019-10-29 11:51:13 +01:00
Raphael Michel
8ac978062f Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3302 of 3302 strings)

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

powered by weblate
2019-10-29 10:50:30 +00:00
Raphael Michel
619435d0f6 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3302 of 3302 strings)

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

powered by weblate
2019-10-29 10:50:29 +00:00
Maarten van den Berg
7e9e03cef4 Translated on translate.pretix.eu (Dutch)
Currently translated at 97.5% (3219 of 3302 strings)

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

powered by weblate
2019-10-29 08:42:26 +00:00
Raphael Michel
dc3e76d6d3 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-10-29 09:42:15 +01:00
Raphael Michel
df2c9bdce5 Adjust strings in tests 2019-10-29 09:41:33 +01:00
Raphael Michel
20a6decb3a Notifications: Add details about subevents and products 2019-10-28 22:54:56 +01:00
Raphael Michel
3d31b95201 Event list: Autocomplete meta values 2019-10-28 22:35:16 +01:00
Raphael Michel
cc970caad8 Allow to filter global event list using organizer parameters 2019-10-28 22:03:09 +01:00
Raphael Michel
845f231446 Replace "Tickets on sale" with "Book now" in frontend 2019-10-28 21:46:33 +01:00
Raphael Michel
538d1b6b58 Adjust order of events on dashboard 2019-10-28 17:01:05 +01:00
Raphael Michel
601fefc57f Remove any doubt from dashboard order 2019-10-28 16:58:32 +01:00
Raphael Michel
645b9696b7 Increase length of OAuthGrant.redirect_uri 2019-10-28 16:42:17 +01:00
Raphael Michel
ee5ce900d2 Update from Weblate (#1460)
Update from Weblate
2019-10-25 17:04:58 +02:00
Martin Gross
ab0be57106 API: Allow to filter events by attributes (#1466)
* Allow filtern events-ressource on API by attrs

* Document meta-data attr-filter for order resource

* Actually document the change and not some random other nonsensical thing...

* Doc for subevent-filtering

* Test for attr-filtering on events resource

* Allow attr-filtering on subevents

* Add attr-filter test for subevent

* Update doc/api/resources/subevents.rst

* Update src/tests/api/test_subevents.py

* Update doc/api/resources/events.rst
2019-10-25 17:04:20 +02:00
oocf
ee6c4551b3 Translated on translate.pretix.eu (Spanish)
Currently translated at 90.9% (3000 of 3302 strings)

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

powered by weblate
2019-10-25 14:50:08 +00:00
yichengsd
d63956fe25 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 94.1% (3107 of 3302 strings)

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

powered by weblate
2019-10-25 14:50:08 +00:00
yichengsd
27fb9cd8b0 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 92.5% (3056 of 3302 strings)

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

powered by weblate
2019-10-25 14:50:08 +00:00
yichengsd
ada2de8bc9 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 92.2% (3046 of 3302 strings)

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

powered by weblate
2019-10-25 14:50:08 +00:00
yichengsd
7fe3ac3b5e Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 91.6% (3026 of 3302 strings)

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

powered by weblate
2019-10-25 14:50:08 +00:00
yichengsd
13cf24d7fa Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 90.3% (2981 of 3302 strings)

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

powered by weblate
2019-10-25 14:50:08 +00:00
Felix Rindt
6d95603aa8 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3302 of 3302 strings)

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

powered by weblate
2019-10-25 14:50:08 +00:00
Raphael Michel
97fbec804b Stripe: Include event name in statement descriptors 2019-10-25 16:49:13 +02:00
Raphael Michel
8f16145dda Order creation API: Use correct language for email subjects 2019-10-25 09:02:38 +02:00
Raphael Michel
3f5e835367 Add safeguards and tests against duplicate cancellations 2019-10-24 16:07:59 +02:00
Tobias Kunze
fe92af10fb Ignore whitespace on typeahead queries (#1464)
Whitespace is often added when you copy/paste order or voucher codes
2019-10-24 10:29:57 +02:00
Martin Gross
5e88dcf329 Fix saleschannels testmode-support-message check 2019-10-23 17:54:07 +02:00
Raphael Michel
d7c8460c28 Fix failing widget tests 2019-10-23 17:40:33 +02:00
Raphael Michel
837775c8d4 Correctly respect mail_attach_ical setting on order payments 2019-10-23 16:17:39 +02:00
Raphael Michel
2a51969b04 Add (hidden) location field to event list widget 2019-10-23 15:28:15 +02:00
Martin Gross
357972c8f8 Rearrange order_gracefully_delete signal-call (#1463) 2019-10-23 15:17:46 +02:00
Raphael Michel
08460f9918 Add loading callback to widget 2019-10-23 15:09:20 +02:00
Raphael Michel
277b7ad71c Hide manual refunds from users as well 2019-10-23 14:39:21 +02:00
Raphael Michel
d0340044d3 Order transition: Deal with empty cancellation fee 2019-10-22 17:58:51 +02:00
Raphael Michel
8abaa16fab Change log level of widget loading 2019-10-22 10:40:14 +02:00
Raphael Michel
5e4a16bd44 Allow to filter event list in organizer view by meta data 2019-10-21 19:03:51 +02:00
Raphael Michel
b219faafaa Work around a potential Django bug on MySQL
On MySQL, we observe that the preloaded items are missing everywhere
except the first time they occur.
2019-10-21 16:13:48 +02:00
Martin Gross
3006d35622 Skip ticket-attachment for text/uri-list 2019-10-21 16:00:42 +02:00
Martin Gross
a879a86dff Remove unused property 2019-10-21 15:02:44 +02:00
Raphael Michel
926b94c88f Update from Weblate (#1459)
Update from Weblate
2019-10-21 14:43:56 +02:00
Raphael Michel
638f1324fe Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3302 of 3302 strings)

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

powered by weblate
2019-10-21 12:43:10 +00:00
Raphael Michel
be7fea4e2e Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3302 of 3302 strings)

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

powered by weblate
2019-10-21 12:42:57 +00:00
Raphael Michel
adf7b6a4ed Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-10-21 14:07:43 +02:00
pajowu
fc4c6444cb Sendmail plugin: Allow filtering for check-ins (#1382)
* Allow filtering mass-emails for Checkin Lists

* Allow filtering mass emails for not checked in

* Fix email filtering logic issue

* Use Select2 for checkin lists selection

* sendmail plugin: Make checkin list filtering optional

* Remove unused constant

* Re-size panel to only fit the right column

* Revert incorrect JavaScript change

* Change semantics of not_checked_in

* Introduce a subquery to filter on position properties
2019-10-21 14:06:11 +02:00
Martin Gross
03c760c2bb Allow ticket output providers to handle downloads externally (#1402)
* TicketOutput-Providers: Make preview optional; download/attachable optional; optional specific target; update doc

* Spelling fixes in doc

* Changes after code-review

* Changes after code-review

* Commit missing template file

* Allow for redirects instead of files

* Return HTTPResponse with Content-Type text/uri-list on API

* Update API-doc

* Add viewable to spellinglist, fixing doc-test
2019-10-21 14:05:09 +02:00
Raphael Michel
27538d220e Fix #1416 -- Add canonical geodata field (#1458)
* Fix #1416 -- Add canonical geodata field for events and subevents

* Add optional geocoding through OpenCageData

* Fix markup everywhere

* Add Leaflet map to geo coordinates

* Fix tests, add credits

* Fix spelling
2019-10-21 13:07:35 +02:00
Raphael Michel
19b10e3ca4 Add option to attach calendar files to emails (#1457) 2019-10-21 10:41:22 +02:00
Martin Gross
2b18621c76 Add flag testmode_supported to sales channels (#1455)
* Add testmode-support-flag to SalesChannels

* Make saleschannels/testmode-warnings even more dangerous!

* Add warning for payment-methods that do support testmode but are being used in a non-testmode order caused by a saleschannel in a testmode-shop.

* Remove redundant testmode_supported-flag for WebshopSalesChannel

* Raise error on API when sales_channel does not support testmode

* Tests

* Fix style issue after merge
2019-10-21 10:07:02 +02:00
Raphael Michel
e8a2f7e349 Collapse gift card option when the gift card is not enough 2019-10-18 18:18:45 +02:00
Raphael Michel
a326ee8f75 Prevent creating negative gift code values 2019-10-18 17:54:23 +02:00
Raphael Michel
147b90b9e8 Update from Weblate (#1456)
Update from Weblate
2019-10-18 17:54:01 +02:00
Raphael Michel
1a461531eb Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3284 of 3284 strings)

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

powered by weblate
2019-10-18 15:51:52 +00:00
Raphael Michel
f854a0f8d6 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3284 of 3284 strings)

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

powered by weblate
2019-10-18 15:51:51 +00:00
Raphael Michel
137ed5da6a Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3218 of 3218 strings)

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

powered by weblate
2019-10-18 15:23:12 +00:00
Raphael Michel
6695fef3ee Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3218 of 3218 strings)

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

powered by weblate
2019-10-18 15:23:12 +00:00
Raphael Michel
4a8827cc2b Do not show "a refund is on its way" message for internal refunds 2019-10-18 17:22:50 +02:00
Raphael Michel
328b4ec633 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-10-18 17:17:48 +02:00
Raphael Michel
974a84fefe Merge pull request #1396 from pretix/giftcard
Support for gift cards
2019-10-18 17:08:45 +02:00
Raphael Michel
94b6b86696 Fix faulty test 2019-10-18 15:42:46 +02:00
Raphael Michel
8fe9b35dea Add more tests 2019-10-18 15:12:26 +02:00
Raphael Michel
2c87d5ece3 Update src/pretix/control/templates/pretixcontrol/organizers/giftcards.html
Co-Authored-By: Martin Gross <gross@rami.io>
2019-10-18 15:09:06 +02:00
Raphael Michel
f8433b5cc9 Add some tests 2019-10-18 13:08:25 +02:00
Martin Gross
0a200de41c Fix spelling in order_gracefully_delete signal 2019-10-18 12:07:33 +02:00
Martin Gross
25a998a510 Add order_gracefully_delete signal (#1454) 2019-10-18 11:38:26 +02:00
Raphael Michel
a1c7a6f2b0 Fix items test 2019-10-17 21:31:45 +02:00
Raphael Michel
1fe93ac6b7 Do not allow to pay gift cards with gift cards 2019-10-17 18:12:06 +02:00
Raphael Michel
4b2f25ce8a Add testmode for gift cards 2019-10-17 18:05:04 +02:00
Raphael Michel
33cee65d7f Add a tooltip with the date to pending orders 2019-10-17 17:27:33 +02:00
Raphael Michel
302966808e More docs and payments 2019-10-17 17:19:31 +02:00
Raphael Michel
767e679140 Show issued gift card in order view 2019-10-17 17:07:59 +02:00
Raphael Michel
1a07686b79 Merge branch 'giftcard' of github.com:pretix/pretix into giftcard 2019-10-17 17:00:10 +02:00
Raphael Michel
7b4c3a00a0 Update src/pretix/control/templates/pretixcontrol/organizers/giftcards.html
Co-Authored-By: Martin Gross <gross@rami.io>
2019-10-17 16:59:53 +02:00
Raphael Michel
7db87af754 Update src/pretix/control/templates/pretixcontrol/organizers/giftcards.html
Co-Authored-By: Martin Gross <gross@rami.io>
2019-10-17 16:59:44 +02:00
Raphael Michel
da9c30089a Fix sv translation 2019-10-17 16:40:46 +02:00
Raphael Michel
89a85392a9 Fixes 2019-10-17 16:39:42 +02:00
Raphael Michel
02b6cc5aad Update from Weblate (#1450)
Update from Weblate
2019-10-17 16:06:38 +02:00
Raphael Michel
b3e6f44027 Add double-spend safeguard 2019-10-17 16:04:22 +02:00
Raphael Michel
ac2df35db6 Allow configuring cross-organizer acceptance 2019-10-17 16:04:22 +02:00
Raphael Michel
ac212b798d Remove unneeded settings 2019-10-17 16:04:22 +02:00
Raphael Michel
195d418e00 Add spelling word 2019-10-17 16:04:22 +02:00
Raphael Michel
ba286d96cb Rebase and manually squash migrations 2019-10-17 16:04:22 +02:00
Raphael Michel
9842fcf7da Allow order change 2019-10-17 16:04:22 +02:00
Raphael Michel
e97ae04581 Helpful error messages 2019-10-17 16:04:22 +02:00
Raphael Michel
346f215c50 Refator payment provider, deal with cancellations 2019-10-17 16:04:22 +02:00
Raphael Michel
e099fad0ca Refator payment provider, deal with cancellations 2019-10-17 16:04:22 +02:00
Raphael Michel
4aeada0bfb Fix KeyError 2019-10-17 16:04:22 +02:00
Raphael Michel
73dd94fe73 Actually issue giftcards 2019-10-17 16:04:22 +02:00
Raphael Michel
8c50b7409f Add Item.issue_giftcard 2019-10-17 16:04:22 +02:00
Raphael Michel
b07d9d167d Add an API 2019-10-17 16:04:22 +02:00
Raphael Michel
f22d5915ea Handle refunds 2019-10-17 16:04:22 +02:00
Raphael Michel
e37d85f517 Cross-organizer acceptance 2019-10-17 16:04:22 +02:00
Raphael Michel
c68f715e07 Pending display 2019-10-17 16:04:22 +02:00
Raphael Michel
db71ec92be Payment step 2019-10-17 16:04:22 +02:00
Raphael Michel
85e7a16880 Backend management of gift cards 2019-10-17 16:04:22 +02:00
Raphael Michel
ed370fa913 Proof of concept 2019-10-17 16:04:22 +02:00
Raphael Michel
f7f00fe735 Data model 2019-10-17 16:04:22 +02:00
Tobias Sundgren
37b05fb4f4 Translated on translate.pretix.eu (Swedish)
Currently translated at 6.7% (217 of 3218 strings)

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

powered by weblate
2019-10-17 13:34:04 +00:00
Maarten van den Berg
defd00ffdf Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3218 of 3218 strings)

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

powered by weblate
2019-10-17 13:34:04 +00:00
Maarten van den Berg
9f44d7bbf5 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3218 of 3218 strings)

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

powered by weblate
2019-10-17 13:34:04 +00:00
Raphael Michel
28e27e91b2 Added translation on translate.pretix.eu (Latvian) 2019-10-17 13:34:04 +00:00
Raphael Michel
e519a50805 Added translation on translate.pretix.eu (Latvian) 2019-10-17 13:34:04 +00:00
Raphael Michel
b11b79b559 Update .travis.sh 2019-10-17 15:33:59 +02:00
Raphael Michel
88378be14e Add word to wordlist 2019-10-17 14:54:37 +02:00
Raphael Michel
8a8f8ae10a Fix KeyError in question_is_visible if question dependency is unknown 2019-10-17 12:57:17 +02:00
Raphael Michel
404d88a220 Fix #1451 -- Make event slug available in email templates 2019-10-17 11:50:01 +02:00
Raphael Michel
a48d461c22 Docs: Fix incorrect headline 2019-10-17 11:39:44 +02:00
Raphael Michel
5e59d41f6e Offset payment provider: Catch QuotaExceededException 2019-10-17 10:15:10 +02:00
Raphael Michel
7fb77eef34 Offset payment provider: Ignore payment term 2019-10-17 10:13:56 +02:00
Raphael Michel
f1321b67f9 Add missing docs file 2019-10-17 09:52:49 +02:00
Raphael Michel
8a6a515b6a Refs #775 -- Pluggable authentication backends (#1447)
* Drag-and-drop: Force csrf_token to be present

* Rough design

* Missing file

* b.visble

* Forms

* Docs

* Tests

* Fix variable
2019-10-17 09:11:03 +02:00
Martin Gross
e34511b984 Fix #1446 - Migrate NullBooleanSelect from 1/2/3 to unknown/true/false 2019-10-16 10:11:51 +02:00
Raphael Michel
fa3b40f53c Drag-and-drop: Force csrf_token to be present 2019-10-15 15:55:39 +02:00
Sohalt
b870dde301 Replace occurrences of "blacklist" with "banlist" (#1434)
* Rename blacklist to banlist

* Rename more cases of blacklist to banlist

* Rename Blacklist -> Banlist in migrations
2019-10-15 14:58:48 +02:00
Raphael Michel
a4d8c810ce Support for right-to-left languages (#1438)
* play around

* Flip things in presale

* Convert backend

* Remove test settings

* Safe getattr
2019-10-15 11:41:23 +02:00
Felix Schäfer
4152ee4e50 Fix #1408 -- Don't mark upload question fields as required if… (#1443) 2019-10-15 11:40:28 +02:00
Raphael Michel
7eb5b09b84 Merge remote-tracking branch 'origin/stripe-connect-fees' 2019-10-15 10:06:24 +02:00
Raphael Michel
2f7c16d18b Remove JavaScript lambda function (not supported in IE) 2019-10-15 10:04:17 +02:00
Raphael Michel
49bff3cc33 Fix field requirement display 2019-10-14 13:56:53 +02:00
Raphael Michel
80c9d1ff9e Allow requiring invoice name even if invoice address is required 2019-10-14 13:55:19 +02:00
Martin Gross
dc51d51338 Add hint to devices in checkinlist overview 2019-10-14 11:40:57 +02:00
Raphael Michel
8aa8a2265f Update from Weblate (#1441)
Update from Weblate
2019-10-14 11:24:49 +02:00
Maarten van den Berg
971275e774 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3218 of 3218 strings)

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

powered by weblate
2019-10-14 08:54:27 +00:00
Sohalt
91b586ce08 Reordering questions with drag'n'drop (#1433)
* Reordering questions with drag'n'drop

* Add permission check

* Test permissions for question reordering

* Handle malformed requests for question reordering

* Show first up arrow and last down arrow

* Provide page offset

* Revert "Provide page offset"

This reverts commit 8090bd573f.

* Reorder questions endpoint with pagination support

* Rudimentary test for reordering endpoint

* Make reordering questions atomic

* cache questions

* Properly support pagination for reorder_questions

* appease linter

* Fix test
2019-10-14 10:54:20 +02:00
Johan von Forstner
f10d1bd236 fix typo (#1442) 2019-10-14 10:44:03 +02:00
Martin Gross
eafed2e213 Enforce that questions cannot depend on other question which are asked during checkin (Z#2352753) 2019-10-14 10:12:09 +02:00
Raphael Michel
02c81e0fa7 Stripe connect: Allow to set application fee 2019-10-13 13:30:58 +02:00
Raphael Michel
8e9a5e371c Fix documentation of Enterprise instlalations 2019-10-11 17:14:09 +02:00
Martin Gross
25345275c7 Link to ticket cancelation settings in timeline 2019-10-11 13:30:40 +02:00
Raphael Michel
b9a911dd97 Fix #1440 -- API confusion about creating free orders 2019-10-11 09:00:46 +02:00
Raphael Michel
361488f3e6 Bump to 3.3.0.dev0 2019-10-10 18:17:27 +02:00
Raphael Michel
b704d21e88 Bump to 3.2.0 2019-10-10 18:16:25 +02:00
Raphael Michel
4bfe0e3784 Order change manager: Allow to add multiple products 2019-10-10 12:59:16 +02:00
Raphael Michel
d4d046ca60 Order change manager: Allow to disable invoice issuing 2019-10-10 12:19:06 +02:00
Raphael Michel
fb3fc05522 Remove references to our legacy apps 2019-10-10 09:27:14 +02:00
Raphael Michel
fdcd750487 Disable autocomplete in context switcher 2019-10-09 17:16:47 +02:00
Raphael Michel
92754136a6 Refs #1432 -- Proper grouping of autocomplete properties 2019-10-09 12:40:05 +02:00
Raphael Michel
3b4d39ec27 Fix #1432 -- Correct autocomplete attributes of name part fields 2019-10-09 12:40:05 +02:00
Sohalt
88bb3b483c Only disable up arrow on first page and down arrow on last page (#1437) 2019-10-09 09:15:35 +02:00
Raphael Michel
247370839b Fix placeholder form validation 2019-10-09 08:58:05 +02:00
Raphael Michel
b0510f47b3 Move new option down a little 2019-10-08 17:43:28 +02:00
Raphael Michel
4fa086bbc5 Update from Weblate (#1435)
Update from Weblate
2019-10-08 16:09:46 +02:00
Raphael Michel
cb03e7c843 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3218 of 3218 strings)

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

powered by weblate
2019-10-08 14:09:12 +00:00
Raphael Michel
d012d0804a Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3218 of 3218 strings)

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

powered by weblate
2019-10-08 14:09:11 +00:00
Raphael Michel
3731d5f431 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-10-08 15:57:05 +02:00
Raphael Michel
9f04d53564 Add safety mechanism for order code generation 2019-10-08 15:53:37 +02:00
Martin Gross
748a389acb Auto-check-in for specific sales channels (#1409)
* Autocheckin data model/cosmetics

* Expose automatically checked-in OrderPositions

* Expose automatically checked-in OrderPositions in CSV/PDF Exports

* Fix some tests, try to fix MultiStringField/CheckboxSelectMultiple

* Actually fix MultiStringField/CheckboxSelectMultiple.
(Not pretty, but it works)

* Fix more tests

* Squash migration

* Also fix CSV/nameparts-test

* Changes for Autocheckin code-review

* Perform Auto-Checkins through new core plugin

* Update config-doc to reflect also checkinlists

* Explicitly output AutoCheckin Yes/No for CSV-Export (+ fix test)

* Move autocheckin from plugin to service

* API-doc

* Fix API-doc spelling

* Checkinlist-API and autocheckin order tests

* Performance improvement when reading checkinlists for autocheckin

Co-Authored-By: Raphael Michel <michel@rami.io>

* Autocheckin test for order created through API

* Resolve migration conflict
2019-10-08 15:50:22 +02:00
Sohalt
05a1df244b Fix #1388 -- Prevent some words from occurring in order codes (#1422)
* prevent some words from occurring in order codes

* Use regex to match against blacklist

* Prevent some words from occurring in voucher codes

* Rename blacklist to banlist
2019-10-08 14:28:51 +02:00
Raphael Michel
9f7d5156cc Refs #1430 -- Fix untranslated string 2019-10-08 12:29:10 +02:00
Martin Gross
143fe6c1a6 Fix #1430 - Fix fieldname-filter for BaseInvoiceNameForm 2019-10-07 17:48:18 +02:00
Raphael Michel
1e3ccc4449 Merge pull request #1431 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-10-07 12:16:32 +02:00
Raphael Michel
d09000b716 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3210 of 3210 strings)

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

powered by weblate
2019-10-07 10:14:52 +00:00
Raphael Michel
fc4572767e Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3210 of 3210 strings)

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

powered by weblate
2019-10-07 10:14:51 +00:00
Raphael Michel
eeedd9a9c2 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-10-07 11:49:10 +02:00
Raphael Michel
1d0c148170 Fix #467 -- Pluggable email placeholders (#1429)
* Fix #467 -- Pluggable email placeholders

* Previews

* Polishing

* Fix tests

* Add missing doc file
2019-10-07 11:48:25 +02:00
Raphael Michel
cb37e7435d Use a different-colored favicon in development mode 2019-10-07 09:03:46 +02:00
Raphael Michel
749ddbf21c Allow topbar navigation subitems not to have an URL 2019-10-06 17:02:53 +02:00
Raphael Michel
9f6634025f Update from Weblate (#1425)
Update from Weblate
2019-10-06 11:56:32 +02:00
Chris Spy
ad039ae08e Translated on translate.pretix.eu (Greek)
Currently translated at 96.1% (99 of 103 strings)

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

powered by weblate
2019-10-06 09:53:51 +00:00
Chris Spy
eeaaca574d Translated on translate.pretix.eu (Greek)
Currently translated at 95.5% (3076 of 3220 strings)

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

powered by weblate
2019-10-06 09:53:51 +00:00
Fabian Rodriguez
bef15ec442 Translated on translate.pretix.eu (French)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-10-06 09:53:51 +00:00
Raphael Michel
447a8b0a8c Add missing mark_safe call 2019-10-06 11:53:37 +02:00
Sohalt
05b4d954d9 Make voucher code in notification clickable (#1423)
* Make voucher code in notification clickable

* Move html out of translated string
2019-10-06 11:52:34 +02:00
Felix Rindt
515d8c4899 remove .csv from default filename in List Exporter (#1428) 2019-10-06 11:51:24 +02:00
Raphael Michel
82497cfb89 Lazy-load widgets on global dashboard 2019-10-06 11:46:42 +02:00
Raphael Michel
4ade9d39cd Add "back" parameter to logout view 2019-10-06 11:35:29 +02:00
Raphael Michel
4360d5652b ALlow to pre-select organizer for event creation 2019-10-06 11:35:29 +02:00
Raphael Michel
9fca3188b2 Device and team creation: List events ordered and with date 2019-10-04 17:28:48 +02:00
Raphael Michel
0ed48fac7f Add pagination to event list on organizer detail page 2019-10-04 17:28:48 +02:00
Raphael Michel
27a32173e6 Move more code into change_payment_provider 2019-10-04 17:28:48 +02:00
Martin Gross
81b6188777 New ApplePay domain association file for Stripe 2019-10-02 21:31:40 +02:00
Raphael Michel
9e85d3c94c PDF editor: Catch ValueError during float conversion 2019-09-30 14:37:09 +02:00
Raphael Michel
4e58ba7594 money_filter: Idempotency on empty strings
PRETIXEU-1EH
2019-09-30 14:37:09 +02:00
Raphael Michel
248493dbf2 Stripe plugin: Fix AttributeError 2019-09-29 18:32:19 +02:00
Raphael Michel
f1bd240096 Update from Weblate (#1417)
Update from Weblate
2019-09-29 14:05:15 +02:00
Serge Bazanski
0b673dc68c Translated on translate.pretix.eu (Polish)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-09-27 07:11:22 +00:00
Serge Bazanski
b2a7fe13da Translated on translate.pretix.eu (Polish)
Currently translated at 14.7% (474 of 3220 strings)

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

powered by weblate
2019-09-27 07:11:22 +00:00
Martin Gross
d28f735fca Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3220 of 3220 strings)

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

powered by weblate
2019-09-27 07:11:22 +00:00
Jonathan Oberländer
7a3b450bc6 Added translation on translate.pretix.eu (Romanian) 2019-09-27 07:11:22 +00:00
Raphael Michel
f1ec129c0a Fix ZeroDivisionError 2019-09-27 09:11:03 +02:00
Raphael Michel
ce6e46dfd2 Fix performance of check-in list API list 2019-09-26 15:18:53 +02:00
Martin Gross
f296f262e6 Properly indent handling for non-addons 2019-09-23 11:12:50 +02:00
Martin Gross
7f8d290ae1 Add-Ons inhert question-answers from parent item if necessary 2019-09-23 10:47:05 +02:00
Raphael Michel
ca0c0f4ae3 Revert to multipart/related 2019-09-20 11:22:11 +02:00
Sohalt
ac1e69f2d8 Install pretix into system-site-packages in Docker image (#1410) 2019-09-20 09:43:00 +02:00
Raphael Michel
6338cc69ae docs: fix BaseDataShredder class documentation (#1412)
The BaseDataShredder documentation contained two references to BaseInvoiceRenderer, probably due to a minor copy/paste mistake
2019-09-20 09:41:32 +02:00
Felix Rindt
39eaf3ad6a Code style improvements (#1411)
* docstring corrections

* move omit_hyphen formfield
2019-09-20 09:34:24 +02:00
Raphael Michel
76e75bef65 Fix broken organizer settings test 2019-09-19 18:33:30 +02:00
Raphael Michel
a39822aedc Use transaction aware task for regenerate_css 2019-09-19 18:17:43 +02:00
Raphael Michel
73d5a2cec0 Revert "Make all EventTasks transaction-aware"
This reverts commit 3f7807d242.
2019-09-19 18:15:47 +02:00
Raphael Michel
9f668e5fd6 Fix crash in OrderEmailHistory for unknown orders 2019-09-19 18:04:31 +02:00
Raphael Michel
1b92a891d7 Fix issues with context providers in error pages 2019-09-19 18:03:35 +02:00
Raphael Michel
827925e3c9 Fix bug in 3f7807d24 2019-09-19 18:03:28 +02:00
Johannes Lauinger
c0be574974 docs: fix BaseDataShredder class documentation
The BaseDataShredder documentation contained two references to BaseInvoiceRenderer, probably due to a minor copy/paste mistake
2019-09-19 17:57:07 +02:00
Raphael Michel
738413e8fd Allow to copy categories and quotas 2019-09-19 16:59:25 +02:00
Raphael Michel
2c6125adeb Allow to copy vouchers 2019-09-19 16:33:59 +02:00
Raphael Michel
3f7807d242 Make all EventTasks transaction-aware 2019-09-19 16:23:40 +02:00
Raphael Michel
59b7f0a03d Fix a new bug in Stripe webhooks PRETIXEU-64 2019-09-19 10:36:33 +02:00
Raphael Michel
b7dea16db3 Promote danish to an inofficial language 2019-09-19 10:13:34 +02:00
Raphael Michel
3a81706aeb Fix KeyError in bank import 2019-09-17 16:48:41 +02:00
Raphael Michel
ad3369b059 Fix issue on old-style webhook URLs (#1385)
* Fix issue on old-style webhook URLs

* Fix test issue
2019-09-17 13:59:57 +02:00
Martin Gross
ab0709558d Fix #1389: PaymentIntents are 'processing' and not 'pending' 2019-09-17 13:03:22 +02:00
Raphael Michel
e2e64ac01d Event selector: Prevent race conditions 2019-09-17 10:08:58 +02:00
Martin Gross
cf14dcf889 Add Subevent-Filter for Voucher-Tags (#1407)
* Add Subevent-Filter for Voucher-Tags

* Filter Subevent Voucher-Tags with proper Filter

* Apply filter before annotating totals and usage
2019-09-16 14:08:23 +02:00
Raphael Michel
a884a25d2b Update from Weblate (#1403)
Update from Weblate
2019-09-16 13:42:41 +02:00
Gianmarco Palumbo
8493778028 Translated on translate.pretix.eu (Italian)
Currently translated at 44.7% (46 of 103 strings)

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

powered by weblate
2019-09-13 18:00:10 +00:00
Martin Gross
36a276c360 Translated on translate.pretix.eu (French)
Currently translated at 72.5% (2335 of 3220 strings)

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

powered by weblate
2019-09-13 18:00:09 +00:00
Maarten van den Berg
795c423d73 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3220 of 3220 strings)

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

powered by weblate
2019-09-12 22:00:12 +00:00
Maarten van den Berg
3ce9ec79f0 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3220 of 3220 strings)

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

powered by weblate
2019-09-12 22:00:10 +00:00
Translators EN IT Team
debc5e255b Translated on translate.pretix.eu (Italian)
Currently translated at 4.7% (151 of 3220 strings)

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

powered by weblate
2019-09-12 00:00:09 +00:00
Raphael Michel
145aa0d7fd Merge pull request #1397 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-09-11 08:58:42 +02:00
Raphael Michel
3997bdd098 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3220 of 3220 strings)

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

powered by weblate
2019-09-11 06:58:06 +00:00
Raphael Michel
a51a905512 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3220 of 3220 strings)

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

powered by weblate
2019-09-11 06:58:06 +00:00
Maarten van den Berg
d3ac9e8880 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3220 of 3220 strings)

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

powered by weblate
2019-09-11 06:55:48 +00:00
Maarten van den Berg
07402c9ea0 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3220 of 3220 strings)

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

powered by weblate
2019-09-11 06:55:47 +00:00
Raphael Michel
5f81bf55ca Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-09-11 08:55:36 +02:00
Raphael Michel
0120a5a930 Clarify cancellation description 2019-09-11 08:55:03 +02:00
Raphael Michel
ba555f956e Fix #1395 -- Incorrect data in tax list exporter if an order contains multiple tax rates 2019-09-10 14:41:49 +02:00
Raphael Michel
5e3876ddde Tax exporters: Use lazy gettext calls 2019-09-10 14:40:05 +02:00
Raphael Michel
f5719687aa Use lazy translation for exporter name 2019-09-10 13:13:15 +02:00
Raphael Michel
18449efcc7 Fix #1394 -- Forcefully update defusedcsv 2019-09-10 12:10:41 +02:00
Raphael Michel
3bb20d943a Fix incorrect condition in template 2019-09-10 11:58:02 +02:00
Raphael Michel
586e544fce Add "resend link" option to attendees 2019-09-10 11:44:59 +02:00
Raphael Michel
3a4fc69db1 Add link to ticket page to order page in backend 2019-09-10 11:34:52 +02:00
Raphael Michel
8b5d49d82f Dekodi exporter: Fix AttributeError 2019-09-10 11:34:19 +02:00
Raphael Michel
7665faa39f Bump version to 3.2.0.dev0 2019-09-10 10:54:25 +02:00
Raphael Michel
027d28e646 Bump verstion to 3.1.0 2019-09-10 10:50:18 +02:00
Raphael Michel
babcf66a2e Update from Weblate (#1393)
Update from Weblate
2019-09-10 10:18:46 +02:00
Raphael Michel
20f9e88d61 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3220 of 3220 strings)

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

powered by weblate
2019-09-10 08:16:51 +00:00
Raphael Michel
9594607a8c Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3220 of 3220 strings)

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

powered by weblate
2019-09-10 08:16:50 +00:00
Raphael Michel
ad3bcaf43a Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-09-10 10:00:56 +02:00
Raphael Michel
2c4ee3b3c7 Replace U2F with WebAuthn (#1392)
* Replace U2F with WebAuthn

* Imports

* Fix backwards compatibility

* Add explanatory comment

* Fix tests
2019-09-10 09:58:31 +02:00
Raphael Michel
21451db412 Fix Greek VAT IDs 2019-09-10 09:46:00 +02:00
Raphael Michel
12b1f7d90e Fix #1374 -- Correct encoding of images 2019-09-10 09:39:16 +02:00
Raphael Michel
c3901c567e Correctly respect both attention flag in check-in list exports 2019-09-09 11:07:22 +02:00
Raphael Michel
0a3eddcd5c Merge pull request #1384 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-09-09 09:48:36 +02:00
Raphael Michel
dfa99cd325 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3219 of 3219 strings)

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

powered by weblate
2019-09-09 07:48:08 +00:00
Raphael Michel
62a040255e Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3219 of 3219 strings)

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

powered by weblate
2019-09-09 07:48:07 +00:00
Maarten van den Berg
0e2b02c778 Translated on translate.pretix.eu (Dutch)
Currently translated at 99.9% (3216 of 3219 strings)

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

powered by weblate
2019-09-09 05:00:09 +00:00
Ture Gjørup
0f0ed90be9 Translated on translate.pretix.eu (Danish)
Currently translated at 61.2% (63 of 103 strings)

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

powered by weblate
2019-09-08 19:26:46 +00:00
Raphael Michel
b1e19d776c Fix exporter tests 2019-09-08 21:26:31 +02:00
Raphael Michel
0bcc784aaf Include attention flag and comment in order and check-in list exports 2019-09-08 20:52:15 +02:00
Raphael Michel
262fb82237 Add-ons should not have a seat 2019-09-06 11:54:18 +02:00
Raphael Michel
adbe959314 Merge pull request #1383 from pajowu/pajowu/no_test_data
Remove obsolete reference to test data
2019-09-05 23:04:15 +02:00
pajowu
02d0a68d57 Remove obsolete reference to test data 2019-09-05 19:29:04 +02:00
Raphael Michel
d6985123b4 Regenerate event CSS on plugins change 2019-09-04 15:39:23 +02:00
Raphael Michel
f7a356c340 Fixed bug in card extensions 2019-09-03 12:51:38 +02:00
Raphael Michel
c6265b4517 Fix allow_ignore_quota with bundled items 2019-09-03 12:25:22 +02:00
Raphael Michel
1ee352e114 Fix error in locale file 2019-09-03 11:59:05 +02:00
Raphael Michel
6c830a7d36 Do not enforce voucher constraints for bundled items 2019-09-03 11:51:29 +02:00
Raphael Michel
129c360fff Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-09-03 11:36:55 +02:00
Raphael Michel
b5c7ad92b6 Merge pull request #1379 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-09-03 11:36:19 +02:00
Raphael Michel
33efd8c157 order_overview: Allow to restrict to admission products 2019-09-03 11:34:00 +02:00
Bostjan Marusic
f318c8e017 Translated on translate.pretix.eu (Slovenian)
Currently translated at 21.9% (702 of 3208 strings)

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

powered by weblate
2019-09-03 09:22:31 +00:00
Bostjan Marusic
319334706d Translated on translate.pretix.eu (Slovenian)
Currently translated at 18.8% (602 of 3208 strings)

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

powered by weblate
2019-09-03 09:22:31 +00:00
Raphael Michel
1a25138bef PDF editor: Allow to easily change the page size 2019-09-03 11:22:21 +02:00
Raphael Michel
daa5383b89 Fix a layout animation issue in the sidebar 2019-09-02 18:07:10 +02:00
Raphael Michel
31333280d2 Fix download link for addons on ticket pages 2019-08-31 11:59:17 +02:00
Raphael Michel
b78f8d70e8 Add serializable to spelling whitelist 2019-08-30 17:46:15 +02:00
Raphael Michel
a847538a2e Merge pull request #1367 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-08-30 17:40:38 +02:00
Raphael Michel
e3ef9eba9e Widget: Fix invalid coloring of days in mobile calendar 2019-08-30 17:37:37 +02:00
Bostjan Marusic
9e4a4402fb Translated on translate.pretix.eu (Slovenian)
Currently translated at 15.6% (502 of 3208 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
Bostjan Marusic
96ed9f5cf5 Translated on translate.pretix.eu (Slovenian)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
oocf
7216cebce5 Translated on translate.pretix.eu (Spanish)
Currently translated at 94.3% (3025 of 3208 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
Bostjan Marusic
a3892fd4de Translated on translate.pretix.eu (Slovenian)
Currently translated at 10.7% (343 of 3208 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
saad91
7718462528 Translated on translate.pretix.eu (Arabic)
Currently translated at 0.2% (8 of 3208 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
Maarten van den Berg
60b20829f3 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3208 of 3208 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
Maarten van den Berg
e11d03f418 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3208 of 3208 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
Tobias Sundgren
69e4db58fd Translated on translate.pretix.eu (Swedish)
Currently translated at 100.0% (103 of 103 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
Tobias Sundgren
9f27a84f52 Translated on translate.pretix.eu (Swedish)
Currently translated at 1.0% (33 of 3208 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
Maarten van den Berg
e5c204dc95 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3208 of 3208 strings)

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

powered by weblate
2019-08-30 15:25:28 +00:00
Raphael Michel
7fc7dd0163 Allow to print question answers on invoices 2019-08-30 17:24:57 +02:00
Raphael Michel
aa99dbc830 Add payment provider specific details to the API 2019-08-30 17:04:22 +02:00
Raphael Michel
e3a4ec93fc Fix user log that always shows empty 2019-08-30 13:01:35 +02:00
Raphael Michel
67da6a18a8 Add missing signal to documentation 2019-08-30 12:58:20 +02:00
Raphael Michel
adc4128f9f Fix exceptions introduced in last commit 2019-08-30 11:56:04 +02:00
Raphael Michel
eed217262f Add signal global_email_filter 2019-08-30 11:02:59 +02:00
Raphael Michel
4bae824a03 Add user argument to email_filter 2019-08-30 11:02:59 +02:00
Martin Gross
be1a1f7995 Fix widget-URL for subevents 2019-08-30 10:41:08 +02:00
Martin Gross
f4b81aa032 Fix import sorting 2019-08-28 17:17:42 +02:00
Martin Gross
2559439c4e Fix eMail-previews for per-attendant emails 2019-08-28 16:48:59 +02:00
Martin Gross
fce9117dfd Fix explanation on per-attendant emails 2019-08-28 16:47:54 +02:00
Martin Gross
4c8dc8f31c Show attendee-name of item - even if item is an add-on 2019-08-28 09:04:41 +02:00
Martin Gross
b4f69fb13f Move question hint-icons into their own columns 2019-08-27 15:43:02 +02:00
Martin Gross
2aed894bd4 Hide hidden questions from users in presale 2019-08-27 15:31:22 +02:00
Martin Gross
2ff5416afb Show hints for hidden/check-in questions in question overview 2019-08-27 15:19:28 +02:00
Raphael Michel
59f7098a70 Clean up duplicate seats when generating seats 2019-08-26 16:57:48 +02:00
Raphael Michel
83dd865b78 Fix crash when de-selecting all languages 2019-08-26 16:44:20 +02:00
Raphael Michel
ebf411b7a0 Fail gracefully if seats exist multiple times 2019-08-26 16:33:35 +02:00
Raphael Michel
733a4ce8f4 Fix seatingframe for subevents 2019-08-26 16:27:43 +02:00
Raphael Michel
ad94263374 Fix i18n of OrderError 2019-08-17 14:07:56 +02:00
Raphael Michel
102772ec55 Disable Sentry in shell sessions 2019-08-17 14:05:06 +02:00
Raphael Michel
9a826b694f Fix a type error PRETIXEU-1BF 2019-08-17 12:39:38 +02:00
Raphael Michel
bcf8e9cd04 Add short option to get_date_*_display 2019-08-16 16:44:01 +02:00
Raphael Michel
200ce93bb4 Prevent autoresponders 2019-08-14 15:09:19 +02:00
Raphael Michel
0582a4d9e5 Lazy widgets: Fix missing ID 2019-08-14 09:57:55 +02:00
Raphael Michel
1cbab04108 Docs: Add API guide on custom checkout 2019-08-14 09:22:58 +02:00
Raphael Michel
98c18b162f Order creation API: Add support for reverse charge 2019-08-14 09:22:58 +02:00
Raphael Michel
d972cd4c49 Add new bundled plugin "returnurl" 2019-08-14 09:22:58 +02:00
Raphael Michel
985f354293 Order API: Add order URL 2019-08-14 09:22:58 +02:00
Raphael Michel
9c23216bd1 Order creation API: Do not allow empty orders 2019-08-14 09:22:58 +02:00
Raphael Michel
f8bf44c262 Order payment flow: Allow to be used as an iframe session start 2019-08-14 09:22:58 +02:00
Raphael Michel
6badfdf576 Order API: Expose explicit URL of payment steps 2019-08-14 09:22:58 +02:00
Raphael Michel
c2eba21359 Order creation API: Allow to create orders without payment provider 2019-08-14 09:22:58 +02:00
Raphael Michel
5cda04a994 Order creation API: Allow to pass vouchers 2019-08-14 09:22:58 +02:00
Raphael Michel
bc9d8f5bd8 Order creation API: Do not consume expired carts 2019-08-14 09:22:58 +02:00
Raphael Michel
9d6ff20191 Order creation API: Allow to auto-calculate prices 2019-08-14 09:22:58 +02:00
Raphael Michel
82684e6df3 Order creation API: Allow to send emails 2019-08-14 09:22:58 +02:00
Martin Gross
d681ae4dce Only output GA if event is using seating 2019-08-13 13:26:44 +02:00
Martin Gross
213e724e18 Include Seating in default PDF ticket template 2019-08-13 13:26:21 +02:00
Raphael Michel
6e0b80706c Badge exporter: Exclude add-on positions by default 2019-08-13 10:11:24 +02:00
Raphael Michel
5363f4206e Lazy-load dashboard widgets 2019-08-12 12:19:02 +02:00
Raphael Michel
9bdb715874 Re-order list of devices 2019-08-12 11:48:50 +02:00
Raphael Michel
90a9709838 Redirect to profile after creating a new organizer 2019-08-12 11:45:54 +02:00
Raphael Michel
669b438c91 Allow to disables fee in stats module 2019-08-09 15:02:59 +02:00
Raphael Michel
a1353b3773 Add a note on Google Analytics in the widget documentation 2019-08-09 15:02:59 +02:00
Martin Gross
f5c611982a Do not localize date, time, datetime in csv/excel exports 2019-08-09 13:40:32 +02:00
Raphael Michel
e9ab56486a Fix broken test case 2019-08-09 12:53:56 +02:00
Raphael Michel
74bc495eb7 PDF exporter: Deal with duplicate question answers 2019-08-09 12:36:01 +02:00
Raphael Michel
b0b0f7474d Allow state selection without JavaScript 2019-08-09 12:13:09 +02:00
Raphael Michel
663ff60d0a Update from Weblate (#1363)
Update from Weblate
2019-08-09 11:51:59 +02:00
Raphael Michel
7bafb0bc76 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3208 of 3208 strings)

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

powered by weblate
2019-08-09 08:26:33 +00:00
Raphael Michel
1ab225f40a Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3208 of 3208 strings)

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

powered by weblate
2019-08-09 08:26:33 +00:00
Raphael Michel
3b09456755 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-08-09 09:58:52 +02:00
Raphael Michel
d919605d79 Invoice addresses: Ask for a state in some countries (#1362)
* Invoice addresses: Ask for a state in some countries

* API, tests, noscript

* Fix shredder tests

* Add test for addresses with long state names
2019-08-09 09:55:46 +02:00
Raphael Michel
547f71aac6 Widget builder: explicit encoding for file reading 2019-08-08 19:43:44 +02:00
Raphael Michel
191729c07a Remove "3DS mode" setting as it no longer has an effect 2019-08-08 19:25:31 +02:00
Raphael Michel
8f0a5d859d Bump version to 3.1.0.dev0 2019-08-08 10:48:59 +02:00
602 changed files with 228320 additions and 98265 deletions

View File

@@ -14,18 +14,18 @@ if [ "$PRETIX_CONFIG_FILE" == "tests/travis_postgres.cfg" ]; then
fi
if [ "$1" == "style" ]; then
XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt
XDG_CACHE_HOME=/cache pip3 install --no-use-pep517 -Ur src/requirements.txt -r src/requirements/dev.txt
cd src
flake8 .
isort -c -rc -df .
fi
if [ "$1" == "doctests" ]; then
XDG_CACHE_HOME=/cache pip3 install -Ur doc/requirements.txt
XDG_CACHE_HOME=/cache pip3 install --no-use-pep517 -Ur doc/requirements.txt
cd doc
make doctest
fi
if [ "$1" == "doc-spelling" ]; then
XDG_CACHE_HOME=/cache pip3 install -Ur doc/requirements.txt
XDG_CACHE_HOME=/cache pip3 install --no-use-pep517 -Ur doc/requirements.txt
cd doc
make spelling
if [ -s _build/spelling/output.txt ]; then
@@ -33,26 +33,26 @@ if [ "$1" == "doc-spelling" ]; then
fi
fi
if [ "$1" == "translation-spelling" ]; then
XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements/dev.txt
XDG_CACHE_HOME=/cache pip3 install --no-use-pep517 -Ur src/requirements/dev.txt
cd src
potypo
fi
if [ "$1" == "tests" ]; then
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt
pip3 install -r src/requirements.txt --no-use-pep517 -Ur src/requirements/dev.txt
cd src
python manage.py check
make all compress
py.test --reruns 5 -n 3 tests
fi
if [ "$1" == "tests-cov" ]; then
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt
pip3 install -r src/requirements.txt --no-use-pep517 -Ur src/requirements/dev.txt
cd src
python manage.py check
make all compress
coverage run -m py.test --reruns 5 tests && codecov
fi
if [ "$1" == "plugins" ]; then
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt
pip3 install -r src/requirements.txt --no-use-pep517 -Ur src/requirements/dev.txt
cd src
python setup.py develop
make all compress

View File

@@ -3,7 +3,7 @@ Contributing to pretix
Hey there and welcome to pretix!
We've got an contributors guide in [our documentation](https://docs.pretix.eu/en/latest/development/contribution/)
We've got a contributors guide in [our documentation](https://docs.pretix.eu/en/latest/development/contribution/)
together with notes on the [development setup](https://docs.pretix.eu/en/latest/development/setup.html).
Please note that we have a [Code of Conduct](https://docs.pretix.eu/en/latest/development/contribution/codeofconduct.html)

View File

@@ -57,6 +57,8 @@ 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 chmod +x /usr/local/bin/pretix && \
rm /etc/nginx/sites-enabled/default && \
cd /pretix/src && \

View File

@@ -4,7 +4,7 @@ pid /var/run/nginx.pid;
daemon off;
events {
worker_connections 768;
worker_connections 4096;
}
http {
@@ -39,7 +39,7 @@ http {
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
listen 80 backlog=4096 default_server;
listen [::]:80 ipv6only=on default_server;
server_name _;
index index.php index.html;

View File

@@ -57,6 +57,9 @@ Example::
A comma-separated list of plugins that are not available even though they are installed.
Defaults to an empty string.
``auth_backends``
A comma-separated list of available auth backends. Defaults to ``pretix.base.auth.NativeAuthBackend``.
``cookie_domain``
The cookie domain to be set. Defaults to ``None``.
@@ -87,6 +90,11 @@ Example::
proxy that actively removes and re-adds the header to make sure the correct client IP is the first value.
Defaults to ``off``.
``trust_x_forwarded_proto``
Specifies whether the ``X-Forwarded-Proto`` header can be trusted. Only set to ``on`` if you have a reverse
proxy that actively removes and re-adds the header to make sure the correct client IP is the first value.
Defaults to ``off``.
Locale settings
---------------

View File

@@ -125,6 +125,8 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
; DO NOT change the following value, it has to be set to the location of the
; directory *inside* the docker container
datadir=/data
trust_x_forwarded_for=on
trust_x_forwarded_proto=on
[database]
; Replace postgresql with mysql for MySQL
@@ -180,6 +182,7 @@ named ``/etc/systemd/system/pretix.service`` with the following content::
-v /var/pretix-data:/data \
-v /etc/pretix:/etc/pretix \
-v /var/run/redis:/var/run/redis \
--sysctl net.core.somaxconn=4096
pretix/standalone:stable all
ExecStop=/usr/bin/docker stop %n
@@ -276,7 +279,7 @@ choice)::
Then, go to that directory and build the image::
$ docker build -t mypretix
$ docker build . -t mypretix
You can now use that image ``mypretix`` instead of ``pretix/standalone`` in your service file (see above). Be sure
to re-build your custom image after you pulled ``pretix/standalone`` if you want to perform an update.

View File

@@ -68,7 +68,7 @@ generated key and installs the plugin from the URL we told you::
mkdir -p /etc/ssh && \
ssh-keyscan -t rsa -p 10022 code.rami.io >> /root/.ssh/known_hosts && \
echo StrictHostKeyChecking=no >> /root/.ssh/config && \
pip3 install -Ue "git+ssh://git@code.rami.io:10022/pretix/pretix-slack.git@stable#egg=pretix-slack" && \
DJANGO_SETTINGS_MODULE=pretix.settings pip3 install -U "git+ssh://git@code.rami.io:10022/pretix/pretix-slack.git@stable#egg=pretix-slack" && \
cd /pretix/src && \
sudo -u pretixuser make production
USER pretixuser

View File

@@ -85,6 +85,8 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
url=https://pretix.mydomain.com
currency=EUR
datadir=/var/pretix/data
trust_x_forwarded_for=on
trust_x_forwarded_proto=on
[database]
; For MySQL, replace with "mysql"

View File

@@ -170,6 +170,19 @@ Date String in ISO 8601 format ``2017-12-27``
Multi-lingual string Object of strings ``{"en": "red", "de": "rot", "de_Informal": "rot"}``
Money String with decimal number ``"23.42"``
Currency String with ISO 4217 code ``"EUR"``, ``"USD"``
Relative datetime *either* String in ISO 8601 ``"2017-12-27T10:00:00.596934Z"``,
format *or* specification of ``"RELDATE/3/12:00:00/presale_start/"``
a relative datetime,
constructed from a number of
days before the base point,
a time of day, and the base
point.
Relative date *either* String in ISO 8601 ``"2017-12-27"``,
format *or* specification of ``"RELDATE/3/-/presale_start/"``
a relative date,
constructed from a number of
days before the base point
and the base point.
===================== ============================ ===================================
Query parameters

View File

@@ -0,0 +1,132 @@
Creating an external checkout process
=====================================
Occasionally, we get asked whether it is possible to just use pretix' powerful backend as a ticketing engine but use
a fully-customized checkout process that only communicates via the API. This is possible, but with a few limitations.
If you go down this route, you will miss out on many of pretix features and safeguards, as well as the added flexibility
by most of pretix' plugins. We strongly recommend to talk this through with us before you decide this is the way to go.
However, this is really useful if you need to tightly integrate pretix into existing web applications that e.g. control
the pricing of your products in a way that cannot be mapped to pretix' product structures.
Creating orders
---------------
After letting your user select the products to buy in your application, you should create a new order object inside
pretix. Below, you can see an example of such an order, but most fields are optional and there are some more features
supported. Read :ref:`rest-orders-create` to learn more about this endpoint.
Please note that this endpoint assumes trustworthy input for the most part. By default, the endpoint checks that
you do not exceed any quotas, do not sell any seats twice, or do not use any redeemed vouchers. However, it will not
complain about violation of any other availability constraints, such as violation of time frames or minimum/maximum
amounts of either your product or event. Bundled products will not be added in automatically and fees will not be
calculated automatically.
.. sourcecode:: http
POST /api/v1/organizers/democon/events/3vjrh/orders/ HTTP/1.1
Host: test.pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Authorization: …
{
"email": "dummy@example.org",
"locale": "en",
"sales_channel": "web",
"payment_provider": "banktransfer",
"invoice_address": {
"is_business": false,
"company": "Sample company",
"name_parts": {"full_name": "John Doe"},
"street": "Sesam Street 12",
"zipcode": "12345",
"city": "Sample City",
"country": "US",
"state": "NY",
"internal_reference": "",
"vat_id": ""
},
"positions": [
{
"item": 21,
"variation": null,
"attendee_name_parts": {
"full_name": "Peter"
},
"answers": [
{
"question": 1,
"answer": "23",
"options": []
}
],
"subevent": null
}
],
"fees": []
}
You will be returned a full order object that you can inspect, store, or use to build emails or confirmation pages for
the user. If you don't want to do that yourself, it will also contain the URL to our confirmation page in the ``url``
attribute. If you pass the ``"send_mail": true`` option, pretix will also send order confirmations for you.
Handling payments yourself
--------------------------
If you want to handle payments in your application, you can either just create the orders with status "paid" or you can
create them in "pending" state (the default) and later confirm the payment. We strongly advise to use the payment
provider ``"manual"`` in this case to avoid interference with payment code with pretix.
However, it is often unfeasible to implement the payment process yourself, and it also requires you to give up a
lot of pretix functionality, such as automatic refunds. Therefore, it is also possible to utilize pretix' native
payment process even in this case:
Using pretix payment providers
------------------------------
If you passed a ``payment_provider`` during order creation above, pretix will have created a payment object with state
``created`` that you can see in the returned order object. This payment object will have an attribute ``payment_url``
that you can use to let the user pay. For example, you could link or redirect to this page.
If you want the user to return to your application after the payment is complete, you can pass a query parameter
``return_url``. To prepare your event for this, open your event in the pretix backend and go to "Settings", then
"Plugins". Enable the plugin "Redirection from order page". Then, go to the new page "Settings", then "Redirection".
Enter the base URL of your web application. This will allow you to redirect to pages under this base URL later on.
For example, if you want users to be redirected to ``https://example.org/order/return?tx_id=1234``, you could now
either enter ``https://example.org`` or ``https://example.org/order/``.
The user will be redirected back to your page instead of pretix' order confirmation page after the payment,
**regardless of whether it was successful or not**. Make sure you use our API to check if the payment actually
worked! Your final URL could look like this::
https://test.pretix.eu/democon/3vjrh/order/NSLEZ/ujbrnsjzbq4dzhck/pay/123/?return_url=https%3A%2F%2Fexample.org%2Forder%2Freturn%3Ftx_id%3D1234
You can also embed this page in an ``<iframe>`` instead. Note, however, that this causes problems with some payment
methods such as PayPal which do not allow being opened in an iframe. pretix can partly work around these issues by
opening a new window, but will only to so if you also append an ``iframe=1`` parameter to the URL::
https://test.pretix.eu/democon/3vjrh/order/NSLEZ/ujbrnsjzbq4dzhck/pay/123/?return_url=https%3A%2F%2Fexample.org%2Forder%2Freturn%3Ftx_id%3D1234&iframe=1
If you did **not** pass a payment method since you want us to ask the user which payment method they want to use, you
need to construct the URL from the ``url`` attribute of the order and the sub-path ``pay/change```. For example, you
would end up with the following URL::
https://test.pretix.eu/democon/3vjrh/order/NSLEZ/ujbrnsjzbq4dzhck/pay/change
Of course, you can also use the ``iframe`` and ``return_url`` parameters here.
Optional: Cart reservations
---------------------------
Creating orders is an atomic operation: The order is either created as a whole or not at all. However, pretix'
built-in checkout automatically reserves tickets in a user's cart for a configurable amount of time to ensure users
will actually get their tickets once they started entering all their details. If you want a similar behavior in your
application, you need to create :ref:`rest-carts` through the API.
When creating your order, you can pass a ``consume_carts`` parameter with the cart ID(s) of your user. This way, the
quota reserved by the cart will be credited towards the order and the carts will be destroyed if (and only if) the
order creation succeeds.
Cart creation is currently even more limited than the order creation endpoints, as cart creation currently does not
support vouchers or automatic price calculation. If you require these features, please get in touch with us.

11
doc/api/guides/index.rst Normal file
View File

@@ -0,0 +1,11 @@
.. _`rest-api-guides`:
API Usage Guides
================
This part of the documentation contains how-to guides on some special use cases of our API.
.. toctree::
:maxdepth: 2
custom_checkout

View File

@@ -18,3 +18,4 @@ in functionality over time.
resources/index
ratelimit
webhooks
guides/index

View File

@@ -61,7 +61,7 @@ access to the API. The ``token`` endpoint expects you to authenticate using `HTT
ID as a username and your client secret as a password. You are also required to again supply the same ``redirect_uri``
parameter that you used for the authorization.
.. http:get:: /api/v1/oauth/token
.. http:post:: /api/v1/oauth/token
Request a new access token

View File

@@ -0,0 +1,148 @@
pretix Hosted reseller API
==========================
This API is only accessible to our `value-added reseller partners`_ on pretix Hosted.
.. note:: This API is only accessible with user-level permissions, not with API tokens. Therefore, you will need to
create an :ref:`OAuth application <rest-oauth>` and obtain an OAuth access token for a user account that has
permission to your reseller account.
Reseller account resource
-------------------------
The resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Your reseller ID
name string Internal name of your reseller account
public_name string Public name of your reseller account
public_url string Public URL of your company
support_email string Your support email address
support_phone string Your support phone number
communication_language string Language code we use to communicate with you
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/var/
Returns a list of all reseller accounts you have access to.
**Example request**:
.. sourcecode:: http
GET /api/v1/var/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "ticketshop.live Ltd & Co. KG",
"public_name": "ticketshop.live",
"public_url": "https://ticketshop.live",
"support_email": "support@ticketshop.live",
"support_phone": "+4962213217750",
"communication_language": "de"
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:statuscode 200: no error
:statuscode 401: Authentication failure
.. http:get:: /api/v1/var/(id)/
Returns information on one reseller account, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/var/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": "ticketshop.live Ltd & Co. KG",
"public_name": "ticketshop.live",
"public_url": "https://ticketshop.live",
"support_email": "support@ticketshop.live",
"support_phone": "+4962213217750",
"communication_language": "de"
}
:param id: The ``id`` field of the reseller account to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 404: The requested account does not exist **or** you have no permission to view this resource.
.. http:post:: /api/v1/var/(id)/create_organizer/
Creates a new organizer account that will be associated with a given reseller account.
**Example request**:
.. sourcecode:: http
POST /api/v1/var/1/create_organizer/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 123
{
"name": "My new client",
"slug": "New client"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": "My new client",
"slug": "New client"
}
:param id: The ``id`` field of the reseller account to fetch
:statuscode 201: no error
:statuscode 400: Invalid request body, usually the slug is invalid or already taken.
:statuscode 401: Authentication failure
:statuscode 404: The requested account does not exist **or** you have no permission to view this resource.
.. _value-added reseller partners: https://pretix.eu/about/en/var

View File

@@ -194,6 +194,7 @@ Cart position endpoints
* ``subevent`` (optional)
* ``expires`` (optional)
* ``includes_tax`` (optional)
* ``sales_channel`` (optional)
* ``answers``
* ``question``

View File

@@ -1,3 +1,5 @@
.. spelling:: checkin
Check-in lists
==============
@@ -27,6 +29,7 @@ subevent integer ID of the date
position_count integer Number of tickets that match this list (read-only).
checkin_count integer Number of check-ins performed on this list (read-only).
include_pending boolean If ``true``, the check-in list also contains tickets from orders in pending state.
auto_checkin_sales_channels list of strings All items on the check-in list will be automatically marked as checked-in when purchased through any of the listed sales channels.
===================================== ========================== =======================================================
.. versionchanged:: 1.10
@@ -41,6 +44,10 @@ include_pending boolean If ``true``, th
The ``include_pending`` field has been added.
.. versionchanged:: 3.2
The ``auto_checkin_sales_channels`` field has been added.
Endpoints
---------
@@ -81,7 +88,10 @@ Endpoints
"all_products": true,
"limit_products": [],
"include_pending": false,
"subevent": null
"subevent": null,
"auto_checkin_sales_channels": [
"pretixpos"
]
}
]
}
@@ -122,7 +132,10 @@ Endpoints
"all_products": true,
"limit_products": [],
"include_pending": false,
"subevent": null
"subevent": null,
"auto_checkin_sales_channels": [
"pretixpos"
]
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -215,7 +228,10 @@ Endpoints
"name": "VIP entry",
"all_products": false,
"limit_products": [1, 2],
"subevent": null
"subevent": null,
"auto_checkin_sales_channels": [
"pretixpos"
]
}
**Example response**:
@@ -234,7 +250,10 @@ Endpoints
"all_products": false,
"limit_products": [1, 2],
"include_pending": false,
"subevent": null
"subevent": null,
"auto_checkin_sales_channels": [
"pretixpos"
]
}
:param organizer: The ``slug`` field of the organizer of the event/item to create a list for
@@ -283,7 +302,10 @@ Endpoints
"all_products": false,
"limit_products": [1, 2],
"include_pending": false,
"subevent": null
"subevent": null,
"auto_checkin_sales_channels": [
"pretixpos"
]
}
:param organizer: The ``slug`` field of the organizer to modify
@@ -342,6 +364,11 @@ Order position endpoints
``ignore_status`` filter. The ``attendee_name`` field is now "smart" (see below) and the redemption endpoint
returns ``400`` instead of ``404`` on tickets which are known but not paid.
.. versionchanged:: 3.2
The ``checkins`` dict now also contains a ``auto_checked_in`` value to indicate if the check-in has been performed
automatically by the system.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/
Returns a list of all order positions within a given event. The result is the same as
@@ -400,7 +427,8 @@ Order position endpoints
"checkins": [
{
"list": 1,
"datetime": "2017-12-25T12:45:23Z"
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": true
}
],
"answers": [
@@ -510,7 +538,8 @@ Order position endpoints
"checkins": [
{
"list": 1,
"datetime": "2017-12-25T12:45:23Z"
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": true
}
],
"answers": [

View File

@@ -1,3 +1,9 @@
.. spelling::
geo
lat
lon
Events
======
@@ -25,6 +31,8 @@ is_public boolean If ``true``, th
presale_start datetime The date at which the ticket shop opens (or ``null``)
presale_end datetime The date at which the ticket shop closes (or ``null``)
location multi-lingual string The event location (or ``null``)
geo_lat float Latitude of the location (or ``null``)
geo_lon float Longitude of the location (or ``null``)
has_subevents boolean ``true`` if the event series feature is active for this
event. Cannot change after event is created.
meta_data object Values set for organizer-specific meta data parameters.
@@ -34,6 +42,8 @@ seating_plan integer If reserved sea
plan. Otherwise ``null``.
seat_category_mapping object An object mapping categories of the seating plan
(strings) to items in the event (integers or ``null``).
timezone string Event timezone name
item_meta_properties object Item-specific meta data parameters and default values.
===================================== ========================== =======================================================
@@ -62,9 +72,25 @@ seat_category_mapping object An object mappi
The attributes ``seating_plan`` and ``seat_category_mapping`` have been added.
.. versionchanged:: 3.3
The attributes ``geo_lat`` and ``geo_lon`` have been added.
.. versionchanged:: 3.4
The attribute ``timezone`` has been added.
.. versionchanged:: 3.7
The attribute ``item_meta_properties`` has been added.
Endpoints
---------
.. versionchanged:: 3.3
The events resource can now be filtered by meta data attributes.
.. http:get:: /api/v1/organizers/(organizer)/events/
Returns a list of all events within a given organizer the authenticated user/token has access to.
@@ -105,10 +131,14 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"has_subevents": false,
"meta_data": {},
"seating_plan": null,
"seat_category_mapping": {},
"timezone": "Europe/Berlin",
"item_meta_properties": {},
"plugins": [
"pretix.plugins.banktransfer"
"pretix.plugins.stripe"
@@ -129,6 +159,10 @@ Endpoints
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``date_from`` and
``slug``. Keep in mind that ``date_from`` of event series does not really tell you anything.
Default: ``slug``.
:query array attr[meta_data_key]: By providing the key and value of a meta data attribute, the list of events will
only contain the events matching the set criteria. Providing ``?attr[Format]=Seminar`` would return only those
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.
:param organizer: The ``slug`` field of a valid organizer
:statuscode 200: no error
:statuscode 401: Authentication failure
@@ -169,10 +203,14 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"has_subevents": false,
"seating_plan": null,
"seat_category_mapping": {},
"meta_data": {},
"timezone": "Europe/Berlin",
"item_meta_properties": {},
"plugins": [
"pretix.plugins.banktransfer"
"pretix.plugins.stripe"
@@ -220,8 +258,12 @@ Endpoints
"seating_plan": null,
"seat_category_mapping": {},
"location": null,
"geo_lat": null,
"geo_lon": null,
"has_subevents": false,
"meta_data": {},
"timezone": "Europe/Berlin",
"item_meta_properties": {},
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
@@ -249,10 +291,14 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"seating_plan": null,
"seat_category_mapping": {},
"has_subevents": false,
"meta_data": {},
"timezone": "Europe/Berlin",
"item_meta_properties": {},
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
@@ -268,11 +314,11 @@ Endpoints
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/clone/
Creates a new event with properties as set in the request body. The properties that are copied are: 'is_public',
`testmode`, settings, plugin settings, items, variations, add-ons, quotas, categories, tax rules, questions.
Creates a new event with properties as set in the request body. The properties that are copied are: ``is_public``,
``testmode``, ``has_subevents``, settings, plugin settings, items, variations, add-ons, quotas, categories, tax rules, questions.
If the 'plugins' and/or 'is_public' fields are present in the post body this will determine their value. Otherwise
their value will be copied from the existing event.
If the ``plugins``, ``has_subevents`` and/or ``is_public`` fields are present in the post body this will determine their
value. Otherwise their value will be copied from the existing event.
Please note that you can only copy from events under the same organizer.
@@ -300,10 +346,14 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"seating_plan": null,
"seat_category_mapping": {},
"has_subevents": false,
"meta_data": {},
"timezone": "Europe/Berlin",
"item_meta_properties": {},
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
@@ -331,10 +381,14 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"has_subevents": false,
"seating_plan": null,
"seat_category_mapping": {},
"meta_data": {},
"timezone": "Europe/Berlin",
"item_meta_properties": {},
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
@@ -394,10 +448,14 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"has_subevents": false,
"seating_plan": null,
"seat_category_mapping": {},
"meta_data": {},
"timezone": "Europe/Berlin",
"item_meta_properties": {},
"plugins": [
"pretix.plugins.banktransfer",
"pretix.plugins.stripe",
@@ -440,3 +498,123 @@ Endpoints
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
Event settings
--------------
pretix events have lots and lots of parameters of different types that are stored in a key-value store on our system.
Since many of these settings depend on each other in complex ways, we can not give direct access to all of these
settings through the API. However, we do expose many of the simple and useful flags through the API.
Please note that the available settings flags change between pretix versions and also between events, depending on the
installed plugins, and we do not give a guarantee on backwards-compatibility like with other parts of the API.
Therefore, we're also not including a list of the options here, but instead recommend to look at the endpoint output
to see available options. The ``explain=true`` flag enables a verbose mode that provides you with human-readable
information about the properties.
.. note:: Please note that this is not a complete representation of all event settings. You will find more settings
in the web interface.
.. warning:: This API is intended for advanced users. Even though we take care to validate your input, you will be
able to break your event using this API by creating situations of conflicting settings. Please take care.
.. versionchanged:: 3.6
Initial support for settings has been added to the API.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/settings/
Get current values of event settings.
Permission required: "Can change event settings"
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/settings/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example standard response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"imprint_url": "https://pretix.eu",
}
**Example verbose response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"imprint_url":
{
"value": "https://pretix.eu",
"label": "Imprint URL",
"help_text": "This should point e.g. to a part of your website that has your contact details and legal information."
}
},
}
:param organizer: The ``slug`` field of the organizer of the event to access
:param event: The ``slug`` field of the event to access
:query explain: Set to ``true`` to enable verbose response mode
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/settings/
Updates event settings. Note that ``PUT`` is not allowed here, only ``PATCH``.
.. warning::
Settings can be stored at different levels in pretix. If a value is not set on event level, a default setting
from a higher level (organizer, global) will be returned. If you explicitly set a setting on event level, it
will no longer be inherited from the higher levels. Therefore, we recommend you to send only settings that you
explicitly want to set on event level. To unset a settings, pass ``null``.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/settings/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"imprint_url": "https://example.org/imprint/"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"imprint_url": "https://example.org/imprint/",
}
:param organizer: The ``slug`` field of the organizer of the event to update
:param event: The ``slug`` field of the event to update
:statuscode 200: no error
:statuscode 400: The event could not be updated due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this resource.

View File

@@ -0,0 +1,239 @@
.. _`rest-giftcards`:
Gift cards
==========
Resource description
--------------------
The gift card resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the gift card
secret string Gift card code (can not be modified later)
value money (string) Current gift card value
currency string Currency of the value (can not be modified later)
testmode boolean Whether this is a test gift card
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/giftcards/
Returns a list of all gift cards issued by a given organizer.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/giftcards/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"secret": "HLBYVELFRC77NCQY",
"currency": "EUR",
"testmode": false,
"value": "13.37"
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string secret: Only show gift cards with the given secret.
:query boolean testmode: Filter for gift cards that are (not) in test mode.
:query boolean include_accepted: Also show gift cards issued by other organizers that are accepted by this organizer.
:param organizer: The ``slug`` field of the organizer to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/giftcards/(id)/
Returns information on one gift card, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/giftcards/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"secret": "HLBYVELFRC77NCQY",
"currency": "EUR",
"testmode": false,
"value": "13.37"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param id: The ``id`` field of the gift card to fetch
:query boolean include_accepted: Also show gift cards issued by other organizers that are accepted by this organizer.
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:post:: /api/v1/organizers/(organizer)/giftcards/
Creates a new gift card
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/giftcards/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"secret": "HLBYVELFRC77NCQY",
"currency": "EUR",
"value": "13.37"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"secret": "HLBYVELFRC77NCQY",
"testmode": false,
"currency": "EUR",
"value": "13.37"
}
:param organizer: The ``slug`` field of the organizer to create a gift card for
:statuscode 201: no error
:statuscode 400: The gift card could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
.. http:patch:: /api/v1/organizers/(organizer)/giftcards/(id)/
Update a gift card. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
You can change all fields of the resource except the ``id``, ``secret``, ``testmode``, and ``currency`` fields. Be
careful when modifying the ``value`` field to avoid race conditions. We recommend to use the ``transact`` method
described below.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/giftcards/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"value": "14.00"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"secret": "HLBYVELFRC77NCQY",
"testmode": false,
"currency": "EUR",
"value": "14.00"
}
:param organizer: The ``slug`` field of the organizer to modify
:param id: The ``id`` field of the gift card to modify
:statuscode 200: no error
:statuscode 400: The gift card could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource.
.. http:post:: /api/v1/organizers/(organizer)/giftcards/(id)/transact/
Atomically change the value of a gift card. A positive amount will increase the value of the gift card,
a negative amount will decrease it.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/giftcards/1/transact/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"value": "2.00"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"secret": "HLBYVELFRC77NCQY",
"currency": "EUR",
"testmode": false,
"value": "15.37"
}
.. versionchanged:: 3.5
This endpoint now returns status code ``409`` if the transaction would lead to a negative gift card value.
:param organizer: The ``slug`` field of the organizer to modify
:param id: The ``id`` field of the gift card to modify
:query boolean include_accepted: Also show gift cards issued by other organizers that are accepted by this organizer.
:statuscode 200: no error
:statuscode 400: The gift card could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource.
:statuscode 409: There is not sufficient credit on the gift card.

View File

@@ -21,7 +21,10 @@ Resources and endpoints
vouchers
checkinlists
waitinglist
giftcards
carts
teams
webhooks
seatingplans
billing_invoices
billing_var

View File

@@ -28,6 +28,7 @@ payment_provider_text string Text to be prin
payment information
footer_text string Text to be printed in the page footer area
lines list of objects The actual invoice contents
├ position integer Number of the line within an invoice.
├ description string Text representing the invoice line (e.g. product name)
├ gross_value money (string) Price including taxes
├ tax_value money (string) Tax amount included
@@ -63,6 +64,11 @@ internal_reference string Customer's refe
The attribute ``internal_reference`` has been added.
.. versionchanged:: 3.4
The attribute ``lines.number`` has been added.
Endpoints
---------
@@ -107,6 +113,7 @@ Endpoints
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [
{
"position": 1,
"description": "Budget Ticket",
"gross_value": "23.00",
"tax_value": "0.00",
@@ -171,6 +178,7 @@ Endpoints
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [
{
"position": 1,
"description": "Budget Ticket",
"gross_value": "23.00",
"tax_value": "0.00",

View File

@@ -77,6 +77,7 @@ generate_tickets boolean If ``false``, t
rules apply.
allow_waitinglist boolean If ``false``, no waiting list will be shown for this
product when it is sold out.
issue_giftcard boolean If ``true``, buying this product will yield a gift card.
show_quota_left boolean Publicly show how many tickets are still available.
If this is ``null``, the event default is used.
has_variations boolean Shows whether or not this item has variations.
@@ -113,6 +114,7 @@ bundles list of objects Definition of b
└ designated_price money (string) Designated price of the bundled product. This will be
used to split the price of the base item e.g. for mixed
taxation. This is not added to the price.
meta_data object Values set for event-specific meta data parameters.
===================================== ========================== =======================================================
.. versionchanged:: 2.7
@@ -153,6 +155,10 @@ bundles list of objects Definition of b
The ``show_quota_left``, ``allow_waitinglist``, and ``hidden_if_available`` attributes have been added.
.. versionchanged:: 3.7
The attribute ``meta_data`` has been added.
Notes
-----
@@ -206,6 +212,8 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
"picture": null,
"available_from": null,
@@ -300,6 +308,8 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
"picture": null,
"available_from": null,
@@ -375,6 +385,8 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
"picture": null,
"available_from": null,
@@ -437,6 +449,8 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
"picture": null,
"available_from": null,
@@ -531,6 +545,8 @@ Endpoints
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
"issue_giftcard": false,
"meta_data": {},
"position": 0,
"picture": null,
"available_from": null,

View File

@@ -53,15 +53,18 @@ invoice_address object Invoice address
├ street string Customer street
├ zipcode string Customer ZIP code
├ city string Customer city
├ country string Customer country
├ country string Customer country code
├ state string Customer state (ISO 3166-2 code). Only supported in
AU, BR, CA, CN, MY, MX, and US.
├ internal_reference string Customer's internal reference to be printed on the invoice
├ vat_id string Customer VAT ID
└ vat_id_validated string ``true``, if the VAT ID has been validated against the
EU VAT service and validation was successful. This only
happens in rare cases.
positions list of objects List of non-canceled order positions (see below)
fees list of objects List of non-canceled fees included in the order total
(i.e. payment fees)
positions list of objects List of order positions (see below). By default, only
non-canceled positions are included.
fees list of objects List of fees included in the order total. By default, only
non-canceled fees are included.
├ fee_type string Type of fee (currently ``payment``, ``passbook``,
``other``)
├ value money (string) Fee amount
@@ -70,7 +73,8 @@ fees list of objects List of non-can
can be empty
├ tax_rate decimal (string) VAT rate applied for this fee
├ tax_value money (string) VAT included in this fee
tax_rule integer The ID of the used tax rule (or ``null``)
tax_rule integer The ID of the used tax rule (or ``null``)
└ canceled boolean Whether or not this fee has been canceled.
downloads list of objects List of ticket download options for order-wise ticket
downloading. This might be a multi-page PDF or a ZIP
file of tickets for outputs that do not support
@@ -82,6 +86,7 @@ require_approval boolean If ``true`` and
needs approval by an organizer before it can
continue. If ``true`` and the order is canceled,
this order has been denied by the event organizer.
url string The full URL to the order confirmation page
payments list of objects List of payment processes (see below)
refunds list of objects List of refund processes (see below)
last_modified datetime Last modification of this object
@@ -128,15 +133,25 @@ last_modified datetime Last modificati
The ``sales_channel`` attribute has been added.
.. versionchanged:: 2.4:
.. versionchanged:: 2.4
``order.status`` can no longer be ``r``, ``…/mark_canceled/`` now accepts a ``cancellation_fee`` parameter and
``…/mark_refunded/`` has been deprecated.
.. versionchanged:: 2.5:
.. versionchanged:: 2.5
The ``testmode`` attribute has been added and ``DELETE`` has been implemented for orders.
.. versionchanged:: 3.1
The ``invoice_address.state`` and ``url`` attributes have been added. When creating orders through the API,
vouchers are now supported and many fields are now optional.
.. versionchanged:: 3.5
The ``order.fees.canceled`` attribute has been added.
.. _order-position-resource:
Order position resource
@@ -150,6 +165,8 @@ Field Type Description
id integer Internal ID of the order position
order string Order code of the order the position belongs to
positionid integer Number of the position within the order
canceled boolean Whether or not this position has been canceled. Note that
by default, only non-canceled positions are shown.
item integer ID of the purchased item
variation integer ID of the purchased variation (or ``null``)
price money (string) Price of this position
@@ -166,7 +183,8 @@ subevent integer ID of the date
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
├ list integer Internal ID of the check-in list
datetime datetime Time of check-in
datetime datetime Time of check-in
└ 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``)
└ url string Download URL
@@ -205,6 +223,19 @@ pdf_data object Data object req
The attribute ``seat`` has been added.
.. versionchanged:: 3.2
The value ``auto_checked_in`` has been added to the ``checkins``-attribute.
.. versionchanged:: 3.3
The ``url`` of a ticket ``download`` can now also return a ``text/uri-list`` instead of a file. See
:ref:`order-position-ticket-download` for details.
.. versionchanged:: 3.5
The attribute ``canceled`` has been added.
.. _order-payment-resource:
Order payment resource
@@ -221,13 +252,27 @@ amount money (string) Payment amount
created datetime Date and time of creation of this payment
payment_date datetime Date and time of completion of this payment (or ``null``)
provider string Identification string of the payment provider
payment_url string The URL where an user can continue with the payment (or ``null``)
details object Payment-specific information. This is a dictionary
with various fields that can be different between
payment providers, versions, payment states, etc. If
you read this field, you always need to be able to
deal with situations where values that you expect are
missing. Mostly, the field contains various IDs that
can be used for matching with other systems. If a
payment provider does not implement this feature,
the object is empty.
===================================== ========================== =======================================================
.. versionchanged:: 2.0
This resource has been added.
.. _order-payment-resource:
.. versionchanged:: 3.1
The attributes ``payment_url`` and ``details`` have been added.
.. _order-refund-resource:
Order refund resource
---------------------
@@ -257,6 +302,10 @@ List of all orders
Filtering for emails or order codes is now case-insensitive.
.. versionchanged:: 3.5
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/
Returns a list of all orders within a given event.
@@ -288,6 +337,7 @@ List of all orders
"status": "p",
"testmode": false,
"secret": "k24fiuwvu8kxz3y1",
"url": "https://test.pretix.eu/dummy/dummy/order/ABC12/k24fiuwvu8kxz3y1/",
"email": "tester@example.org",
"locale": "en",
"sales_channel": "web",
@@ -310,7 +360,8 @@ List of all orders
"street": "Test street 12",
"zipcode": "12345",
"city": "Testington",
"country": "Testikistan",
"country": "DE",
"state": "",
"internal_reference": "",
"vat_id": "EU123456789",
"vat_id_validated": false
@@ -320,6 +371,7 @@ List of all orders
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -340,7 +392,8 @@ List of all orders
"checkins": [
{
"list": 44,
"datetime": "2017-12-25T12:45:23Z"
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"answers": [
@@ -373,6 +426,8 @@ List of all orders
"amount": "23.00",
"created": "2017-12-01T10:00:00Z",
"payment_date": "2017-12-04T12:13:12Z",
"payment_url": null,
"details": {},
"provider": "banktransfer"
}
],
@@ -389,6 +444,9 @@ List of all orders
:query boolean testmode: Only return orders with ``testmode`` set to ``true`` or ``false``
:query boolean require_approval: If set to ``true`` or ``false``, only categories with this value for the field
``require_approval`` will be returned.
:query include_canceled_positions: If set to ``true``, the output will contain canceled order positions. Note that this
only affects position-level cancellations, not fully-canceled orders.
:query include_canceled_fees: If set to ``true``, the output will contain canceled order fees.
:query string email: Only return orders created with the given email address
:query string locale: Only return orders with the given customer locale
:query datetime modified_since: Only return orders that have changed since the given date. Be careful: We only
@@ -406,6 +464,10 @@ List of all orders
Fetching individual orders
--------------------------
.. versionchanged:: 3.5
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/
Returns information on one order, identified by its order code.
@@ -431,6 +493,7 @@ Fetching individual orders
"status": "p",
"testmode": false,
"secret": "k24fiuwvu8kxz3y1",
"url": "https://test.pretix.eu/dummy/dummy/order/ABC12/k24fiuwvu8kxz3y1/",
"email": "tester@example.org",
"locale": "en",
"sales_channel": "web",
@@ -453,7 +516,8 @@ Fetching individual orders
"street": "Test street 12",
"zipcode": "12345",
"city": "Testington",
"country": "Testikistan",
"country": "DE",
"state": "",
"internal_reference": "",
"vat_id": "EU123456789",
"vat_id_validated": false
@@ -463,6 +527,7 @@ Fetching individual orders
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -483,7 +548,8 @@ Fetching individual orders
"checkins": [
{
"list": 44,
"datetime": "2017-12-25T12:45:23Z"
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"answers": [
@@ -516,6 +582,8 @@ Fetching individual orders
"amount": "23.00",
"created": "2017-12-01T10:00:00Z",
"payment_date": "2017-12-04T12:13:12Z",
"payment_url": null,
"details": {},
"provider": "banktransfer"
}
],
@@ -525,6 +593,9 @@ Fetching individual orders
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param code: The ``code`` field of the order to fetch
:query include_canceled_positions: If set to ``true``, the output will contain canceled order positions. Note that this
only affects position-level cancellations, not fully-canceled orders.
:query include_canceled_fees: If set to ``true``, the output will contain canceled order fees.
: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.
@@ -691,6 +762,8 @@ Deleting orders
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource **or** the order may not be deleted.
:statuscode 404: The requested order does not exist.
.. _rest-orders-create:
Creating orders
---------------
@@ -716,25 +789,21 @@ Creating orders
* does not validate the number of items per order or the number of times an item can be included in an order
* does not validate any requirements related to add-on products
* does not validate any requirements related to add-on products and does not add bundled products automatically
* does not check or calculate prices but believes any prices you send
* does not support the redemption of vouchers
* does not check prices but believes any prices you send
* does not prevent you from buying items that can only be bought with a voucher
* does not calculate fees
* does not calculate fees automatically
* does not allow to pass data to plugins and will therefore cause issues with some plugins like the shipping
module
* does not send order confirmations via email
* does not support reverse charge taxation
* does not support file upload questions
* does not support redeeming gift cards
You can supply the following fields of the resource:
* ``code`` (optional)
@@ -750,9 +819,10 @@ Creating orders
* ``email``
* ``locale``
* ``sales_channel``
* ``payment_provider`` The identifier of the payment provider set for this order. This needs to be an existing
payment provider. You should use ``"free"`` for free orders, and we strongly advise to use ``"manual"`` for all
orders you create as paid.
* ``payment_provider`` (optional) The identifier of the payment provider set for this order. This needs to be an
existing payment provider. You should use ``"free"`` for free orders, and we strongly advise to use ``"manual"``
for all orders you create as paid. This field is optional when the order status is ``"n"`` or the order total is
zero, otherwise it is required.
* ``payment_info`` (optional) You can pass a nested JSON object that will be set as the internal ``info``
value of the payment object that will be created. How this value is handled is up to the payment provider and you
should only use this if you know the specific payment provider in detail. Please keep in mind that the payment
@@ -770,17 +840,22 @@ Creating orders
* ``zipcode``
* ``city``
* ``country``
* ``state``
* ``internal_reference``
* ``vat_id``
* ``vat_id_validated`` (optional) If you need support for reverse charge (rarely the case), you need to check
yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will
trigger reverse charge taxation. Don't forget to set ``is_business`` as well!
* ``positions``
* ``positionid`` (optional, see below)
* ``item``
* ``variation``
* ``price``
* ``price`` (optional, if set to ``null`` or missing the price will be computed from the given product)
* ``seat`` (The ``seat_guid`` attribute of a seat. Required when the specified ``item`` requires a seat, otherwise must be ``null``.)
* ``attendee_name`` **or** ``attendee_name_parts``
* ``voucher`` (optional, the ``code`` attribute of a valid voucher)
* ``attendee_email``
* ``secret`` (optional)
* ``addon_to`` (optional, see below)
@@ -798,14 +873,31 @@ Creating orders
* ``description``
* ``internal_type``
* ``tax_rule``
* ``_treat_value_as_percentage`` (Optional convenience flag. If set to ``true``, your ``value`` parameter will
be treated as a percentage and the fee will be calculated using that percentage and the sum of all product
prices. Note that this will not include other fees and is calculated once during order generation and will not
be respected automatically when the order changes later.)
* ``_split_taxes_like_products`` (Optional convenience flag. If set to ``true``, your ``tax_rule`` will be ignored
and the fee will be taxed like the products in the order. If the products have multiple tax rates, multiple fees
will be generated with weights adjusted to the net price of the products. Note that this will be calculated once
during order generation and is not respected automatically when the order changes later.)
* ``force`` (optional). If set to ``true``, quotas will be ignored.
* ``send_mail`` (optional). If set to ``true``, the same emails will be sent as for a regular order. Defaults to
``false``.
If you want to use add-on products, you need to set the ``positionid`` fields of all positions manually
to incrementing integers starting with ``1``. Then, you can reference one of these
IDs in the ``addon_to`` field of another position. Note that all add_ons for a specific position need to come
immediately after the position itself.
Starting with pretix 3.7, you can add ``"simulate": true`` to the body to do a "dry run" of your order. This will
validate your order and return you an order object with the resulting prices, but will not create an actual order.
You can use this for testing or to look up prices. In this case, some attributes are ignored, such as whether
to send an email or what payment provider will be used. Note that some returned fields will contain empty values
(e.g. all ``id`` fields of positions will be zero) and some will contain fake values (e.g. the order code will
always be ``PREVIEW``). pretix plugins will not be triggered, so some special behavior might be missing as well.
**Example request**:
.. sourcecode:: http
@@ -837,6 +929,7 @@ Creating orders
"zipcode": "12345",
"city": "Sample City",
"country": "UK",
"state": "",
"internal_reference": "",
"vat_id": ""
},
@@ -860,7 +953,7 @@ Creating orders
],
"subevent": null
}
],
]
}
**Example response**:
@@ -1251,7 +1344,13 @@ List of all order positions
The order positions endpoint has been extended by the filter queries ``voucher``, ``voucher__code`` and
``pseudonymization_id``.
.. note:: Individually canceled order positions are currently not visible via the API at all.
.. versionchanged:: 3.2
The value ``auto_checked_in`` has been added to the ``checkins``-attribute.
.. versionchanged:: 3.5
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/
@@ -1282,6 +1381,7 @@ List of all order positions
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -1302,7 +1402,8 @@ List of all order positions
"checkins": [
{
"list": 44,
"datetime": "2017-12-25T12:45:23Z"
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"answers": [
@@ -1350,6 +1451,8 @@ List of all order positions
comma-separated IDs.
:query string voucher: Only return positions with a specific voucher.
:query string voucher__code: Only return positions with a specific voucher code.
:query include_canceled_positions: If set to ``true``, the output will contain canceled order positions. Note that this
only affects position-level cancellations, not fully-canceled orders.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:statuscode 200: no error
@@ -1383,6 +1486,7 @@ Fetching individual positions
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -1403,7 +1507,8 @@ Fetching individual positions
"checkins": [
{
"list": 44,
"datetime": "2017-12-25T12:45:23Z"
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"answers": [
@@ -1426,11 +1531,14 @@ Fetching individual positions
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the order position to fetch
:query include_canceled_positions: If set to ``true``, canceled positions may be returned (otherwise, they return 404).
: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.
:statuscode 404: The requested order position does not exist.
.. _`order-position-ticket-download`:
Order position ticket download
------------------------------
@@ -1440,6 +1548,11 @@ Order position ticket download
Depending on the chosen output, the response might be a ZIP file, PDF file or something else. The order details
response contains a list of output options for this particular order position.
Be aware that the output does not have to be a file, but can also be a regular HTTP response with a ``Content-Type``
set to ``text/uri-list``. In this case, the user is expected to navigate to that URL in order to access their ticket.
The referenced URL can provide a download or a regular, human-viewable website - so it is advised to open this URL
in a webbrowser and leave it up to the user to handle the result.
Tickets can be only downloaded if the order is paid and if ticket downloads are active. Also, depending on event
configuration downloads might be only unavailable for add-on products or non-admission products.
Note that in some cases the ticket file might not yet have been created. In that case, you will receive a status
@@ -1515,6 +1628,10 @@ Order payment endpoints
These endpoints have been added.
.. versionchanged:: 3.6
Payments can now be created through the API.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/payments/
Returns a list of all payments for an order.
@@ -1546,6 +1663,8 @@ Order payment endpoints
"amount": "23.00",
"created": "2017-12-01T10:00:00Z",
"payment_date": "2017-12-04T12:13:12Z",
"payment_url": null,
"details": {},
"provider": "banktransfer"
}
]
@@ -1586,6 +1705,8 @@ Order payment endpoints
"amount": "23.00",
"created": "2017-12-01T10:00:00Z",
"payment_date": "2017-12-04T12:13:12Z",
"payment_url": null,
"details": {},
"provider": "banktransfer"
}
@@ -1719,6 +1840,61 @@ Order payment endpoints
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested order or payment does not exist.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/payments/
Creates a new payment.
Be careful with the ``info`` parameter: You can pass a nested JSON object that will be set as the internal ``info``
value of the payment object that will be created. How this value is handled is up to the payment provider and you
should only use this if you know the specific payment provider in detail. Please keep in mind that the payment
provider will not be called to do anything about this (i.e. if you pass a bank account to a debit provider, *no*
charge will be created), this is just informative in case you *handled the payment already*.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/payments/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"state": "confirmed",
"amount": "23.00",
"payment_date": "2017-12-04T12:13:12Z",
"info": {},
"provider": "banktransfer"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"local_id": 1,
"state": "confirmed",
"amount": "23.00",
"created": "2017-12-01T10:00:00Z",
"payment_date": "2017-12-04T12:13:12Z",
"payment_url": null,
"details": {},
"provider": "banktransfer"
}
:param organizer: The ``slug`` field of the organizer to access
:param event: The ``slug`` field of the event to access
:param order: The ``code`` field of the order to access
:statuscode 201: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested order does not exist.
Order refund endpoints
----------------------
@@ -1837,7 +2013,8 @@ Order refund endpoints
"payment": 1,
"execution_date": null,
"provider": "manual",
"mark_canceled": false
"mark_canceled": false,
"mark_pending": true
}
**Example response**:

View File

@@ -18,6 +18,7 @@ Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the question
question multi-lingual string The field label shown to the customer
help_text multi-lingual string The help text shown to the customer
type string The expected type of answer. Valid options:
* ``N`` number
@@ -31,6 +32,7 @@ type string The expected ty
* ``H`` time
* ``W`` date and time
* ``CC`` country code (ISO 3666-1 alpha-2)
* ``TEL`` telephone number
required boolean If ``true``, the question needs to be filled out.
position integer An integer, used for sorting
items list of integers List of item IDs this question is assigned to.
@@ -41,6 +43,8 @@ ask_during_checkin boolean If ``true``, th
the ticket instead.
hidden boolean If ``true``, the question will only be shown in the
backend.
print_on_invoice boolean If ``true``, the question will only be shown on
invoices.
options list of objects In case of question type ``C`` or ``M``, this lists the
available objects. Only writable during creation,
use separate endpoint to modify this later.
@@ -80,6 +84,14 @@ dependency_value string An old version
The attribute ``dependency_values`` has been added.
.. versionchanged:: 3.1
The attribute ``print_on_invoice`` has been added.
.. versionchanged:: 3.5
The attribute ``help_text`` has been added.
Endpoints
---------
@@ -116,6 +128,7 @@ Endpoints
{
"id": 1,
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],
@@ -123,6 +136,7 @@ Endpoints
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"dependency_question": null,
"dependency_value": null,
"dependency_values": [],
@@ -185,6 +199,7 @@ Endpoints
{
"id": 1,
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],
@@ -192,6 +207,7 @@ Endpoints
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"dependency_question": null,
"dependency_value": null,
"dependency_values": [],
@@ -239,12 +255,14 @@ Endpoints
{
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],
"position": 1,
"ask_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"dependency_question": null,
"dependency_values": [],
"options": [
@@ -272,6 +290,7 @@ Endpoints
{
"id": 1,
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],
@@ -279,6 +298,7 @@ Endpoints
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"dependency_question": null,
"dependency_value": null,
"dependency_values": [],
@@ -345,6 +365,7 @@ Endpoints
{
"id": 1,
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],
@@ -352,6 +373,7 @@ Endpoints
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"dependency_question": null,
"dependency_value": null,
"dependency_values": [],

View File

@@ -1,3 +1,9 @@
.. spelling::
geo
lat
lon
.. _rest-subevents:
Event series dates / Sub-events
@@ -28,6 +34,8 @@ date_admission datetime The sub-event's
presale_start datetime The sub-date at which the ticket shop opens (or ``null``)
presale_end datetime The sub-date at which the ticket shop closes (or ``null``)
location multi-lingual string The sub-event location (or ``null``)
geo_lat float Latitude of the location (or ``null``)
geo_lon float Longitude of the location (or ``null``)
item_price_overrides list of objects List of items for which this sub-event overrides the
default price
├ item integer The internal item ID
@@ -62,9 +70,17 @@ seat_category_mapping object An object mappi
The attributes ``seating_plan`` and ``seat_category_mapping`` have been added.
.. versionchanged:: 3.3
The attributes ``geo_lat`` and ``geo_lon`` have been added.
Endpoints
---------
.. versionchanged:: 3.3
The sub-events resource can now be filtered by meta data attributes.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/subevents/
Returns a list of all sub-events of an event.
@@ -104,6 +120,8 @@ Endpoints
"seating_plan": null,
"seat_category_mapping": {},
"location": null,
"geo_lat": null,
"geo_lon": null,
"item_price_overrides": [
{
"item": 2,
@@ -123,6 +141,11 @@ Endpoints
:query ends_after: If set to a date and time, only events that happen during of after the given time are returned.
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of the main event
:query array attr[meta_data_key]: By providing the key and value of a meta data attribute, the list of sub-events
will only contain the sub-events matching the set criteria. Providing ``?attr[Format]=Seminar`` would return
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.
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
@@ -152,6 +175,8 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"seating_plan": null,
"seat_category_mapping": {},
"item_price_overrides": [
@@ -184,6 +209,8 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"seating_plan": null,
"seat_category_mapping": {},
"item_price_overrides": [
@@ -237,6 +264,8 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"seating_plan": null,
"seat_category_mapping": {},
"item_price_overrides": [
@@ -303,6 +332,8 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"seating_plan": null,
"seat_category_mapping": {},
"item_price_overrides": [
@@ -389,6 +420,8 @@ Endpoints
"presale_start": null,
"presale_end": null,
"location": null,
"geo_lat": null,
"geo_lon": null,
"seating_plan": null,
"seat_category_mapping": {},
"item_price_overrides": [

671
doc/api/resources/teams.rst Normal file
View File

@@ -0,0 +1,671 @@
.. spelling:: fullname
.. _`rest-teams`:
Teams
=====
.. warning:: Unlike our user interface, the team API **does** allow you to lock yourself out by deleting or modifying
the team your user or API key belongs to. Be careful around here!
Team resource
-------------
The team resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the team
name string Team name
all_events boolean Whether this team has access to all events
limit_events list List of event slugs this team has access to
can_create_events boolean
can_change_teams boolean
can_change_organizer_settings boolean
can_manage_gift_cards boolean
can_change_event_settings boolean
can_change_items boolean
can_view_orders boolean
can_change_orders boolean
can_view_vouchers boolean
can_change_vouchers boolean
===================================== ========================== =======================================================
Team member resource
--------------------
The team member resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the user
email string The user's email address
fullname string The user's full name (or ``null``)
require_2fa boolean Whether this user uses two-factor-authentication
===================================== ========================== =======================================================
Team invite resource
--------------------
The team invite resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the invite
email string The invitee's email address
===================================== ========================== =======================================================
Team API token resource
-----------------------
The team API token resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the invite
name string Name of this API token
active boolean Whether this API token is active (can never be set to
``true`` again once ``false``)
token string The actual API token. Will only be sent back during
token creation.
===================================== ========================== =======================================================
Team endpoints
--------------
.. http:get:: /api/v1/organizers/(organizer)/teams/
Returns a list of all teams within a given organizer.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/teams/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "Admin team",
"all_events": true,
"limit_events": [],
"can_create_events": true,
...
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of the organizer to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/teams/(id)/
Returns information on one team, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/teams/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": "Admin team",
"all_events": true,
"limit_events": [],
"can_create_events": true,
...
}
:param organizer: The ``slug`` field of the organizer to fetch
:param id: The ``id`` field of the team to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:post:: /api/v1/organizers/(organizer)/teams/
Creates a new team
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/teams/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"name": "Admin team",
"all_events": true,
"limit_events": [],
"can_create_events": true,
...
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 2,
"name": "Admin team",
"all_events": true,
"limit_events": [],
"can_create_events": true,
...
}
:param organizer: The ``slug`` field of the organizer to create a team for
:statuscode 201: no error
:statuscode 400: The team could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
.. http:patch:: /api/v1/organizers/(organizer)/teams/(id)/
Update a team. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/teams/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"can_create_events": true
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": "Admin team",
"all_events": true,
"limit_events": [],
"can_create_events": true,
...
}
:param organizer: The ``slug`` field of the organizer to modify
:param id: The ``id`` field of the team to modify
:statuscode 200: no error
:statuscode 400: The team could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource.
.. http:delete:: /api/v1/organizers/(organizer)/teams/(id)/
Deletes a team.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/teams/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
:param organizer: The ``slug`` field of the organizer to modify
:param id: The ``id`` field of the team to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource.
Team member endpoints
---------------------
.. http:get:: /api/v1/organizers/(organizer)/teams/(team)/members/
Returns a list of all members of a team.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/teams/1/members/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"fullname": "John Doe",
"email": "john@example.com",
"require_2fa": true
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of the organizer to fetch
:param team: The ``id`` field of the team to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested team does not exist
.. http:get:: /api/v1/organizers/(organizer)/teams/(team)/members/(id)/
Returns information on one team member, identified by their ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/teams/1/members/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"fullname": "John Doe",
"email": "john@example.com",
"require_2fa": true
}
:param organizer: The ``slug`` field of the organizer to fetch
:param team: The ``id`` field of the team to fetch
:param id: The ``id`` field of the member to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested team or member does not exist
.. http:delete:: /api/v1/organizers/(organizer)/teams/(team)/members/(id)/
Removes a member from the team.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/teams/1/members/1/ HTTP/1.1
Host: pretix.eu
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
:param organizer: The ``slug`` field of the organizer to modify
:param team: The ``id`` field of the team to modify
:param id: The ``id`` field of the member to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
:statuscode 404: The requested team or member does not exist
Team invite endpoints
---------------------
.. http:get:: /api/v1/organizers/(organizer)/teams/(team)/invites/
Returns a list of all invitations to a team.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/teams/1/invites/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"email": "john@example.com"
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of the organizer to fetch
:param team: The ``id`` field of the team to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested team does not exist
.. http:get:: /api/v1/organizers/(organizer)/teams/(team)/invites/(id)/
Returns information on one invite, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/teams/1/invites/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"email": "john@example.org"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param team: The ``id`` field of the team to fetch
:param id: The ``id`` field of the invite to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested team or invite does not exist
.. http:post:: /api/v1/organizers/(organizer)/teams/(team)/invites/
Invites someone into the team. Note that if the user already has a pretix account, you will receive a response without
an ``id`` and instead of an invite being created, the user will be directly added to the team.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/teams/1/invites/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"email": "mark@example.org"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"email": "mark@example.org"
}
:param organizer: The ``slug`` field of the organizer to modify
:param team: The ``id`` field of the team to modify
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
:statuscode 404: The requested team does not exist
.. http:delete:: /api/v1/organizers/(organizer)/teams/(team)/invites/(id)/
Revokes an invite.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/teams/1/invites/1/ HTTP/1.1
Host: pretix.eu
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
:param organizer: The ``slug`` field of the organizer to modify
:param team: The ``id`` field of the team to modify
:param id: The ``id`` field of the invite to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
:statuscode 404: The requested team or invite does not exist
Team API token endpoints
------------------------
.. http:get:: /api/v1/organizers/(organizer)/teams/(team)/tokens/
Returns a list of all API tokens of a team.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/teams/1/tokens/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"active": true,
"name": "Test token"
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of the organizer to fetch
:param team: The ``id`` field of the team to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested team does not exist
.. http:get:: /api/v1/organizers/(organizer)/teams/(team)/tokens/(id)/
Returns information on one token, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/teams/1/tokens/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"active": true,
"name": "Test token"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param team: The ``id`` field of the team to fetch
:param id: The ``id`` field of the token to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested team or token does not exist
.. http:post:: /api/v1/organizers/(organizer)/teams/(team)/tokens/
Creates a new token.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/teams/1/tokens/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"name": "New token"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 2,
"name": "New token",
"active": true,
"token": "",
}
:param organizer: The ``slug`` field of the organizer to modify
:param team: The ``id`` field of the team to create a token for
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
:statuscode 404: The requested team does not exist
.. http:delete:: /api/v1/organizers/(organizer)/teams/(team)/tokens/(id)/
Disables a token.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/teams/1/tokens/1/ HTTP/1.1
Host: pretix.eu
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": "My token",
"active": false
}
:param organizer: The ``slug`` field of the organizer to modify
:param team: The ``id`` field of the team to modify
:param id: The ``id`` field of the token to delete
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
:statuscode 404: The requested team or token does not exist

View File

@@ -38,6 +38,7 @@ quota integer An ID of a quot
attached either to a specific product or to all
products within one quota or it can be available
for all items without restriction.
seat string ``seat_guid`` attribute of a specific seat (or ``null``)
tag string A string that is used for grouping vouchers
comment string An internal comment on the voucher
subevent integer ID of the date inside an event series this voucher belongs to (or ``null``).
@@ -53,6 +54,10 @@ show_hidden_items boolean Only if set to
The attribute ``show_hidden_items`` has been added.
.. versionchanged:: 3.4
The attribute ``seat`` has been added.
Endpoints
---------
@@ -96,7 +101,8 @@ Endpoints
"quota": null,
"tag": "testvoucher",
"comment": "",
"subevent": null
"seat": null,
"subevent": null,
}
]
}
@@ -162,6 +168,7 @@ Endpoints
"quota": null,
"tag": "testvoucher",
"comment": "",
"seat": null,
"subevent": null
}
@@ -225,6 +232,7 @@ Endpoints
"quota": null,
"tag": "testvoucher",
"comment": "",
"seat": null,
"subevent": null
}
@@ -352,6 +360,7 @@ Endpoints
"quota": null,
"tag": "testvoucher",
"comment": "",
"seat": null,
"subevent": null
}

12
doc/contents.rst Normal file
View File

@@ -0,0 +1,12 @@
Table of contents
=================
.. toctree::
:maxdepth: 3
user/index
admin/index
api/index
development/index
plugins/index

View File

@@ -0,0 +1,70 @@
.. highlight:: python
:linenothreshold: 5
Pluggable authentication backends
=================================
Plugins can supply additional authentication backends. This is mainly useful in self-hosted installations
and allows you to use company-wide login mechanisms such as LDAP or OAuth for accessing pretix' backend.
Every authentication backend contains an implementation of the interface defined in ``pretix.base.auth.BaseAuthBackend``
(see below). Note that pretix authentication backends work differently than plain Django authentication backends.
Basically, three pre-defined flows are supported:
* Authentication mechanisms that rely on a **set of input parameters**, e.g. a username and a password. These can be
implemented by supplying the ``login_form_fields`` property and a ``form_authenticate`` method.
* Authentication mechanisms that rely on **external sessions**, e.g. a cookie or a proxy HTTP header. These can be
implemented by supplying a ``request_authenticate`` method.
* Authentication mechanisms that rely on **redirection**, e.g. to an OAuth provider. These can be implemented by
supplying a ``authentication_url`` method and implementing a custom return view.
Authentication backends are *not* collected through a signal. Instead, they must explicitly be set through the
``auth_backends`` directive in the ``pretix.cfg`` :ref:`configuration file <config>`.
In each of these methods (``form_authenticate``, ``request_authenticate`` or your custom view) you are supposed to
either get an existing :py:class:`pretix.base.models.User` object from the database or create a new one. There are a
few rules you need to follow:
* You **MUST** only return users with the ``auth_backend`` attribute set to the ``identifier`` value of your backend.
* You **MUST** create new users with the ``auth_backend`` attribute set to the ``identifier`` value of your backend.
* Every user object **MUST** have an email address. Email addresses are globally unique. If the email address is
already registered to a user who signs in through a different backend, you **SHOULD** refuse the login.
The backend interface
---------------------
.. class:: pretix.base.auth.BaseAuthBackend
The central object of each backend is the subclass of ``BaseAuthBackend``.
.. autoattribute:: identifier
This is an abstract attribute, you **must** override this!
.. autoattribute:: verbose_name
This is an abstract attribute, you **must** override this!
.. autoattribute:: login_form_fields
.. autoattribute:: visible
.. automethod:: form_authenticate
.. automethod:: request_authenticate
.. automethod:: authentication_url
Logging users in
----------------
If you return a user from ``form_authenticate`` or ``request_authenticate``, the system will handle everything else
for you correctly. However, if you use a redirection method and build a custom view to verify the login, we strongly
recommend that you use the following utility method to correctly set session values and enforce two-factor
authentication (if activated):
.. autofunction:: pretix.control.views.auth.process_login

View File

@@ -12,7 +12,7 @@ Core
.. automodule:: pretix.base.signals
:members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types,
item_copy_data, register_sales_channels, register_global_settings, quota_availability
item_copy_data, register_sales_channels, register_global_settings, quota_availability, global_email_filter
Order events
""""""""""""
@@ -20,13 +20,20 @@ Order events
There are multiple signals that will be sent out in the ordering cycle:
.. automodule:: pretix.base.signals
:members: validate_cart, validate_cart_addons, validate_order, order_fee_calculation, order_paid, order_placed, order_canceled, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split
:members: validate_cart, validate_cart_addons, validate_order, order_fee_calculation, order_paid, order_placed, order_canceled, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text
Check-ins
"""""""""
.. automodule:: pretix.base.signals
:members: checkin_created
Frontend
--------
.. automodule:: pretix.presale.signals
:members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble, render_seating_plan, checkout_flow_steps, position_info
:members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble, render_seating_plan, checkout_flow_steps, position_info, item_description
.. automodule:: pretix.presale.signals
@@ -49,8 +56,8 @@ Backend
.. automodule:: pretix.control.signals
:members: nav_event, html_head, html_page_start, quota_detail_html, nav_topbar, nav_global, nav_organizer, nav_event_settings,
order_info, event_settings_widget, oauth_application_registered, order_position_buttons, subevent_forms, item_formsets
order_info, event_settings_widget, oauth_application_registered, order_position_buttons, subevent_forms,
item_formsets, order_search_filter_q
.. automodule:: pretix.base.signals
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events
@@ -78,3 +85,12 @@ Ticket designs
.. automodule:: pretix.base.signals
:members: layout_text_variables
.. automodule:: pretix.plugins.ticketoutputpdf.signals
:members: override_layout
API
---
.. automodule:: pretix.base.signals
:members: validate_event_settings, api_event_settings_fields

View File

@@ -0,0 +1,112 @@
.. highlight:: python
:linenothreshold: 5
.. _`importcol`:
Extending the order import process
==================================
It's possible through the backend to import orders into pretix, for example from a legacy ticketing system. If your
plugins defines additional data structures around orders, it might be useful to make it possible to import them as well.
Import process
--------------
Here's a short description of pretix' import process to show you where the system will need to interact with your plugin.
You can find more detailed descriptions of the attributes and methods further below.
1. The user uploads a CSV file. The system tries to parse the CSV file and understand its column headers.
2. A preview of the file is shown to the user and the user is asked to assign the various different input parameters to
columns of the file or static values. For example, the user either needs to manually select a product or specify a
column that contains a product. For this purpose, a select field is rendered for every possible input column,
allowing the user to choose between a default/empty value (defined by your ``default_value``/``default_label``)
attributes, the columns of the uploaded file, or a static value (defined by your ``static_choices`` method).
3. The user submits its assignment and the system uses the ``resolve`` method of all columns to get the raw value for
all columns.
4. The system uses the ``clean`` method of all columns to verify that all input fields are valid and transformed to the
correct data type.
5. The system prepares internal model objects (``Order`` etc) and uses the ``assign`` method of all columns to assign
these objects with actual values.
6. The system saves all of these model objects to the database in a database transaction. Plugins can create additional
objects in this stage through their ``save`` method.
Column registration
-------------------
The import API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available import columns. Your plugin
should listen for this signal and return the subclass of ``pretix.base.orderimport.ImportColumn``
that we'll provide in this plugin:
.. sourcecode:: python
from django.dispatch import receiver
from pretix.base.signals import order_import_columns
@receiver(order_import_columns, dispatch_uid="custom_columns")
def register_column(sender, **kwargs):
return [
EmailColumn(sender),
]
The column class API
--------------------
.. class:: pretix.base.orderimport.ImportColumn
The central object of each import extension is the subclass of ``ImportColumn``.
.. py:attribute:: ImportColumn.event
The default constructor sets this property to the event we are currently
working for.
.. autoattribute:: identifier
This is an abstract attribute, you **must** override this!
.. autoattribute:: verbose_name
This is an abstract attribute, you **must** override this!
.. autoattribute:: default_value
.. autoattribute:: default_label
.. autoattribute:: initial
.. automethod:: static_choices
.. automethod:: resolve
.. automethod:: clean
.. automethod:: assign
.. automethod:: save
Example
-------
For example, the import column responsible for assigning email addresses looks like this:
.. sourcecode:: python
class EmailColumn(ImportColumn):
identifier = 'email'
verbose_name = _('E-mail address')
def clean(self, value, previous_values):
if value:
EmailValidator()(value)
return value
def assign(self, value, order, position, invoice_address, **kwargs):
order.email = value

View File

@@ -12,8 +12,11 @@ Contents:
payment
payment_2.0
email
placeholder
invoice
shredder
import
customview
auth
general
quality

View File

@@ -62,6 +62,8 @@ The provider class
.. autoattribute:: is_enabled
.. autoattribute:: priority
.. autoattribute:: settings_form_fields
.. automethod:: settings_form_clean
@@ -108,8 +110,16 @@ The provider class
.. automethod:: execute_refund
.. automethod:: refund_control_render
.. automethod:: api_payment_details
.. automethod:: matching_id
.. automethod:: shred_payment_info
.. automethod:: cancel_payment
.. autoattribute:: is_implicit
.. autoattribute:: is_meta

View File

@@ -0,0 +1,79 @@
.. highlight:: python
:linenothreshold: 5
Writing an e-mail placeholder plugin
====================================
An email placeholder is a dynamic value that pretix users can use in their email templates.
Please read :ref:`Creating a plugin <pluginsetup>` first, if you haven't already.
Placeholder registration
------------------------
The placeholder API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available email placeholders. Your plugin
should listen for this signal and return an instance of a subclass of ``pretix.base.email.BaseMailTextPlaceholder``::
from django.dispatch import receiver
from pretix.base.signals import register_mail_placeholders
@receiver(register_mail_placeholders, dispatch_uid="placeholder_custom")
def register_mail_renderers(sender, **kwargs):
from .email import MyPlaceholderClass
return MyPlaceholder()
Context mechanism
-----------------
Emails are sent in different "contexts" within pretix. For example, many emails are sent in the
the context of an order, but some are not, such as the notification of a waiting list voucher.
Not all placeholders make sense in every email, and placeholders usually depend some parameters
themselves, such as the ``Order`` object. Therefore, placeholders are expected to explicitly declare
what values they depend on and they will only be available in an email if all those dependencies are
met. Currently, placeholders can depend on the following context parameters:
* ``event``
* ``order``
* ``position``
* ``waiting_list_entry``
* ``invoice_address``
* ``payment``
There are a few more that are only to be used internally but not by plugins.
The placeholder class
---------------------
.. class:: pretix.base.email.BaseMailTextPlaceholder
.. autoattribute:: identifier
This is an abstract attribute, you **must** override this!
.. autoattribute:: required_context
This is an abstract attribute, you **must** override this!
.. automethod:: render
This is an abstract method, you **must** implement this!
.. automethod:: render_sample
This is an abstract method, you **must** implement this!
Helper class for simple placeholders
------------------------------------
pretix ships with a helper class that makes it easy to provide placeholders based on simple
functions::
placeholder = SimpleFunctionalMailTextPlaceholder(
'code', ['order'], lambda order: order.code, sample='F8VVL'
)

View File

@@ -46,6 +46,9 @@ name string The human-readable name of your plugin
author string Your name
version string A human-readable version code of your plugin
description string A more verbose description of what your plugin does.
category string Category of a plugin. Either one of ``"FEATURE"``, ``"PAYMENT"``,
``"INTEGRATION"``, ``"CUSTOMIZATION"``, ``"FORMAT"``, or ``"API"``,
or any other string.
visible boolean (optional) ``True`` by default, can hide a plugin so it cannot be normally activated.
restricted boolean (optional) ``False`` by default, restricts a plugin such that it can only be enabled
for an event by system administrators / superusers.
@@ -69,6 +72,7 @@ A working example would be::
name = _("PayPal")
author = _("the pretix team")
version = '1.0.0'
category = 'PAYMENT
visible = True
restricted = False
description = _("This plugin allows you to receive payments via PayPal")

View File

@@ -35,9 +35,9 @@ The shredder class
.. class:: pretix.base.shredder.BaseDataShredder
The central object of each invoice renderer is the subclass of ``BaseInvoiceRenderer``.
The central object of each data shredder is the subclass of ``BaseDataShredder``.
.. py:attribute:: BaseInvoiceRenderer.event
.. py:attribute:: BaseDataShredder.event
The default constructor sets this property to the event we are currently
working for.

View File

@@ -69,3 +69,9 @@ The output class
.. automethod:: generate_order
.. autoattribute:: download_button_text
.. autoattribute:: download_button_icon
.. autoattribute:: preview_allowed
.. autoattribute:: javascript_required

View File

@@ -65,7 +65,7 @@ Then, create the local database::
python manage.py migrate
A first user with username ``admin@localhost`` and password ``admin`` will be automatically
created.
created.
If you want to see pretix in a different language than English, you have to compile our language
files::
@@ -81,8 +81,7 @@ To run the local development webserver, execute::
and head to http://localhost:8000/
As we did not implement an overall front page yet, you need to go directly to
http://localhost:8000/control/ for the admin view or, if you imported the test
data as suggested above, to the event page at http://localhost:8000/bigevents/2019/
http://localhost:8000/control/ for the admin view.
.. note:: If you want the development server to listen on a different interface or
port (for example because you develop on `pretixdroid`_), you can check

View File

@@ -1,12 +1 @@
Table of contents
=================
.. toctree::
:maxdepth: 3
user/index
admin/index
api/index
development/index
plugins/index
.. include:: contents.rst

224
doc/plugins/campaigns.rst Normal file
View File

@@ -0,0 +1,224 @@
Campaigns
=========
The campaigns plugin provides a HTTP API that allows you to create new campaigns.
Resource description
--------------------
The campaign resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal campaign ID
code string The URL component of the campaign, e.g. with code ``BAR``
the campaign URL would to be ``https://<server>/<organizer>/<event>/c/BAR/``.
This value needs to be *globally unique* and we do not
recommend setting it manually. If you omit it, a random
value will be chosen.
description string An internal, human-readable name of the campaign.
external_target string An URL to redirect to from the tracking link. To redirect to
the ticket shop, use an empty string.
order_count integer Number of orders tracked on this campaign (read-only)
click_count integer Number of clicks tracked on this campaign (read-only)
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/campaigns/
Returns a list of all campaigns configured for an event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/campaigns/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"code": "wZnL11fjq",
"description": "Facebook",
"external_target": "",
"order_count:" 0,
"click_count:" 0
}
]
}
:query page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of the event to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to view it.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/campaigns/(id)/
Returns information on one campaign, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/campaigns/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"code": "wZnL11fjq",
"description": "Facebook",
"external_target": "",
"order_count:" 0,
"click_count:" 0
}
: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 campaign to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/campaign does not exist **or** you have no permission to view it.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/campaigns/
Create a new campaign.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/campaigns/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 166
{
"description": "Twitter"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 2,
"code": "IfVJQzSBL",
"description": "Twitter",
"external_target": "",
"order_count:" 0,
"click_count:" 0
}
:param organizer: The ``slug`` field of the organizer to create a campaign for
:param event: The ``slug`` field of the event to create a campaign for
:statuscode 201: no error
:statuscode 400: The campaign could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create campaigns.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/campaigns/(id)/
Update a campaign. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/campaigns/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 34
{
"external_target": "https://mywebsite.com"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"id": 2,
"code": "IfVJQzSBL",
"description": "Twitter",
"external_target": "https://mywebsite.com",
"order_count:" 0,
"click_count:" 0
}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the campaign to modify
:statuscode 200: no error
:statuscode 400: The campaign could not be modified due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/campaign does not exist **or** you have no permission to change it.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/campaigns/(id)/
Delete a campaign and all associated data.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/campaigns/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Vary: Accept
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the campaign to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/campaign does not exist **or** you have no permission to change it

View File

@@ -14,3 +14,4 @@ If you want to **create** a plugin, please go to the
banktransfer
ticketoutputpdf
badges
campaigns

View File

@@ -1,8 +1,9 @@
-r ../src/requirements.txt
sphinx==1.6.*
sphinx==2.3.*
sphinx-rtd-theme
sphinxcontrib-httpdomain
sphinxcontrib-images
sphinxcontrib-spelling
pygments-markdown-lexer
# See https://github.com/rfk/pyenchant/pull/130
git+https://github.com/raphaelm/pyenchant.git@patch-1#egg=pyenchant

View File

@@ -31,6 +31,7 @@ deprovision
discoverable
django
dockerfile
downloadable
durations
eu
filename
@@ -41,11 +42,13 @@ formsets
frontend
frontpage
gettext
giftcard
gunicorn
guid
hardcoded
hostname
idempotency
iframe
incrementing
inofficial
invalidations
@@ -54,6 +57,7 @@ Jimdo
libpretixprint
libsass
linters
login
memcached
metadata
middleware
@@ -75,6 +79,7 @@ overpayment
param
passphrase
percental
pluggable
positionid
pre
prepend
@@ -98,15 +103,18 @@ regex
renderer
renderers
reportlab
reseller
SaaS
scalability
screenshot
scss
searchable
selectable
serializers
serializable
serializer
serializers
sexualized
SQL
startup
stdout
stylesheet
@@ -133,8 +141,10 @@ untrusted
uptime
username
url
validator
versa
versioning
viewable
viewset
viewsets
waitinglist

View File

@@ -0,0 +1,71 @@
.. spelling::
Warengutschein
Wertgutschein
Gift cards
==========
Gift cards, also known as "gift coupons" or "gift certificates" are a mechanism that allows you to sell tokens that
can later be used to pay for tickets.
Gift cards are very different feature than **vouchers**. The difference is:
* Vouchers can be used to give a discount. When a voucher is used, the price of a ticket is reduced by the configured
discount and sold at a lower price. They therefore reduce both revenue as well as taxes. Vouchers (in pretix) are
always specific to a certain product in an order. Vouchers are usually not sold but given out as part of a
marketing campaign or to specific groups of people. Vouchers in pretix are bound to a specific event.
* Gift cards are not a discount, but rather a means of payment. If you buy a €20 ticket with a €10 gift card, it is
still a €20 ticket and will still count towards your revenue with €20. Gift cards are usually bought for the money
that they are worth. Gift cards in pretix can be used across events (and even organizers).
Selling gift cards
------------------
Selling gift cards works like selling every other type of product in pretix: Create a new product, then head to
"Additional settings" and select the option "This product is a gift card". Whenever someone buys this product and
pays for it, a new gift card will be created.
In this case, the gift card code corresponds to the "ticket secret" in the PDF ticket. Therefore, if selling gift cards,
you can use ticket downloads just as with normal tickets and use our ticket editor to create beautiful gift certificates
people can give to their loved ones.
Of course, you can use pretix' flexible options to modify your product. For example, you can configure that the customer
can freely choose the price of the gift card.
.. note::
pretix currently does not support charging sales tax or VAT when selling gift cards, but instead charges VAT on
the full price when the gift card is redeemed. This is the correct behavior in Germany and some other countries for
gift cards which are not bound to a very specific service ("Warengutschein"), but instead to a monetary amount
("Wertgutschein").
.. note::
The ticket PDF will not contain the correct gift card code before the order has been paid, so we recommend not
selling gift cards in events where tickets are issued before payments arrive.
Accepting gift cards
--------------------
All your events have have the payment provider "Gift card" enabled by default, but it will only show up in the ticket
shop once the very first gift card has been issued on your organizer account. Of course, you can turn off gift card
payments if you do not want them for a specific event.
If gift card payments are enabled, buyers will be able to select "Gift card" as a payment method during checkout. If
a gift card with a value less than the order total is used, the buyer will be asked to select a second payment method
for the remaining payment. If a gift card with a value greater than the order total is used, the surplus amount
remains on the gift card and can be used in a different purchase.
If it possible to accept gift cards across organizer accounts. To do so, you need to have access to both organizer
accounts. Then, you will see a configuration section at the bottom of the "Gift cards" page of your organizer settings
where you can specify which gift cards should be accepted.
Manually issuing or using gift cards
------------------------------------
Of course, you can also issue or redeem gift cards manually through our backend using the "Gift cards" menu item in your
organizer profile or using our API. These gift cards will be tracked by pretix, but do not correspond to any purchase
within pretix. You will therefore need to account for them in your books separately.

View File

@@ -114,6 +114,17 @@ If you want to disable voucher input in the widget, you can pass the ``disable-v
<pretix-widget event="https://pretix.eu/demo/democon/" disable-vouchers></pretix-widget>
Filtering products
------------------
You can filter the products shown in the widget by passing in a list of product IDs::
<pretix-widget event="https://pretix.eu/demo/democon/" items="23,42"></pretix-widget>
Alternatively, you can select one or more categories to be shown::
<pretix-widget event="https://pretix.eu/demo/democon/" categories="12,25"></pretix-widget>
Multi-event selection
---------------------
@@ -183,6 +194,24 @@ Just as the widget, the button supports the optional attributes ``voucher`` and
You can style the button using the ``pretix-button`` CSS class.
Dynamically opening the widget
------------------------------
You can get the behavior of the pretix Button without a button at all, so you can trigger it from your own code in
response to a user action. Usually, this will open an overlay with your ticket shop, however in some cases, such as
missing HTTPS encryption on your case or a really small screen (mobile), it will open a new tab instead of an overlay.
Therefore, make sure you call this *in direct response to a user action*, otherwise most browser will block it as an
unwanted pop-up.
.. js:function:: window.PretixWidget.open(target_url [, voucher [, subevent [, items, [, widget_data [, skip_ssl_check ]]]]])
:param string target_url: The URL of the ticket shop.
:param string voucher: A voucher code to be pre-selected, or ``null``.
:param string subevent: A subevent to be pre-selected, or ``null``.
:param array items: A collection of items to be put in the cart, of the form ``[{"item": "item_3", "count": 1}, {"item": "variation_5_6", "count": 4}]``
:param object widget_data: Additional data to be passed to the shop, see below.
:param boolean skip_ssl_check: Whether to ignore the check for HTTPS. Only to be used during development.
Dynamically loading the widget
------------------------------
@@ -206,6 +235,21 @@ If you want, you can suppress us loading the widget and/or modify the user data
If you then later want to trigger loading the widgets, just call ``window.PretixWidget.buildWidgets()``.
Waiting for the widget to load
------------------------------
If you want to run custom JavaScript once the widget is fully loaded, you can register a callback function. Note that
this function might be run multiple times, for example if you have multiple widgets on a page or if the user switches
e.g. from an event list to an event detail view::
<script type="text/javascript">
window.pretixWidgetCallback = function () {
window.PretixWidget.addLoadListener(function () {
console.log("Widget has loaded!");
});
}
</script>
Passing user data to the widget
-------------------------------
@@ -223,7 +267,8 @@ with that information::
data-question-L9G8NG9M="Foobar">
</pretix-widget>
This works for the pretix Button as well. Currently, the following attributes are understood by pretix itself:
This works for the pretix Button as well, if you also specify a product.
Currently, the following attributes are understood by pretix itself:
* ``data-email`` will pre-fill the order email field as well as the attendee email field (if enabled).
@@ -241,6 +286,11 @@ This works for the pretix Button as well. Currently, the following attributes ar
naming scheme such as ``name-title`` or ``name-given-name`` (see above). ``country`` expects a two-character
country code.
* If ``data-fix="true"`` is given, the user will not be able to change the other given values later. This currently
only works for the order email address as well as the invoice address. Attendee-level fields and questions can
always be modified. Note that this is not a security feature and can easily be overridden by users, so do not rely
on this for authentication.
Any configured pretix plugins might understand more data fields. For example, if the appropriate plugins on pretix
Hosted or pretix Enterprise are active, you can pass the following fields:
@@ -274,10 +324,17 @@ Hosted or pretix Enterprise are active, you can pass the following fields:
};
</script>
In some combinations with Google Tag Manager, the widget does not load this way. In this case, try replacing
``tracker.get('clientId')`` with ``ga.getAll()[0].get('clientId')``.
.. versionchanged:: 2.3
Data passing options have been added in pretix 2.3. If you use a self-hosted version of pretix, they only work
fully if you configured a redis server.
.. versionchanged:: 3.6
Dynamically opening the widget has been added in pretix 3.6.
.. _Let's Encrypt: https://letsencrypt.org/

File diff suppressed because one or more lines are too long

View File

@@ -12,5 +12,6 @@ wanting to use pretix to sell tickets.
events/settings
events/structureguide
events/widget
events/giftcards
faq
markdown
markdown

View File

@@ -22,3 +22,5 @@ recursive-include pretix/plugins/ticketoutputpdf/templates *
recursive-include pretix/plugins/ticketoutputpdf/static *
recursive-include pretix/plugins/badges/templates *
recursive-include pretix/plugins/badges/static *
recursive-include pretix/plugins/returnurl/templates *
recursive-include pretix/plugins/returnurl/static *

View File

@@ -1 +1 @@
__version__ = "3.0.0"
__version__ = "3.7.0"

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.2.1 on 2019-10-28 15:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixapi', '0004_auto_20190405_1048'),
]
operations = [
migrations.AlterField(
model_name='oauthgrant',
name='redirect_uri',
field=models.CharField(max_length=2500),
),
]

View File

@@ -43,6 +43,7 @@ class OAuthGrant(AbstractGrant):
OAuthApplication, on_delete=models.CASCADE
)
organizers = models.ManyToManyField('pretixbase.Organizer')
redirect_uri = models.CharField(max_length=2500) # Only 255 in AbstractGrant, which caused problems
class OAuthAccessToken(AbstractAccessToken):

View File

@@ -30,11 +30,12 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
expires = serializers.DateTimeField(required=False)
attendee_name = serializers.CharField(required=False, allow_null=True)
seat = serializers.CharField(required=False, allow_null=True)
sales_channel = serializers.CharField(required=False, default='sales_channel')
class Meta:
model = CartPosition
fields = ('cart_id', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'subevent', 'expires', 'includes_tax', 'answers', 'seat')
'subevent', 'expires', 'includes_tax', 'answers', 'seat', 'sales_channel')
def create(self, validated_data):
answers_data = validated_data.pop('answers')
@@ -82,13 +83,16 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
seat = self.context['event'].seats.get(seat_guid=validated_data['seat'], subevent=validated_data.get('subevent'))
except Seat.DoesNotExist:
raise ValidationError('The specified seat does not exist.')
except Seat.MultipleObjectsReturned:
raise ValidationError('The specified seat ID is not unique.')
else:
validated_data['seat'] = seat
if not seat.is_available():
if not seat.is_available(sales_channel=validated_data.get('sales_channel', 'web')):
raise ValidationError(ugettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name))
elif seated:
raise ValidationError('The specified product requires to choose a seat.')
validated_data.pop('sales_channel')
cp = CartPosition.objects.create(event=self.context['event'], **validated_data)
for answ_data in answers_data:

View File

@@ -3,6 +3,7 @@ from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.channels import get_all_sales_channels
from pretix.base.models import CheckinList
@@ -13,7 +14,7 @@ class CheckinListSerializer(I18nAwareModelSerializer):
class Meta:
model = CheckinList
fields = ('id', 'name', 'all_products', 'limit_products', 'subevent', 'checkin_count', 'position_count',
'include_pending')
'include_pending', 'auto_checkin_sales_channels')
def validate(self, data):
data = super().validate(data)
@@ -35,4 +36,8 @@ class CheckinListSerializer(I18nAwareModelSerializer):
if full_data.get('subevent'):
raise ValidationError(_('The subevent does not belong to this event.'))
for channel in full_data.get('auto_checkin_sales_channels') or []:
if channel not in get_all_sales_channels():
raise ValidationError(_('Unknown sales channel.'))
return data

View File

@@ -4,7 +4,10 @@ from django.db import transaction
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from django_countries.serializers import CountryFieldMixin
from rest_framework.fields import Field
from hierarkey.proxy import HierarkeyProxy
from pytz import common_timezones
from rest_framework import serializers
from rest_framework.fields import ChoiceField, Field
from rest_framework.relations import SlugRelatedField
from pretix.api.serializers.i18n import I18nAwareModelSerializer
@@ -14,6 +17,8 @@ from pretix.base.models.items import SubEventItem, SubEventItemVariation
from pretix.base.services.seating import (
SeatProtected, generate_seats, validate_plan_change,
)
from pretix.base.settings import DEFAULTS, validate_settings
from pretix.base.signals import api_event_settings_fields
class MetaDataField(Field):
@@ -29,6 +34,19 @@ class MetaDataField(Field):
}
class MetaPropertyField(Field):
def to_representation(self, value):
return {
v.name: v.default for v in value.item_meta_properties.all()
}
def to_internal_value(self, data):
return {
'item_meta_properties': data
}
class SeatCategoryMappingField(Field):
def to_representation(self, value):
@@ -61,17 +79,28 @@ class PluginsField(Field):
}
class TimeZoneField(ChoiceField):
def get_attribute(self, instance):
return instance.cache.get_or_set(
'timezone_name',
lambda: instance.settings.timezone,
3600
)
class EventSerializer(I18nAwareModelSerializer):
meta_data = MetaDataField(required=False, source='*')
item_meta_properties = MetaPropertyField(required=False, source='*')
plugins = PluginsField(required=False, source='*')
seat_category_mapping = SeatCategoryMappingField(source='*', required=False)
timezone = TimeZoneField(required=False, choices=[(a, a) for a in common_timezones])
class Meta:
model = Event
fields = ('name', 'slug', 'live', 'testmode', 'currency', 'date_from',
'date_to', 'date_admission', 'is_public', 'presale_start',
'presale_end', 'location', 'has_subevents', 'meta_data', 'seating_plan',
'plugins', 'seat_category_mapping')
'presale_end', 'location', 'geo_lat', 'geo_lon', 'has_subevents', 'meta_data', 'seating_plan',
'plugins', 'seat_category_mapping', 'timezone', 'item_meta_properties')
def validate(self, data):
data = super().validate(data)
@@ -116,6 +145,12 @@ class EventSerializer(I18nAwareModelSerializer):
raise ValidationError(_('Meta data property \'{name}\' does not exist.').format(name=key))
return value
@cached_property
def item_meta_props(self):
return {
p.name: p for p in self.context['request'].event.item_meta_properties.all()
}
def validate_seating_plan(self, value):
if value and value.organizer != self.context['request'].organizer:
raise ValidationError('Invalid seating plan.')
@@ -127,8 +162,11 @@ class EventSerializer(I18nAwareModelSerializer):
return value
def validate_seat_category_mapping(self, value):
if value and value['seat_category_mapping'] and (not self.instance or not self.instance.pk):
raise ValidationError('You cannot specify seat category mappings on event creation.')
if not self.instance or not self.instance.pk:
if value and value['seat_category_mapping']:
raise ValidationError('You cannot specify seat category mappings on event creation.')
else:
return {'seat_category_mapping': {}}
item_cache = {i.pk: i for i in self.instance.items.all()}
result = {}
for k, item in value['seat_category_mapping'].items():
@@ -154,10 +192,15 @@ class EventSerializer(I18nAwareModelSerializer):
@transaction.atomic
def create(self, validated_data):
meta_data = validated_data.pop('meta_data', None)
item_meta_properties = validated_data.pop('item_meta_properties', None)
validated_data.pop('seat_category_mapping', None)
plugins = validated_data.pop('plugins', settings.PRETIX_PLUGINS_DEFAULT.split(','))
tz = validated_data.pop('timezone', None)
event = super().create(validated_data)
if tz:
event.settings.timezone = tz
# Meta data
if meta_data is not None:
for key, value in meta_data.items():
@@ -166,6 +209,15 @@ class EventSerializer(I18nAwareModelSerializer):
value=value
)
# Item Meta properties
if item_meta_properties is not None:
for key, value in item_meta_properties.items():
event.item_meta_properties.create(
name=key,
default=value,
event=event
)
# Seats
if event.seating_plan:
generate_seats(event, None, event.seating_plan, {})
@@ -180,10 +232,15 @@ class EventSerializer(I18nAwareModelSerializer):
@transaction.atomic
def update(self, instance, validated_data):
meta_data = validated_data.pop('meta_data', None)
item_meta_properties = validated_data.pop('item_meta_properties', None)
plugins = validated_data.pop('plugins', None)
seat_category_mapping = validated_data.pop('seat_category_mapping', None)
tz = validated_data.pop('timezone', None)
event = super().update(instance, validated_data)
if tz:
event.settings.timezone = tz
# Meta data
if meta_data is not None:
current = {mv.property: mv for mv in event.meta_values.select_related('property')}
@@ -202,6 +259,26 @@ class EventSerializer(I18nAwareModelSerializer):
if prop.name not in meta_data:
current_object.delete()
# Item Meta properties
if item_meta_properties is not None:
current = [imp for imp in event.item_meta_properties.all()]
for key, value in item_meta_properties.items():
prop = self.item_meta_props.get(key)
if prop in current:
prop.default = value
prop.save()
else:
prop = event.item_meta_properties.create(
name=key,
default=value,
event=event
)
current.append(prop)
for prop in current:
if prop.name not in list(item_meta_properties.keys()):
prop.delete()
# Seats
if seat_category_mapping is not None or ('seating_plan' in validated_data and validated_data['seating_plan'] is None):
current_mappings = {
@@ -239,6 +316,8 @@ class CloneEventSerializer(EventSerializer):
plugins = validated_data.pop('plugins', None)
is_public = validated_data.pop('is_public', None)
testmode = validated_data.pop('testmode', None)
has_subevents = validated_data.pop('has_subevents', None)
tz = validated_data.pop('timezone', None)
new_event = super().create(validated_data)
event = Event.objects.filter(slug=self.context['event'], organizer=self.context['organizer'].pk).first()
@@ -250,7 +329,11 @@ class CloneEventSerializer(EventSerializer):
new_event.is_public = is_public
if testmode is not None:
new_event.testmode = testmode
if has_subevents is not None:
new_event.has_subevents = has_subevents
new_event.save()
if tz:
new_event.settings.timezone = tz
return new_event
@@ -277,8 +360,9 @@ class SubEventSerializer(I18nAwareModelSerializer):
class Meta:
model = SubEvent
fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission',
'presale_start', 'presale_end', 'location', 'event', 'is_public', 'seating_plan',
'item_price_overrides', 'variation_price_overrides', 'meta_data', 'seat_category_mapping')
'presale_start', 'presale_end', 'location', 'geo_lat', 'geo_lon', 'event', 'is_public',
'seating_plan', 'item_price_overrides', 'variation_price_overrides', 'meta_data',
'seat_category_mapping')
def validate(self, data):
data = super().validate(data)
@@ -440,3 +524,127 @@ class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
class Meta:
model = TaxRule
fields = ('id', 'name', 'rate', 'price_includes_tax', 'eu_reverse_charge', 'home_country')
class EventSettingsSerializer(serializers.Serializer):
default_fields = [
'imprint_url',
'checkout_email_helptext',
'presale_has_ended_text',
'voucher_explanation_text',
'show_date_to',
'show_times',
'show_items_outside_presale_period',
'display_net_prices',
'presale_start_show_date',
'locales',
'locale',
'last_order_modification_date',
'show_quota_left',
'waiting_list_enabled',
'waiting_list_hours',
'waiting_list_auto',
'max_items_per_order',
'reservation_time',
'contact_mail',
'show_variations_expanded',
'hide_sold_out',
'meta_noindex',
'redirect_to_checkout_directly',
'frontpage_subevent_ordering',
'frontpage_text',
'attendee_names_asked',
'attendee_names_required',
'attendee_emails_asked',
'attendee_emails_required',
'confirm_text',
'order_email_asked_twice',
'payment_term_days',
'payment_term_last',
'payment_term_weekdays',
'payment_term_expire_automatically',
'payment_term_accept_late',
'payment_explanation',
'ticket_download',
'ticket_download_date',
'ticket_download_addons',
'ticket_download_nonadm',
'ticket_download_pending',
'mail_prefix',
'mail_from',
'mail_from_name',
'mail_attach_ical',
'invoice_address_asked',
'invoice_address_required',
'invoice_address_vatid',
'invoice_address_company_required',
'invoice_address_beneficiary',
'invoice_address_custom_field',
'invoice_name_required',
'invoice_address_not_asked_free',
'invoice_show_payments',
'invoice_reissue_after_modify',
'invoice_include_free',
'invoice_generate',
'invoice_numbers_consecutive',
'invoice_numbers_prefix',
'invoice_numbers_prefix_cancellations',
'invoice_attendee_name',
'invoice_include_expire_date',
'invoice_address_explanation_text',
'invoice_email_attachment',
'invoice_address_from_name',
'invoice_address_from',
'invoice_address_from_zipcode',
'invoice_address_from_city',
'invoice_address_from_country',
'invoice_address_from_tax_id',
'invoice_address_from_vat_id',
'invoice_introductory_text',
'invoice_additional_text',
'invoice_footer_text',
'cancel_allow_user',
'cancel_allow_user_until',
'cancel_allow_user_paid',
'cancel_allow_user_paid_until',
'cancel_allow_user_paid_keep',
'cancel_allow_user_paid_keep_fees',
'cancel_allow_user_paid_keep_percentage',
]
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
super().__init__(*args, **kwargs)
for fname in self.default_fields:
kwargs = DEFAULTS[fname].get('serializer_kwargs', {})
kwargs.setdefault('required', False)
kwargs.setdefault('allow_null', True)
form_kwargs = DEFAULTS[fname].get('form_kwargs', {})
if 'serializer_class' not in DEFAULTS[fname]:
raise ValidationError('{} has no serializer class'.format(fname))
f = DEFAULTS[fname]['serializer_class'](
**kwargs
)
f._label = form_kwargs.get('label', fname)
f._help_text = form_kwargs.get('help_text')
self.fields[fname] = f
for recv, resp in api_event_settings_fields.send(sender=self.event):
for fname, field in resp.items():
field.required = False
self.fields[fname] = field
def update(self, instance: HierarkeyProxy, validated_data):
for attr, value in validated_data.items():
if value is None:
instance.delete(attr)
elif instance.get(attr, as_type=type(value)) != value:
instance.set(attr, value)
return instance
def validate(self, data):
data = super().validate(data)
settings_dict = self.instance.freeze()
settings_dict.update(data)
validate_settings(self.event, settings_dict)
return data

View File

@@ -2,13 +2,15 @@ from decimal import Decimal
from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from pretix.api.serializers.event import MetaDataField
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import (
Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation, Question,
QuestionOption, Quota,
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemVariation,
Question, QuestionOption, Quota,
)
@@ -110,6 +112,7 @@ class ItemSerializer(I18nAwareModelSerializer):
bundles = InlineItemBundleSerializer(many=True, required=False)
variations = InlineItemVariationSerializer(many=True, required=False)
tax_rate = ItemTaxRateField(source='*', read_only=True)
meta_data = MetaDataField(required=False, source='*')
class Meta:
model = Item
@@ -119,12 +122,9 @@ class ItemSerializer(I18nAwareModelSerializer):
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
'show_quota_left', 'hidden_if_available', 'allow_waitinglist')
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data')
read_only_fields = ('has_variations', 'picture')
def get_serializer_context(self):
return {"has_variations": self.kwargs['has_variations']}
def validate(self, data):
data = super().validate(data)
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data):
@@ -134,6 +134,17 @@ class ItemSerializer(I18nAwareModelSerializer):
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order'))
Item.clean_available(data.get('available_from'), data.get('available_until'))
if data.get('issue_giftcard'):
if data.get('tax_rule') and data.get('tax_rule').rate > 0:
raise ValidationError(
_("Gift card products should not be associated with non-zero tax rates since sales tax will be "
"applied when the gift card is redeemed.")
)
if data.get('admission'):
raise ValidationError(_(
"Gift card products should not be admission products at the same time."
))
return data
def validate_category(self, value):
@@ -159,18 +170,65 @@ class ItemSerializer(I18nAwareModelSerializer):
ItemAddOn.clean_max_min_count(addon_data['max_count'], addon_data['min_count'])
return value
@cached_property
def item_meta_properties(self):
return {
p.name: p for p in self.context['request'].event.item_meta_properties.all()
}
def validate_meta_data(self, value):
for key in value['meta_data'].keys():
if key not in self.item_meta_properties:
raise ValidationError(_('Item meta data property \'{name}\' does not exist.').format(name=key))
return value
@transaction.atomic
def create(self, validated_data):
variations_data = validated_data.pop('variations') if 'variations' in validated_data else {}
addons_data = validated_data.pop('addons') if 'addons' in validated_data else {}
bundles_data = validated_data.pop('bundles') if 'bundles' in validated_data else {}
meta_data = validated_data.pop('meta_data', None)
item = Item.objects.create(**validated_data)
for variation_data in variations_data:
ItemVariation.objects.create(item=item, **variation_data)
for addon_data in addons_data:
ItemAddOn.objects.create(base_item=item, **addon_data)
for bundle_data in bundles_data:
ItemBundle.objects.create(base_item=item, **bundle_data)
# Meta data
if meta_data is not None:
for key, value in meta_data.items():
ItemMetaValue.objects.create(
property=self.item_meta_properties.get(key),
value=value,
item=item
)
return item
def update(self, instance, validated_data):
meta_data = validated_data.pop('meta_data', None)
item = super().update(instance, validated_data)
# Meta data
if meta_data is not None:
current = {mv.property: mv for mv in item.meta_values.select_related('property')}
for key, value in meta_data.items():
prop = self.item_meta_properties.get(key)
if prop in current:
current[prop].value = value
current[prop].save()
else:
item.meta_values.create(
property=self.item_meta_properties.get(key),
value=value
)
for prop, current_object in current.items():
if prop.name not in meta_data:
current_object.delete()
return item
@@ -219,7 +277,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
model = Question
fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position',
'ask_during_checkin', 'identifier', 'dependency_question', 'dependency_values',
'hidden', 'dependency_value')
'hidden', 'dependency_value', 'print_on_invoice', 'help_text')
def validate_identifier(self, value):
Question._clean_identifier(self.context['event'], value, self.instance)
@@ -249,6 +307,9 @@ class QuestionSerializer(I18nAwareModelSerializer):
dep = full_data.get('dependency_question')
if dep:
if dep.ask_during_checkin:
raise ValidationError(_('Question cannot depend on a question asked during check-in.'))
seen_ids = {self.instance.pk} if self.instance else set()
while dep:
if dep.pk in seen_ids:
@@ -256,6 +317,9 @@ class QuestionSerializer(I18nAwareModelSerializer):
seen_ids.add(dep.pk)
dep = dep.dependency_question
if full_data.get('ask_during_checkin') and full_data.get('type') in Question.ASK_DURING_CHECKIN_UNSUPPORTED:
raise ValidationError(_('This type of question cannot be asked during check-in.'))
Question.clean_items(event, full_data.get('items'))
return data

View File

@@ -1,6 +1,9 @@
import json
from collections import Counter, defaultdict
from decimal import Decimal
import pycountry
from django.db.models import F, Q
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy
from django_countries.fields import Country
@@ -11,16 +14,22 @@ from rest_framework.reverse import reverse
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.channels import get_all_sales_channels
from pretix.base.decimal import round_decimal
from pretix.base.i18n import language
from pretix.base.models import (
Checkin, Invoice, InvoiceAddress, InvoiceLine, Item, ItemVariation, Order,
OrderPosition, Question, QuestionAnswer, Seat, SubEvent,
OrderPosition, Question, QuestionAnswer, Seat, SubEvent, TaxRule, Voucher,
)
from pretix.base.models.orders import (
CartPosition, OrderFee, OrderPayment, OrderRefund,
)
from pretix.base.pdf import get_variables
from pretix.base.services.cart import error_messages
from pretix.base.services.locking import NoLockManager
from pretix.base.services.pricing import get_price
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
from pretix.base.signals import register_ticket_outputs
from pretix.multidomain.urlreverse import build_absolute_uri
class CompatibleCountryField(serializers.Field):
@@ -41,8 +50,8 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
class Meta:
model = InvoiceAddress
fields = ('last_modified', 'is_business', 'company', 'name', 'name_parts', 'street', 'zipcode', 'city', 'country',
'vat_id', 'vat_id_validated', 'internal_reference')
read_only_fields = ('last_modified', 'vat_id_validated')
'state', 'vat_id', 'vat_id_validated', 'internal_reference')
read_only_fields = ('last_modified',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -57,6 +66,24 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
)
if data.get('name_parts') and '_scheme' not in data.get('name_parts'):
data['name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
if data.get('country'):
if not pycountry.countries.get(alpha_2=data.get('country')):
raise ValidationError(
{'country': ['Invalid country code.']}
)
if data.get('state'):
cc = str(data.get('country') or self.instance.country or '')
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
raise ValidationError(
{'state': ['States are not supported in country "{}".'.format(cc)]}
)
if not pycountry.subdivisions.get(code=cc + '-' + data.get('state')):
raise ValidationError(
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
)
return data
@@ -70,6 +97,11 @@ class AnswerQuestionOptionsIdentifierField(serializers.Field):
return [o.identifier for o in instance.options.all()]
class AnswerQuestionOptionsField(serializers.Field):
def to_representation(self, instance: QuestionAnswer):
return [o.pk for o in instance.options.all()]
class InlineSeatSerializer(I18nAwareModelSerializer):
class Meta:
@@ -80,6 +112,7 @@ class InlineSeatSerializer(I18nAwareModelSerializer):
class AnswerSerializer(I18nAwareModelSerializer):
question_identifier = AnswerQuestionIdentifierField(source='*', read_only=True)
option_identifiers = AnswerQuestionOptionsIdentifierField(source='*', read_only=True)
options = AnswerQuestionOptionsField(source='*', read_only=True)
class Meta:
model = QuestionAnswer
@@ -89,7 +122,7 @@ class AnswerSerializer(I18nAwareModelSerializer):
class CheckinSerializer(I18nAwareModelSerializer):
class Meta:
model = Checkin
fields = ('datetime', 'list')
fields = ('datetime', 'list', 'auto_checked_in')
class OrderDownloadsField(serializers.Field):
@@ -163,6 +196,11 @@ class PdfDataSerializer(serializers.Field):
for k, v in ev._cached_meta_data.items():
res['meta:' + k] = v
if not hasattr(instance.item, '_cached_meta_data'):
instance.item._cached_meta_data = instance.item.meta_data
for k, v in instance.item._cached_meta_data.items():
res['itemmeta:' + k] = v
return res
@@ -178,7 +216,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
model = OrderPosition
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat')
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -258,13 +296,36 @@ class OrderPaymentDateField(serializers.DateField):
class OrderFeeSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderFee
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule')
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule', 'canceled')
class PaymentURLField(serializers.URLField):
def to_representation(self, instance: OrderPayment):
if instance.state != OrderPayment.PAYMENT_STATE_CREATED:
return None
return build_absolute_uri(self.context['event'], 'presale:event.order.pay', kwargs={
'order': instance.order.code,
'secret': instance.order.secret,
'payment': instance.pk,
})
class PaymentDetailsField(serializers.Field):
def to_representation(self, value: OrderPayment):
pp = value.payment_provider
if not pp:
return {}
return pp.api_payment_details(value)
class OrderPaymentSerializer(I18nAwareModelSerializer):
payment_url = PaymentURLField(source='*', allow_null=True, read_only=True)
details = PaymentDetailsField(source='*', allow_null=True, read_only=True)
class Meta:
model = OrderPayment
fields = ('local_id', 'state', 'amount', 'created', 'payment_date', 'provider')
fields = ('local_id', 'state', 'amount', 'created', 'payment_date', 'provider', 'payment_url',
'details')
class OrderRefundSerializer(I18nAwareModelSerializer):
@@ -275,6 +336,14 @@ class OrderRefundSerializer(I18nAwareModelSerializer):
fields = ('local_id', 'state', 'source', 'amount', 'payment', 'created', 'execution_date', 'provider')
class OrderURLField(serializers.URLField):
def to_representation(self, instance: Order):
return build_absolute_uri(self.context['event'], 'presale:event.order', kwargs={
'order': instance.code,
'secret': instance.secret,
})
class OrderSerializer(I18nAwareModelSerializer):
invoice_address = InvoiceAddressSerializer(allow_null=True)
positions = OrderPositionSerializer(many=True, read_only=True)
@@ -284,13 +353,15 @@ class OrderSerializer(I18nAwareModelSerializer):
refunds = OrderRefundSerializer(many=True, read_only=True)
payment_date = OrderPaymentDateField(source='*', read_only=True)
payment_provider = OrderPaymentTypeField(source='*', read_only=True)
url = OrderURLField(source='*', read_only=True)
class Meta:
model = Order
fields = (
'code', 'status', 'testmode', 'secret', 'email', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel'
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url'
)
read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
@@ -329,7 +400,7 @@ class OrderSerializer(I18nAwareModelSerializer):
}
try:
ia = instance.invoice_address
if iadata.get('vat_id') != ia.vat_id:
if iadata.get('vat_id') != ia.vat_id and 'vat_id_validated' not in iadata:
ia.vat_id_validated = False
self.fields['invoice_address'].update(ia, iadata)
except InvoiceAddress.DoesNotExist:
@@ -419,9 +490,13 @@ class AnswerCreateSerializer(I18nAwareModelSerializer):
class OrderFeeCreateSerializer(I18nAwareModelSerializer):
_treat_value_as_percentage = serializers.BooleanField(default=False, required=False)
_split_taxes_like_products = serializers.BooleanField(default=False, required=False)
class Meta:
model = OrderFee
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rule')
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rule',
'_treat_value_as_percentage', '_split_taxes_like_products')
def validate_tax_rule(self, tr):
if tr and tr.event != self.context['event']:
@@ -437,11 +512,15 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
secret = serializers.CharField(required=False)
attendee_name = serializers.CharField(required=False, allow_null=True)
seat = serializers.CharField(required=False, allow_null=True)
price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2,
max_digits=10)
voucher = serializers.SlugRelatedField(slug_field='code', queryset=Voucher.objects.none(),
required=False, allow_null=True)
class Meta:
model = OrderPosition
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'secret', 'addon_to', 'subevent', 'answers', 'seat')
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher')
def validate_secret(self, secret):
if secret and OrderPosition.all.filter(order__event=self.context['event'], secret=secret).exists():
@@ -513,9 +592,31 @@ class CompatibleJSONField(serializers.JSONField):
return value
class WrappedList:
def __init__(self, data):
self._data = data
def all(self):
return self._data
class WrappedModel:
def __init__(self, model):
self._wrapped = model
def __getattr__(self, item):
return getattr(self._wrapped, item)
def save(self, *args, **kwargs):
raise NotImplementedError
def delete(self, *args, **kwargs):
raise NotImplementedError
class OrderCreateSerializer(I18nAwareModelSerializer):
invoice_address = InvoiceAddressSerializer(required=False)
positions = OrderPositionCreateSerializer(many=True, required=False)
positions = OrderPositionCreateSerializer(many=True, required=True)
fees = OrderFeeCreateSerializer(many=True, required=False)
status = serializers.ChoiceField(choices=(
('n', Order.STATUS_PENDING),
@@ -527,18 +628,27 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
min_length=5
)
comment = serializers.CharField(required=False, allow_blank=True)
payment_provider = serializers.CharField(required=True)
payment_provider = serializers.CharField(required=False, allow_null=True)
payment_info = CompatibleJSONField(required=False)
consume_carts = serializers.ListField(child=serializers.CharField(), required=False)
force = serializers.BooleanField(default=False, required=False)
payment_date = serializers.DateTimeField(required=False, allow_null=True)
send_mail = serializers.BooleanField(default=False, required=False)
simulate = serializers.BooleanField(default=False, required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['positions'].child.fields['voucher'].queryset = self.context['event'].vouchers.all()
class Meta:
model = Order
fields = ('code', 'status', 'testmode', 'email', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts', 'force')
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts',
'force', 'send_mail', 'simulate')
def validate_payment_provider(self, pp):
if pp is None:
return None
if pp not in self.context['event'].get_payment_providers():
raise ValidationError('The given payment provider is not known.')
return pp
@@ -605,13 +715,29 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
raise ValidationError(errs)
return data
def validate_testmode(self, testmode):
if 'sales_channel' in self.initial_data:
try:
sales_channel = get_all_sales_channels()[self.initial_data['sales_channel']]
if testmode and not sales_channel.testmode_supported:
raise ValidationError('This sales channel does not provide support for testmode.')
except KeyError:
# We do not need to raise a ValidationError here, since there is another check to validate the
# sales_channel
pass
return testmode
def create(self, validated_data):
fees_data = validated_data.pop('fees') if 'fees' in validated_data else []
positions_data = validated_data.pop('positions') if 'positions' in validated_data else []
payment_provider = validated_data.pop('payment_provider')
payment_provider = validated_data.pop('payment_provider', None)
payment_info = validated_data.pop('payment_info', '{}')
payment_date = validated_data.pop('payment_date', now())
force = validated_data.pop('force', False)
simulate = validated_data.pop('simulate', False)
self._send_mail = validated_data.pop('send_mail', False)
if 'invoice_address' in validated_data:
iadata = validated_data.pop('invoice_address')
@@ -624,14 +750,21 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
else:
ia = None
with self.context['event'].lock() as now_dt:
lockfn = self.context['event'].lock
if simulate:
lockfn = NoLockManager
with lockfn() as now_dt:
free_seats = set()
seats_seen = set()
consume_carts = validated_data.pop('consume_carts', [])
delete_cps = []
quota_avail_cache = {}
v_budget = {}
voucher_usage = Counter()
if consume_carts:
for cp in CartPosition.objects.filter(event=self.context['event'], cart_id__in=consume_carts):
for cp in CartPosition.objects.filter(
event=self.context['event'], cart_id__in=consume_carts, expires__gt=now()
):
quotas = (cp.variation.quotas.filter(subevent=cp.subevent)
if cp.variation else cp.item.quotas.filter(subevent=cp.subevent))
for quota in quotas:
@@ -639,6 +772,8 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
quota_avail_cache[quota] = list(quota.availability())
if quota_avail_cache[quota][1] is not None:
quota_avail_cache[quota][1] += 1
if cp.voucher:
voucher_usage[cp.voucher] -= 1
if cp.expires > now_dt:
if cp.seat:
free_seats.add(cp.seat)
@@ -646,8 +781,98 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
errs = [{} for p in positions_data]
for i, pos_data in enumerate(positions_data):
if pos_data.get('voucher'):
v = pos_data['voucher']
if pos_data.get('addon_to'):
errs[i]['voucher'] = ['Vouchers are currently not supported for add-on products.']
continue
if not v.applies_to(pos_data['item'], pos_data.get('variation')):
errs[i]['voucher'] = [error_messages['voucher_invalid_item']]
continue
if v.subevent_id and pos_data.get('subevent').pk != v.subevent_id:
errs[i]['voucher'] = [error_messages['voucher_invalid_subevent']]
continue
if v.valid_until is not None and v.valid_until < now_dt:
errs[i]['voucher'] = [error_messages['voucher_expired']]
continue
voucher_usage[v] += 1
if voucher_usage[v] > 0:
redeemed_in_carts = CartPosition.objects.filter(
Q(voucher=pos_data['voucher']) & Q(event=self.context['event']) & Q(expires__gte=now_dt)
).exclude(pk__in=[cp.pk for cp in delete_cps])
v_avail = v.max_usages - v.redeemed - redeemed_in_carts.count()
if v_avail < voucher_usage[v]:
errs[i]['voucher'] = [
'The voucher has already been used the maximum number of times.'
]
if v.budget is not None:
price = pos_data.get('price')
if price is None:
price = get_price(
item=pos_data.get('item'),
variation=pos_data.get('variation'),
voucher=v,
custom_price=None,
subevent=pos_data.get('subevent'),
addon_to=pos_data.get('addon_to'),
invoice_address=ia,
).gross
pbv = get_price(
item=pos_data['item'],
variation=pos_data.get('variation'),
voucher=None,
custom_price=None,
subevent=pos_data.get('subevent'),
addon_to=pos_data.get('addon_to'),
invoice_address=ia,
)
if v not in v_budget:
v_budget[v] = v.budget - v.budget_used()
disc = pbv.gross - price
if disc > v_budget[v]:
new_disc = v_budget[v]
v_budget[v] -= new_disc
if new_disc == Decimal('0.00') or pos_data.get('price') is not None:
errs[i]['voucher'] = [
'The voucher has a remaining budget of {}, therefore a discount of {} can not be '
'given.'.format(v_budget[v] + new_disc, disc)
]
continue
pos_data['price'] = price + (disc - new_disc)
else:
v_budget[v] -= disc
seated = pos_data.get('item').seat_category_mappings.filter(subevent=pos_data.get('subevent')).exists()
if pos_data.get('seat'):
if not seated:
errs[i]['seat'] = ['The specified product does not allow to choose a seat.']
try:
seat = self.context['event'].seats.get(seat_guid=pos_data['seat'], subevent=pos_data.get('subevent'))
except Seat.DoesNotExist:
errs[i]['seat'] = ['The specified seat does not exist.']
else:
pos_data['seat'] = seat
if (seat not in free_seats and not seat.is_available(sales_channel=validated_data.get('sales_channel', 'web'))) or seat in seats_seen:
errs[i]['seat'] = [ugettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name)]
seats_seen.add(seat)
elif seated:
errs[i]['seat'] = ['The specified product requires to choose a seat.']
if not force:
for i, pos_data in enumerate(positions_data):
if pos_data.get('voucher'):
if pos_data['voucher'].allow_ignore_quota or pos_data['voucher'].block_quota:
continue
new_quotas = (pos_data.get('variation').quotas.filter(subevent=pos_data.get('subevent'))
if pos_data.get('variation')
else pos_data.get('item').quotas.filter(subevent=pos_data.get('subevent')))
@@ -669,23 +894,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
)
]
for i, pos_data in enumerate(positions_data):
seated = pos_data.get('item').seat_category_mappings.filter(subevent=pos_data.get('subevent')).exists()
if pos_data.get('seat'):
if not seated:
errs[i]['seat'] = ['The specified product does not allow to choose a seat.']
try:
seat = self.context['event'].seats.get(seat_guid=pos_data['seat'], subevent=pos_data.get('subevent'))
except Seat.DoesNotExist:
errs[i]['seat'] = ['The specified seat does not exist.']
else:
pos_data['seat'] = seat
if (seat not in free_seats and not seat.is_available()) or seat in seats_seen:
errs[i]['seat'] = [ugettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name)]
seats_seen.add(seat)
elif seated:
errs[i]['seat'] = ['The specified product requires to choose a seat.']
if any(errs):
raise ValidationError({'positions': errs})
@@ -693,38 +901,23 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
validated_data['locale'] = self.context['event'].settings.locale
order = Order(event=self.context['event'], **validated_data)
order.set_expires(subevents=[p.get('subevent') for p in positions_data])
order.total = sum([p['price'] for p in positions_data]) + sum([f['value'] for f in fees_data], Decimal('0.00'))
order.meta_info = "{}"
order.save()
if order.total == Decimal('0.00') and validated_data.get('status') != Order.STATUS_PAID:
order.status = Order.STATUS_PAID
order.total = Decimal('0.00')
if simulate:
order = WrappedModel(order)
order.last_modified = now()
order.code = 'PREVIEW'
else:
order.save()
order.payments.create(
amount=order.total, provider='free', state=OrderPayment.PAYMENT_STATE_CONFIRMED,
payment_date=now()
)
elif payment_provider == "free" and order.total != Decimal('0.00'):
raise ValidationError('You cannot use the "free" payment provider for non-free orders.')
elif validated_data.get('status') == Order.STATUS_PAID:
order.payments.create(
amount=order.total,
provider=payment_provider,
info=payment_info,
payment_date=payment_date,
state=OrderPayment.PAYMENT_STATE_CONFIRMED
)
elif payment_provider:
order.payments.create(
amount=order.total,
provider=payment_provider,
info=payment_info,
state=OrderPayment.PAYMENT_STATE_CREATED
)
if ia:
ia.order = order
ia.save()
if not simulate:
ia.order = order
ia.save()
else:
order.invoice_address = ia
ia.last_modified = now()
pos_map = {}
for pos_data in positions_data:
answers_data = pos_data.pop('answers', [])
@@ -735,32 +928,171 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
'_legacy': attendee_name
}
pos = OrderPosition(**pos_data)
pos.order = order
pos._calculate_tax()
if simulate:
pos.order = order._wrapped
else:
pos.order = order
if addon_to:
pos.addon_to = pos_map[addon_to]
pos.save()
pos_map[pos.positionid] = pos
for answ_data in answers_data:
options = answ_data.pop('options', [])
answ = pos.answers.create(**answ_data)
answ.options.add(*options)
for cp in delete_cps:
cp.delete()
if pos.price is None:
price = get_price(
item=pos.item,
variation=pos.variation,
voucher=pos.voucher,
custom_price=None,
subevent=pos.subevent,
addon_to=pos.addon_to,
invoice_address=ia,
)
pos.price = price.gross
pos.tax_rate = price.rate
pos.tax_value = price.tax
pos.tax_rule = pos.item.tax_rule
else:
pos._calculate_tax()
pos.price_before_voucher = get_price(
item=pos.item,
variation=pos.variation,
voucher=None,
custom_price=None,
subevent=pos.subevent,
addon_to=pos.addon_to,
invoice_address=ia,
).gross
if simulate:
pos = WrappedModel(pos)
pos.id = 0
answers = []
for answ_data in answers_data:
options = answ_data.pop('options', [])
answ = WrappedModel(QuestionAnswer(**answ_data))
answ.options = WrappedList(options)
answers.append(answ)
pos.answers = answers
pos.pseudonymization_id = "PREVIEW"
else:
if pos.voucher:
Voucher.objects.filter(pk=pos.voucher.pk).update(redeemed=F('redeemed') + 1)
pos.save()
for answ_data in answers_data:
options = answ_data.pop('options', [])
answ = pos.answers.create(**answ_data)
answ.options.add(*options)
pos_map[pos.positionid] = pos
if not simulate:
for cp in delete_cps:
cp.delete()
order.total = sum([p.price for p in pos_map.values()])
fees = []
for fee_data in fees_data:
f = OrderFee(**fee_data)
f.order = order
f._calculate_tax()
f.save()
is_percentage = fee_data.pop('_treat_value_as_percentage', False)
if is_percentage:
fee_data['value'] = round_decimal(order.total * (fee_data['value'] / Decimal('100.00')),
self.context['event'].currency)
is_split_taxes = fee_data.pop('_split_taxes_like_products', False)
if is_split_taxes:
d = defaultdict(lambda: Decimal('0.00'))
trz = TaxRule.zero()
for p in pos_map.values():
tr = p.tax_rule
d[tr] += p.price - p.tax_value
base_values = sorted([tuple(t) for t in d.items()], key=lambda t: (t[0] or trz).rate)
sum_base = sum(t[1] for t in base_values)
fee_values = [(t[0], round_decimal(fee_data['value'] * t[1] / sum_base, self.context['event'].currency))
for t in base_values]
sum_fee = sum(t[1] for t in fee_values)
# If there are rounding differences, we fix them up, but always leaning to the benefit of the tax
# authorities
if sum_fee > fee_data['value']:
fee_values[0] = (fee_values[0][0], fee_values[0][1] + (fee_data['value'] - sum_fee))
elif sum_fee < fee_data['value']:
fee_values[-1] = (fee_values[-1][0], fee_values[-1][1] + (fee_data['value'] - sum_fee))
for tr, val in fee_values:
fee_data['tax_rule'] = tr
fee_data['value'] = val
f = OrderFee(**fee_data)
f.order = order._wrapped if simulate else order
f._calculate_tax()
fees.append(f)
if not simulate:
f.save()
else:
f = OrderFee(**fee_data)
f.order = order._wrapped if simulate else order
f._calculate_tax()
fees.append(f)
if not simulate:
f.save()
order.total += sum([f.value for f in fees])
if simulate:
order.fees = fees
order.positions = pos_map.values()
return order # ignore payments
else:
order.save(update_fields=['total'])
if order.total == Decimal('0.00') and validated_data.get('status') == Order.STATUS_PAID and not payment_provider:
payment_provider = 'free'
if order.total == Decimal('0.00') and validated_data.get('status') != Order.STATUS_PAID:
order.status = Order.STATUS_PAID
order.save()
order.payments.create(
amount=order.total, provider='free', state=OrderPayment.PAYMENT_STATE_CONFIRMED,
payment_date=now()
)
elif payment_provider == "free" and order.total != Decimal('0.00'):
raise ValidationError('You cannot use the "free" payment provider for non-free orders.')
elif validated_data.get('status') == Order.STATUS_PAID:
if not payment_provider:
raise ValidationError('You cannot create a paid order without a payment provider.')
order.payments.create(
amount=order.total,
provider=payment_provider,
info=payment_info,
payment_date=payment_date,
state=OrderPayment.PAYMENT_STATE_CONFIRMED
)
elif payment_provider:
order.payments.create(
amount=order.total,
provider=payment_provider,
info=payment_info,
state=OrderPayment.PAYMENT_STATE_CREATED
)
return order
class LinePositionField(serializers.IntegerField):
"""
Internally, the position field is stored starting at 0, but for the API, starting at 1 makes it
more consistent with other models
"""
def to_representation(self, value):
return super().to_representation(value) + 1
def to_internal_value(self, data):
return super().to_internal_value(data) - 1
class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
position = LinePositionField(read_only=True)
class Meta:
model = InvoiceLine
fields = ('description', 'gross_value', 'tax_value', 'tax_rate', 'tax_name')
fields = ('position', 'description', 'gross_value', 'tax_value', 'tax_rate', 'tax_name')
class InvoiceSerializer(I18nAwareModelSerializer):
@@ -776,6 +1108,20 @@ class InvoiceSerializer(I18nAwareModelSerializer):
'internal_reference')
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
provider = serializers.CharField(required=True, allow_null=False, allow_blank=False)
info = CompatibleJSONField(required=False)
class Meta:
model = OrderPayment
fields = ('state', 'amount', 'payment_date', 'provider', 'info')
def create(self, validated_data):
order = OrderPayment(order=self.context['order'], **validated_data)
order.save()
return order
class OrderRefundCreateSerializer(I18nAwareModelSerializer):
payment = serializers.IntegerField(required=False, allow_null=True)
provider = serializers.CharField(required=True, allow_null=False, allow_blank=False)

View File

@@ -1,7 +1,19 @@
from decimal import Decimal
from django.db.models import Q
from django.utils.translation import get_language, ugettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import CompatibleJSONField
from pretix.base.models import Organizer, SeatingPlan
from pretix.base.auth import get_auth_backends
from pretix.base.models import (
GiftCard, Organizer, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
)
from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.services.mail import SendMailException, mail
from pretix.helpers.urls import build_absolute_uri
class OrganizerSerializer(I18nAwareModelSerializer):
@@ -18,3 +30,140 @@ class SeatingPlanSerializer(I18nAwareModelSerializer):
class Meta:
model = SeatingPlan
fields = ('id', 'name', 'layout')
class GiftCardSerializer(I18nAwareModelSerializer):
value = serializers.DecimalField(max_digits=10, decimal_places=2, min_value=Decimal('0.00'))
def validate(self, data):
data = super().validate(data)
s = data['secret']
qs = GiftCard.objects.filter(
secret=s
).filter(
Q(issuer=self.context["organizer"]) | Q(
issuer__gift_card_collector_acceptance__collector=self.context["organizer"])
)
if self.instance:
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
raise ValidationError(
{'secret': _(
'A gift card with the same secret already exists in your or an affiliated organizer account.')}
)
return data
class Meta:
model = GiftCard
fields = ('id', 'secret', 'issuance', 'value', 'currency', 'testmode')
class EventSlugField(serializers.SlugRelatedField):
def get_queryset(self):
return self.context['organizer'].events.all()
class TeamSerializer(serializers.ModelSerializer):
limit_events = EventSlugField(slug_field='slug', many=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class Meta:
model = Team
fields = (
'id', 'name', 'all_events', 'limit_events', 'can_create_events', 'can_change_teams',
'can_change_organizer_settings', 'can_manage_gift_cards', 'can_change_event_settings',
'can_change_items', 'can_view_orders', 'can_change_orders', 'can_view_vouchers',
'can_change_vouchers'
)
def validate(self, data):
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
if full_data.get('limit_events') and full_data.get('all_events'):
raise ValidationError('Do not set both limit_events and all_events.')
return data
class TeamInviteSerializer(serializers.ModelSerializer):
class Meta:
model = TeamInvite
fields = (
'id', 'email'
)
def _send_invite(self, instance):
try:
mail(
instance.email,
_('pretix account invitation'),
'pretixcontrol/email/invitation.txt',
{
'user': self,
'organizer': self.context['organizer'].name,
'team': instance.team.name,
'url': build_absolute_uri('control:auth.invite', kwargs={
'token': instance.token
})
},
event=None,
locale=get_language() # TODO: expose?
)
except SendMailException:
pass # Already logged
def create(self, validated_data):
if 'email' in validated_data:
try:
user = User.objects.get(email__iexact=validated_data['email'])
except User.DoesNotExist:
if self.context['team'].invites.filter(email__iexact=validated_data['email']).exists():
raise ValidationError(_('This user already has been invited for this team.'))
if 'native' not in get_auth_backends():
raise ValidationError('Users need to have a pretix account before they can be invited.')
invite = self.context['team'].invites.create(email=validated_data['email'])
self._send_invite(invite)
invite.team.log_action(
'pretix.team.invite.created',
data={
'email': validated_data['email']
},
**self.context['log_kwargs']
)
return invite
else:
if self.context['team'].members.filter(pk=user.pk).exists():
raise ValidationError(_('This user already has permissions for this team.'))
self.context['team'].members.add(user)
self.context['team'].log_action(
'pretix.team.member.added',
data={
'email': user.email,
'user': user.pk,
},
**self.context['log_kwargs']
)
return TeamInvite(email=user.email)
else:
raise ValidationError('No email address given.')
class TeamAPITokenSerializer(serializers.ModelSerializer):
active = serializers.BooleanField(default=True, read_only=True)
class Meta:
model = TeamAPIToken
fields = (
'id', 'name', 'active'
)
class TeamMemberSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'id', 'email', 'fullname', 'require_2fa'
)

View File

@@ -2,15 +2,23 @@ from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import Voucher
from pretix.base.models import Seat, Voucher
class VoucherListSerializer(serializers.ListSerializer):
def create(self, validated_data):
codes = set()
seats = set()
errs = []
err = False
for voucher_data in validated_data:
if voucher_data.get('seat') and (voucher_data.get('seat'), voucher_data.get('subevent')) in seats:
err = True
errs.append({'code': ['Duplicate seat ID in request.']})
continue
else:
seats.add((voucher_data.get('seat'), voucher_data.get('subevent')))
if voucher_data['code'] in codes:
err = True
errs.append({'code': ['Duplicate voucher code in request.']})
@@ -22,12 +30,19 @@ class VoucherListSerializer(serializers.ListSerializer):
return super().create(validated_data)
class SeatGuidField(serializers.CharField):
def to_representation(self, val: Seat):
return val.seat_guid
class VoucherSerializer(I18nAwareModelSerializer):
seat = SeatGuidField(allow_null=True, required=False)
class Meta:
model = Voucher
fields = ('id', 'code', 'max_usages', 'redeemed', 'valid_until', 'block_quota',
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
'tag', 'comment', 'subevent', 'show_hidden_items')
'tag', 'comment', 'subevent', 'show_hidden_items', 'seat')
read_only_fields = ('id', 'redeemed')
list_serializer_class = VoucherListSerializer
@@ -39,7 +54,8 @@ class VoucherSerializer(I18nAwareModelSerializer):
Voucher.clean_item_properties(
full_data, self.context.get('event'),
full_data.get('quota'), full_data.get('item'), full_data.get('variation')
full_data.get('quota'), full_data.get('item'), full_data.get('variation'),
block_quota=full_data.get('block_quota')
)
Voucher.clean_subevent(
full_data, self.context.get('event')
@@ -61,4 +77,10 @@ class VoucherSerializer(I18nAwareModelSerializer):
)
Voucher.clean_voucher_code(full_data, self.context.get('event'), self.instance.pk if self.instance else None)
if full_data.get('seat'):
data['seat'] = Voucher.clean_seat_id(
full_data, full_data.get('item'), full_data.get('quota'), self.context.get('event'),
self.instance.pk if self.instance else None
)
return data

View File

@@ -7,8 +7,8 @@ from rest_framework import routers
from pretix.api.views import cart
from .views import (
checkin, device, event, item, oauth, order, organizer, user, voucher,
waitinglist, webhooks,
checkin, device, event, item, oauth, order, organizer, user, version,
voucher, waitinglist, webhooks,
)
router = routers.DefaultRouter()
@@ -19,6 +19,13 @@ orga_router.register(r'events', event.EventViewSet)
orga_router.register(r'subevents', event.SubEventViewSet)
orga_router.register(r'webhooks', webhooks.WebHookViewSet)
orga_router.register(r'seatingplans', organizer.SeatingPlanViewSet)
orga_router.register(r'giftcards', organizer.GiftCardViewSet)
orga_router.register(r'teams', organizer.TeamViewSet)
team_router = routers.DefaultRouter()
team_router.register(r'members', organizer.TeamMemberViewSet)
team_router.register(r'invites', organizer.TeamInviteViewSet)
team_router.register(r'tokens', organizer.TeamAPITokenViewSet)
event_router = routers.DefaultRouter()
event_router.register(r'subevents', event.SubEventViewSet)
@@ -60,7 +67,10 @@ for app in apps.get_app_configs():
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/', include(orga_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/settings/$', event.EventSettingsView.as_view(),
name="event.settings"),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/', include(event_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/teams/(?P<team>[^/]+)/', include(team_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/items/(?P<item>[^/]+)/', include(item_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/questions/(?P<question>[^/]+)/',
include(question_router.urls)),
@@ -75,4 +85,5 @@ urlpatterns = [
url(r"^device/roll$", device.RollKeyView.as_view(), name="device.roll"),
url(r"^device/revoke$", device.RevokeKeyView.as_view(), name="device.revoke"),
url(r"^me$", user.MeView.as_view(), name="user.me"),
url(r"^version$", version.VersionView.as_view(), name="version"),
]

View File

@@ -44,7 +44,6 @@ class CheckinListViewSet(viewsets.ModelViewSet):
qs = self.request.event.checkin_lists.prefetch_related(
'limit_products',
)
qs = CheckinList.annotate_with_numbers(qs, self.request.event)
return qs
def perform_create(self, serializer):

View File

@@ -4,13 +4,14 @@ from django.db.models import ProtectedError, Q
from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import filters, viewsets
from rest_framework import filters, views, viewsets
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
from pretix.api.auth.permission import EventCRUDPermission
from pretix.api.serializers.event import (
CloneEventSerializer, EventSerializer, SubEventSerializer,
TaxRuleSerializer,
CloneEventSerializer, EventSerializer, EventSettingsSerializer,
SubEventSerializer, TaxRuleSerializer,
)
from pretix.api.views import ConditionalListView
from pretix.base.models import (
@@ -18,6 +19,7 @@ from pretix.base.models import (
)
from pretix.base.models.event import SubEvent
from pretix.helpers.dicts import merge_dicts
from pretix.presale.views.organizer import filter_qs_by_attr
with scopes_disabled():
class EventFilter(FilterSet):
@@ -85,6 +87,8 @@ class EventViewSet(viewsets.ModelViewSet):
organizer=self.request.organizer
)
qs = filter_qs_by_attr(qs, self.request)
return qs.prefetch_related(
'meta_values', 'meta_values__property', 'seat_category_mappings'
)
@@ -130,6 +134,7 @@ class EventViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer):
serializer.save(organizer=self.request.organizer)
serializer.instance.set_defaults()
serializer.instance.log_action(
'pretix.event.added',
user=self.request.user,
@@ -241,6 +246,9 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
event__organizer=self.request.organizer,
event__in=self.request.user.get_events_with_any_permission()
)
qs = filter_qs_by_attr(qs, self.request)
return qs.prefetch_related(
'subeventitem_set', 'subeventitemvariation_set', 'seat_category_mappings'
)
@@ -326,3 +334,33 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
auth=self.request.auth,
)
super().perform_destroy(instance)
class EventSettingsView(views.APIView):
permission = 'can_change_event_settings'
def get(self, request, *args, **kwargs):
s = EventSettingsSerializer(instance=request.event.settings, event=request.event)
if 'explain' in request.GET:
return Response({
fname: {
'value': s.data[fname],
'label': getattr(field, '_label', fname),
'help_text': getattr(field, '_help_text', None)
} for fname, field in s.fields.items()
})
return Response(s.data)
def patch(self, request, *wargs, **kwargs):
s = EventSettingsSerializer(instance=request.event.settings, data=request.data, partial=True,
event=request.event)
s.is_valid(raise_exception=True)
with transaction.atomic():
s.save()
self.request.event.log_action(
'pretix.event.settings', user=self.request.user, auth=self.request.auth, data={
k: v for k, v in s.validated_data.items()
}
)
s = EventSettingsSerializer(instance=request.event.settings, event=request.event)
return Response(s.data)

View File

@@ -62,7 +62,6 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['has_variations'] = self.request.data.get('has_variations')
return ctx
def perform_update(self, serializer):

View File

@@ -6,7 +6,7 @@ import pytz
from django.db import transaction
from django.db.models import F, Prefetch, Q
from django.db.models.functions import Coalesce, Concat
from django.http import FileResponse
from django.http import FileResponse, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.timezone import make_aware, now
from django.utils.translation import ugettext as _
@@ -23,15 +23,16 @@ from rest_framework.response import Response
from pretix.api.models import OAuthAccessToken
from pretix.api.serializers.order import (
InvoiceSerializer, OrderCreateSerializer, OrderPaymentSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer,
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer,
InvoiceSerializer, OrderCreateSerializer, OrderPaymentCreateSerializer,
OrderPaymentSerializer, OrderPositionSerializer,
OrderRefundCreateSerializer, OrderRefundSerializer, OrderSerializer,
PriceCalcSerializer,
)
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Device, Event, Invoice, InvoiceAddress,
Order, OrderPayment, OrderPosition, OrderRefund, Quota, TeamAPIToken,
generate_position_secret, generate_secret,
Order, OrderFee, OrderPayment, OrderPosition, OrderRefund, Quota,
TeamAPIToken, generate_position_secret, generate_secret,
)
from pretix.base.payment import PaymentException
from pretix.base.services import tickets
@@ -41,13 +42,14 @@ from pretix.base.services.invoices import (
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import (
OrderChangeManager, OrderError, approve_order, cancel_order, deny_order,
OrderChangeManager, OrderError, _order_placed_email,
_order_placed_email_attendee, approve_order, cancel_order, deny_order,
extend_order, mark_order_expired, mark_order_refunded,
)
from pretix.base.services.pricing import get_price
from pretix.base.services.tickets import generate
from pretix.base.signals import (
order_modified, order_placed, register_ticket_outputs,
order_modified, order_paid, order_placed, register_ticket_outputs,
)
from pretix.base.templatetags.money import money_filter
@@ -81,20 +83,29 @@ class OrderViewSet(viewsets.ModelViewSet):
return ctx
def get_queryset(self):
if self.request.query_params.get('include_canceled_fees', 'false') == 'true':
fqs = OrderFee.all
else:
fqs = OrderFee.objects
qs = self.request.event.orders.prefetch_related(
'fees', 'payments', 'refunds', 'refunds__payment'
Prefetch('fees', queryset=fqs.all()),
'payments', 'refunds', 'refunds__payment'
).select_related(
'invoice_address'
)
if self.request.query_params.get('include_canceled_positions', 'false') == 'true':
opq = OrderPosition.all
else:
opq = OrderPosition.objects
if self.request.query_params.get('pdf_data', 'false') == 'true':
qs = qs.prefetch_related(
Prefetch(
'positions',
OrderPosition.objects.all().prefetch_related(
opq.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
'item__category', 'addon_to', 'seat',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation', 'seat'))
Prefetch('addons', opq.select_related('item', 'variation', 'seat'))
)
)
)
@@ -102,7 +113,7 @@ class OrderViewSet(viewsets.ModelViewSet):
qs = qs.prefetch_related(
Prefetch(
'positions',
OrderPosition.objects.all().prefetch_related(
opq.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question', 'seat',
)
)
@@ -147,12 +158,16 @@ class OrderViewSet(viewsets.ModelViewSet):
generate.apply_async(args=('order', order.pk, provider.identifier))
raise RetryException()
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
self.request.event.slug.upper(), order.code,
provider.identifier, ct.extension
)
return resp
if ct.type == 'text/uri-list':
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
return resp
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
self.request.event.slug.upper(), order.code,
provider.identifier, ct.extension
)
return resp
@action(detail=True, methods=['POST'])
def mark_paid(self, request, **kwargs):
@@ -168,9 +183,26 @@ class OrderViewSet(viewsets.ModelViewSet):
amount=ps
)
except OrderPayment.DoesNotExist:
order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
OrderPayment.PAYMENT_STATE_CREATED)) \
.update(state=OrderPayment.PAYMENT_STATE_CANCELED)
for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
OrderPayment.PAYMENT_STATE_CREATED)):
try:
with transaction.atomic():
p.payment_provider.cancel_payment(p)
order.log_action('pretix.event.order.payment.canceled', {
'local_id': p.local_id,
'provider': p.provider,
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
except PaymentException as e:
order.log_action(
'pretix.event.order.payment.canceled.failed',
{
'local_id': p.local_id,
'provider': p.provider,
'error': str(e)
},
user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth
)
p = order.payments.create(
state=OrderPayment.PAYMENT_STATE_CREATED,
provider='manual',
@@ -431,22 +463,64 @@ class OrderViewSet(viewsets.ModelViewSet):
serializer.is_valid(raise_exception=True)
with transaction.atomic():
self.perform_create(serializer)
send_mail = serializer._send_mail
order = serializer.instance
serializer = OrderSerializer(order, context=serializer.context)
if not order.pk:
# Simulation
return Response(serializer.data, status=status.HTTP_201_CREATED)
order.log_action(
'pretix.event.order.placed',
user=request.user if request.user.is_authenticated else None,
auth=request.auth,
)
order_placed.send(self.request.event, order=order)
gen_invoice = invoice_qualified(order) and (
(order.event.settings.get('invoice_generate') == 'True') or
(order.event.settings.get('invoice_generate') == 'paid' and order.status == Order.STATUS_PAID)
) and not order.invoices.last()
if gen_invoice:
generate_invoice(order, trigger_pdf=True)
with language(order.locale):
order_placed.send(self.request.event, order=order)
if order.status == Order.STATUS_PAID:
order_paid.send(self.request.event, order=order)
gen_invoice = invoice_qualified(order) and (
(order.event.settings.get('invoice_generate') == 'True') or
(order.event.settings.get('invoice_generate') == 'paid' and order.status == Order.STATUS_PAID)
) and not order.invoices.last()
invoice = None
if gen_invoice:
invoice = generate_invoice(order, trigger_pdf=True)
if send_mail:
payment = order.payments.last()
free_flow = (
payment and order.total == Decimal('0.00') and order.status == Order.STATUS_PAID and
not order.require_approval and payment.provider == "free"
)
if free_flow:
email_template = request.event.settings.mail_text_order_free
log_entry = 'pretix.event.order.email.order_free'
email_attendees = request.event.settings.mail_send_order_free_attendee
email_attendees_template = request.event.settings.mail_text_order_free_attendee
else:
email_template = request.event.settings.mail_text_order_placed
log_entry = 'pretix.event.order.email.order_placed'
email_attendees = request.event.settings.mail_send_order_placed_attendee
email_attendees_template = request.event.settings.mail_text_order_placed_attendee
_order_placed_email(
request.event, order, payment.payment_provider if payment else None, email_template,
log_entry, invoice, payment
)
if email_attendees:
for p in order.positions.all():
if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email:
_order_placed_email_attendee(request.event, order, p, email_attendees_template, log_entry)
if not free_flow and order.status == Order.STATUS_PAID and payment:
payment._send_paid_mail(invoice, None, '')
if self.request.event.settings.mail_send_order_paid_attendee:
for p in order.positions.all():
if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email:
payment._send_paid_mail_attendee(p, None)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
@@ -593,11 +667,16 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
}
def get_queryset(self):
qs = OrderPosition.objects.filter(order__event=self.request.event)
if self.request.query_params.get('include_canceled_positions', 'false') == 'true':
qs = OrderPosition.all
else:
qs = OrderPosition.objects
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('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('addons', qs.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
'event',
@@ -605,7 +684,7 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
),
Prefetch(
'positions',
OrderPosition.objects.prefetch_related(
qs.prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
@@ -615,7 +694,7 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
)
else:
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question'
'checkins', 'answers', 'answers__options', 'answers__question',
).select_related(
'item', 'order', 'order__event', 'order__event__organizer', 'seat'
)
@@ -723,12 +802,16 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
generate.apply_async(args=('orderposition', pos.pk, provider.identifier))
raise RetryException()
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
self.request.event.slug.upper(), pos.order.code, pos.positionid,
provider.identifier, ct.extension
)
return resp
if ct.type == 'text/uri-list':
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
return resp
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
self.request.event.slug.upper(), pos.order.code, pos.positionid,
provider.identifier, ct.extension
)
return resp
def perform_destroy(self, instance):
try:
@@ -746,17 +829,62 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
raise ValidationError(str(e))
class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPaymentSerializer
queryset = OrderPayment.objects.none()
permission = 'can_view_orders'
write_permission = 'can_change_orders'
lookup_field = 'local_id'
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['order'] = get_object_or_404(Order, code=self.kwargs['order'], event=self.request.event)
return ctx
def get_queryset(self):
order = get_object_or_404(Order, code=self.kwargs['order'], event=self.request.event)
return order.payments.all()
def create(self, request, *args, **kwargs):
serializer = OrderPaymentCreateSerializer(data=request.data, context=self.get_serializer_context())
serializer.is_valid(raise_exception=True)
with transaction.atomic():
mark_confirmed = False
if serializer.validated_data['state'] == OrderPayment.PAYMENT_STATE_CONFIRMED:
serializer.validated_data['state'] = OrderPayment.PAYMENT_STATE_PENDING
mark_confirmed = True
self.perform_create(serializer)
r = serializer.instance
if mark_confirmed:
try:
r.confirm(
user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth,
count_waitinglist=False,
force=request.data.get('force', False)
)
except Quota.QuotaExceededException:
pass
except SendMailException:
pass
serializer = OrderPaymentSerializer(r, context=serializer.context)
r.order.log_action(
'pretix.event.order.payment.started', {
'local_id': r.local_id,
'provider': r.provider,
},
user=request.user if request.user.is_authenticated else None,
auth=request.auth
)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
@action(detail=True, methods=['POST'])
def confirm(self, request, **kwargs):
payment = self.get_object()
@@ -850,13 +978,16 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
if payment.state not in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
return Response({'detail': 'Invalid state of payment'}, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
payment.save()
payment.order.log_action('pretix.event.order.payment.canceled', {
'local_id': payment.local_id,
'provider': payment.provider,
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
try:
with transaction.atomic():
payment.payment_provider.cancel_payment(payment)
payment.order.log_action('pretix.event.order.payment.canceled', {
'local_id': payment.local_id,
'provider': payment.provider,
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
except PaymentException as e:
return Response({'detail': 'External error: {}'.format(str(e))},
status=status.HTTP_400_BAD_REQUEST)
return self.retrieve(request, [], **kwargs)
@@ -933,6 +1064,7 @@ class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
mark_refunded = request.data.pop('mark_refunded', False)
else:
mark_refunded = request.data.pop('mark_canceled', False)
mark_pending = request.data.pop('mark_pending', False)
serializer = OrderRefundCreateSerializer(data=request.data, context=self.get_serializer_context())
serializer.is_valid(raise_exception=True)
with transaction.atomic():
@@ -949,11 +1081,23 @@ class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
auth=request.auth
)
if mark_refunded:
mark_order_refunded(
r.order,
user=request.user if request.user.is_authenticated else None,
auth=(request.auth if request.auth else None),
)
try:
mark_order_refunded(
r.order,
user=request.user if request.user.is_authenticated else None,
auth=(request.auth if request.auth else None),
)
except OrderError as e:
raise ValidationError(str(e))
elif mark_pending:
if r.order.status == Order.STATUS_PAID and r.order.pending_sum > 0:
r.order.status = Order.STATUS_PENDING
r.order.set_expires(
now(),
r.order.event.subevents.filter(
id__in=r.order.positions.values_list('subevent_id', flat=True))
)
r.order.save(update_fields=['status', 'expires'])
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

View File

@@ -1,11 +1,26 @@
from rest_framework import filters, viewsets
from rest_framework.exceptions import PermissionDenied
from decimal import Decimal
import django_filters
from django.db import transaction
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import filters, serializers, status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
from rest_framework.response import Response
from pretix.api.models import OAuthAccessToken
from pretix.api.serializers.organizer import (
OrganizerSerializer, SeatingPlanSerializer,
GiftCardSerializer, OrganizerSerializer, SeatingPlanSerializer,
TeamAPITokenSerializer, TeamInviteSerializer, TeamMemberSerializer,
TeamSerializer,
)
from pretix.base.models import (
GiftCard, Organizer, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
)
from pretix.base.models import Organizer, SeatingPlan
from pretix.helpers.dicts import merge_dicts
@@ -50,6 +65,7 @@ class SeatingPlanViewSet(viewsets.ModelViewSet):
ctx['organizer'] = self.request.organizer
return ctx
@transaction.atomic()
def perform_create(self, serializer):
inst = serializer.save(organizer=self.request.organizer)
self.request.organizer.log_action(
@@ -59,6 +75,7 @@ class SeatingPlanViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'id': inst.pk})
)
@transaction.atomic()
def perform_update(self, serializer):
if serializer.instance.events.exists() or serializer.instance.subevents.exists():
raise PermissionDenied('This plan can not be changed while it is in use for an event.')
@@ -71,6 +88,7 @@ class SeatingPlanViewSet(viewsets.ModelViewSet):
)
return inst
@transaction.atomic()
def perform_destroy(self, instance):
if instance.events.exists() or instance.subevents.exists():
raise PermissionDenied('This plan can not be deleted while it is in use for an event.')
@@ -81,3 +99,256 @@ class SeatingPlanViewSet(viewsets.ModelViewSet):
data={'id': instance.pk}
)
instance.delete()
with scopes_disabled():
class GiftCardFilter(FilterSet):
secret = django_filters.CharFilter(field_name='secret', lookup_expr='iexact')
class Meta:
model = GiftCard
fields = ['secret', 'testmode']
class GiftCardViewSet(viewsets.ModelViewSet):
serializer_class = GiftCardSerializer
queryset = GiftCard.objects.none()
permission = 'can_manage_gift_cards'
write_permission = 'can_manage_gift_cards'
filter_backends = (DjangoFilterBackend,)
filterset_class = GiftCardFilter
def get_queryset(self):
if self.request.GET.get('include_accepted') == 'true':
qs = self.request.organizer.accepted_gift_cards
else:
qs = self.request.organizer.issued_gift_cards.all()
return qs
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
return ctx
@transaction.atomic()
def perform_create(self, serializer):
value = serializer.validated_data.pop('value')
inst = serializer.save(issuer=self.request.organizer)
inst.transactions.create(value=value)
inst.log_action(
'pretix.giftcards.transaction.manual',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': inst.pk})
)
@transaction.atomic()
def perform_update(self, serializer):
if 'include_accepted' in self.request.GET:
raise PermissionDenied("Accepted gift cards cannot be updated, use transact instead.")
GiftCard.objects.select_for_update().get(pk=self.get_object().pk)
old_value = serializer.instance.value
value = serializer.validated_data.pop('value')
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
testmode=serializer.instance.testmode)
diff = value - old_value
inst.transactions.create(value=diff)
inst.log_action(
'pretix.giftcards.transaction.manual',
user=self.request.user,
auth=self.request.auth,
data={'value': diff}
)
return inst
@action(detail=True, methods=["POST"])
@transaction.atomic()
def transact(self, request, **kwargs):
gc = GiftCard.objects.select_for_update().get(pk=self.get_object().pk)
value = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
request.data.get('value')
)
text = serializers.CharField(allow_blank=True, allow_null=True).to_internal_value(
request.data.get('text', '')
)
if gc.value + value < Decimal('0.00'):
return Response({
'value': ['The gift card does not have sufficient credit for this operation.']
}, status=status.HTTP_409_CONFLICT)
gc.transactions.create(value=value, text=text)
gc.log_action(
'pretix.giftcards.transaction.manual',
user=self.request.user,
auth=self.request.auth,
data={'value': value, 'text': text}
)
return Response(GiftCardSerializer(gc).data, status=status.HTTP_200_OK)
def perform_destroy(self, instance):
raise MethodNotAllowed("Gift cards cannot be deleted.")
class TeamViewSet(viewsets.ModelViewSet):
serializer_class = TeamSerializer
queryset = Team.objects.none()
permission = 'can_change_teams'
write_permission = 'can_change_teams'
def get_queryset(self):
return self.request.organizer.teams.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
return ctx
@transaction.atomic()
def perform_create(self, serializer):
inst = serializer.save(organizer=self.request.organizer)
inst.log_action(
'pretix.team.created',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': inst.pk})
)
@transaction.atomic()
def perform_update(self, serializer):
inst = serializer.save()
inst.log_action(
'pretix.team.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
return inst
def perform_destroy(self, instance):
instance.log_action('pretix.team.deleted', user=self.request.user, auth=self.request.auth)
instance.delete()
class TeamMemberViewSet(DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = TeamMemberSerializer
queryset = User.objects.none()
permission = 'can_change_teams'
write_permission = 'can_change_teams'
@cached_property
def team(self):
return get_object_or_404(self.request.organizer.teams, pk=self.kwargs.get('team'))
def get_queryset(self):
return self.team.members.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
return ctx
@transaction.atomic()
def perform_destroy(self, instance):
self.team.members.remove(instance)
self.team.log_action(
'pretix.team.member.removed', user=self.request.user, auth=self.request.auth, data={
'email': instance.email,
'user': instance.pk
}
)
class TeamInviteViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = TeamInviteSerializer
queryset = TeamInvite.objects.none()
permission = 'can_change_teams'
write_permission = 'can_change_teams'
@cached_property
def team(self):
return get_object_or_404(self.request.organizer.teams, pk=self.kwargs.get('team'))
def get_queryset(self):
return self.team.invites.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
ctx['team'] = self.team
ctx['log_kwargs'] = {
'user': self.request.user,
'auth': self.request.auth,
}
return ctx
@transaction.atomic()
def perform_destroy(self, instance):
self.team.log_action(
'pretix.team.invite.deleted', user=self.request.user, auth=self.request.auth, data={
'email': instance.email,
}
)
instance.delete()
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(team=self.team)
class TeamAPITokenViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = TeamAPITokenSerializer
queryset = TeamAPIToken.objects.none()
permission = 'can_change_teams'
write_permission = 'can_change_teams'
@cached_property
def team(self):
return get_object_or_404(self.request.organizer.teams, pk=self.kwargs.get('team'))
def get_queryset(self):
return self.team.tokens.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
ctx['team'] = self.team
ctx['log_kwargs'] = {
'user': self.request.user,
'auth': self.request.auth,
}
return ctx
@transaction.atomic()
def perform_destroy(self, instance):
instance.active = False
instance.save()
self.team.log_action(
'pretix.team.token.deleted', user=self.request.user, auth=self.request.auth, data={
'name': instance.name,
}
)
@transaction.atomic()
def perform_create(self, serializer):
instance = serializer.save(team=self.team)
self.team.log_action(
'pretix.team.token.created', auth=self.request.auth, user=self.request.user, data={
'name': instance.name,
'id': instance.pk
}
)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
d = serializer.data
d['token'] = serializer.instance.token
return Response(d, status=status.HTTP_201_CREATED, headers=headers)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
serializer = self.get_serializer_class()(instance)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_200_OK, headers=headers)

View File

@@ -0,0 +1,56 @@
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
from packaging import version
from rest_framework.authentication import SessionAuthentication
from rest_framework.response import Response
from rest_framework.views import APIView
from pretix import __version__
from pretix.api.auth.device import DeviceTokenAuthentication
from pretix.api.auth.token import TeamTokenAuthentication
def numeric_version(v):
# Converts a pretix version to a large int
# e.g. 30060001000
# |--------------------- Major version
# |-|------------------ Minor version
# |-|--------------- Patch version
# ||------------- Stage (10 dev, 20 alpha, 30 beta, 40 rc, 50 release, 60 post)
# ||----------- Stage version (number of dev/alpha/beta/rc/post release)
v = version.parse(v)
phases = {
'dev': 10,
'a': 20,
'b': 30,
'rc': 40,
'release': 50,
'post': 60
}
vnum = 0
if v.is_postrelease:
vnum += v.post
vnum += phases['post'] * 100
elif v.dev is not None:
vnum += v.dev
vnum += phases['dev'] * 100
elif v.is_prerelease and v.pre:
vnum += v.pre[0]
vnum += phases[v.pre[1]] * 100
else:
vnum += phases['release'] * 100
for i, part in enumerate(reversed(v.release)):
vnum += part * (1000 ** i) * 10000
return vnum
class VersionView(APIView):
authentication_classes = (
SessionAuthentication, OAuth2Authentication, DeviceTokenAuthentication, TeamTokenAuthentication
)
def get(self, request, format=None):
return Response({
'pretix': __version__,
'pretix_numeric': numeric_version(__version__),
})

View File

@@ -45,7 +45,7 @@ class VoucherViewSet(viewsets.ModelViewSet):
write_permission = 'can_change_vouchers'
def get_queryset(self):
return self.request.event.vouchers.all()
return self.request.event.vouchers.select_related('seat').all()
def _predict_quota_check(self, data, instance):
# This method predicts if Voucher.clean_quota_needs_checking

View File

@@ -1,5 +1,4 @@
from django.apps import AppConfig
from django.conf import settings
class PretixBaseConfig(AppConfig):
@@ -13,7 +12,8 @@ class PretixBaseConfig(AppConfig):
from . import invoice # NOQA
from . import notifications # NOQA
from . import email # NOQA
from .services import auth, export, mail, tickets, cart, orders, invoices, cleanup, update_check, quotas, notifications # NOQA
from .services import auth, checkin, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
from django.conf import settings
try:
from .celery_app import app as celery_app # NOQA

109
src/pretix/base/auth.py Normal file
View File

@@ -0,0 +1,109 @@
from collections import OrderedDict
from importlib import import_module
from django import forms
from django.conf import settings
from django.contrib.auth import authenticate
from django.utils.translation import gettext_lazy as _
def get_auth_backends():
backends = {}
for b in settings.PRETIX_AUTH_BACKENDS:
mod, name = b.rsplit('.', 1)
b = getattr(import_module(mod), name)()
backends[b.identifier] = b
return backends
class BaseAuthBackend:
"""
This base class defines the interface that needs to be implemented by every class that supplies
an authentication method to pretix. Please note that pretix authentication backends are different
from plain Django authentication backends! Be sure to read the documentation chapter on authentication
backends before you implement one.
"""
@property
def identifier(self):
"""
A short and unique identifier for this authentication backend.
This should only contain lowercase letters and in most cases will
be the same as your package name.
"""
raise NotImplementedError()
@property
def verbose_name(self):
"""
A human-readable name of this authentication backend.
"""
raise NotImplementedError()
@property
def visible(self):
"""
Whether or not this backend can be selected by users actively. Set this to ``False``
if you only implement ``request_authenticate``.
"""
return True
@property
def login_form_fields(self) -> dict:
"""
This property may return form fields that the user needs to fill in to log in.
"""
return {}
def form_authenticate(self, request, form_data):
"""
This method will be called after the user filled in the login form. ``request`` will contain
the current request and ``form_data`` the input for the form fields defined in ``login_form_fields``.
You are expected to either return a ``User`` object (if login was successful) or ``None``.
"""
return
def request_authenticate(self, request):
"""
This method will be called when the user opens the login form. If the user already has a valid session
according to your login mechanism, for example a cookie set by a different system or HTTP header set by a
reverse proxy, you can directly return a ``User`` object that will be logged in.
``request`` will contain the current request.
You are expected to either return a ``User`` object (if login was successful) or ``None``.
"""
return
def authentication_url(self, request):
"""
This method will be called to populate the URL for your authentication method's tab on the login page.
For example, if your method works through OAuth, you could return the URL of the OAuth authorization URL the
user needs to visit.
If you return ``None`` (the default), the link will point to a page that shows the form defined by
``login_form_fields``.
"""
return
class NativeAuthBackend(BaseAuthBackend):
identifier = 'native'
verbose_name = _('pretix User')
@property
def login_form_fields(self) -> dict:
"""
This property may return form fields that the user needs to fill in
to log in.
"""
d = OrderedDict([
('email', forms.EmailField(label=_("E-mail"), max_length=254,
widget=forms.EmailInput(attrs={'autofocus': 'autofocus'}))),
('password', forms.CharField(label=_("Password"), widget=forms.PasswordInput)),
])
return d
def form_authenticate(self, request, form_data):
u = authenticate(request=request, email=form_data['email'].lower(), password=form_data['password'])
if u and u.auth_backend == self.identifier:
return u

View File

@@ -0,0 +1,80 @@
import re
# banlist based on http://www.bannedwordlist.com/lists/swearWords.txt
banlist = [
"anal",
"anus",
"arse",
"ass",
"balls",
"bastard",
"bitch",
"biatch",
"bloody",
"blowjob",
"bollock",
"bollok",
"boner",
"boob",
"bugger",
"bum",
"butt",
"clitoris",
"cock",
"coon",
"crap",
"cunt",
"damn",
"dick",
"dildo",
"dyke",
"fag",
"feck",
"fellate",
"fellatio",
"felching",
"fuck",
"fudgepacker",
"flange",
"goddamn",
"hell",
"homo",
"jerk",
"jizz",
"knobend",
"labia",
"lmao",
"lmfao",
"muff",
"nigger",
"nigga",
"omg",
"penis",
"piss",
"poop",
"prick",
"pube",
"pussy",
"queer",
"scrotum",
"sex",
"shit",
"sh1t",
"slut",
"smegma",
"spunk",
"tit",
"tosser",
"turd",
"twat",
"vagina",
"wank",
"whore",
"wtf"
]
blacklist_regex = re.compile('(' + '|'.join(banlist) + ')')
def banned(string):
return bool(blacklist_regex.search(string.lower()))

View File

@@ -35,6 +35,32 @@ class SalesChannel:
"""
return "circle"
@property
def testmode_supported(self) -> bool:
"""
Indication, if a saleschannels supports test mode orders
"""
return True
@property
def payment_restrictions_supported(self) -> bool:
"""
If this property is ``True``, organizers can restrict the usage of payment providers to this sales channel.
Example: pretixPOS provides its own sales channel, ignores the configured payment providers completely and
handles payments locally. Therefor, this property should be set to ``False`` for the pretixPOS sales channel as
the event organizer cannot restrict the usage of any payment provider through the backend.
"""
return True
@property
def unlimited_items_per_order(self) -> bool:
"""
If this property is ``True``, purchases made using this sales channel are not limited to the maximum amount of
items defined in the event settings.
"""
return False
def get_all_sales_channels():
global _ALL_CHANNELS

View File

@@ -0,0 +1,15 @@
import sys
from django.conf import settings
def contextprocessor(request):
ctx = {
'rtl': getattr(request, 'LANGUAGE_CODE', 'en') in settings.LANGUAGES_RTL,
}
if settings.DEBUG and 'runserver' not in sys.argv:
ctx['debug_warning'] = True
elif 'runserver' in sys.argv:
ctx['development_warning'] = True
return ctx

View File

@@ -1,15 +1,23 @@
import inspect
import logging
from datetime import timedelta
from decimal import Decimal
from smtplib import SMTPResponseException
from django.conf import settings
from django.core.mail.backends.smtp import EmailBackend
from django.dispatch import receiver
from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import now
from django.utils.translation import get_language, ugettext_lazy as _
from inlinestyler.utils import inline_css
from pretix.base.models import Event, Order, OrderPosition
from pretix.base.signals import register_html_mail_renderers
from pretix.base.i18n import LazyCurrencyNumber, LazyDate, LazyNumber
from pretix.base.models import Event
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import (
register_html_mail_renderers, register_mail_placeholders,
)
from pretix.base.templatetags.rich_text import markdown_compile_email
logger = logging.getLogger('pretix.base.email')
@@ -44,8 +52,8 @@ class BaseHTMLMailRenderer:
def __str__(self):
return self.identifier
def render(self, plain_body: str, plain_signature: str, subject: str, order: Order=None,
position: OrderPosition=None) -> str:
def render(self, plain_body: str, plain_signature: str, subject: str, order=None,
position=None) -> str:
"""
This method should generate the HTML part of the email.
@@ -97,14 +105,15 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
def template_name(self):
raise NotImplementedError()
def render(self, plain_body: str, plain_signature: str, subject: str, order: Order, position: OrderPosition) -> str:
def render(self, plain_body: str, plain_signature: str, subject: str, order, position) -> str:
body_md = markdown_compile_email(plain_body)
htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL,
'body': body_md,
'subject': str(subject),
'color': '#8E44B3'
'color': '#8E44B3',
'rtl': get_language() in settings.LANGUAGES_RTL
}
if self.event:
htmlctx['event'] = self.event
@@ -127,12 +136,330 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
class ClassicMailRenderer(TemplateBasedMailRenderer):
verbose_name = _('pretix default')
verbose_name = _('Default')
identifier = 'classic'
thumbnail_filename = 'pretixbase/email/thumb.png'
template_name = 'pretixbase/email/plainwrapper.html'
class UnembellishedMailRenderer(TemplateBasedMailRenderer):
verbose_name = _('Simple with logo')
identifier = 'simple_logo'
thumbnail_filename = 'pretixbase/email/thumb_simple_logo.png'
template_name = 'pretixbase/email/simple_logo.html'
@receiver(register_html_mail_renderers, dispatch_uid="pretixbase_email_renderers")
def base_renderers(sender, **kwargs):
return [ClassicMailRenderer]
return [ClassicMailRenderer, UnembellishedMailRenderer]
class BaseMailTextPlaceholder:
"""
This is the base class for for all email text placeholders.
"""
@property
def required_context(self):
"""
This property should return a list of all attribute names that need to be
contained in the base context so that this placeholder is available. By default,
it returns a list containing the string "event".
"""
return ["event"]
@property
def identifier(self):
"""
This should return the identifier of this placeholder in the email.
"""
raise NotImplementedError()
def render(self, context):
"""
This method is called to generate the actual text that is being
used in the email. You will be passed a context dictionary with the
base context attributes specified in ``required_context``. You are
expected to return a plain-text string.
"""
raise NotImplementedError()
def render_sample(self, event):
"""
This method is called to generate a text to be used in email previews.
This may only depend on the event.
"""
raise NotImplementedError()
class SimpleFunctionalMailTextPlaceholder(BaseMailTextPlaceholder):
def __init__(self, identifier, args, func, sample):
self._identifier = identifier
self._args = args
self._func = func
self._sample = sample
@property
def identifier(self):
return self._identifier
@property
def required_context(self):
return self._args
def render(self, context):
return self._func(**{k: context[k] for k in self._args})
def render_sample(self, event):
if callable(self._sample):
return self._sample(event)
else:
return self._sample
def get_available_placeholders(event, base_parameters):
if 'order' in base_parameters:
base_parameters.append('invoice_address')
params = {}
for r, val in register_mail_placeholders.send(sender=event):
if not isinstance(val, (list, tuple)):
val = [val]
for v in val:
if all(rp in base_parameters for rp in v.required_context):
params[v.identifier] = v
return params
def get_email_context(**kwargs):
from pretix.base.models import InvoiceAddress
event = kwargs['event']
if 'order' in kwargs:
try:
kwargs['invoice_address'] = kwargs['order'].invoice_address
except InvoiceAddress.DoesNotExist:
kwargs['invoice_address'] = InvoiceAddress()
ctx = {}
for r, val in register_mail_placeholders.send(sender=event):
if not isinstance(val, (list, tuple)):
val = [val]
for v in val:
if all(rp in kwargs for rp in v.required_context):
ctx[v.identifier] = v.render(kwargs)
return ctx
def _placeholder_payment(order, payment):
if not payment:
return None
if 'payment' in inspect.signature(payment.payment_provider.order_pending_mail_render).parameters:
return str(payment.payment_provider.order_pending_mail_render(order, payment))
else:
return str(payment.payment_provider.order_pending_mail_render(order))
@receiver(register_mail_placeholders, dispatch_uid="pretixbase_register_mail_placeholders")
def base_placeholders(sender, **kwargs):
from pretix.base.models import InvoiceAddress
from pretix.multidomain.urlreverse import build_absolute_uri
ph = [
SimpleFunctionalMailTextPlaceholder(
'event', ['event'], lambda event: event.name, lambda event: event.name
),
SimpleFunctionalMailTextPlaceholder(
'event', ['event_or_subevent'], lambda event_or_subevent: event_or_subevent.name,
lambda event_or_subevent: event_or_subevent.name
),
SimpleFunctionalMailTextPlaceholder(
'event_slug', ['event'], lambda event: event.slug, lambda event: event.slug
),
SimpleFunctionalMailTextPlaceholder(
'code', ['order'], lambda order: order.code, 'F8VVL'
),
SimpleFunctionalMailTextPlaceholder(
'total', ['order'], lambda order: LazyNumber(order.total), lambda event: LazyNumber(Decimal('42.23'))
),
SimpleFunctionalMailTextPlaceholder(
'currency', ['event'], lambda event: event.currency, lambda event: event.currency
),
SimpleFunctionalMailTextPlaceholder(
'refund_amount', ['event_or_subevent', 'refund_amount'],
lambda event_or_subevent, refund_amount: LazyCurrencyNumber(refund_amount, event_or_subevent.currency),
lambda event_or_subevent: LazyCurrencyNumber(Decimal('42.23'), event_or_subevent.currency)
),
SimpleFunctionalMailTextPlaceholder(
'total_with_currency', ['event', 'order'], lambda event, order: LazyCurrencyNumber(order.total,
event.currency),
lambda event: LazyCurrencyNumber(Decimal('42.23'), event.currency)
),
SimpleFunctionalMailTextPlaceholder(
'expire_date', ['event', 'order'], lambda event, order: LazyDate(order.expires.astimezone(event.timezone)),
lambda event: LazyDate(now() + timedelta(days=15))
# TODO: This used to be "date" in some placeholders, add a migration!
),
SimpleFunctionalMailTextPlaceholder(
'url', ['order', 'event'], lambda order, event: build_absolute_uri(
event,
'presale:event.order.open', kwargs={
'order': order.code,
'secret': order.secret,
'hash': order.email_confirm_hash()
}
), lambda event: build_absolute_uri(
event,
'presale:event.order.open', kwargs={
'order': 'F8VVL',
'secret': '6zzjnumtsx136ddy',
'hash': '98kusd8ofsj8dnkd'
}
),
),
SimpleFunctionalMailTextPlaceholder(
'url', ['event', 'position'], lambda event, position: build_absolute_uri(
event,
'presale:event.order.position',
kwargs={
'order': position.order.code,
'secret': position.web_secret,
'position': position.positionid
}
),
lambda event: build_absolute_uri(
event,
'presale:event.order.position', kwargs={
'order': 'F8VVL',
'secret': '6zzjnumtsx136ddy',
'position': '123'
}
),
),
SimpleFunctionalMailTextPlaceholder(
'url', ['waiting_list_entry', 'event'],
lambda waiting_list_entry, event: build_absolute_uri(
event, 'presale:event.redeem'
) + '?voucher=' + waiting_list_entry.voucher.code,
lambda event: build_absolute_uri(
event,
'presale:event.redeem',
) + '?voucher=68CYU2H6ZTP3WLK5',
),
SimpleFunctionalMailTextPlaceholder(
'invoice_name', ['invoice_address'], lambda invoice_address: invoice_address.name or '',
_('John Doe')
),
SimpleFunctionalMailTextPlaceholder(
'invoice_company', ['invoice_address'], lambda invoice_address: invoice_address.company or '',
_('Sample Corporation')
),
SimpleFunctionalMailTextPlaceholder(
'orders', ['event', 'orders'], lambda event, orders: '\n' + '\n\n'.join(
'* {} - {}'.format(
order.full_code,
build_absolute_uri(event, 'presale:event.order', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
'order': order.code,
'secret': order.secret
}),
)
for order in orders
), lambda event: '\n' + '\n\n'.join(
'* {} - {}'.format(
'{}-{}'.format(event.slug.upper(), order['code']),
build_absolute_uri(event, 'presale:event.order', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
'order': order['code'],
'secret': order['secret']
}),
)
for order in [
{'code': 'F8VVL', 'secret': '6zzjnumtsx136ddy'},
{'code': 'HIDHK', 'secret': '98kusd8ofsj8dnkd'},
{'code': 'OPKSB', 'secret': '09pjdksflosk3njd'}
]
),
),
SimpleFunctionalMailTextPlaceholder(
'hours', ['event', 'waiting_list_entry'], lambda event, waiting_list_entry:
event.settings.waiting_list_hours,
lambda event: event.settings.waiting_list_hours
),
SimpleFunctionalMailTextPlaceholder(
'product', ['waiting_list_entry'], lambda waiting_list_entry: waiting_list_entry.item.name,
_('Sample Admission Ticket')
),
SimpleFunctionalMailTextPlaceholder(
'code', ['waiting_list_entry'], lambda waiting_list_entry: waiting_list_entry.voucher.code,
'68CYU2H6ZTP3WLK5'
),
SimpleFunctionalMailTextPlaceholder(
'voucher_list', ['voucher_list'], lambda voucher_list: '\n'.join(voucher_list),
' 68CYU2H6ZTP3WLK5\n 7MB94KKPVEPSMVF2'
),
SimpleFunctionalMailTextPlaceholder(
'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
}), lambda event: build_absolute_uri(event, 'presale:event.index', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
})
),
SimpleFunctionalMailTextPlaceholder(
'name', ['name'], lambda name: name,
_('John Doe')
),
SimpleFunctionalMailTextPlaceholder(
'comment', ['comment'], lambda comment: comment,
_('An individual text with a reason can be inserted here.'),
),
SimpleFunctionalMailTextPlaceholder(
'payment_info', ['order', 'payment'], _placeholder_payment,
_('The amount has been charged to your card.'),
),
SimpleFunctionalMailTextPlaceholder(
'payment_info', ['payment_info'], lambda payment_info: payment_info,
_('Please transfer money to this bank account: 9999-9999-9999-9999'),
),
SimpleFunctionalMailTextPlaceholder(
'attendee_name', ['position'], lambda position: position.attendee_name,
_('John Doe'),
),
SimpleFunctionalMailTextPlaceholder(
'name', ['position_or_address'],
lambda position_or_address: (
position_or_address.name
if isinstance(position_or_address, InvoiceAddress)
else position_or_address.attendee_name
),
_('John Doe'),
),
]
name_scheme = PERSON_NAME_SCHEMES[sender.settings.name_scheme]
for f, l, w in name_scheme['fields']:
if f == 'full_name':
continue
ph.append(SimpleFunctionalMailTextPlaceholder(
'attendee_name_%s' % f, ['position'], lambda position, f=f: position.attendee_name_parts.get(f, ''),
name_scheme['sample'][f]
))
ph.append(SimpleFunctionalMailTextPlaceholder(
'name_%s' % f, ['position_or_address'],
lambda position_or_address, f=f: (
position_or_address.name_parts.get(f, '')
if isinstance(position_or_address, InvoiceAddress)
else position_or_address.attendee_name_parts.get(f, '')
),
name_scheme['sample'][f]
))
for k, v in sender.meta_data.items():
ph.append(SimpleFunctionalMailTextPlaceholder(
'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k],
v
))
return ph

View File

@@ -1,10 +1,12 @@
import io
import tempfile
from collections import OrderedDict
from decimal import Decimal
from typing import Tuple
from defusedcsv import csv
from django import forms
from django.utils.formats import localize
from django.utils.translation import ugettext, ugettext_lazy as _
from openpyxl import Workbook
from openpyxl.cell.cell import KNOWN_TYPES
@@ -111,24 +113,32 @@ class ListExporter(BaseExporter):
raise NotImplementedError() # noqa
def get_filename(self):
return 'export.csv'
return 'export'
def _render_csv(self, form_data, output_file=None, **kwargs):
if output_file:
writer = csv.writer(output_file, **kwargs)
for line in self.iterate_list(form_data):
line = [
localize(f) if isinstance(f, Decimal) else f
for f in line
]
writer.writerow(line)
return self.get_filename() + '.csv', 'text/csv', None
else:
output = io.StringIO()
writer = csv.writer(output, **kwargs)
for line in self.iterate_list(form_data):
line = [
localize(f) if isinstance(f, Decimal) else f
for f in line
]
writer.writerow(line)
return self.get_filename() + '.csv', 'text/csv', output.getvalue().encode("utf-8")
def _render_xlsx(self, form_data, output_file=None):
wb = Workbook()
ws = wb.get_active_sheet()
ws = wb.active
try:
ws.title = str(self.verbose_name)
except:
@@ -196,18 +206,26 @@ class MultiSheetListExporter(ListExporter):
if output_file:
writer = csv.writer(output_file, **kwargs)
for line in self.iterate_sheet(form_data, sheet):
line = [
localize(f) if isinstance(f, Decimal) else f
for f in line
]
writer.writerow(line)
return self.get_filename() + '.csv', 'text/csv', None
else:
output = io.StringIO()
writer = csv.writer(output, **kwargs)
for line in self.iterate_sheet(form_data, sheet):
line = [
localize(f) if isinstance(f, Decimal) else f
for f in line
]
writer.writerow(line)
return self.get_filename() + '.csv', 'text/csv', output.getvalue().encode("utf-8")
def _render_xlsx(self, form_data, output_file=None):
wb = Workbook()
ws = wb.get_active_sheet()
ws = wb.active
wb.remove(ws)
for s, l in self.sheets:
ws = wb.create_sheet(str(l))

View File

@@ -129,8 +129,11 @@ class DekodiNREIExporter(BaseExporter):
'DIDt': invoice.order.datetime.isoformat().replace('Z', '+00:00'),
'DT': '30' if invoice.is_cancellation else '10',
'EM': invoice.order.email,
'FamN': invoice.invoice_to_name.rsplit(' ', 1)[-1],
'FN': invoice.invoice_to_name.rsplit(' ', 1)[0] if ' ' in invoice.invoice_to_name else '',
'FamN': invoice.invoice_to_name.rsplit(' ', 1)[-1] if invoice.invoice_to_name else '',
'FN': (
invoice.invoice_to_name.rsplit(' ', 1)[0]
if invoice.invoice_to_name and ' ' in invoice.invoice_to_name else ''
),
'IDt': invoice.date.isoformat() + 'T08:00:00+01:00',
'INo': invoice.full_invoice_no,
'IsNet': invoice.reverse_charge,

View File

@@ -3,13 +3,15 @@ from decimal import Decimal
import pytz
from django import forms
from django.db.models import DateTimeField, F, Max, OuterRef, Subquery, Sum
from django.db.models import (
Count, DateTimeField, F, IntegerField, Max, OuterRef, Subquery, Sum,
)
from django.dispatch import receiver
from django.utils.formats import date_format, localize
from django.utils.formats import date_format
from django.utils.translation import pgettext, ugettext as _, ugettext_lazy
from pretix.base.models import (
InvoiceAddress, InvoiceLine, Order, OrderPosition,
InvoiceAddress, InvoiceLine, Order, OrderPosition, Question,
)
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
from pretix.base.settings import PERSON_NAME_SCHEMES
@@ -80,8 +82,12 @@ class OrderListExporter(MultiSheetListExporter):
'm'
).order_by()
s = OrderPosition.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(k=Count('id')).values('k')
qs = self.event.orders.annotate(
payment_date=Subquery(p_date, output_field=DateTimeField())
payment_date=Subquery(p_date, output_field=DateTimeField()),
pcnt=Subquery(s, output_field=IntegerField())
).select_related('invoice_address').prefetch_related('invoices')
if form_data['paid_only']:
qs = qs.filter(status=Order.STATUS_PAID)
@@ -96,7 +102,7 @@ class OrderListExporter(MultiSheetListExporter):
for k, label, w in name_scheme['fields']:
headers.append(label)
headers += [
_('Address'), _('ZIP code'), _('City'), _('Country'), _('VAT ID'),
_('Address'), _('ZIP code'), _('City'), _('Country'), pgettext('address', 'State'), _('VAT ID'),
_('Date of last payment'), _('Fees'), _('Order locale')
]
@@ -109,6 +115,9 @@ class OrderListExporter(MultiSheetListExporter):
headers.append(_('Invoice numbers'))
headers.append(_('Sales channel'))
headers.append(_('Requires special attention'))
headers.append(_('Comment'))
headers.append(_('Positions'))
yield headers
@@ -132,7 +141,7 @@ class OrderListExporter(MultiSheetListExporter):
for order in qs.order_by('datetime'):
row = [
order.code,
localize(order.total),
order.total,
order.get_status_display(),
order.email,
order.datetime.astimezone(tz).strftime('%Y-%m-%d'),
@@ -153,14 +162,15 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:
row += [''] * (7 + (len(name_scheme['fields']) if len(name_scheme['fields']) > 1 else 0))
row += [''] * (8 + (len(name_scheme['fields']) if len(name_scheme['fields']) > 1 else 0))
row += [
order.payment_date.astimezone(tz).strftime('%Y-%m-%d') if order.payment_date else '',
localize(full_fee_sum_cache.get(order.id) or Decimal('0.00')),
full_fee_sum_cache.get(order.id) or Decimal('0.00'),
order.locale,
]
@@ -170,14 +180,19 @@ class OrderListExporter(MultiSheetListExporter):
{'grosssum': Decimal('0.00'), 'taxsum': Decimal('0.00')})
row += [
localize(taxrate_values['grosssum'] + fee_taxrate_values['grosssum']),
localize(taxrate_values['grosssum'] - taxrate_values['taxsum']
+ fee_taxrate_values['grosssum'] - fee_taxrate_values['taxsum']),
localize(taxrate_values['taxsum'] + fee_taxrate_values['taxsum']),
taxrate_values['grosssum'] + fee_taxrate_values['grosssum'],
(
taxrate_values['grosssum'] - taxrate_values['taxsum'] +
fee_taxrate_values['grosssum'] - fee_taxrate_values['taxsum']
),
taxrate_values['taxsum'] + fee_taxrate_values['taxsum'],
]
row.append(', '.join([i.number for i in order.invoices.all()]))
row.append(order.sales_channel)
row.append(_('Yes') if order.checkin_attention else _('No'))
row.append(order.comment or "")
row.append(order.pcnt)
yield row
def iterate_fees(self, form_data: dict):
@@ -208,7 +223,7 @@ class OrderListExporter(MultiSheetListExporter):
for k, label, w in name_scheme['fields']:
headers.append(_('Invoice address name') + ': ' + str(label))
headers += [
_('Address'), _('ZIP code'), _('City'), _('Country'), _('VAT ID'),
_('Address'), _('ZIP code'), _('City'), _('Country'), pgettext('address', 'State'), _('VAT ID'),
]
yield headers
@@ -243,10 +258,11 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:
row += [''] * (7 + (len(name_scheme['fields']) if len(name_scheme['fields']) > 1 else 0))
row += [''] * (8 + (len(name_scheme['fields']) if len(name_scheme['fields']) > 1 else 0))
yield row
def iterate_positions(self, form_data: dict):
@@ -258,7 +274,7 @@ class OrderListExporter(MultiSheetListExporter):
'order', 'order__invoice_address', 'item', 'variation',
'voucher', 'tax_rule'
).prefetch_related(
'answers', 'answers__question'
'answers', 'answers__question', 'answers__options'
)
if form_data['paid_only']:
qs = qs.filter(order__status=Order.STATUS_PAID)
@@ -272,6 +288,8 @@ class OrderListExporter(MultiSheetListExporter):
]
if self.event.has_subevents:
headers.append(pgettext('subevent', 'Date'))
headers.append(_('Start date'))
headers.append(_('End date'))
headers += [
_('Product'),
_('Variation'),
@@ -291,8 +309,15 @@ class OrderListExporter(MultiSheetListExporter):
_('Pseudonymization ID'),
]
questions = list(self.event.questions.all())
options = {}
for q in questions:
headers.append(str(q.question))
if q.type == Question.TYPE_CHOICE_MULTIPLE:
options[q.pk] = []
for o in q.options.all():
headers.append(str(q.question) + ' ' + str(o.answer))
options[q.pk].append(o)
else:
headers.append(str(q.question))
headers += [
_('Company'),
_('Invoice address name'),
@@ -301,7 +326,7 @@ class OrderListExporter(MultiSheetListExporter):
for k, label, w in name_scheme['fields']:
headers.append(_('Invoice address name') + ': ' + str(label))
headers += [
_('Address'), _('ZIP code'), _('City'), _('Country'), _('VAT ID'),
_('Address'), _('ZIP code'), _('City'), _('Country'), pgettext('address', 'State'), _('VAT ID'),
]
headers.append(_('Sales channel'))
@@ -317,7 +342,12 @@ class OrderListExporter(MultiSheetListExporter):
order.datetime.astimezone(tz).strftime('%Y-%m-%d'),
]
if self.event.has_subevents:
row.append(op.subevent)
row.append(op.subevent.name)
row.append(op.subevent.date_from.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M:%S'))
if op.subevent.date_to:
row.append(op.subevent.date_to.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M:%S'))
else:
row.append('')
row += [
str(op.item),
str(op.variation) if op.variation else '',
@@ -339,9 +369,21 @@ class OrderListExporter(MultiSheetListExporter):
]
acache = {}
for a in op.answers.all():
acache[a.question_id] = str(a)
# We do not want to localize Date, Time and Datetime question answers, as those can lead
# to difficulties parsing the data (for example 2019-02-01 may become Février, 2019 01 in French).
if a.question.type == Question.TYPE_CHOICE_MULTIPLE:
acache[a.question_id] = set(o.pk for o in a.options.all())
elif a.question.type in Question.UNLOCALIZED_TYPES:
acache[a.question_id] = a.answer
else:
acache[a.question_id] = str(a)
for q in questions:
row.append(acache.get(q.pk, ''))
if q.type == Question.TYPE_CHOICE_MULTIPLE:
for o in options[q.pk]:
row.append(_('Yes') if o.pk in acache.get(q.pk, set()) else _('No'))
else:
row.append(acache.get(q.pk, ''))
try:
row += [
order.invoice_address.company,
@@ -358,10 +400,11 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:
row += [''] * (7 + (len(name_scheme['fields']) if len(name_scheme['fields']) > 1 else 0))
row += [''] * (8 + (len(name_scheme['fields']) if len(name_scheme['fields']) > 1 else 0))
row.append(order.sales_channel)
yield row
@@ -431,7 +474,7 @@ class PaymentListExporter(ListExporter):
d2,
obj.get_state_display(),
obj.state,
localize(obj.amount * (-1 if isinstance(obj, OrderRefund) else 1)),
obj.amount * (-1 if isinstance(obj, OrderRefund) else 1),
provider_names.get(obj.provider, obj.provider)
]
yield row
@@ -503,6 +546,7 @@ class InvoiceDataExporter(MultiSheetListExporter):
_('Invoice recipient:') + ' ' + _('ZIP code'),
_('Invoice recipient:') + ' ' + _('City'),
_('Invoice recipient:') + ' ' + _('Country'),
_('Invoice recipient:') + ' ' + pgettext('address', 'State'),
_('Invoice recipient:') + ' ' + _('VAT ID'),
_('Invoice recipient:') + ' ' + _('Beneficiary'),
_('Invoice recipient:') + ' ' + _('Internal reference'),
@@ -511,10 +555,11 @@ class InvoiceDataExporter(MultiSheetListExporter):
_('Foreign currency rate'),
_('Total value (with taxes)'),
_('Total value (without taxes)'),
_('Payment matching IDs'),
]
qs = self.event.invoices.order_by('full_invoice_no').select_related(
'order', 'refers'
).annotate(
).prefetch_related('order__payments').annotate(
total_gross=Subquery(
InvoiceLine.objects.filter(
invoice=OuterRef('pk')
@@ -531,6 +576,16 @@ class InvoiceDataExporter(MultiSheetListExporter):
)
)
for i in qs:
pmis = []
for p in i.order.payments.all():
if p.state in (OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_CREATED,
OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_REFUNDED):
pprov = p.payment_provider
if pprov:
mid = pprov.matching_id(p)
if mid:
pmis.append(mid)
pmi = '\n'.join(pmis)
yield [
i.full_invoice_no,
date_format(i.date, "SHORT_DATE_FORMAT"),
@@ -552,6 +607,7 @@ class InvoiceDataExporter(MultiSheetListExporter):
i.invoice_to_zipcode,
i.invoice_to_city,
i.invoice_to_country,
i.invoice_to_state,
i.invoice_to_vat_id,
i.invoice_to_beneficiary,
i.internal_reference,
@@ -560,6 +616,7 @@ class InvoiceDataExporter(MultiSheetListExporter):
i.foreign_currency_rate,
i.total_gross if i.total_gross else Decimal('0.00'),
Decimal(i.total_net if i.total_net else '0.00').quantize(Decimal('0.01')),
pmi
]
elif sheet == 'lines':
yield [
@@ -591,6 +648,7 @@ class InvoiceDataExporter(MultiSheetListExporter):
_('Invoice recipient:') + ' ' + _('ZIP code'),
_('Invoice recipient:') + ' ' + _('City'),
_('Invoice recipient:') + ' ' + _('Country'),
_('Invoice recipient:') + ' ' + pgettext('address', 'State'),
_('Invoice recipient:') + ' ' + _('VAT ID'),
_('Invoice recipient:') + ' ' + _('Beneficiary'),
_('Invoice recipient:') + ' ' + _('Internal reference'),
@@ -630,6 +688,7 @@ class InvoiceDataExporter(MultiSheetListExporter):
i.invoice_to_zipcode,
i.invoice_to_city,
i.invoice_to_country,
i.invoice_to_state,
i.invoice_to_vat_id,
i.invoice_to_beneficiary,
i.internal_reference,

View File

@@ -8,7 +8,6 @@ from django.utils.crypto import get_random_string
from formtools.wizard.views import SessionWizardView
from hierarkey.forms import HierarkeyForm
from pretix.base.models import Event
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
from .validators import PlaceholderValidator # NOQA
@@ -51,19 +50,33 @@ class I18nInlineFormSet(i18nfield.forms.I18nInlineFormSet):
class SettingsForm(i18nfield.forms.I18nFormMixin, HierarkeyForm):
auto_fields = []
def __init__(self, *args, **kwargs):
from pretix.base.settings import DEFAULTS
self.obj = kwargs.get('obj', None)
self.locales = self.obj.settings.get('locales') if self.obj else kwargs.pop('locales', None)
kwargs['attribute_name'] = 'settings'
kwargs['locales'] = self.locales
kwargs['initial'] = self.obj.settings.freeze()
super().__init__(*args, **kwargs)
for fname in self.auto_fields:
kwargs = DEFAULTS[fname].get('form_kwargs', {})
kwargs.setdefault('required', False)
field = DEFAULTS[fname]['form_class'](
**kwargs
)
if isinstance(field, i18nfield.forms.I18nFormField):
field.widget.enabled_locales = self.locales
self.fields[fname] = field
for k, f in self.fields.items():
if isinstance(f, (RelativeDateTimeField, RelativeDateField)):
f.set_event(self.obj)
def get_new_filename(self, name: str) -> str:
from pretix.base.models import Event
nonce = get_random_string(length=8)
if isinstance(self.obj, Event):
fname = '%s/%s/%s.%s.%s' % (

View File

@@ -1,6 +1,5 @@
from django import forms
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.password_validation import (
password_validators_help_texts, validate_password,
)
@@ -14,32 +13,34 @@ class LoginForm(forms.Form):
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
email = forms.EmailField(label=_("E-mail"), max_length=254, widget=forms.EmailInput(attrs={'autofocus': 'autofocus'}))
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
keep_logged_in = forms.BooleanField(label=_("Keep me logged in"), required=False)
error_messages = {
'invalid_login': _("Please enter a correct email address and password."),
'invalid_login': _("This combination of credentials is not known to our system."),
'inactive': _("This account is inactive.")
}
def __init__(self, request=None, *args, **kwargs):
def __init__(self, backend, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user_cache = None
self.backend = backend
super().__init__(*args, **kwargs)
if not settings.PRETIX_LONG_SESSIONS:
for k, f in backend.login_form_fields.items():
self.fields[k] = f
# Authentication backends which use urls cannot have long sessions.
if not settings.PRETIX_LONG_SESSIONS or backend.url:
del self.fields['keep_logged_in']
else:
self.fields.move_to_end('keep_logged_in')
def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if email and password:
self.user_cache = authenticate(request=self.request, email=email.lower(), password=password)
if all(k in self.cleaned_data for k, f in self.fields.items() if f.required):
self.user_cache = self.backend.form_authenticate(self.request, self.cleaned_data)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
@@ -181,3 +182,45 @@ class PasswordForgotForm(forms.Form):
def clean_email(self):
return self.cleaned_data['email']
class ReauthForm(forms.Form):
error_messages = {
'invalid_login': _("This combination of credentials is not known to our system."),
'inactive': _("This account is inactive.")
}
def __init__(self, backend, user, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user = user
self.backend = backend
self.backend.url = backend.authentication_url(self.request)
super().__init__(*args, **kwargs)
for k, f in backend.login_form_fields.items():
self.fields[k] = f
if 'email' in self.fields:
self.fields['email'].disabled = True
def clean(self):
self.cleaned_data['email'] = self.user.email
user_cache = self.backend.form_authenticate(self.request, self.cleaned_data)
if user_cache != self.user:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login'
)
else:
self.confirm_login_allowed(user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user: User):
if not user.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)

View File

@@ -5,9 +5,11 @@ from decimal import Decimal
from urllib.error import HTTPError
import dateutil.parser
import pycountry
import pytz
import vat_moss.errors
import vat_moss.id
from babel import localedata
from django import forms
from django.contrib import messages
from django.core.exceptions import ValidationError
@@ -15,17 +17,28 @@ from django.db.models import QuerySet
from django.forms import Select
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import get_language, ugettext_lazy as _
from django.utils.translation import (
get_language, pgettext_lazy, ugettext_lazy as _,
)
from django_countries import countries
from django_countries.fields import Country, CountryField
from phonenumber_field.formfields import PhoneNumberField
from phonenumber_field.phonenumber import PhoneNumber
from phonenumber_field.widgets import PhoneNumberPrefixWidget
from phonenumbers import NumberParseException
from phonenumbers.data import _COUNTRY_CODE_TO_REGION_CODE
from pretix.base.forms.widgets import (
BusinessBooleanRadio, DatePickerWidget, SplitDateTimePickerWidget,
TimePickerWidget, UploadedFileWidget,
)
from pretix.base.i18n import language
from pretix.base.models import InvoiceAddress, Question, QuestionOption
from pretix.base.models.tax import EU_COUNTRIES
from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS
from pretix.base.models.tax import EU_COUNTRIES, cc_to_vat_prefix
from pretix.base.settings import (
COUNTRIES_WITH_STATE_IN_ADDRESS, PERSON_NAME_SCHEMES,
PERSON_NAME_TITLE_GROUPS,
)
from pretix.base.templatetags.rich_text import rich_text
from pretix.control.forms import SplitDateTimeField
from pretix.helpers.escapejson import escapejson_attr
@@ -35,8 +48,19 @@ from pretix.presale.signals import question_form_fields
logger = logging.getLogger(__name__)
REQUIRED_NAME_PARTS = ['given_name', 'family_name', 'full_name']
class NamePartsWidget(forms.MultiWidget):
widget = forms.TextInput
autofill_map = {
'given_name': 'given-name',
'family_name': 'family-name',
'middle_name': 'additional-name',
'title': 'honorific-prefix',
'full_name': 'name',
'calling_name': 'nickname',
}
def __init__(self, scheme: dict, field: forms.Field, attrs=None, titles: list=None):
widgets = []
@@ -77,14 +101,21 @@ class NamePartsWidget(forms.MultiWidget):
except (IndexError, TypeError):
widget_value = None
if id_:
final_attrs = dict(
these_attrs = dict(
final_attrs,
id='%s_%s' % (id_, i),
title=self.scheme['fields'][i][1],
placeholder=self.scheme['fields'][i][1],
)
final_attrs['data-size'] = self.scheme['fields'][i][2]
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs, renderer=renderer))
if self.scheme['fields'][i][0] in REQUIRED_NAME_PARTS:
if self.field.required:
these_attrs['required'] = 'required'
these_attrs.pop('data-no-required-attr', None)
these_attrs['autocomplete'] = (self.attrs.get('autocomplete', '') + ' ' + self.autofill_map.get(self.scheme['fields'][i][0], 'off')).strip()
these_attrs['data-size'] = self.scheme['fields'][i][2]
else:
these_attrs = final_attrs
output.append(widget.render(name + '_%s' % i, widget_value, these_attrs, renderer=renderer))
return mark_safe(self.format_output(output))
def format_output(self, rendered_widgets) -> str:
@@ -144,13 +175,45 @@ class NamePartsFormField(forms.MultiValueField):
def clean(self, value) -> dict:
value = super().clean(value)
if self.one_required and (not value or not any(v for v in value)):
if self.one_required and (not value or not any(v for v in value.values())):
raise forms.ValidationError(self.error_messages['required'], code='required')
if self.one_required:
for k, v in value.items():
if k in REQUIRED_NAME_PARTS and not v:
raise forms.ValidationError(self.error_messages['required'], code='required')
if self.require_all_fields and not all(v for v in value):
raise forms.ValidationError(self.error_messages['incomplete'], code='required')
return value
class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
def render(self, name, value, attrs=None, renderer=None):
output = super().render(name, value, attrs, renderer)
return mark_safe(self.format_output(output))
def format_output(self, rendered_widgets) -> str:
return '<div class="nameparts-form-group">%s</div>' % ''.join(rendered_widgets)
def guess_country(event):
# Try to guess the initial country from either the country of the merchant
# or the locale. This will hopefully save at least some users some scrolling :)
locale = get_language()
country = event.settings.invoice_address_from_country
if not country:
valid_countries = countries.countries
if '-' in locale:
parts = locale.split('-')
if parts[1].upper() in valid_countries:
country = Country(parts[1].upper())
elif parts[0].upper() in valid_countries:
country = Country(parts[0].upper())
else:
if locale in valid_countries:
country = Country(locale.upper())
return country
class BaseQuestionsForm(forms.Form):
"""
This form class is responsible for asking order-related questions. This includes
@@ -188,7 +251,12 @@ class BaseQuestionsForm(forms.Form):
self.fields['attendee_email'] = forms.EmailField(
required=event.settings.attendee_emails_required,
label=_('Attendee email'),
initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email)
initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email),
widget=forms.EmailInput(
attrs={
'autocomplete': 'email'
}
)
)
for q in questions:
@@ -295,6 +363,32 @@ class BaseQuestionsForm(forms.Form):
initial=dateutil.parser.parse(initial.answer).astimezone(tz) if initial and initial.answer else None,
widget=SplitDateTimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')),
)
elif q.type == Question.TYPE_PHONENUMBER:
babel_locale = 'en'
# Babel, and therefore django-phonenumberfield, do not support our custom locales such das de_Informal
if localedata.exists(get_language()):
babel_locale = get_language()
elif localedata.exists(get_language()[:2]):
babel_locale = get_language()[:2]
with language(babel_locale):
default_country = guess_country(event)
default_prefix = None
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
if str(default_country) in values:
default_prefix = prefix
try:
initial = PhoneNumber().from_string(initial.answer) if initial else "+{}.".format(default_prefix)
except NumberParseException:
initial = None
field = PhoneNumberField(
label=label, required=required,
help_text=help_text,
# We now exploit an implementation detail in PhoneNumberPrefixWidget to allow us to pass just
# a country code but no number as an initial value. It's a bit hacky, but should be stable for
# the future.
initial=initial,
widget=WrappedPhoneNumberPrefixWidget()
)
field.question = q
if answers:
# Cache the answer object for later use
@@ -318,12 +412,18 @@ class BaseQuestionsForm(forms.Form):
self.fields[key] = value
value.initial = data.get('question_form_data', {}).get(key)
for k, v in self.fields.items():
if v.widget.attrs.get('autocomplete') or k == 'attendee_name_parts':
v.widget.attrs['autocomplete'] = 'section-{} '.format(self.prefix) + v.widget.attrs.get('autocomplete', '')
def clean(self):
d = super().clean()
question_cache = {f.question.pk: f.question for f in self.fields.values() if getattr(f, 'question', None)}
def question_is_visible(parentid, qvals):
if parentid not in question_cache:
return False
parentq = question_cache[parentid]
if parentq.dependency_question_id and not question_is_visible(parentq.dependency_question_id, parentq.dependency_values):
return False
@@ -356,13 +456,29 @@ class BaseInvoiceAddressForm(forms.ModelForm):
class Meta:
model = InvoiceAddress
fields = ('is_business', 'company', 'name_parts', 'street', 'zipcode', 'city', 'country', 'vat_id',
'internal_reference', 'beneficiary')
fields = ('is_business', 'company', 'name_parts', 'street', 'zipcode', 'city', 'country', 'state',
'vat_id', 'internal_reference', 'beneficiary', 'custom_field')
widgets = {
'is_business': BusinessBooleanRadio,
'street': forms.Textarea(attrs={'rows': 2, 'placeholder': _('Street and Number')}),
'street': forms.Textarea(attrs={
'rows': 2,
'placeholder': _('Street and Number'),
'autocomplete': 'street-address'
}),
'beneficiary': forms.Textarea(attrs={'rows': 3}),
'company': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
'country': forms.Select(attrs={
'autocomplete': 'country',
}),
'zipcode': forms.TextInput(attrs={
'autocomplete': 'postal-code',
}),
'city': forms.TextInput(attrs={
'autocomplete': 'address-level2',
}),
'company': forms.TextInput(attrs={
'data-display-dependency': '#id_is_business_1',
'autocomplete': 'organization',
}),
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
'internal_reference': forms.TextInput,
}
@@ -378,28 +494,39 @@ class BaseInvoiceAddressForm(forms.ModelForm):
kwargs.setdefault('initial', {})
if not kwargs.get('instance') or not kwargs['instance'].country:
# Try to guess the initial country from either the country of the merchant
# or the locale. This will hopefully save at least some users some scrolling :)
locale = get_language()
country = event.settings.invoice_address_from_country
if not country:
valid_countries = countries.countries
if '-' in locale:
parts = locale.split('-')
if parts[1].upper() in valid_countries:
country = Country(parts[1].upper())
elif parts[0].upper() in valid_countries:
country = Country(parts[0].upper())
else:
if locale in valid_countries:
country = Country(locale.upper())
kwargs['initial']['country'] = country
kwargs['initial']['country'] = guess_country(self.event)
super().__init__(*args, **kwargs)
if not event.settings.invoice_address_vatid:
del self.fields['vat_id']
c = [('', pgettext_lazy('address', 'Select state'))]
fprefix = self.prefix + '-' if self.prefix else ''
cc = None
if fprefix + 'country' in self.data:
cc = str(self.data[fprefix + 'country'])
elif 'country' in self.initial:
cc = str(self.initial['country'])
elif self.instance and self.instance.country:
cc = str(self.instance.country)
if cc and cc in COUNTRIES_WITH_STATE_IN_ADDRESS:
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
c += sorted([(s.code[3:], s.name) for s in statelist], key=lambda s: s[1])
elif fprefix + 'state' in self.data:
self.data = self.data.copy()
del self.data[fprefix + 'state']
self.fields['state'] = forms.ChoiceField(
label=pgettext_lazy('address', 'State'),
required=False,
choices=c,
widget=forms.Select(attrs={
'autocomplete': 'address-level1',
}),
)
self.fields['state'].widget.is_required = True
if not event.settings.invoice_address_required or self.all_optional:
for k, f in self.fields.items():
f.required = False
@@ -426,13 +553,23 @@ class BaseInvoiceAddressForm(forms.ModelForm):
initial=(self.instance.name_parts if self.instance else self.instance.name_parts),
)
if event.settings.invoice_address_required and not event.settings.invoice_address_company_required and not self.all_optional:
self.fields['name_parts'].widget.attrs['data-required-if'] = '#id_is_business_0'
if not event.settings.invoice_name_required:
self.fields['name_parts'].widget.attrs['data-required-if'] = '#id_is_business_0'
self.fields['name_parts'].widget.attrs['data-no-required-attr'] = '1'
self.fields['company'].widget.attrs['data-required-if'] = '#id_is_business_1'
if not event.settings.invoice_address_beneficiary:
del self.fields['beneficiary']
if event.settings.invoice_address_custom_field:
self.fields['custom_field'].label = event.settings.invoice_address_custom_field
else:
del self.fields['custom_field']
for k, v in self.fields.items():
if v.widget.attrs.get('autocomplete') or k == 'name_parts':
v.widget.attrs['autocomplete'] = 'section-invoice billing ' + v.widget.attrs.get('autocomplete', '')
def clean(self):
data = self.cleaned_data
if not data.get('is_business'):
@@ -446,6 +583,10 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if 'vat_id' in self.changed_data or not data.get('vat_id'):
self.instance.vat_id_validated = False
if data.get('city') and data.get('country') and str(data['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
if not data.get('state'):
self.add_error('state', _('This field is required.'))
self.instance.name_parts = data.get('name_parts')
if all(
@@ -457,7 +598,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if self.validate_vat_id and self.instance.vat_id_validated and 'vat_id' not in self.changed_data:
pass
elif self.validate_vat_id and data.get('is_business') and data.get('country') in EU_COUNTRIES and data.get('vat_id'):
if data.get('vat_id')[:2] != str(data.get('country')):
if data.get('vat_id')[:2] != cc_to_vat_prefix(str(data.get('country'))):
raise ValidationError(_('Your VAT ID does not match the selected country.'))
try:
result = vat_moss.id.validate(data.get('vat_id'))
@@ -488,9 +629,8 @@ class BaseInvoiceAddressForm(forms.ModelForm):
class BaseInvoiceNameForm(BaseInvoiceAddressForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for f in list(self.fields.keys()):
if f != 'name':
if f != 'name_parts':
del self.fields[f]

View File

@@ -56,6 +56,11 @@ class UserSettingsForm(forms.ModelForm):
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['email'].required = True
if self.user.auth_backend != 'native':
del self.fields['old_pw']
del self.fields['new_pw']
del self.fields['new_pw_repeat']
self.fields['email'].disabled = True
def clean_old_pw(self):
old_pw = self.cleaned_data.get('old_pw')
@@ -115,5 +120,5 @@ class User2FADeviceAddForm(forms.Form):
name = forms.CharField(label=_('Device name'), max_length=64)
devicetype = forms.ChoiceField(label=_('Device type'), widget=forms.RadioSelect, choices=(
('totp', _('Smartphone with the Authenticator application')),
('u2f', _('U2F-compatible hardware token (e.g. Yubikey)')),
('webauthn', _('WebAuthn-compatible hardware token (e.g. Yubikey)')),
))

View File

@@ -26,7 +26,7 @@ class PlaceholderValidator(BaseValidator):
if value.count('{') != value.count('}'):
raise ValidationError(
_('Invalid placeholder syntax: You used a different number of "{" than of "}".'),
code='invalid',
code='invalid_placeholder_syntax',
)
data_placeholders = list(re.findall(r'({[^}]*})', value, re.X))
@@ -37,7 +37,7 @@ class PlaceholderValidator(BaseValidator):
if invalid_placeholders:
raise ValidationError(
_('Invalid placeholder(s): %(value)s'),
code='invalid',
code='invalid_placeholders',
params={'value': ", ".join(invalid_placeholders,)})
def clean(self, x):

View File

@@ -6,9 +6,6 @@ from django.utils.functional import lazy
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import OrderPosition
from pretix.multidomain.urlreverse import eventreverse
class DatePickerWidget(forms.DateInput):
def __init__(self, attrs=None, date_format=None):
@@ -46,6 +43,14 @@ class TimePickerWidget(forms.TimeInput):
class UploadedFileWidget(forms.ClearableFileInput):
def __init__(self, *args, **kwargs):
# Browsers can't recognize that the server already has a file uploaded
# Don't mark this input as being required if we already have an answer
# (this needs to be done via the attrs, otherwise we wouldn't get the "required" star on the field label)
attrs = kwargs.get('attrs', {})
if kwargs.get('required') and kwargs.get('initial'):
attrs.update({'required': None})
kwargs.update({'attrs': attrs})
self.position = kwargs.pop('position')
self.event = kwargs.pop('event')
self.answer = kwargs.pop('answer')
@@ -63,6 +68,9 @@ class UploadedFileWidget(forms.ClearableFileInput):
@property
def url(self):
from pretix.base.models import OrderPosition
from pretix.multidomain.urlreverse import eventreverse
if isinstance(self.position, OrderPosition):
return eventreverse(self.event, 'presale:event.order.download.answer', kwargs={
'order': self.position.order.code,

View File

@@ -28,7 +28,7 @@ from reportlab.platypus import (
)
from pretix.base.decimal import round_decimal
from pretix.base.models import Event, Invoice
from pretix.base.models import Event, Invoice, Order
from pretix.base.signals import register_invoice_renderers
from pretix.base.templatetags.money import money_filter
@@ -457,18 +457,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
id='normal')
]
def _get_story(self, doc):
has_taxes = any(il.tax_value for il in self.invoice.lines.all())
story = [
NextPageTemplate('FirstPage'),
Paragraph(pgettext('invoice', 'Invoice')
if not self.invoice.is_cancellation
else pgettext('invoice', 'Cancellation'),
self.stylesheet['Heading1']),
Spacer(1, 5 * mm),
NextPageTemplate('OtherPages'),
]
def _get_intro(self):
story = []
if self.invoice.custom_field:
story.append(Paragraph(
'{}: {}'.format(self.invoice.event.settings.invoice_address_custom_field, self.invoice.custom_field),
self.stylesheet['Normal']
))
if self.invoice.internal_reference:
story.append(Paragraph(
@@ -478,7 +473,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
if self.invoice.invoice_to_vat_id:
story.append(Paragraph(
pgettext('invoice', 'Customer VAT ID') + ':<br />' +
pgettext('invoice', 'Customer VAT ID') + ': ' +
bleach.clean(self.invoice.invoice_to_vat_id, tags=[]).replace("\n", "<br />\n"),
self.stylesheet['Normal']
))
@@ -494,6 +489,25 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
story.append(Paragraph(self.invoice.introductory_text, self.stylesheet['Normal']))
story.append(Spacer(1, 10 * mm))
return story
def _get_story(self, doc):
has_taxes = any(il.tax_value for il in self.invoice.lines.all())
story = [
NextPageTemplate('FirstPage'),
Paragraph(
(
pgettext('invoice', 'Tax Invoice') if str(self.invoice.invoice_from_country) == 'AU'
else pgettext('invoice', 'Invoice')
) if not self.invoice.is_cancellation else pgettext('invoice', 'Cancellation'),
self.stylesheet['Heading1']
),
Spacer(1, 5 * mm),
NextPageTemplate('OtherPages'),
]
story += self._get_intro()
taxvalue_map = defaultdict(Decimal)
grossvalue_map = defaultdict(Decimal)
@@ -551,6 +565,20 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
])
colwidths = [a * doc.width for a in (.65, .05, .30)]
if self.invoice.event.settings.invoice_show_payments and not self.invoice.is_cancellation and \
self.invoice.order.status == Order.STATUS_PENDING:
pending_sum = self.invoice.order.pending_sum
if pending_sum != total:
tdata.append([pgettext('invoice', 'Received payments')] + (['', '', ''] if has_taxes else ['']) + [
money_filter(pending_sum - total, self.invoice.event.currency)
])
tdata.append([pgettext('invoice', 'Outstanding payments')] + (['', '', ''] if has_taxes else ['']) + [
money_filter(pending_sum, self.invoice.event.currency)
])
tstyledata += [
('FONTNAME', (0, len(tdata) - 3), (-1, len(tdata) - 3), self.font_bold),
]
table = Table(tdata, colWidths=colwidths, repeatRows=1)
table.setStyle(TableStyle(tstyledata))
story.append(table)

View File

@@ -23,7 +23,7 @@ modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("default_manager_name")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("permissions")
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("default_permissions")
IGNORED_ATTRS = [
# (field type, attribute name, blacklist of field sub-types)
# (field type, attribute name, banlist of field sub-types)
(models.Field, 'verbose_name', []),
(models.Field, 'help_text', []),
(models.Field, 'validators', []),
@@ -38,8 +38,8 @@ original_deconstruct = models.Field.deconstruct
def new_deconstruct(self):
name, path, args, kwargs = original_deconstruct(self)
for ftype, attr, blacklist in IGNORED_ATTRS:
if isinstance(self, ftype) and not any(isinstance(self, ft) for ft in blacklist):
for ftype, attr, banlist in IGNORED_ATTRS:
if isinstance(self, ftype) and not any(isinstance(self, ft) for ft in banlist):
kwargs.pop(attr, None)
return name, path, args, kwargs

View File

@@ -11,13 +11,13 @@ from django.core.management.commands.migrate import Command as Parent
class OutputFilter(OutputWrapper):
blacklist = (
banlist = (
"Your models have changes that are not yet reflected",
"Run 'manage.py makemigrations' to make new "
)
def write(self, msg, style_func=None, ending=None):
if any(b in msg for b in self.blacklist):
if any(b in msg for b in self.banlist):
return
super().write(msg, style_func, ending)

View File

@@ -13,11 +13,17 @@ class Command(BaseCommand):
return parser
def handle(self, *args, **options):
try:
from django_extensions.management.commands import shell_plus # noqa
cmd = 'shell_plus'
except ImportError:
cmd = 'shell'
parser = self.create_parser(sys.argv[0], sys.argv[1])
flags = parser.parse_known_args(sys.argv[2:])[1]
if "--override" in flags:
with scopes_disabled():
return call_command("shell_plus", *args, **options)
return call_command(cmd, *args, **options)
lookups = {}
for flag in flags:
@@ -36,4 +42,4 @@ class Command(BaseCommand):
for app_name, app_value in lookups.items()
}
with scope(**scope_options):
return call_command("shell_plus", *args, **options)
return call_command(cmd, *args, **options)

View File

@@ -14,6 +14,7 @@ from django.utils.translation.trans_real import (
parse_accept_lang_header,
)
from pretix.base.settings import GlobalSettingsObject
from pretix.multidomain.urlreverse import get_domain
_supported = None
@@ -187,6 +188,11 @@ class SecurityMiddleware(MiddlewareMixin):
# https://github.com/pretix/pretix/issues/765
resp['P3P'] = 'CP=\"ALL DSP COR CUR ADM TAI OUR IND COM NAV INT\"'
img_src = []
gs = GlobalSettingsObject()
if gs.settings.leaflet_tiles:
img_src.append(gs.settings.leaflet_tiles[:gs.settings.leaflet_tiles.index("/", 10)].replace("{s}", "*"))
h = {
'default-src': ["{static}"],
'script-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
@@ -196,7 +202,7 @@ class SecurityMiddleware(MiddlewareMixin):
'child-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
'style-src': ["{static}", "{media}"],
'connect-src': ["{dynamic}", "{media}", "https://checkout.stripe.com"],
'img-src': ["{static}", "{media}", "data:", "https://*.stripe.com"],
'img-src': ["{static}", "{media}", "data:", "https://*.stripe.com"] + img_src,
'font-src': ["{static}"],
'media-src': ["{static}", "data:"],
# form-action is not only used to match on form actions, but also on URLs

View File

@@ -182,12 +182,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBlacklistValidator()], verbose_name='Slug'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBanlistValidator()], verbose_name='Slug'),
),
migrations.AlterField(
model_name='organizer',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Slug'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBanlistValidator()], verbose_name='Slug'),
),
migrations.AlterField(
model_name='voucher',

View File

@@ -23,12 +23,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBlacklistValidator()], verbose_name='Slug'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBanlistValidator()], verbose_name='Slug'),
),
migrations.AlterField(
model_name='organizer',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Slug'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBanlistValidator()], verbose_name='Slug'),
),
migrations.AlterField(
model_name='voucher',

View File

@@ -124,7 +124,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This will be used in order codes, invoice numbers, links and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBlacklistValidator()], verbose_name='Short form'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This will be used in order codes, invoice numbers, links and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.AddField(
model_name='requiredaction',
@@ -179,7 +179,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='organizer',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Short form'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.RunPython(
code=merge_names,

View File

@@ -346,7 +346,7 @@ class Migration(migrations.Migration):
help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. We recommend some kind of abbreviation or a date with less than 10 characters that can be easily remembered, but you can also choose to use a random value. This will be used in URLs, order codes, invoice numbers, and bank transfer references.',
validators=[django.core.validators.RegexValidator(
message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'),
pretix.base.validators.EventSlugBlacklistValidator()], verbose_name='Short form'),
pretix.base.validators.EventSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.AlterField(
model_name='invoice',

View File

@@ -33,7 +33,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This will be used in order codes, invoice numbers, links and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBlacklistValidator()], verbose_name='Short form'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This will be used in order codes, invoice numbers, links and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.AddField(
model_name='requiredaction',

View File

@@ -36,7 +36,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='organizer',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Short form'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.RunPython(merge_names, migrations.RunPython.noop)
]

View File

@@ -38,7 +38,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. We recommend some kind of abbreviation or a date with less than 10 characters that can be easily remembered, but you can also choose to use a random value. This will be used in URLs, order codes, invoice numbers, and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBlacklistValidator()], verbose_name='Short form'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. We recommend some kind of abbreviation or a date with less than 10 characters that can be easily remembered, but you can also choose to use a random value. This will be used in URLs, order codes, invoice numbers, and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.AlterField(
model_name='invoice',

View File

@@ -44,7 +44,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters, numbers, dots, and dashes, and must be unique among your events. We recommend some kind of abbreviation or a date with less than 10 characters that can be easily remembered, but you can also choose to use a random value. This will be used in URLs, order codes, invoice numbers, and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBlacklistValidator()], verbose_name='Short form'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters, numbers, dots, and dashes, and must be unique among your events. We recommend some kind of abbreviation or a date with less than 10 characters that can be easily remembered, but you can also choose to use a random value. This will be used in URLs, order codes, invoice numbers, and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.EventSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.AlterField(
model_name='eventmetaproperty',
@@ -54,7 +54,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='organizer',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters, numbers, dots, and dashes. Every slug can only be used once. This is being used in URLs to refer to your organizer accounts and your events.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Short form'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters, numbers, dots, and dashes. Every slug can only be used once. This is being used in URLs to refer to your organizer accounts and your events.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.CreateModel(
name='CheckinList',

View File

@@ -105,7 +105,7 @@ class Migration(migrations.Migration):
'references.',
validators=[django.core.validators.RegexValidator(
message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'),
pretix.base.validators.EventSlugBlacklistValidator()], verbose_name='Short form'),
pretix.base.validators.EventSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.AlterField(
model_name='eventmetaproperty',
@@ -125,7 +125,7 @@ class Migration(migrations.Migration):
' events.',
validators=[django.core.validators.RegexValidator(
message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'),
pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Short form'),
pretix.base.validators.OrganizerSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.CreateModel(
name='CheckinList',

View File

@@ -27,7 +27,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='organizer',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters, numbers, dots, and dashes. Every slug can only be used once. This is being used in URLs to refer to your organizer accounts and your events.', unique=True, validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Short form'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters, numbers, dots, and dashes. Every slug can only be used once. This is being used in URLs to refer to your organizer accounts and your events.', unique=True, validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.AlterField(
model_name='staffsession',

View File

@@ -29,7 +29,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='organizer',
name='slug',
field=models.SlugField(help_text='Should be short, only contain lowercase letters, numbers, dots, and dashes. Every slug can only be used once. This is being used in URLs to refer to your organizer accounts and your events.', unique=True, validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Short form'),
field=models.SlugField(help_text='Should be short, only contain lowercase letters, numbers, dots, and dashes. Every slug can only be used once. This is being used in URLs to refer to your organizer accounts and your events.', unique=True, validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBanlistValidator()], verbose_name='Short form'),
),
migrations.AlterField(
model_name='staffsession',

View File

@@ -0,0 +1,27 @@
# Generated by Django 2.2.1 on 2019-08-08 12:53
import django.db.models.deletion
from django.db import migrations, models
import pretix.base.models.fields
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0131_auto_20190729_1422'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='invoice_to_state',
field=models.CharField(max_length=190, null=True),
),
migrations.AddField(
model_name='invoiceaddress',
name='state',
field=models.CharField(default='', max_length=255),
preserve_default=False,
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 2.2.4 on 2019-08-30 15:13
import django.db.models.deletion
from django.db import migrations, models
import pretix.base.models.fields
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0132_auto_20190808_1253'),
]
operations = [
migrations.AddField(
model_name='question',
name='print_on_invoice',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,35 @@
# Generated by Django 2.2.4 on 2019-09-09 10:42
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import pretix.base.models.fields
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0133_auto_20190830_1513'),
]
operations = [
migrations.CreateModel(
name='WebAuthnDevice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('confirmed', models.BooleanField(default=True)),
('credential_id', models.CharField(max_length=255, null=True)),
('rp_id', models.CharField(max_length=255, null=True)),
('icon_url', models.CharField(max_length=255, null=True)),
('ukey', models.TextField(null=True)),
('pub_key', models.TextField(null=True)),
('sign_count', models.IntegerField(default=0)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,27 @@
# Generated by Django 2.2.4 on 2019-10-07 08:03
from django.core.cache import cache
from django.db import migrations
def mail_migrator(app, schema_editor):
Event_SettingsStore = app.get_model('pretixbase', 'Event_SettingsStore')
for ss in Event_SettingsStore.objects.filter(
key__in=['mail_text_order_approved', 'mail_text_order_placed', 'mail_text_order_placed_require_approval']
):
chgd = ss.value.replace("{date}", "{expire_date}")
if chgd != ss.value:
ss.value = chgd
ss.save()
cache.delete('hierarkey_{}_{}'.format('event', ss.object_id))
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0134_auto_20190909_1042'),
]
operations = [
migrations.RunPython(mail_migrator, migrations.RunPython.noop)
]

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