Compare commits

...

1242 Commits

Author SHA1 Message Date
Raphael Michel
e89aaf4059 Bump to 2.7.0 2019-05-09 16:20:40 +02:00
Raphael Michel
db270b3bf2 Update from Weblate (#1276)
Update from Weblate
2019-05-09 14:48:51 +02:00
Raphael Michel
d8b78c3a7a Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3067 of 3067 strings)

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

powered by weblate
2019-05-09 12:48:23 +00:00
Raphael Michel
67c448a29e Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3067 of 3067 strings)

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

powered by weblate
2019-05-09 12:48:22 +00:00
Raphael Michel
5b7906f2a1 Translated on translate.pretix.eu (German (informal))
Currently translated at 99.8% (3060 of 3067 strings)

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

powered by weblate
2019-05-09 12:45:12 +00:00
Raphael Michel
0612d42607 Translated on translate.pretix.eu (German)
Currently translated at 99.8% (3060 of 3067 strings)

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

powered by weblate
2019-05-09 12:45:12 +00:00
Raphael Michel
83f866034a Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-05-09 14:45:03 +02:00
Raphael Michel
b1fa214869 Clarify a description 2019-05-09 14:44:06 +02:00
Raphael Michel
aa53b5235a Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-05-09 14:39:58 +02:00
Raphael Michel
61a13256a0 Update from Weblate (#1269)
Update from Weblate
2019-05-09 14:39:16 +02:00
Raphael Michel
64e2336014 Added translation on translate.pretix.eu (Slovenian) 2019-05-09 11:47:16 +00:00
Bostjan Marusic
3411abd1e6 Added translation on translate.pretix.eu (Slovenian) 2019-05-09 11:47:16 +00:00
Allan Nordhøy
2a34e54fae Added translation on translate.pretix.eu (Norwegian Bokmål) 2019-05-09 11:47:16 +00:00
Alvaro Enrique Ruano
9863dc35d6 Translated on translate.pretix.eu (Spanish)
Currently translated at 99.0% (3031 of 3063 strings)

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

powered by weblate
2019-05-09 11:47:16 +00:00
Raphael Michel
690883a198 Fix #480 -- Allow plugins to specify a minimum pretix version 2019-05-09 13:46:54 +02:00
Raphael Michel
d8ded08a46 Checkin list PDF: Remove date from headline, it's in the page header now 2019-05-09 10:52:42 +02:00
Raphael Michel
4aab5daa57 Fixing import order 2019-05-09 10:22:09 +02:00
Raphael Michel
e87628c902 Ensure that we document all signals 2019-05-09 10:02:12 +02:00
Raphael Michel
3c7bf46268 Resolve requests/urllib versions 2019-05-09 10:02:12 +02:00
Raphael Michel
a1dacb1897 Remove outdated reference from docs 2019-05-09 10:02:12 +02:00
Raphael Michel
08d5626704 Simplify the future of our migration history 2019-05-09 10:02:12 +02:00
Raphael Michel
c8a1481f93 Fix #1154 -- Add country-typed questions 2019-05-09 10:02:12 +02:00
Raphael Michel
e7c4121745 Add hidden questions 2019-05-09 10:02:12 +02:00
Sohalt
35ddd8dd28 Typo (#1274) 2019-05-08 13:13:27 +02:00
Raphael Michel
e2ec6eb156 Dekodi: Change semantics of signs 2019-05-08 11:47:57 +02:00
Raphael Michel
42edc4c3aa money_filter: Ignore case of currency 2019-05-07 16:20:24 +02:00
Raphael Michel
1cb2f99f3a Tax calculation of "original prices" 2019-05-06 12:33:21 +02:00
Raphael Michel
d4146e08b1 Fix widget tests 2019-05-06 12:06:45 +02:00
Raphael Michel
79ae9b6501 Revert "updatestyles: Fix a TypeError"
This reverts commit 53053f19e4.
2019-05-06 11:43:40 +02:00
Raphael Michel
c23f71a19c Widget: Add voucher explanation text 2019-05-06 11:33:48 +02:00
Raphael Michel
53053f19e4 updatestyles: Fix a TypeError 2019-05-06 11:33:36 +02:00
Raphael Michel
a42b2d76f6 Add scalability to docs word list 2019-05-06 08:50:56 +02:00
Raphael Michel
51392f73a8 Locking optimizations 2019-05-05 17:31:08 +02:00
Raphael Michel
465a5b01b9 Offload more work to database replica 2019-05-05 17:31:08 +02:00
Raphael Michel
74a6004613 Documentation on scaling 2019-05-05 17:31:08 +02:00
Sohalt
f9fc33eba1 Fix #1266 -- Make references to plugin settings clickable links (#1268) 2019-05-02 09:18:42 +02:00
Raphael Michel
363dc74c31 Dekodi: Try to find correct PayPal ID 2019-05-02 09:12:57 +02:00
Raphael Michel
efb598e93a Update from Weblate (#1267)
Update from Weblate
2019-05-01 14:13:47 +02:00
Raphael Michel
bcfaf2801d Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3063 of 3063 strings)

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

powered by weblate
2019-05-01 12:13:28 +00:00
Raphael Michel
98db417fe6 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (99 of 99 strings)

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

powered by weblate
2019-05-01 12:13:18 +00:00
Raphael Michel
a03ffd949e Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3063 of 3063 strings)

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

powered by weblate
2019-05-01 12:12:58 +00:00
Raphael Michel
88ef46dee9 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (99 of 99 strings)

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

powered by weblate
2019-05-01 12:12:39 +00:00
Raphael Michel
9bc6941c14 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-05-01 14:02:11 +02:00
Raphael Michel
987da83894 Refs #1102 -- Accept order URLs in order lookup 2019-05-01 14:01:26 +02:00
Raphael Michel
d029d92a92 Fix #1102 -- "View in backend" (doesn't work with custom domains) 2019-05-01 14:01:26 +02:00
Raphael Michel
f1b07777bc Timezone indicators in the backend 2019-05-01 14:01:26 +02:00
Raphael Michel
db187a2537 Fix #1126 -- Use short datetime format on order details page 2019-05-01 14:01:26 +02:00
Raphael Michel
e9a340d9ca Refs #1128 -- Popover on disabled "add to cart" button 2019-05-01 14:01:26 +02:00
Raphael Michel
6841a30d8f Fix #1153 -- Show preview of uploaded pictures in the backend 2019-05-01 14:01:26 +02:00
Raphael Michel
30b8c0f4b9 Fix ClearableBasenameFileInput with current Django 2019-05-01 14:01:26 +02:00
Raphael Michel
3e8f32e7e3 Fix #1178 -- Invalidate ticket cache after order locale change 2019-05-01 14:01:26 +02:00
Raphael Michel
2b145e254b Fix #1211 -- Locale selection on organizer profile 2019-05-01 14:01:26 +02:00
Raphael Michel
e5c2470fde "Go to shop" for organizers 2019-05-01 14:01:26 +02:00
Raphael Michel
2da93eba26 Fix #1230 -- Stripe: Recognize canceled sources in webhook 2019-05-01 14:01:26 +02:00
Raphael Michel
788f73d842 Fix #1255 -- Approvals of free orders after last date of payments 2019-05-01 14:01:26 +02:00
Raphael Michel
d86b3a2173 Update from Weblate (#1265)
Update from Weblate
2019-04-30 09:51:49 +02:00
Tobias Sundgren
7be6046ed5 Translated on translate.pretix.eu (Swedish)
Currently translated at 100.0% (97 of 97 strings)

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

powered by weblate
2019-04-30 07:51:24 +00:00
Maarten van den Berg
6b90689067 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (97 of 97 strings)

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

powered by weblate
2019-04-30 07:51:24 +00:00
Maarten van den Berg
815816b9d6 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (97 of 97 strings)

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

powered by weblate
2019-04-30 07:51:24 +00:00
Tobias Sundgren
3199687fe4 Translated on translate.pretix.eu (Swedish)
Currently translated at 0.8% (24 of 3060 strings)

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

powered by weblate
2019-04-30 07:51:24 +00:00
Maarten van den Berg
6d8b8c6346 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3060 of 3060 strings)

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

powered by weblate
2019-04-30 07:51:24 +00:00
Maarten van den Berg
8a850773f4 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3060 of 3060 strings)

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

powered by weblate
2019-04-30 07:51:24 +00:00
Raphael Michel
2a10f875e4 Added translation on translate.pretix.eu (Swedish) 2019-04-30 07:51:24 +00:00
Raphael Michel
d8d2a21bda Added translation on translate.pretix.eu (Swedish) 2019-04-30 07:51:24 +00:00
oocf
18eb468d8e Translated on translate.pretix.eu (Spanish)
Currently translated at 99.0% (3028 of 3060 strings)

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

powered by weblate
2019-04-30 07:51:24 +00:00
ThanosTeste
2842b0e720 Translated on translate.pretix.eu (Greek)
Currently translated at 81.4% (79 of 97 strings)

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

powered by weblate
2019-04-30 07:51:24 +00:00
Chris Spy
42936a931b Translated on translate.pretix.eu (Greek)
Currently translated at 81.4% (79 of 97 strings)

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

powered by weblate
2019-04-30 07:51:24 +00:00
Raphael Michel
a6c72abe75 Change semantics of changing orders (#1260)
* Change semantics of changing orders

This basically does two things to the "Change products" view of orders and the
OrderChangeManager program API:

1) It decouples changing items or subevents from changing prices.
   OrderChangeManager.change_item() and .change_subevent() no longer
   touch the price of a position. Instead .change_price() needs to be
   called explicitly. However, a client-side JavaScript component now
   *proposes* a new price based on the changed item or subevent.

2) The user interface now exposes the possibility of doing multiple
   things at the same time, i.e. changing the item, subevent and price
   in the same operation. OrderChangeManager already allowed this
   before.

(1) is basically a consequence of (2), while (2) is a prerequesite for
e.g. the `seating` branch, where changing the subevent will always
require changing the seat.

* Add tests for price calculation API
2019-04-30 09:51:19 +02:00
Raphael Michel
df3e6f4b9a dekodi: Fix version and mandatory fields 2019-04-30 09:50:47 +02:00
Raphael Michel
8ef99ba828 Dekodi: Merchant PayPal IDs 2019-04-30 09:50:17 +02:00
Raphael Michel
e8e5f5c7bf Dekodi: Get rid of null values 2019-04-29 15:46:48 +02:00
Martin Gross
f0128429e4 Format amount in GiroCode/EPC-QR with dot instead of locale 2019-04-29 13:54:53 +02:00
Raphael Michel
cc8e5a7f83 Widget: original price for variations 2019-04-29 09:30:03 +02:00
Raphael Michel
d4d3928146 Expose is_public in subevent editor 2019-04-29 09:30:03 +02:00
Raphael Michel
cc4602c308 API Auth: Respect staff sessions 2019-04-26 16:24:13 +02:00
Raphael Michel
2bc0dd6076 Dekodi export: date filter 2019-04-26 15:22:10 +02:00
Raphael Michel
f286c5af28 Dekodi: Never encode money as strings 2019-04-25 21:07:10 +02:00
Raphael Michel
ec27ed198b Add Dekodi exporter 2019-04-25 20:36:24 +02:00
Raphael Michel
2ee0f684c5 PDF variable: price including add-ons 2019-04-25 19:34:51 +02:00
Raphael Michel
951386b32c Add subevent column to order list export 2019-04-25 15:08:22 +02:00
Raphael Michel
f498e8fafa Fix faulty test cases 2019-04-25 14:00:55 +02:00
Raphael Michel
b79947fba4 Widget: Original price for variations 2019-04-25 11:54:21 +02:00
Raphael Michel
ef600ceddb Fix invalid handling of variations with quota-level vouchers 2019-04-25 11:54:03 +02:00
Raphael Michel
13bf975dd5 Fix KeyError during form validation 2019-04-25 10:36:29 +02:00
Raphael Michel
8e56c8dcf7 Fix documentation typos 2019-04-23 17:39:09 +02:00
Raphael Michel
a42b31560c Check-in API: Fall back from attendee_name 2019-04-23 17:25:35 +02:00
Raphael Michel
e15e7a5877 Check-in API: Return 400 instead of 404 on checking in unpaid orders 2019-04-23 17:18:16 +02:00
Raphael Michel
e7384f7e85 Check-in API: require_attention and ignore_status 2019-04-23 17:06:24 +02:00
Raphael Michel
840b30c3c2 Linkify email addresses 2019-04-23 17:06:24 +02:00
Martin Gross
1adabec989 Fix test: Price override shows old price as <del> 2019-04-23 15:37:51 +02:00
Martin Gross
171bea59df Show strikethrough price when voucher is granting discount 2019-04-23 14:26:21 +02:00
Martin Gross
3c4b086992 Show strikethrough original_price when redeeming voucher 2019-04-23 11:55:31 +02:00
Raphael Michel
6a4e6e227c Fix isort issue 2019-04-23 11:19:19 +02:00
Raphael Michel
9c3abc5338 More precise log message for skipped attachments 2019-04-23 11:18:03 +02:00
Raphael Michel
91b2d7989a Update from Weblate (#1261)
Update from Weblate
2019-04-23 10:55:30 +02:00
Raphael Michel
c5a80e6daf Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (97 of 97 strings)

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

powered by weblate
2019-04-23 08:55:15 +00:00
Raphael Michel
37ce9fa9af Translated on translate.pretix.eu (German)
Currently translated at 100.0% (97 of 97 strings)

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

powered by weblate
2019-04-23 08:55:14 +00:00
Raphael Michel
64fe3d772c Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3060 of 3060 strings)

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

powered by weblate
2019-04-23 08:55:14 +00:00
Raphael Michel
5c82781fcc Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3060 of 3060 strings)

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

powered by weblate
2019-04-23 08:54:56 +00:00
Raphael Michel
0d70e3c8e3 Specify minor version of urllib3 2019-04-23 10:50:59 +02:00
Raphael Michel
85a7f0c0cc Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-04-23 10:50:48 +02:00
Raphael Michel
6d0e1097e6 Update from Weblate (#1257)
Update from Weblate
2019-04-23 10:50:10 +02:00
David100mark
c557087252 Translated on translate.pretix.eu (French)
Currently translated at 77.3% (2366 of 3059 strings)

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

powered by weblate
2019-04-23 08:49:35 +00:00
David100mark
62796cdc5f Translated on translate.pretix.eu (French)
Currently translated at 77.2% (2362 of 3059 strings)

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

powered by weblate
2019-04-23 08:49:35 +00:00
David100mark
bbe5f9bd98 Translated on translate.pretix.eu (French)
Currently translated at 76.1% (2329 of 3059 strings)

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

powered by weblate
2019-04-23 08:49:35 +00:00
Maarten van den Berg
003ccd83bf Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3059 of 3059 strings)

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

powered by weblate
2019-04-23 08:49:35 +00:00
Maarten van den Berg
f8f6dc4a51 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3059 of 3059 strings)

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

powered by weblate
2019-04-23 08:49:35 +00:00
Raphael Michel
cddf716784 Translated on translate.pretix.eu (German (informal))
Currently translated at 99.9% (3057 of 3059 strings)

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

powered by weblate
2019-04-23 08:49:35 +00:00
Raphael Michel
ee495f2777 Add property SubEvent.is_public 2019-04-23 10:46:09 +02:00
Raphael Michel
5bdc9011c1 Widget: Specific wording for mobing back to subevents 2019-04-23 10:37:25 +02:00
Raphael Michel
c6ea30ec1e Widget: Handle resize events 2019-04-23 10:35:07 +02:00
Raphael Michel
f9341b4d47 Downgrade urllib3 2019-04-23 10:15:01 +02:00
Raphael Michel
2205e57650 Fail consistently on invalid payment providers 2019-04-23 09:47:55 +02:00
Raphael Michel
ad8fdd6935 Ignore quota errors during order creation 2019-04-23 09:47:44 +02:00
Raphael Michel
02e936ee7a Fix #522 -- Do not allow any orders after the last date of payments 2019-04-23 09:46:34 +02:00
Raphael Michel
45a6923220 Refs #522 -- Do not allow to create orders after the last date of payments 2019-04-23 09:41:01 +02:00
Raphael Michel
e4417305a2 Fix updatestyles not being sent to background queue 2019-04-18 17:44:14 +02:00
Raphael Michel
bc5d0bea00 updatestyles: Prioritize future events over past ones 2019-04-18 17:27:34 +02:00
Raphael Michel
dbce9b0395 Allow error pages to be embedded in frames (to ease widget troubleshooting) 2019-04-18 17:19:42 +02:00
Martin Gross
2eb88840bd Original price for variations (#1258)
* Original price for variations

* Documentation

* API-GET

* Fix existing tests to accomodate new attribute

* Test for variation's original_price on API
2019-04-18 16:13:49 +02:00
Martin Gross
4838835b1b Remove debug-toolbar template override 2019-04-18 12:21:42 +02:00
Raphael Michel
ab452bd9e3 Fix typo 2019-04-18 09:50:07 +02:00
Raphael Michel
ae298bddb8 Make FakeRedis play nice with metrics 2019-04-18 09:17:55 +02:00
Raphael Michel
9ad4607d26 Move ticket cache invalidation to background task 2019-04-18 09:17:01 +02:00
Raphael Michel
b3684377cd Fix crash in item validation
Fixes Sentry PRETIXEU-10B
2019-04-17 15:40:25 +02:00
Raphael Michel
441badfdbd Bank transfer: Move ack field 2019-04-17 15:38:26 +02:00
Raphael Michel
0d242a0304 Fix internal error during validation
Sentry PRETIXEU-10A
2019-04-17 15:21:42 +02:00
Raphael Michel
2fac8592d4 Add modern invoice renderer 2019-04-17 15:08:58 +02:00
Raphael Michel
58b1a2f115 Fix timezone handling in widget 2019-04-17 14:42:00 +02:00
Raphael Michel
420d44e909 Fix #1170 -- E-mail address in check-in list 2019-04-17 12:12:07 +02:00
Raphael Michel
e0063fce52 Allow superusers to inspect payments and refunds 2019-04-17 10:15:14 +02:00
Raphael Michel
21ef6c7950 Update framework classifier 2019-04-17 10:07:02 +02:00
Sohalt
651f429ffb Fix #1247 -- Allow team invites to be resent (#1250)
* Fix #1247 -- Allow team invites to be resent

* Test resending invalid invites

* Fix tooltip

* Fix test

* Handle invalid types for pk parameter

* Style button
2019-04-16 16:39:31 +02:00
Raphael Michel
66dd7c448b Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-04-16 13:35:36 +02:00
Raphael Michel
e9b4205145 Fix translation of widget headlines 2019-04-16 13:35:07 +02:00
Raphael Michel
6dedea1025 Items API: Note that tax_rate is read-only 2019-04-16 13:35:07 +02:00
Raphael Michel
348ed4e909 Merge pull request #1244 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-04-16 13:34:26 +02:00
Maarten van den Berg
091b3358e4 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (96 of 96 strings)

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

powered by weblate
2019-04-16 05:00:07 +00:00
Maarten van den Berg
186e2a6b9a Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (96 of 96 strings)

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

powered by weblate
2019-04-16 05:00:06 +00:00
Maarten van den Berg
198b90972c Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3057 of 3057 strings)

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

powered by weblate
2019-04-16 05:00:06 +00:00
Maarten van den Berg
4989b6235c Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3057 of 3057 strings)

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

powered by weblate
2019-04-16 05:00:05 +00:00
mussol
4cfebab11c Translated on translate.pretix.eu (Catalan)
Currently translated at 35.1% (1073 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
Martin Gross
fe944ec643 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3057 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
Martin Gross
9d92c7b10f Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3057 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
Maarten van den Berg
3b810a3a76 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3057 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
Maarten van den Berg
7860417177 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 99.4% (3038 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
Maarten van den Berg
1438edb3c8 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3057 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
mussol
a17720062b Translated on translate.pretix.eu (Catalan)
Currently translated at 31.8% (972 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
mussol
37ab45b352 Translated on translate.pretix.eu (Catalan)
Currently translated at 31.6% (966 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
mussol
6be212df8c Translated on translate.pretix.eu (Catalan)
Currently translated at 31.1% (952 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
mussol
b4e85780f4 Translated on translate.pretix.eu (Catalan)
Currently translated at 30.0% (917 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
mussol
a9732c4788 Translated on translate.pretix.eu (Catalan)
Currently translated at 30.0% (917 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
mussol
273316be25 Translated on translate.pretix.eu (Catalan)
Currently translated at 29.4% (900 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
Maarten van den Berg
0f3b269931 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 98.9% (3023 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
Maarten van den Berg
4462054d0e Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3057 of 3057 strings)

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

powered by weblate
2019-04-15 13:47:00 +00:00
Raphael Michel
ec53022cc8 Do not call task synchronously inside task (celery doesn't allow it any more) 2019-04-15 15:46:37 +02:00
Raphael Michel
0b65b18459 Send emails in an TransactionAwareTask 2019-04-15 15:22:58 +02:00
Raphael Michel
2fac03f47b Add a test case for free orders 2019-04-15 15:14:35 +02:00
Raphael Michel
750d5eda48 Do not mark free orders as paid that require approval 2019-04-15 15:12:26 +02:00
Raphael Michel
f2cd9a2002 Fix logic bug in attachment size check 2019-04-15 12:58:36 +02:00
Raphael Michel
874b38db17 Mark order as paid immediately 2019-04-15 12:58:20 +02:00
Raphael Michel
0f58e1c396 CSV import: Do not skip rows without a reference 2019-04-08 17:55:28 +02:00
Raphael Michel
36e0afc09e Further improvements to the print stylesheet 2019-04-08 17:42:06 +02:00
Raphael Michel
7164124a70 Display category description in add-on step 2019-04-08 15:23:40 +02:00
Raphael Michel
887d8832c0 Improve print CSS of order details 2019-04-07 18:12:12 +02:00
Raphael Michel
beb144f9a0 Fix API log cleanup 2019-04-07 15:31:35 +02:00
Raphael Michel
6d1dea7922 Upgrade to Django 2.2 and modern DRF and py.test (#1246)
* Upgrade django and stuff

* Update to Django 2.2 and recent versions of similar packages

* Provide explicit orderings to all models used in paginated queries

* Resolve naive datetime warnings in test suite

* Deal with deprecation warnings

* Fix sqlparse version
2019-04-07 14:09:49 +01:00
Raphael Michel
cb531a7a6a Cut test time by 65% by caching templates and not compiling sass 2019-04-07 13:53:59 +02:00
Raphael Michel
d5820d74d3 Fix #1025 -- Python 3.7 support (#1245)
* Fix #1025 -- Python 3.7 support

* Upgrade redis-py

* Travis: xenial

* Fix version specifier
2019-04-06 22:58:36 +01:00
Raphael Michel
b686978074 Add order lifecycle signals 2019-04-06 15:05:39 +02:00
Raphael Michel
c372bffc57 Fix tests on PostgreSQL 2019-04-05 16:17:57 +02:00
Raphael Michel
282c6108bf Remove duplicate test 2019-04-05 15:32:25 +02:00
Raphael Michel
f2437c7ff7 Correcly read bytesfield 2019-04-05 15:04:47 +02:00
Raphael Michel
dd0b6e6647 Adjust test to internal type change 2019-04-05 14:59:05 +02:00
Raphael Michel
f3128591d8 More flexible response content handling 2019-04-05 14:54:36 +02:00
Raphael Michel
d395db8142 Box office payments: Always display device and receipt ID 2019-04-05 14:40:58 +02:00
Raphael Michel
0c82e92882 REST API: Add support for idempotency keys 2019-04-05 14:21:51 +02:00
Raphael Michel
db0c13a3c2 REST API: Order creation: Allow to set payment_date 2019-04-05 08:55:57 +02:00
Raphael Michel
19a2f4163a Add a few permission tests 2019-04-04 18:17:56 +02:00
Raphael Michel
76526465c0 Fix a test failure in test_items 2019-04-04 18:14:27 +02:00
Raphael Michel
d0d0f9aa4c Fix logic flaw in cart position deletion 2019-04-04 17:18:12 +02:00
Martin Gross
482f6b1eb8 Fix Item/Question tests to also include obligatory items[] as imposed by b931d27486 2019-04-04 16:12:20 +02:00
Raphael Michel
327418299a Cart view: Make questions a little bit less bold 2019-04-04 14:22:36 +02:00
Raphael Michel
5dfd1e6337 Prefill attendee name/email of first ticket with contact email and invoice recipient 2019-04-04 14:13:08 +02:00
Raphael Michel
bc01124584 Fix stepping back to the invoice address 2019-04-04 14:12:51 +02:00
Raphael Michel
c0df418265 Make sure package pinning is copied to setup.py 2019-04-04 13:45:07 +02:00
Martin Gross
af06f6fc38 Pin pytest-xdist to 1.27.*, as 1.28.0++ requires pytest>=4.4.0 2019-04-04 10:24:59 +02:00
Raphael Michel
4c0e8f69ea Cancellation: Do not display refund notices if not required 2019-04-04 09:57:57 +02:00
Raphael Michel
243e4ac4c8 Allow not to ask for invoice addresses on free orders 2019-04-04 09:57:57 +02:00
Raphael Michel
b931d27486 Solve cart deletion issues once and for all 2019-04-04 09:57:57 +02:00
Raphael Michel
2810e2a760 CartManager: Do not try to extend positions while they are being removed 2019-04-04 09:57:57 +02:00
Martin Gross
04465393b2 Set explicit description for Stripe Charges 2019-04-03 19:30:56 +02:00
Raphael Michel
4c9032f2a8 Bump version to 2.6.0 2019-04-03 16:02:39 +02:00
Raphael Michel
cae2bb944a Merge pull request #1243 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-04-03 15:02:23 +01:00
Raphael Michel
724e745b8d Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3057 of 3057 strings)

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

powered by weblate
2019-04-03 14:02:01 +00:00
Raphael Michel
f4cead1c20 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3057 of 3057 strings)

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

powered by weblate
2019-04-03 13:26:15 +00:00
Raphael Michel
7cab1924bb Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-04-03 15:19:57 +02:00
Raphael Michel
641148fecc Merge pull request #1239 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-04-03 14:19:23 +01:00
mussol
9b3860e5fd Translated on translate.pretix.eu (Catalan)
Currently translated at 27.5% (839 of 3047 strings)

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

powered by weblate
2019-04-03 11:13:19 +00:00
mussol
cb9d4c10df Translated on translate.pretix.eu (Catalan)
Currently translated at 22.0% (669 of 3047 strings)

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

powered by weblate
2019-04-03 11:13:19 +00:00
oocf
84105b9585 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (96 of 96 strings)

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

powered by weblate
2019-04-03 11:13:19 +00:00
oocf
3f38caeb24 Translated on translate.pretix.eu (Spanish)
Currently translated at 99.3% (3026 of 3047 strings)

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

powered by weblate
2019-04-03 11:13:19 +00:00
mussol
eae552e474 Translated on translate.pretix.eu (Catalan)
Currently translated at 21.1% (643 of 3047 strings)

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

powered by weblate
2019-04-03 11:13:19 +00:00
mussol
f27c10c2ac Translated on translate.pretix.eu (Catalan)
Currently translated at 8.8% (267 of 3047 strings)

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

powered by weblate
2019-04-03 11:13:19 +00:00
Raphael Michel
abd237b969 Checkout redirection: Respect cart_namespace 2019-04-03 13:12:49 +02:00
Raphael Michel
99c61c9060 Orders API: Add a missing sorting method to the documentation 2019-04-03 11:18:13 +02:00
Raphael Michel
246f307e21 Pin version of pillow (incompatibility with reportlab) 2019-04-02 11:31:01 +02:00
Raphael Michel
1f672e7df2 Fix incorrect test 2019-04-02 11:30:47 +02:00
Raphael Michel
b261a2041a Actually set the revoked flag 2019-04-02 09:44:31 +02:00
Raphael Michel
2d37c6d94d Make device token revokation more explicit 2019-04-02 09:36:07 +02:00
Raphael Michel
e75ae80fb5 REST API: Allow to filter orders by datetime 2019-03-29 17:15:15 +01:00
Raphael Michel
73ec5bac79 Allow to set a custom error message when presale is ended 2019-03-29 16:38:47 +01:00
Raphael Michel
46166159b0 Allow to force order creation through the API 2019-03-28 18:11:06 +01:00
Raphael Michel
598693fab2 Add Chinese as a selectable language 2019-03-28 17:06:28 +01:00
Raphael Michel
2420d884fc Merge pull request #1232 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-28 16:06:26 +00:00
Raphael Michel
f95005a8d4 Added translation on translate.pretix.eu (Catalan) 2019-03-28 16:04:15 +00:00
Raphael Michel
e773096df3 Added translation on translate.pretix.eu (Catalan) 2019-03-28 16:03:59 +00:00
yichengsd
c42905421d Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 100.0% (96 of 96 strings)

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

powered by weblate
2019-03-28 15:58:20 +00:00
Alvaro Enrique Ruano
46c2e28def Translated on translate.pretix.eu (Spanish)
Currently translated at 99.0% (3018 of 3047 strings)

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

powered by weblate
2019-03-28 15:58:20 +00:00
yichengsd
07bc3df6d3 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 99.8% (3041 of 3047 strings)

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

powered by weblate
2019-03-28 15:58:20 +00:00
Maarten van den Berg
2992c4c48a Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (96 of 96 strings)

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

powered by weblate
2019-03-28 15:58:20 +00:00
Maarten van den Berg
c53718381e Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3047 of 3047 strings)

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

powered by weblate
2019-03-28 15:58:20 +00:00
Maarten van den Berg
98e5f0b95d Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 71.9% (69 of 96 strings)

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

powered by weblate
2019-03-28 15:58:20 +00:00
Maarten van den Berg
7f11f06f3f Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (96 of 96 strings)

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

powered by weblate
2019-03-28 15:58:20 +00:00
Raphael Michel
949057a9cc Allow to persist filter attributes in session 2019-03-28 16:58:05 +01:00
Raphael Michel
edd643cc32 Event index: Filter subevent list as well 2019-03-28 16:54:21 +01:00
Raphael Michel
5f15ebc46f Fix TypeError in offset calculation
sentry issue PRETIXEU-ZB
2019-03-27 18:12:49 +01:00
Raphael Michel
3415fd947a Hotfix: Redirect with a / 2019-03-27 17:46:14 +01:00
Raphael Michel
a70a42c273 Hotfix: Do not use absolute URLs 2019-03-27 17:02:22 +01:00
Raphael Michel
697cdfd5c9 Allow to redirect to checkout directly after adding a product to the cart 2019-03-27 16:45:15 +01:00
Raphael Michel
d8a7de8b23 Allow to filter subevents by attributes in query parameters 2019-03-27 16:15:16 +01:00
Raphael Michel
9f7f0e74ff Fix arrow position in month button 2019-03-27 16:15:16 +01:00
Martin Gross
7ef289da45 Minor JSON spelling mistakes 2019-03-27 15:41:56 +01:00
Raphael Michel
e82bc732a3 Docs: Fix spelling issues 2019-03-27 12:08:22 +01:00
Raphael Michel
4636ccac3b Add signals html_page_header, sass_preamble, sass_postamble 2019-03-27 09:14:51 +01:00
Raphael Michel
e3518bfb4b Fix date-dependent test 2019-03-26 10:20:26 +01:00
Raphael Michel
b2471169af Bank transfer: Improve error message 2019-03-26 09:46:40 +01:00
Raphael Michel
487418678c Banktransfer: Workaround for OrderPayment.MultipleObjectsReturned
Fix sentry issue PRETIXEU-Z7
2019-03-26 09:44:26 +01:00
Raphael Michel
d4795868d6 Correcly cancel payments when starting a new one 2019-03-26 09:41:03 +01:00
Raphael Michel
45af18a23d Work around SubEvent.DoesNotExist in refresh_quota_caches
Fix PRETIXEU-Z8
2019-03-26 09:06:34 +01:00
Raphael Michel
a6de586b80 Make ItemBundle.designated_price non-nullable 2019-03-23 23:42:58 +01:00
Raphael Michel
e6859fa82b Docs: Allow "subnet" in word list 2019-03-23 15:25:39 +01:00
Raphael Michel
2d5e14e517 Fix error in tests 2019-03-23 15:06:29 +01:00
Raphael Michel
7219575b84 Fix #1066 -- Change installation tutorials to PostgreSQL
This is the recommended database server so this documentation should use that
2019-03-23 15:04:12 +01:00
Raphael Michel
991e4127f6 Refs #654 -- Allow to update invoice addresses 2019-03-23 13:51:25 +01:00
Raphael Michel
420649e10a Refs #654 -- REST API: Allow to resend order link 2019-03-23 13:33:57 +01:00
Raphael Michel
0d02e2fe8c Refs #654 -- REST API: Allow to cycle order secrets 2019-03-23 13:25:23 +01:00
Raphael Michel
afdba9f268 Refs #654 -- REST API: Allow invoice creation 2019-03-23 13:25:21 +01:00
Raphael Michel
394f7e04c3 Docs: Add a guide on building product structures 2019-03-23 13:06:13 +01:00
Martin Gross
c3a5cef051 Merge pull request #1227 from felixrindt/patch-6
Fix doc typo
2019-03-23 11:25:53 +01:00
Felix Rindt
47b7bcbfca Fix doc typo 2019-03-23 11:15:35 +01:00
Raphael Michel
2cd1345035 Adjust item API tests 2019-03-23 00:43:02 +01:00
Raphael Michel
c24ce551ba Refs #654 -- REST API: Allow PATCH for some order fields 2019-03-23 00:08:45 +01:00
Raphael Michel
0bb6e460e8 Fix #1195 -- REST API: Fix wrong data type of variation price 2019-03-23 00:08:45 +01:00
Raphael Michel
26257f0829 Refs #1195 -- Fix missing null annotations in the API 2019-03-23 00:08:45 +01:00
Raphael Michel
6badb342bf Merge pull request #1226 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-22 15:02:47 +00:00
Raphael Michel
865a70d5d5 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (96 of 96 strings)

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

powered by weblate
2019-03-22 15:00:25 +00:00
Raphael Michel
df1c0d4f3a Translated on translate.pretix.eu (German)
Currently translated at 100.0% (96 of 96 strings)

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

powered by weblate
2019-03-22 15:00:25 +00:00
Raphael Michel
adb982c451 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3047 of 3047 strings)

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

powered by weblate
2019-03-22 15:00:24 +00:00
Raphael Michel
94ba26d841 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3047 of 3047 strings)

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

powered by weblate
2019-03-22 15:00:05 +00:00
Raphael Michel
45d5487eb5 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-03-22 15:50:33 +01:00
Raphael Michel
38f5f75a1b Add deprecation note to the PayPal documentation 2019-03-22 15:49:44 +01:00
Raphael Michel
90f881c48e Fix #1001 -- Add product bundles (#1041)
* Data model + Editor

* Cart and order management

* Rebase migrations

* Fix typos, add tests on cart handling

* Add tests for checkout and quotas

* Add API endpoints

* Validation of settings

* Front page tax display

* Voucher handling

* Widget foo

* Show correct net pricing

* Front page tests

* reverse charge foo

* Allow to require bundling

* Fix test failure on postgres
2019-03-22 14:48:48 +00:00
Raphael Michel
c4b18a4c81 Force widget data to be a dictionary 2019-03-22 12:16:20 +01:00
Raphael Michel
8e2ef604f7 Widget API: Fix parameters 2019-03-22 11:37:12 +01:00
Raphael Michel
970e4f6d52 Merge pull request #1225 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-22 09:43:56 +00:00
Raphael Michel
59c9731bae Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (94 of 94 strings)

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

powered by weblate
2019-03-22 09:43:24 +00:00
Raphael Michel
8afd09a647 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3027 of 3027 strings)

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

powered by weblate
2019-03-22 09:43:24 +00:00
Raphael Michel
fa375950a7 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3027 of 3027 strings)

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

powered by weblate
2019-03-22 09:43:23 +00:00
Raphael Michel
ccdfa716c0 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (94 of 94 strings)

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

powered by weblate
2019-03-22 09:42:51 +00:00
Raphael Michel
08ffa17e01 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-03-22 10:22:25 +01:00
Raphael Michel
7f8c91ec9d Merge pull request #1220 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-22 09:20:24 +00:00
yichengsd
dde3a53e09 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-22 09:18:09 +00:00
徐志能
73bc3259e8 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-22 09:18:09 +00:00
徐志能
34767a2029 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 99.9% (3018 of 3019 strings)

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

powered by weblate
2019-03-22 09:18:09 +00:00
Vitor Reis
85bd1a0d44 Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 98.6% (70 of 71 strings)

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

powered by weblate
2019-03-22 09:18:09 +00:00
Lorhan Sohaky
1ad4b6019e Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 98.6% (70 of 71 strings)

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

powered by weblate
2019-03-22 09:18:09 +00:00
yichengsd
a778675857 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 100.0% (71 of 71 strings)

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

powered by weblate
2019-03-22 09:18:09 +00:00
Lorhan Sohaky
e5a980aef4 Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 16.1% (487 of 3019 strings)

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

powered by weblate
2019-03-22 09:18:09 +00:00
Vitor Reis
e8338a2941 Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 16.1% (487 of 3019 strings)

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

powered by weblate
2019-03-22 09:18:09 +00:00
徐志能
289bbf84a9 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 96.1% (2901 of 3019 strings)

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

powered by weblate
2019-03-22 09:18:09 +00:00
Raphael Michel
f13dbb85cb Fix #1224 -- Cache widget responses for a short time 2019-03-22 09:18:04 +00:00
Raphael Michel
49e706a580 Fix #878 -- Add multi-event widget 2019-03-22 09:18:04 +00:00
Martin Gross
ca7d55082b Refs #654 -- Add writable API for subevents (#1217)
- [x] Write operations for subevents
- [x] Tests
- [x] Documentation
2019-03-21 20:40:59 +00:00
Raphael Michel
516fab52da Do not send payment reminders to orders pending approval 2019-03-20 23:09:13 +01:00
Raphael Michel
ddf6af278c Widget: Return a useful error message on disabled events 2019-03-18 20:42:47 +01:00
Raphael Michel
07b4b8c473 Allow to add a custom text above the invoice address 2019-03-18 17:01:23 +01:00
Raphael Michel
a0af0cfb06 Merge pull request #1218 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-18 17:00:00 +01:00
Raphael Michel
3eb86a371a Merge pull request #1216 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-18 16:54:38 +01:00
Raphael Michel
ae7175c00b Translated on translate.pretix.eu (Greek)
Currently translated at 0.6% (17 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
徐志能
4286176e73 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 80.6% (2433 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
徐志能
0f53ab67df Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 80.3% (2424 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
徐志能
0d6db082ca Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 77.5% (2339 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
oocf
2a9b8baa98 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
徐志能
6bdaab5319 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 63.3% (1911 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
oocf
489d5e3d01 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Alvaro Enrique Ruano
86e2cf2786 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
徐志能
f075dbc78e Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 59.1% (1784 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Serge Bazanski
10500ee6a9 Translated on translate.pretix.eu (Polish)
Currently translated at 100.0% (71 of 71 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Serge Bazanski
6c766d872d Translated on translate.pretix.eu (Polish)
Currently translated at 15.7% (475 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Serge Bazanski
c8b1206e61 Translated on translate.pretix.eu (Polish)
Currently translated at 90.1% (64 of 71 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Serge Bazanski
424d1489bf Translated on translate.pretix.eu (Polish)
Currently translated at 15.7% (475 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Serge Bazanski
b3e567a188 Translated on translate.pretix.eu (Polish)
Currently translated at 50.7% (36 of 71 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Serge Bazanski
66e42f66e5 Translated on translate.pretix.eu (Polish)
Currently translated at 15.6% (472 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Serge Bazanski
786fbc6e29 Translated on translate.pretix.eu (Polish)
Currently translated at 15.5% (11 of 71 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Serge Bazanski
f2ff5d7510 Translated on translate.pretix.eu (Polish)
Currently translated at 15.5% (469 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
徐志能
0bcc4de2de Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 53.5% (1615 of 3019 strings)

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

powered by weblate
2019-03-18 15:54:26 +00:00
Raphael Michel
586e7cc997 Add sales channel to order export 2019-03-18 16:54:08 +01:00
Raphael Michel
bf33cc1499 Do not uppercase labels in Greek invoices 2019-03-18 16:53:48 +01:00
Raphael Michel
faff7b4166 Translated on translate.pretix.eu (Greek)
Currently translated at 0.6% (17 of 3019 strings)

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

powered by weblate
2019-03-18 15:52:14 +00:00
徐志能
44263a17e6 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 80.6% (2433 of 3019 strings)

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

powered by weblate
2019-03-18 15:52:14 +00:00
徐志能
87b4d1aaed Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 80.3% (2424 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
徐志能
0d12c1589a Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 77.5% (2339 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
oocf
96ddd8ce4e Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
徐志能
dc2da88220 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 63.3% (1911 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
oocf
b8796b0632 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Alvaro Enrique Ruano
961b1d4efa Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
徐志能
ac674565cf Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 59.1% (1784 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Serge Bazanski
301e9d1d48 Translated on translate.pretix.eu (Polish)
Currently translated at 100.0% (71 of 71 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Serge Bazanski
81e08021db Translated on translate.pretix.eu (Polish)
Currently translated at 15.7% (475 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Serge Bazanski
da1beac49c Translated on translate.pretix.eu (Polish)
Currently translated at 90.1% (64 of 71 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Serge Bazanski
a81fc5bfe0 Translated on translate.pretix.eu (Polish)
Currently translated at 15.7% (475 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Serge Bazanski
eb3ec0f99a Translated on translate.pretix.eu (Polish)
Currently translated at 50.7% (36 of 71 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Serge Bazanski
7e98846315 Translated on translate.pretix.eu (Polish)
Currently translated at 15.6% (472 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Serge Bazanski
22f9178617 Translated on translate.pretix.eu (Polish)
Currently translated at 15.5% (11 of 71 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Serge Bazanski
ef73abc3f6 Translated on translate.pretix.eu (Polish)
Currently translated at 15.5% (469 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
徐志能
ac6e6a526e Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 53.5% (1615 of 3019 strings)

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

powered by weblate
2019-03-18 10:34:58 +00:00
Raphael Michel
8185c6a0d6 Delete all cart positions when disabling test mode 2019-03-18 11:34:48 +01:00
Raphael Michel
a7b294fc61 Add "searchable" to spell-check wordlist 2019-03-18 10:00:45 +01:00
Raphael Michel
b81f07b237 Bring documentation up to date 2019-03-17 21:33:19 +01:00
Raphael Michel
67bdb0ec1f Quick setup: Enable products on all sales channels 2019-03-17 20:31:10 +01:00
Raphael Michel
7b6ff01740 That wasn't an efficient bugfix… 2019-03-15 12:19:39 +01:00
Raphael Michel
83f5182db2 Fix a bug in polish translation 2019-03-15 12:03:23 +01:00
Raphael Michel
ee2050b8f9 Do not ever ask people to select a payment method for 0.00 2019-03-15 11:40:30 +01:00
Raphael Michel
185fc6c73d Fix a test incompatibility 2019-03-15 11:31:32 +01:00
Raphael Michel
a21ea34944 Bank transfer: Properly deal with fees of aborted payment methods 2019-03-15 11:31:32 +01:00
Raphael Michel
130ba3c217 Merge pull request #1215 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-15 11:30:09 +01:00
Raphael Michel
dd39131942 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (71 of 71 strings)

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

powered by weblate
2019-03-15 07:55:50 +00:00
Raphael Michel
295ad9e9c3 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (71 of 71 strings)

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

powered by weblate
2019-03-15 07:55:50 +00:00
oocf
a0969dc7fa Translated on translate.pretix.eu (Spanish)
Currently translated at 99.9% (3018 of 3019 strings)

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

powered by weblate
2019-03-15 07:55:50 +00:00
Serge Bazanski
81a2e0c71c Translated on translate.pretix.eu (Polish)
Currently translated at 15.5% (467 of 3019 strings)

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

powered by weblate
2019-03-15 07:55:50 +00:00
徐志能
71f69c9afb Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 50.2% (1517 of 3019 strings)

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

powered by weblate
2019-03-15 07:55:50 +00:00
徐志能
cfff6f1605 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 50.2% (1517 of 3019 strings)

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

powered by weblate
2019-03-15 07:55:50 +00:00
Raphael Michel
84ccaed94a Fix German strings
How did that happen?
2019-03-15 08:55:35 +01:00
Raphael Michel
ec61e07ab6 Merge pull request #1214 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-14 17:51:31 +01:00
Raphael Michel
4d5fb67b02 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-14 16:51:12 +00:00
Raphael Michel
c885807fa5 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3019 of 3019 strings)

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

powered by weblate
2019-03-14 16:48:11 +00:00
Raphael Michel
88ec54809a Added translation on translate.pretix.eu (Polish (informal)) 2019-03-14 16:46:26 +00:00
Raphael Michel
5f96152d57 Added translation on translate.pretix.eu (Polish (informal)) 2019-03-14 16:46:17 +00:00
Raphael Michel
a9b08660c6 Added translation on translate.pretix.eu (Polish) 2019-03-14 16:44:24 +00:00
Raphael Michel
68e901f76d Added translation on translate.pretix.eu (Polish) 2019-03-14 16:44:12 +00:00
Raphael Michel
e10083379c Merge pull request #1212 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-14 15:38:47 +01:00
pretix Translation Platform
d705102cbb Merge branch 'master' of https://github.com/pretix/pretix 2019-03-14 15:37:44 +01:00
Raphael Michel
122fda27c4 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-03-14 15:35:29 +01:00
Raphael Michel
b83752005a Voucher: Do not show URL if subevent is required 2019-03-14 15:35:01 +01:00
Raphael Michel
7fc926f23e Bank transfer: Send email for underpayments 2019-03-14 15:35:01 +01:00
Raphael Michel
d90686f352 Bank transfer: Ask people if they understood how it works 2019-03-14 15:35:01 +01:00
Raphael Michel
8523f4dfa2 Do not print canceled add-ons on orders 2019-03-14 15:35:01 +01:00
徐志能
9c13676349 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 39.9% (1201 of 3010 strings)

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

powered by weblate
2019-03-14 14:34:52 +00:00
Raphael Michel
19ee8e9802 Update from Weblate (#1201) 2019-03-14 15:34:47 +01:00
徐志能
bbe5ff249b Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 38.6% (1161 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
Maarten van den Berg
7470cda17f Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (69 of 69 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
Maarten van den Berg
a4d50ae4c5 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (69 of 69 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
Maarten van den Berg
e0d7a9d2da Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3010 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
Maarten van den Berg
2159a65643 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3010 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
Alvaro Enrique Ruano
3f05c92602 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (3010 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
yichengsd
d764cdb338 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 30.8% (926 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
yichengsd
7b7bd67ae9 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 30.7% (924 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
徐志能
dc502618dd Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 30.7% (924 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
徐志能
cb6b4c96f8 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 24.8% (746 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
徐志能
de5f094f73 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 24.4% (733 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
徐志能
6b85e89e62 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 24.3% (730 of 3010 strings)

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

powered by weblate
2019-03-14 08:43:14 +00:00
Alvaro Enrique Ruano
8cfc8bc152 Correct documentation for payment forms (#1209) 2019-03-14 09:43:05 +01:00
Raphael Michel
27990b3fbb Prevent users from setting up dependencies for check-in questions 2019-03-13 17:10:23 +01:00
Raphael Michel
307ee36e52 Do not show invisible questions in order overview 2019-03-13 17:06:49 +01:00
Raphael Michel
f95e8f374d Allow dependencies between questions (#1202)
- [x] data model
- [x] api
- [x] backend editor
- [x] backend validation logic
- [x] frontend display logic
- [x] frontend validation logic
- [x] test checkout step
- [x] test modify order in frontend
- [x] test modify order in backend
- [x] validation tests
- [x] correctly evaluate dependency tree in frontend?
- [x] copy events
2019-03-13 16:49:20 +01:00
Raphael Michel
d10cbd07a7 Delete cart positiosn during bulk deletion of subevents 2019-03-13 11:54:58 +01:00
Raphael Michel
5519643782 Voucher redemption: Show a checkbox if max_per_order=1 2019-03-13 11:46:19 +01:00
Raphael Michel
2c91a17927 Item form: Smaller description field 2019-03-13 11:26:13 +01:00
Raphael Michel
875d79536b Re-group voucher options 2019-03-13 11:24:50 +01:00
Raphael Michel
4bf0d2d229 Show voucher link in voucher detail view 2019-03-13 11:22:50 +01:00
Raphael Michel
068983004a ReportlabExportMixin: Encapsule header strings 2019-03-13 11:18:34 +01:00
Martin Gross
0365a1c68d Show SumUp payment details for boxoffice transactions 2019-03-12 14:20:45 +01:00
Raphael Michel
5024fae5ed Improve performance of bulk-generation of ticket PDFs 2019-03-12 09:53:28 +01:00
Raphael Michel
affc6254a8 Fix potential XSS in questions [not a vulnerability, thanks to CSP] 2019-03-12 09:20:48 +01:00
Raphael Michel
bb956c13ba Bump version to 2.6.0.dev0 2019-03-11 18:13:16 +01:00
Raphael Michel
ee70104735 Bump version to 2.5.0 2019-03-11 18:12:29 +01:00
Raphael Michel
8ba38a0254 Order.meta_info_data: Expose null values as an empty dict 2019-03-11 18:12:29 +01:00
Raphael Michel
761a03abdc Merge pull request #1200 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-11 16:42:02 +01:00
Raphael Michel
f3b63acd40 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3010 of 3010 strings)

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

powered by weblate
2019-03-11 15:41:46 +00:00
Raphael Michel
9eee967050 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3010 of 3010 strings)

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

powered by weblate
2019-03-11 15:41:24 +00:00
Raphael Michel
02aee0637a Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-03-11 16:02:41 +01:00
Raphael Michel
dde99c45f3 Merge pull request #1194 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-03-11 16:02:01 +01:00
徐志能
74292535ad Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 21.2% (635 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
徐志能
d1e3ba778d Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 10.7% (320 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
yichengsd
475835959d Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 10.7% (320 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
yichengsd
efdeaeac83 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 100.0% (68 of 68 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
yichengsd
56fe37dd67 Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 6.2% (186 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
yichengsd
ca07f48afd Translated on translate.pretix.eu (Chinese (Simplified))
Currently translated at 6.2% (185 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
Raphael Michel
fa706549ce Added translation on translate.pretix.eu (Chinese (Simplified)) 2019-03-11 13:36:08 +00:00
Raphael Michel
989b28c2f6 Added translation on translate.pretix.eu (Chinese (Simplified)) 2019-03-11 13:36:08 +00:00
Raphael Michel
1c84660c42 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3000 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
Alvaro Enrique Ruano
cf58447cd4 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (3000 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
arabestia
305a3aaf9f Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (3000 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
Alvaro Enrique Ruano
b54a8c120f Translated on translate.pretix.eu (Spanish)
Currently translated at 96.1% (2884 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
Maarten van den Berg
89684c8e0f Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (3000 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
Maarten van den Berg
c5566dfee7 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3000 of 3000 strings)

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

powered by weblate
2019-03-11 13:36:08 +00:00
Raphael Michel
5e7ee3c047 Delete cart positions if deleting subevent
Fixes sentry issue PRETIXEU-Y4
2019-03-11 14:35:49 +01:00
Raphael Michel
815ee29a50 Sendmail: Backwards compatbility of from_log
Fixes sentry issue PRETIXEU-Y3
2019-03-11 14:34:41 +01:00
Raphael Michel
13ee691133 Banktransfer: CSV import of Mac CSV files 2019-03-11 14:30:43 +01:00
Raphael Michel
4e3dd24209 Order list: Prevent type error on empty result set 2019-03-08 13:53:18 +01:00
Raphael Michel
7ef4adeb73 Adjust to new isort version 2019-03-08 12:50:35 +01:00
Raphael Michel
7be5331da5 Show ticket code in check-in list 2019-03-08 12:50:25 +01:00
Raphael Michel
12fc02b2e4 Pagination: Remove hover effect of current page indicator 2019-03-08 12:30:13 +01:00
Raphael Michel
86b4835273 Go to order: Allow to pass an invoice number 2019-03-08 12:22:19 +01:00
Raphael Michel
e53818b025 Sendmail history: Show selected items 2019-03-08 12:18:13 +01:00
Raphael Michel
206a0a28c7 Render markdown in all e-mail previews 2019-03-08 12:15:06 +01:00
Raphael Michel
461b0b639c Sendmail: Use multi-select for product selection 2019-03-08 12:14:40 +01:00
Raphael Michel
2e6f5d0f32 E-Mail rendering: Consistent markdown evaluation between preview and mail 2019-03-08 11:58:26 +01:00
Raphael Michel
12b48948e3 Add a new notification category for overpayments 2019-03-08 11:40:22 +01:00
Raphael Michel
87c7a3d26f PayPal: Even a canceled payment can still succeed 2019-03-08 11:33:03 +01:00
Raphael Michel
4c0789ac20 Fix inconsistent naming of option 2019-03-08 11:30:04 +01:00
Raphael Michel
bc4e6fa549 Add new API endpoints to documentation 2019-03-06 09:39:58 +01:00
Raphael Michel
2b8949dea4 Add API for badge and ticket layout assignments 2019-03-06 09:35:08 +01:00
Raphael Michel
f3ef00e3b7 Set an update_check_id even if update checks are disabled 2019-03-06 09:09:18 +01:00
Martin Gross
c5499df0b4 Improve display of date/time-format in PDF-ticket preview vs. actual rendering (Z#2344558) 2019-03-05 12:10:05 +01:00
Raphael Michel
68dbfedfdf Add database-level uniqueness constraint for check-ins
We measured that this creates a ~10% performance loss on MySQL, but
believe that correctness is more important. Also, in case on concurrent
check-ins on MySQL with default transaction isolation level, this might
lead to Internal Server Errors on all but one check-ins, which is still
better than to show green.
2019-03-04 18:51:52 +01:00
Raphael Michel
e70738ae0c Fix percentage bar in list of check-in lists 2019-03-04 18:44:21 +01:00
Raphael Michel
5750201bc3 Position API: search in attendee_email 2019-03-04 11:04:39 +01:00
Raphael Michel
d4eac76a8d Fix template typo 2019-02-27 09:30:43 +01:00
Raphael Michel
8889607d1c Stripe: Fix test mode recognition 2019-02-27 09:12:04 +01:00
Raphael Michel
5e9e00acec Fix tests that rely on the event wizard 2019-02-26 14:19:04 +01:00
Raphael Michel
0e89d4c0f7 Fix an AttributeError introduced in 104f84b7 2019-02-26 14:18:42 +01:00
Raphael Michel
8b3ce69425 Add clone button to event list within organizer 2019-02-26 13:10:53 +01:00
Raphael Michel
b20d1e8373 Add a second UI option to clone events 2019-02-26 13:10:53 +01:00
Raphael Michel
c278687487 Allow creating multiple events in different tabs at the same time 2019-02-26 13:10:53 +01:00
Raphael Michel
0c45e73456 Event creation: Throw user back if validation of previous step fails 2019-02-26 13:10:53 +01:00
Raphael Michel
104f84b7a8 Log change to quota when creating an item 2019-02-26 13:10:53 +01:00
Raphael Michel
ac4ecfbe69 OrderChangeManager: Fix a type error for orders without tax 2019-02-26 13:10:53 +01:00
Martin Gross
61c6cd2937 Show event date in PDF-export (Z#134372) 2019-02-23 13:47:36 +01:00
Raphael Michel
38066ca5ab Minor CSS helpers 2019-02-22 22:41:42 +01:00
Raphael Michel
373ab29701 Fix #1190 -- Voucher redemption: Default amount one if there is only one option 2019-02-22 15:41:56 +01:00
Raphael Michel
7302bba602 Add order date to CSV attendee list 2019-02-22 14:10:01 +01:00
Raphael Michel
5096121ac7 Improve QR code widget 2019-02-21 15:24:40 +01:00
Raphael Michel
ca4c21a843 Show QR code of a ticket directly from order details 2019-02-21 15:23:29 +01:00
Raphael Michel
407ecdf6c5 Fix spanish translation 2019-02-20 21:02:31 +01:00
Raphael Michel
2faeee8e9c Merge pull request #1187 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-02-20 20:54:07 +01:00
arabestia
e1bbf7139f Translated on translate.pretix.eu (Spanish)
Currently translated at 95.7% (2870 of 3000 strings)

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

powered by weblate
2019-02-20 19:53:28 +00:00
oocf
64fc38a06e Translated on translate.pretix.eu (Spanish)
Currently translated at 95.7% (2870 of 3000 strings)

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

powered by weblate
2019-02-20 19:53:27 +00:00
Alvaro Enrique Ruano
6bcf884b7a Translated on translate.pretix.eu (Spanish)
Currently translated at 95.7% (2870 of 3000 strings)

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

powered by weblate
2019-02-20 19:53:27 +00:00
Raphael Michel
d319293da8 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (3000 of 3000 strings)

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

powered by weblate
2019-02-20 19:53:26 +00:00
Raphael Michel
832c58d288 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (3000 of 3000 strings)

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

powered by weblate
2019-02-20 19:52:39 +00:00
Raphael Michel
c251e0e7d3 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-02-20 17:52:59 +01:00
Raphael Michel
27437e065a Update from Weblate (#1184) 2019-02-20 17:52:26 +01:00
oocf
86534aa7cc Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (68 of 68 strings)

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

powered by weblate
2019-02-20 16:51:29 +00:00
oocf
379a2140c8 Translated on translate.pretix.eu (Spanish)
Currently translated at 96.0% (2843 of 2960 strings)

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

powered by weblate
2019-02-20 16:51:29 +00:00
Raphael Michel
67059fe323 Add a simple test mode (#1181)
- [x] Provide data model and configuration toggle
- [x] Allow to delete individual test orders
- [x] Add tests
- [x] Add a prominent warning message to the backend if test mode orders exist (even though test mode is off), as this leads to wrong statistics
- [x] Decide if and how to generate invoices for test orders as invoice numbers cannot be repeated or should not have gaps.
- [x] Decide if and how we expose test orders through the API, since our difference pull mechanism relies on the fact that orders cannot be deleted.
- [x] Decide if and how we want to couple test modes of payment providers?
- [ ] pretix.eu: Ignore test orders for billing
- [ ] Adjust payment providers: Mollie, bitpay, cash, fakepayment, sepadebit

![download](https://user-images.githubusercontent.com/64280/53009081-fe420d80-343a-11e9-8361-b8511c988598.png)
2019-02-20 17:51:26 +01:00
Martin Gross
8ffc96bf31 Return pdf_data localized to the order's locale (Z#131360) 2019-02-20 16:47:29 +01:00
Raphael Michel
58b688628e Disable logging of unknown hosts 2019-02-20 16:44:00 +01:00
Raphael Michel
3f7348717b Include pending_sum in mail notifications 2019-02-20 15:12:33 +01:00
Raphael Michel
90c8e0c172 Ensure attendee name in email renderer test 2019-02-20 15:09:36 +01:00
Raphael Michel
d35ad345d7 Allow to use event meta data in email templates 2019-02-20 14:33:45 +01:00
Raphael Michel
21634369a8 Fix thumbnail scaling of portrait pictures 2019-02-20 13:50:46 +01:00
Martin Gross
a2b075c0d7 Filter sensitive keys from log-messages (#1186) 2019-02-20 13:37:44 +01:00
Martin Gross
0617abe6e3 Change test eMail address to accomodate RFC2606-sensitive mailservers (Z#134234) 2019-02-20 10:03:31 +01:00
Raphael Michel
040466353c Fix order of imports 2019-02-19 15:47:47 +01:00
Raphael Michel
46b7e9467b ibanformat filter: don't fail on empty values 2019-02-19 15:01:16 +01:00
Raphael Michel
283ff3b5e5 Merge pull request #1159 from pretix-translations/weblate-pretix-pretix
Update from Weblate
2019-02-19 14:16:04 +01:00
Raphael Michel
b0bb22ea38 Translated on translate.pretix.eu (Greek)
Currently translated at 0.6% (17 of 2960 strings)

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

powered by weblate
2019-02-19 13:15:41 +00:00
Alvaro Enrique Ruano
334ee98318 Translated on translate.pretix.eu (Spanish)
Currently translated at 94.4% (2794 of 2960 strings)

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

powered by weblate
2019-02-19 12:49:40 +00:00
Alvaro Enrique Ruano
c4d342029b Translated on translate.pretix.eu (Spanish)
Currently translated at 94.4% (2794 of 2960 strings)

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

powered by weblate
2019-02-19 12:49:40 +00:00
Alvaro Enrique Ruano
bc86f9c059 Translated on translate.pretix.eu (Spanish)
Currently translated at 94.4% (2794 of 2960 strings)

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

powered by weblate
2019-02-19 12:49:40 +00:00
Alvaro Enrique Ruano
51107fe4fd Translated on translate.pretix.eu (Spanish)
Currently translated at 94.2% (2787 of 2960 strings)

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

powered by weblate
2019-02-19 12:49:40 +00:00
Martin Gross
3d65c2fd51 Migration for event.plugins non-null default 2019-02-19 13:48:32 +01:00
Alexander Schwartz
9b394b3833 Enable nl2br plugin for Markdown rendering (#1162)
The frontpage text is already markdown, and will receive its formatting via the rich_text filter.
When applying the additional linebreaksbr filter, it will add unnecessary blank lines.

I'm using the hosted pretix version. 

Test for frontpage text: 

````
Test 

* test1
* test2
````

Before (screenshot): 

---

![before](https://user-images.githubusercontent.com/3957921/52533531-5428fe00-2d35-11e9-891d-f822c3524b0e.png)

----


After (screenshot): 

----
![after](https://user-images.githubusercontent.com/3957921/52533532-568b5800-2d35-11e9-9702-4c713a110c81.png)

----
2019-02-19 12:51:33 +01:00
Martin Gross
d5747084ec Fix: Make event.plugins non-Null by default 2019-02-19 12:38:32 +01:00
Raphael Michel
777772b89e Remove spaces from locale URLs 2019-02-18 15:12:45 +01:00
Raphael Michel
c202286470 Fix #212 -- Different priorization of locale sources between backend and frontend 2019-02-18 15:12:05 +01:00
Raphael Michel
0c1738b9bb Refs #212 -- Do not set user locale if switched in frontend 2019-02-18 15:05:49 +01:00
Raphael Michel
af607083cb Add a custom field renderer for checkout 2019-02-17 21:22:47 +01:00
Raphael Michel
def7918b29 Fix wrong string interpolation in invoice generation 2019-02-17 21:22:47 +01:00
Raphael Michel
0933fc848d Order search: Fight the database optimizer to actually optimize the query 2019-02-15 11:45:06 +01:00
Raphael Michel
166f8b8a2a Fix typo in require_approval webhook 2019-02-15 08:50:36 +01:00
Raphael Michel
70fcba96a5 Add __str__ methods to more models 2019-02-14 18:50:27 +01:00
Raphael Michel
2d2d62045a Do not mark orders as paid when changed to free if they require approval 2019-02-14 18:38:33 +01:00
Raphael Michel
3988f1e2f6 Fix a typo in docs 2019-02-14 18:34:41 +01:00
Raphael Michel
d3ecb92108 Remove "refunded" from state diagram 2019-02-14 18:34:37 +01:00
Raphael Michel
b3debdfb55 Order list: Add filter for canceled with and without paid fee 2019-02-14 10:15:55 +01:00
Raphael Michel
abb770a8e7 Prevent events from being set to None through the API 2019-02-14 10:15:55 +01:00
Raphael Michel
72a2d0da35 Search in e-mail adresses during checkin 2019-02-14 10:15:55 +01:00
Raphael Michel
937cec53f7 Optimize queries for pdf_data=true 2019-02-14 10:15:55 +01:00
Raphael Michel
6e4af5da64 Perform order search on database replica 2019-02-14 10:15:02 +01:00
Raphael Michel
7ed35e06ba Allow to configure a database replica 2019-02-14 10:14:23 +01:00
Raphael Michel
55841ea660 Make sure total is calculated as a Decimal 2019-02-12 16:27:37 +01:00
Raphael Michel
78544cdb30 Implement a strong locking check to avoid race conditions during payment 2019-02-12 16:24:32 +01:00
Martin Gross
37183aced7 Disable Autocomplete for Date/Time-fields 2019-02-12 16:16:12 +01:00
Raphael Michel
a7d3cb134c Fix a token mismatch 2019-02-12 15:40:06 +01:00
Raphael Michel
da8f7f163f Check-in API: Include position data 2019-02-12 15:40:06 +01:00
Raphael Michel
89d612beed Fix bug in checkinlist view 2019-02-11 16:39:50 +01:00
Raphael Michel
f23de7e2c0 Order change: Allow to ignore quotas 2019-02-11 16:15:54 +01:00
Raphael Michel
d073007fd7 Order change: Allow to keep price when changing items 2019-02-11 16:15:13 +01:00
Raphael Michel
d9d1c83218 Optimize the check-in list view 2019-02-11 15:58:39 +01:00
Raphael Michel
ae9b8bafb8 Add missing migration 2019-02-08 15:33:26 +01:00
Raphael Michel
cbf5c2ec1d Fix ZeroDivisionError if a voucher tag is given to a voucher with max_usages=0
Fix PRETIXEU-V7
2019-02-08 13:59:05 +01:00
Raphael Michel
17392f3ef4 Store subevent data for invoice lines 2019-02-08 13:56:04 +01:00
Raphael Michel
bf36ad009f Don't request a refund if there's actually no money involved 2019-02-08 11:27:09 +01:00
Martin Gross
ca9e4823e2 Fix wrong CSV-Checkinlist link 2019-02-08 10:47:17 +01:00
Raphael Michel
d505422e0f Order overview: Make table easier to read 2019-02-07 15:28:51 +01:00
Raphael Michel
33c43ce482 Add columns lines to PDF overview export 2019-02-07 15:21:17 +01:00
Raphael Michel
f273cf4960 Fix misaligned PDF report 2019-02-07 15:21:12 +01:00
Raphael Michel
afdf09eeb4 Merge branch 'master' of github.com:pretix/pretix 2019-02-07 15:02:14 +01:00
Raphael Michel
01e5872f61 Update jsonfallback again 2019-02-07 15:01:55 +01:00
Maximilian Hils
14cc31c810 Fix payment instruction display. (#1161) 2019-02-07 14:30:26 +01:00
Raphael Michel
2972129547 Sentry: Fix a bug leading to it ignoring *everything* 2019-02-06 11:16:38 +01:00
Raphael Michel
ec4227651a Do not try to reduce voucher usage below 0 2019-02-06 10:35:54 +01:00
Raphael Michel
77950de588 Voucher bulk delete: Remove cart positions as well 2019-02-06 10:28:26 +01:00
Raphael Michel
187576eee5 Fix a ProtectedError in cart handling
FIx PRETIXEU-TR
2019-02-06 10:25:53 +01:00
Raphael Michel
0e513a0985 Require specific jsonfallback version 2019-02-06 09:59:44 +01:00
Raphael Michel
1cde728ffe Order search: Add missing field to .only() call 2019-02-06 09:52:11 +01:00
Raphael Michel
76893caffc Sentry: Ignore django.security.DisallowedHost 2019-02-05 18:15:21 +01:00
Raphael Michel
a539999c04 Sentry: Do not report retried celery tasks 2019-02-05 17:19:18 +01:00
Raphael Michel
b9c570b3d8 Sentry: Tune log levels 2019-02-05 16:35:40 +01:00
Raphael Michel
48b399424a Delete voucher even if it is contained in carts
Fix PRETIXEU-R1
2019-02-05 15:47:11 +01:00
Raphael Michel
1c73f000a9 Fix TypeError
PRETIXEU-T6
2019-02-05 15:00:58 +01:00
Raphael Michel
d0721165c1 Add distinct call back in in some cases 2019-02-05 12:10:27 +01:00
Raphael Michel
bed0a0ceeb Switch from raven to sentry_sdk 2019-02-05 11:25:58 +01:00
Raphael Michel
b53ee1dc1d Bump version to 2.5.0.dev0 2019-02-04 16:06:43 +01:00
Raphael Michel
41b56c00e5 Bump version to 2.4.0 2019-02-04 16:05:32 +01:00
Raphael Michel
cb17febf7c Add squashed migration 2019-02-04 15:48:29 +01:00
Raphael Michel
07d42a4d77 Invoice exporter: Fix missing filter on query 2019-02-04 14:47:24 +01:00
Raphael Michel
e3ebf887a4 Implement Invoice.__repr__ 2019-02-04 14:07:18 +01:00
Raphael Michel
0440187e59 Do not break on empty invoices
Sentry PRETIXEU-S8
2019-02-04 14:07:01 +01:00
Raphael Michel
dfcda0fa2c Merge pull request #1158 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-02-04 13:52:53 +01:00
Maarten van den Berg
560c0a8729 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2960 of 2960 strings)

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

powered by weblate
2019-02-04 08:45:08 +00:00
Maarten van den Berg
bc80b60b04 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2960 of 2960 strings)

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

powered by weblate
2019-02-04 08:45:08 +00:00
Maarten van den Berg
08bf3648ea Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2960 of 2960 strings)

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

powered by weblate
2019-02-04 08:45:08 +00:00
Raphael Michel
f8ee7acad6 Fix import order 2019-02-04 09:38:09 +01:00
Raphael Michel
10c86869ea Sendmail: Do not fail to show logs with status "r"
Fix sentry PRETIXEU-S3
2019-02-04 09:10:19 +01:00
Raphael Michel
9034a98df9 Remove empty translation block 2019-02-01 17:38:32 +01:00
Raphael Michel
a7142fdf55 Merge pull request #1156 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-02-01 17:38:19 +01:00
Raphael Michel
ee97c46aec Translated on translate.pretix.eu (German)
Currently translated at 99.9% (2959 of 2960 strings)

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

powered by weblate
2019-02-01 16:38:02 +00:00
Raphael Michel
7063f32f24 Translated on translate.pretix.eu (German (informal))
Currently translated at 99.9% (2959 of 2960 strings)

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

powered by weblate
2019-02-01 16:37:37 +00:00
Raphael Michel
2ec926b7c7 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-02-01 17:27:33 +01:00
Flavia Bastos
834b5a26a5 Adjust message if there's only one addon (#1147)
Relates to #1091
2019-02-01 17:26:37 +01:00
Raphael Michel
90f08d0aca Merge pull request #1146 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-02-01 17:26:00 +01:00
Alvaro Enrique Ruano
d5c2637198 Translated on translate.pretix.eu (Spanish)
Currently translated at 95.3% (2796 of 2934 strings)

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

powered by weblate
2019-02-01 16:25:27 +00:00
Raphael Michel
f517ba51bd Added translation on translate.pretix.eu (Greek) 2019-02-01 16:25:27 +00:00
Raphael Michel
d738198ec5 Added translation on translate.pretix.eu (Greek) 2019-02-01 16:25:27 +00:00
Maarten van den Berg
b1ce58d06c Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-02-01 16:25:27 +00:00
Maarten van den Berg
b26ef74128 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-02-01 16:25:27 +00:00
Maarten van den Berg
4f8c8ea917 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-02-01 16:25:27 +00:00
Maarten van den Berg
0803b049af Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-02-01 16:25:27 +00:00
Raphael Michel
97f3fbdb80 Fix legacy field name
Sentry PRETIXEU-S0
2019-02-01 17:20:48 +01:00
Raphael Michel
434b6e4729 Offer multi-sheet order report 2019-02-01 17:20:00 +01:00
Raphael Michel
f56bceb55f Remove a redundant string 2019-02-01 16:53:37 +01:00
Raphael Michel
2aa246b3d5 Allow to exclude items from ticket generation explicitly 2019-02-01 16:48:58 +01:00
Raphael Michel
f77b551aa6 Badges: Allow to disable per-product 2019-02-01 16:48:58 +01:00
Raphael Michel
c9415cba2b Allow to add a custom text above the payment choice 2019-02-01 16:48:58 +01:00
Raphael Michel
4dae224d73 Statistics: Ellipsize long product names 2019-02-01 16:48:58 +01:00
Raphael Michel
13cc57e98b Add multi-sheet export for invoices 2019-02-01 16:45:48 +01:00
Raphael Michel
6f980b82ac Sort exporters by name alphabetically 2019-01-31 18:44:12 +01:00
Raphael Michel
f32c581a9e Add MultiSheetListExporter base class 2019-01-31 18:43:42 +01:00
Thomas Schüßler
fcadfffb92 fixed a typo (#1152) 2019-01-30 14:36:20 +01:00
Raphael Michel
9e43459879 Widget: Guard against missing xhr.responseURL in Internet Explorer 2019-01-30 12:12:25 +01:00
Raphael Michel
87424c25de Logging: Automatically serialize file objects
Sentry PRETIXEU-RY
2019-01-30 10:59:00 +01:00
Raphael Michel
acdf7d62b5 Sort migrations 2019-01-30 10:17:16 +01:00
Raphael Michel
944138f7a9 Flake8 update 2019-01-30 09:31:34 +01:00
Raphael Michel
5da2eab1fb Fix deletion order 2019-01-30 09:26:01 +01:00
Raphael Michel
d680937a6c Work around flake8 issues 2019-01-30 08:59:58 +01:00
Raphael Michel
f35c2544b6 Do not attach empty files for orders without tickets 2019-01-29 17:12:38 +01:00
Raphael Michel
0285cd12f7 Optimize SQL queries in order list and order search 2019-01-29 16:45:01 +01:00
Martin Gross
03cacace57 Fix missing/redundant favicon 2019-01-28 17:57:12 +01:00
Martin Gross
6ed016e49e Define Favicons on Organizer-level 2019-01-28 17:29:39 +01:00
Raphael Michel
da8da01614 Fix #1148 -- Reduce number of cases in which we show "Reserved" 2019-01-28 10:43:52 +01:00
Raphael Michel
9a2ea6699a Fix obsolete tests 2019-01-28 09:10:18 +01:00
Raphael Michel
51a8bac9e6 Enforce type of log data 2019-01-28 09:10:18 +01:00
Raphael Michel
303ed07504 Voucher API: Never use a list in log_action(data) 2019-01-28 09:10:18 +01:00
bastardop
c7627f631f Docs: Added Debian dependency (#1149)
the libopenjp2-7-dev Packages was required during installation on raspbian
2019-01-28 08:59:49 +01:00
Raphael Michel
604c31c6e2 Bank transfer: Allow to manually accept payments of all amounts 2019-01-28 08:49:19 +01:00
Raphael Michel
c3da6731a1 Allow to delete an event with cart positions 2019-01-25 15:59:34 +01:00
Raphael Michel
6e556ab09b Fix wrong warning message 2019-01-25 13:13:45 +01:00
Raphael Michel
16622883f6 Add referer meta tag to backend 2019-01-25 11:08:56 +01:00
Raphael Michel
cce4379d3e Type-cast cancellation fee 2019-01-24 10:11:24 +01:00
Raphael Michel
5af99f4f1a Check-in: Do not ask questions that are already answered 2019-01-23 16:05:27 +01:00
Raphael Michel
9ed49888b8 Fix #1144 -- Make invoice form all-optional in backend 2019-01-23 10:27:09 +01:00
Raphael Michel
5bfb00db73 Upgrade bs4 and be compatible to latest soupsieve 2019-01-23 09:44:03 +01:00
Raphael Michel
a031d72ca9 Widget: Follow redirects 2019-01-22 18:06:56 +01:00
Raphael Michel
15a190cdf3 Widget: Remove debug output 2019-01-22 17:23:13 +01:00
Raphael Michel
d181375479 Consistent number formatting in widget 2019-01-21 10:54:30 +01:00
Raphael Michel
d8a57b0baa Conditionally show decimal places for tax rates 2019-01-21 10:53:50 +01:00
Raphael Michel
d482bc9de0 Prevent accumulation of tax rates when copying events 2019-01-21 10:43:27 +01:00
Raphael Michel
5c030796d7 Add more information to event copy choice 2019-01-21 10:37:54 +01:00
Raphael Michel
f6eb3bfb80 Remove redundant form option 2019-01-21 10:27:44 +01:00
Raphael Michel
3703fbcacf Do not allow customers to cancel checked-in orders 2019-01-21 09:09:54 +01:00
Lukas Bockstaller
cdea6eb55e Updates the dependency versions for flake 8 (#1143) 2019-01-21 08:58:26 +01:00
Maarten van den Berg
bf1e9d47d0 Fix #1111 -- Duplicate voucher warning (#1142)
Adds a new method to Voucher that selects all distinct orders containing
a position where the Voucher has been used, and changes the Voucher
detail view to use this method for the warning.
2019-01-21 08:52:31 +01:00
Raphael Michel
350df2a3cc Merge pull request #1141 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-01-21 08:51:21 +01:00
Maarten van den Berg
bc6915b251 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-01-21 05:00:28 +00:00
Maarten van den Berg
f9c7eeff9a Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-01-21 05:00:07 +00:00
Maarten van den Berg
247bcf0a20 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-01-20 06:00:09 +00:00
Maarten van den Berg
455c961fc7 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-01-19 21:39:08 +00:00
Maarten van den Berg
9052d4a7a9 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-01-19 21:13:37 +00:00
Raphael Michel
589401e8d2 Merge pull request #1140 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-01-18 17:38:24 +01:00
Raphael Michel
0c366a8473 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-01-18 16:38:00 +00:00
Raphael Michel
c9ddbd0e88 Merge pull request #1139 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-01-18 17:37:56 +01:00
Raphael Michel
31bf0c24f1 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-01-18 16:37:29 +00:00
Raphael Michel
c74386346b Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2934 of 2934 strings)

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

powered by weblate
2019-01-18 16:34:22 +00:00
Raphael Michel
725e1f019e Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-01-18 17:25:27 +01:00
Raphael Michel
06eddb2c6d Self-service refund form (#1135)
* Auto-refund

* Add missing template

* Notification for requested refund

* Model-level tests

* Add front-end tests

* Default to notify
2019-01-18 17:24:42 +01:00
Raphael Michel
80b5750756 New content for / index page 2019-01-18 17:24:28 +01:00
Raphael Michel
f37d265534 Refresh design for auth and error pages 2019-01-18 17:24:28 +01:00
Raphael Michel
7c4a1e5fb8 Merge pull request #1138 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-01-18 16:56:39 +01:00
sohalt
9a045c76ec Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2907 of 2907 strings)

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

powered by weblate
2019-01-18 03:06:42 +00:00
Lorhan Sohaky
447b36fdd3 Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 15.9% (462 of 2907 strings)

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

powered by weblate
2019-01-16 23:00:17 +00:00
Guillaume Petit
5dbd984178 Translated on translate.pretix.eu (French)
Currently translated at 80.0% (2326 of 2907 strings)

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

powered by weblate
2019-01-16 22:00:09 +00:00
Raphael Michel
95f96f8321 Fix default public name of bank transfer 2019-01-16 08:26:56 +01:00
Raphael Michel
3933032778 Merge pull request #1136 from MacLemon/tippfehler-ausbessern
Corrected language typo.
2019-01-14 22:53:09 +01:00
Pepi Zawodsky
d0b18d9f64 Corrected language typo.
Dieser Satz kein Hilfsverb.
2019-01-14 22:41:12 +01:00
Raphael Michel
71de71ed37 PDF: Fix bug with rendering name parts 2019-01-14 11:37:58 +01:00
Raphael Michel
3438d079d5 Fix a sign error 2019-01-13 11:48:52 +01:00
Raphael Michel
e7730333c2 Show refund status to customer on order page 2019-01-12 22:33:09 +01:00
Raphael Michel
e8b9f0a3ae Frontend order view: Do not recommend download for canceled orders 2019-01-12 22:19:10 +01:00
Raphael Michel
77ebd18404 Backend order view: Show canceled fees 2019-01-12 22:18:55 +01:00
Raphael Michel
2d48198c83 Ignore database-level floating point errors 2019-01-12 22:18:36 +01:00
Raphael Michel
d103b0bb84 Weblate script: pull after push 2019-01-12 17:09:18 +01:00
Raphael Michel
01411b84e4 Merge pull request #1134 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-01-12 17:03:02 +01:00
Raphael Michel
b7e154d8c9 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2907 of 2907 strings)

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

powered by weblate
2019-01-12 16:02:41 +00:00
Raphael Michel
f39ac96322 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2907 of 2907 strings)

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

powered by weblate
2019-01-12 16:01:53 +00:00
Raphael Michel
74db808978 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-01-12 16:55:03 +01:00
Raphael Michel
ab72b93706 Invoice: Show number of pages next to page number 2019-01-12 16:54:37 +01:00
Raphael Michel
af5aece639 Add beneficiaries to invoice addresses 2019-01-12 16:54:37 +01:00
Raphael Michel
228ab15900 Bank transfer: Allow to set custom public name 2019-01-12 16:54:37 +01:00
Raphael Michel
66164d8202 Invoice renderer: Make it easier to change fonts 2019-01-12 16:54:37 +01:00
Raphael Michel
d5ac155914 Add is_available hook for plugin configs 2019-01-12 16:54:37 +01:00
Raphael Michel
75a966529e Merge pull request #1132 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-01-11 15:57:47 +01:00
Raphael Michel
28a6a6185d Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2904 of 2904 strings)

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

powered by weblate
2019-01-11 14:57:27 +00:00
Raphael Michel
07cdaa9ca9 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2904 of 2904 strings)

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

powered by weblate
2019-01-11 14:57:13 +00:00
Raphael Michel
1c6935ebd9 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-01-11 15:47:30 +01:00
Raphael Michel
60c1ea8aad Allow to keep cancellation fees (#1130)
* Allow to keep cancellation fees

* Add tests and clarifications

* Add API
2019-01-11 15:42:33 +01:00
Raphael Michel
0b8798a65c Add self-crashing test 2019-01-10 18:17:29 +01:00
Raphael Michel
a8836cbeec Remove some irregularities in 8abfbba9 2019-01-10 17:41:18 +01:00
Raphael Michel
336a34b10b Merge pull request #1131 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-01-10 17:24:31 +01:00
Raphael Michel
c5862cc0a0 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2901 of 2901 strings)

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

powered by weblate
2019-01-10 16:23:32 +00:00
Raphael Michel
89cdcd3781 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2901 of 2901 strings)

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

powered by weblate
2019-01-10 16:21:10 +00:00
Raphael Michel
2837cac554 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2019-01-10 16:57:52 +01:00
Raphael Michel
3b54556739 Remove notification type for refunded event 2019-01-10 16:57:27 +01:00
Raphael Michel
4d6d6ff737 Merge pull request #1129 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-01-10 16:57:10 +01:00
Maarten van den Berg
ffee31e415 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2019-01-10 15:56:44 +00:00
Raphael Michel
8abfbba9d0 Refactor cancelling positions and orders in the data model (#1088)
- [x] Data model
- [x] display in order view in backend
- [x] review all usages of OrderPositions.objects
- [x] review all usages of order.positions
- [x] review all other model usages
- [x] review plugins
- [x] plugins backwards-compatible API?
- [x] decide on way forward for REST API
- [x] need to cancel fees
- [x] tests
- [ ] plugins
  - [ ] gdpr
  - [ ] reports
- [x] docs
2019-01-10 16:52:34 +01:00
Raphael Michel
588955901c Pin oauthlib version 2019-01-09 14:19:02 +01:00
Raphael Michel
4b7bf2f27f Merge pull request #1118 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2019-01-09 12:33:18 +01:00
Raphael Michel
664957e886 Add now_date 2019-01-09 12:29:37 +01:00
Raphael Michel
f15a6d39c3 Add now_* variables to PDFs 2019-01-09 12:17:31 +01:00
Maarten van den Berg
3fd80a9a46 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (68 of 68 strings)

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

powered by weblate
2019-01-08 21:00:37 +00:00
Maarten van den Berg
2fd2716303 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2019-01-08 21:00:08 +00:00
Maarten van den Berg
37315fc380 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2019-01-08 13:00:29 +00:00
Maarten van den Berg
f96fc0744e Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (68 of 68 strings)

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

powered by weblate
2019-01-08 12:30:26 +00:00
Maarten van den Berg
5bb7883020 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2019-01-08 12:25:27 +00:00
Maarten van den Berg
3f95434922 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Maarten van den Berg
08da5a8b91 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (68 of 68 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Maarten van den Berg
97dc4421ea Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Maarten van den Berg
26ca2ff006 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (68 of 68 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Maarten van den Berg
980c359f57 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 2.9% (2 of 68 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Maarten van den Berg
ff1198dec6 Translated on translate.pretix.eu (Dutch (informal))
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Raphael Michel
7275de94af Added translation on translate.pretix.eu (Dutch (informal)) 2019-01-07 09:21:52 +00:00
Raphael Michel
ed46f41f8c Added translation on translate.pretix.eu (Dutch (informal)) 2019-01-07 09:21:52 +00:00
Alexey Zh
1078e38890 Translated on translate.pretix.eu (Russian)
Currently translated at 16.2% (11 of 68 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
amefad
2e9bbff308 Translated on translate.pretix.eu (Italian)
Currently translated at 33.8% (23 of 68 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Maarten van den Berg
13a48701fa Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (68 of 68 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Alexey Zh
ddc9c850c0 Translated on translate.pretix.eu (Russian)
Currently translated at 0.4% (13 of 2890 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
amefad
fa0dae6ed6 Translated on translate.pretix.eu (Italian)
Currently translated at 2.7% (79 of 2890 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Maarten van den Berg
da6176a51e Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Maarten van den Berg
4ef6659551 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (68 of 68 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Alexey Zh
82624a1dc0 Added translation on translate.pretix.eu (Russian) 2019-01-07 09:21:52 +00:00
Alexey Zh
b50add260a Added translation on translate.pretix.eu (Russian) 2019-01-07 09:21:52 +00:00
Bruno Damasceno Martins
f72f97d366 Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 10.4% (300 of 2890 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Alexander Schwartz
ad46e9e541 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2019-01-07 09:21:52 +00:00
Raphael Michel
343dbc00be Bank transfer: Add a note on how to proceed 2019-01-07 10:21:35 +01:00
Raphael Michel
3cb94f702d Revert accidental commit part 2019-01-04 10:24:06 +01:00
Raphael Michel
ddeae224fb Log SMTP failures and retry after some error codes 2019-01-04 09:54:43 +01:00
Raphael Michel
3c57895101 Don't mark orders as pending unnecessarily 2019-01-03 09:50:56 +01:00
Raphael Michel
687c85eb58 Stripe: Full source state handling 2019-01-03 09:43:07 +01:00
Raphael Michel
90ffdbdfa3 Stripe: Allow failed payments to succeed 2019-01-03 09:37:31 +01:00
Raphael Michel
654be0db34 Stripe: Prevent race condition between ReturnView and webhook 2019-01-03 09:34:15 +01:00
Raphael Michel
82e3359b40 Allow to filter subevents by date 2019-01-02 16:59:03 +01:00
Raphael Michel
01a6861453 Always query emails case-insensitively 2019-01-02 15:12:48 +01:00
Raphael Michel
7f6cdd6241 Fix ProtectedError when deleting expired card positions 2019-01-02 15:05:30 +01:00
Raphael Michel
aad1fda31f Register nl_Informal translation 2019-01-02 10:12:17 +01:00
Raphael Michel
ad462921f0 Pin bs4 version due to regression 2019-01-02 09:58:50 +01:00
Raphael Michel
dc433f6420 Reverse on global URL config in build_absolute_uri 2019-01-02 09:24:47 +01:00
Raphael Michel
2d8b3d1c79 PayPal: Fix backwards compatibility 2019-01-02 09:20:35 +01:00
Raphael Michel
eb85fa956e Badges: Add per-position downloads 2018-12-19 12:31:44 +01:00
Raphael Michel
215514fca7 Add ticket downloads to the backend 2018-12-19 12:31:24 +01:00
Raphael Michel
3fe2dfe810 Add signal order_position_buttons 2018-12-19 12:29:52 +01:00
Raphael Michel
041d05eb66 Support product pictures for add-on products 2018-12-19 09:37:30 +01:00
Raphael Michel
d05530ddfc Explicit ordering of check-in lists 2018-12-19 09:20:44 +01:00
Raphael Michel
734e77d1a3 API: Allow to redeem ticket by secret 2018-12-18 12:23:07 +01:00
Raphael Michel
633061e203 Avoid paid orders without payment_date 2018-12-18 10:07:17 +01:00
Raphael Michel
e11ee4a427 Do not allow to delete vouchers assigned to canceled orders 2018-12-18 10:07:17 +01:00
Alvaro Enrique Ruano
1edcd47703 Support for daterange in spanish (#1125) 2018-12-17 22:32:15 +01:00
Raphael Michel
cf4b2544f2 Never create implicit payments for orders that require approval 2018-12-14 10:42:08 +01:00
Raphael Michel
04c3cffd43 Fix severe translation mistake 2018-12-12 16:42:47 +01:00
Raphael Michel
483d41c7a6 Event plugin list: Use a more useful sorting of the list 2018-12-12 16:42:47 +01:00
Martin Gross
b0c4c88d01 Fix #1119 - Proper indent and pluralisation (#1120) 2018-12-12 08:59:54 +01:00
Martin Gross
518298f71c Add media-src CSP to middleware (#1121) 2018-12-12 08:59:22 +01:00
Raphael Michel
62c2e7765b Fix wrong variable 2018-12-11 17:00:05 +01:00
Raphael Michel
2bb2a40509 Add new signal checkout_all_optional 2018-12-11 16:44:15 +01:00
Raphael Michel
49828186b0 Signals: Pretictable call order, not return order 2018-12-11 16:43:07 +01:00
Raphael Michel
c07a6cb4aa Small query optimization 2018-12-11 16:15:54 +01:00
Tobias Kunze
67ad9a0dcb Provide send_robust on EventSignals (#1116) 2018-12-11 16:15:22 +01:00
Raphael Michel
d267dfc682 Fix #785 -- Show availability in (sub)event list (#1112) 2018-12-11 13:59:49 +01:00
Raphael Michel
eed220f14a pytest-xdist is required by now 2018-12-11 12:54:30 +01:00
Raphael Michel
85289fe0d1 Fix error when marking an expired order as paid 2018-12-07 11:04:41 +01:00
Raphael Michel
6293ad34d4 Add a headline to payment provider settings 2018-12-06 10:56:40 +01:00
Raphael Michel
0dc4f61cf0 Fix a docs spelling error 2018-12-06 10:42:50 +01:00
Raphael Michel
6849e682d7 Bump verstion to 2.4.0.dev0 2018-12-06 10:17:39 +01:00
Raphael Michel
6b725a9db9 Bump version to 2.3.0 2018-12-06 10:16:55 +01:00
Raphael Michel
989ebbb444 Merge pull request #1115 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-12-05 17:13:35 +01:00
Raphael Michel
0a6efc1e0f Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2018-12-05 16:12:43 +00:00
Raphael Michel
d577a0d286 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2890 of 2890 strings)

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

powered by weblate
2018-12-05 16:11:04 +00:00
Raphael Michel
6b9b379ce2 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-12-05 17:05:14 +01:00
Raphael Michel
13234b6fd5 Merge pull request #1108 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-12-05 17:04:36 +01:00
Raphael Michel
2fa0067663 Revert "Update po files"
This reverts commit 4e37fa5778.
2018-12-05 17:04:22 +01:00
Raphael Michel
4e37fa5778 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-12-05 17:02:53 +01:00
Alexander Schwartz
bfb74448b1 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (69 of 69 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
arabestia
a255082b07 Translated on translate.pretix.eu (Spanish)
Currently translated at 97.3% (2797 of 2874 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
Matheus Nunes
14df35bd90 Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 10.4% (300 of 2874 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
Alexander Schwartz
bd0ba7baa5 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2874 of 2874 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
Alexander Schwartz
9aa220b95b Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2874 of 2874 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
Alexander Schwartz
3ed4be63fe Translated on translate.pretix.eu (German (informal))
Currently translated at 100,0% (69 of 69 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
Alexander Schwartz
23f4b0b62f Translated on translate.pretix.eu (German)
Currently translated at 100,0% (69 of 69 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
arabestia
4b9acb64da Translated on translate.pretix.eu (Spanish)
Currently translated at 98.6% (68 of 69 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
oocf
ebba0ee0cb Translated on translate.pretix.eu (Spanish)
Currently translated at 97.3% (2796 of 2874 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
arabestia
335ce48d7e Translated on translate.pretix.eu (Spanish)
Currently translated at 97.3% (2796 of 2874 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
Alvaro Enrique Ruano
d9a0c8c523 Translated on translate.pretix.eu (Spanish)
Currently translated at 96.7% (2779 of 2874 strings)

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

powered by weblate
2018-12-05 15:59:59 +00:00
Raphael Michel
a297bd1944 Show that some export parameters are optional 2018-12-05 16:51:08 +01:00
Raphael Michel
953ea26984 Add a custom text to explain usage of vouchers 2018-12-05 16:50:05 +01:00
Raphael Michel
e4f80f7660 Widget: Allow to pre-fill fields in the invoice address 2018-12-05 16:45:05 +01:00
Raphael Michel
128a185957 Improve crashed test handling 2018-12-05 16:16:50 +01:00
Raphael Michel
0bdd14b47a Re-add legacy import 2018-12-05 15:13:50 +01:00
Raphael Michel
3b84b181ad PDF editor: Move questions signal out of ticket provider 2018-12-05 14:45:07 +01:00
Raphael Michel
c9b0626324 Adjust tests to new is_public default 2018-12-04 15:41:57 +01:00
Raphael Michel
dc9a82cade Fix failing tests for previous commits 2018-12-04 15:02:16 +01:00
Raphael Michel
8266733e34 Clarify is_public and turn it on by default 2018-12-04 13:49:25 +01:00
Raphael Michel
246987955b Accept localized input for all fields with localized output 2018-12-02 19:12:21 +01:00
Raphael Michel
b93e7fcb60 Fix #1067 -- Allow to manually create partial payments 2018-12-02 18:32:16 +01:00
Raphael Michel
b1cebdbd99 Fix #582 -- Improve validation of organizer domains 2018-12-02 17:45:48 +01:00
Raphael Michel
d04047abd5 Fix #1105 -- Provide URL in order split log entry 2018-12-02 17:35:31 +01:00
Raphael Michel
efca46945a Fix #953 -- Render markdown in email text previews 2018-12-02 17:11:09 +01:00
Raphael Michel
0f9755e36f Add a warning message to products that are out of timeframe 2018-12-02 16:44:21 +01:00
Raphael Michel
478d8e4116 Add export to .xlsx for lists 2018-11-30 16:10:32 +01:00
Raphael Michel
81693e042c Introduce common base class for CSV exports 2018-11-30 15:56:29 +01:00
Raphael Michel
47b7d7b36c Add separate notification category for orders that require approval 2018-11-30 15:30:35 +01:00
Raphael Michel
ba15c34ce1 Fix #1106 -- Do not send reminders to orders placed in the last two hours 2018-11-30 15:16:12 +01:00
Raphael Michel
94f2ad9325 Highlight items that are unavailable by time 2018-11-29 10:13:43 +01:00
Raphael Michel
d8070ba8a3 Fix missing ticket attachments 2018-11-29 10:01:20 +01:00
Raphael Michel
b1019672b0 Fix file format in real expor 2018-11-27 15:52:57 +01:00
Raphael Michel
631307a4d5 Even with pdftk, use PyPDF to read page size 2018-11-27 09:22:53 +01:00
Raphael Michel
180a26ee1d Fix shredder test 2018-11-26 13:44:40 +01:00
Raphael Michel
7eab1982fe Add support for PDFTK 2018-11-26 13:43:06 +01:00
Raphael Michel
ca59237ebf Use regular asynctasks for order PDF generation 2018-11-26 13:21:25 +01:00
Raphael Michel
cc92210dc2 Retry crashed tests 2018-11-26 12:09:39 +01:00
Raphael Michel
6602afdd6c Use dedicated queue for notifications 2018-11-26 12:05:16 +01:00
Raphael Michel
c7a04bc08a Add cleanup for cached tickets 2018-11-26 12:04:25 +01:00
Raphael Michel
2cc5b7f4e8 Raise error 404 on invalid month 2018-11-26 09:20:48 +01:00
Raphael Michel
453f16af03 Merge pull request #1100 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-11-23 16:22:00 +01:00
Raphael Michel
0f3398ae13 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2874 of 2874 strings)

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

powered by weblate
2018-11-23 15:17:00 +00:00
Raphael Michel
f1b65c8695 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2874 of 2874 strings)

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

powered by weblate
2018-11-23 15:17:00 +00:00
Raphael Michel
2c4c89c8c2 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2874 of 2874 strings)

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

powered by weblate
2018-11-23 15:17:00 +00:00
Raphael Michel
4042b603b7 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2874 of 2874 strings)

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

powered by weblate
2018-11-23 15:17:00 +00:00
Alvaro Enrique Ruano
63b0288383 Translated on translate.pretix.eu (Spanish)
Currently translated at 97.5% (2792 of 2864 strings)

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

powered by weblate
2018-11-23 15:17:00 +00:00
Raphael Michel
7c01fee70b Fix incorrect return statement 2018-11-23 16:16:46 +01:00
Raphael Michel
8127c32ef5 More tolerant i18n deserializing 2018-11-23 16:15:23 +01:00
Raphael Michel
563decdfba Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-11-23 15:37:11 +01:00
Raphael Michel
a205b01d70 Add "paid" note on invoices if there is no open payment 2018-11-23 15:36:39 +01:00
Raphael Michel
b4290384e1 Add sales channels (#1103)
- [x] Data model
- [x] Enforce constraint
- [x] Filter order list
- [x] Set channel on created order
- [x] Products API
- [x] Order API
- [x] Tests
- [x] Filter reports
- [x] Resellers
- [ ] deploy plugins
  - [ ] posbackend
  - [ ] resellers
  - [ ] reports
- [x] Ticketlayouts
- [x] Support in pretixPOS
2018-11-23 15:35:09 +01:00
Raphael Michel
0f76779fb1 Fix involuntarily created invoices 2018-11-21 13:07:28 +01:00
Raphael Michel
f34c528cba Add passphrase to wordlist 2018-11-21 11:51:57 +01:00
Raphael Michel
cf01e04101 PayPal: Improve log display 2018-11-21 11:24:44 +01:00
Martin Gross
a3a63def55 Fix #369 -- Connect with PayPal (#1084)
* Connect with PayPal

* PayPal connect code-review fixes

* PayPal Connect: Global Env selection; Fix for payee-dict

* Fix missing PayPal Connect indicator for Endpoint

* Fix backwards compatibility
2018-11-21 11:14:33 +01:00
Raphael Michel
a3489eea04 PayPal: Properly detect pending sales 2018-11-21 11:14:03 +01:00
Raphael Michel
c6cb98c30a Add documentation on development and enterprise installs 2018-11-21 09:40:44 +01:00
Alvaro Enrique Ruano
332c58c82f Improve docker cache utilization on image construction (#1099)
To provide better use of Docker cache during image build by installing from PIP before creating the layer with the source code (the PIP installations are less probable to change than the rest of the source code, so this layer will not be recreated with all the source code changes)

This was previously discussed in #1094
2018-11-21 09:27:43 +01:00
Raphael Michel
beb0ded6dc Allow to pass user data to the widget (#1095)
- [x] Logic
- [x] Tests
- [x] Docs
- [x] find a way to integrate with tracking
2018-11-20 17:55:37 +01:00
Raphael Michel
b49b2035bd Cancel payments if the pending price of the order changes 2018-11-20 17:41:33 +01:00
Raphael Michel
106c8d373d Fix #1098 -- order search with dash in event slug 2018-11-20 14:33:41 +01:00
Raphael Michel
aee44a3284 Fix marking an overpaid order as paid manually 2018-11-20 10:39:48 +01:00
Raphael Michel
d4c1fcf838 Merge pull request #1087 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-11-20 10:39:26 +01:00
Alvaro Enrique Ruano
832f57c9d7 Translated on translate.pretix.eu (Spanish)
Currently translated at 97.4% (2790 of 2864 strings)

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

powered by weblate
2018-11-20 09:36:17 +00:00
Mikkel Ricky
ac2a9b207d Translated on translate.pretix.eu (Danish)
Currently translated at 57.4% (1645 of 2864 strings)

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

powered by weblate
2018-11-20 09:36:17 +00:00
Maarten van den Berg
f1e5d60a14 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2864 of 2864 strings)

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

powered by weblate
2018-11-20 09:36:17 +00:00
Maarten van den Berg
7b1a1dc754 Translated on translate.pretix.eu (Dutch)
Currently translated at 99.7% (2854 of 2864 strings)

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

powered by weblate
2018-11-20 09:36:17 +00:00
Raphael Michel
c93f804992 Fix #1080 -- Deal with gaps in the invoice database (#1086) 2018-11-20 10:36:13 +01:00
Alvaro Enrique Ruano
1cba4b1d45 Dockerfile improvements for better readability (#1094) 2018-11-20 10:24:53 +01:00
Raphael Michel
22369a5559 Fix CI problems with SQLite (#1076)
* Try to re-enable tests on Py3.5

* Add newer SQLite3
2018-11-19 17:57:50 +01:00
Raphael Michel
a8223ad354 Fix missing return statement 2018-11-19 17:12:48 +01:00
Raphael Michel
c9d3cf7cac Fix exceptions in previous commit 2018-11-19 11:03:54 +01:00
Raphael Michel
bbdbc94f6e Redirect case-insensitive versions of event/organizer slugs 2018-11-19 10:22:40 +01:00
Raphael Michel
5c8d9c4dca Fix incorrect feedback on invite form 2018-11-16 14:13:44 +01:00
Raphael Michel
546ff6e42f Variations: Show a price range on the front page 2018-11-14 15:43:21 +01:00
Raphael Michel
7b7d45ce2e Fix Dutch date format 2018-11-14 15:43:15 +01:00
Raphael Michel
be3ca7c561 Sort cart positions by reasonable values 2018-11-14 15:36:50 +01:00
Raphael Michel
abdb6e2d52 Add "Event date" to PDF editors 2018-11-14 14:39:20 +01:00
Raphael Michel
138ddcdcd7 CSV-export improvements (include voucher, allow semicolon) 2018-11-14 10:02:28 +01:00
Raphael Michel
8ffc6550da Do not allow orders with unavailable items to be completed 2018-11-13 17:55:56 +01:00
Raphael Michel
0734715bab Only warn about bad-contrasat colors 2018-11-13 15:53:18 +01:00
Raphael Michel
7528bfb10b Fix tests for color saving 2018-11-13 14:15:52 +01:00
Raphael Michel
2798fb3468 Avoid setting name_parts to None 2018-11-13 12:59:12 +01:00
Raphael Michel
4e6f4716ec Allow to configure accent colors 2018-11-13 12:56:52 +01:00
Raphael Michel
e523a4e610 Allow to manually generate invoices like in c131ad8c 2018-11-12 13:08:12 +01:00
Raphael Michel
31cec76809 Generate invoice after expired order is extended 2018-11-12 13:08:12 +01:00
Raphael Michel
fdfd9f9275 Fix cart cleanup 2018-11-12 13:08:12 +01:00
oocf
b658c73c19 Removed permissions page from events settings (#1063)
* Removed old configuration for events settings

* Urls file to remove permissions file

* Removing not needed test

* Removing test in permissions
2018-11-12 12:23:40 +01:00
Raphael Michel
ebd3e6f31a Fix TypeError in typeahead 2018-11-12 12:17:49 +01:00
Raphael Michel
ccec114653 Merge pull request #1085 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-11-12 11:41:06 +01:00
Raphael Michel
f0716dc482 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2864 of 2864 strings)

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

powered by weblate
2018-11-12 10:40:47 +00:00
Raphael Michel
513778b2c4 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2864 of 2864 strings)

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

powered by weblate
2018-11-12 10:34:26 +00:00
Raphael Michel
742e403ae2 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-11-12 11:31:32 +01:00
Raphael Michel
09a9d610f8 Make navigation structure more approachable to new users (#1083)
* Move event selector to sidebar

* Unify navigation

* Fix confusing icons
2018-11-12 11:30:36 +01:00
Raphael Michel
b9534f23f5 Delete add-ons explicitly 2018-11-12 11:11:33 +01:00
Raphael Michel
b053f61001 Delete add-on positions explicitly 2018-11-12 11:11:33 +01:00
Felix Rindt
21042f2111 Fix #1071 -- Make payments and invoice address full width panels (#1072)
Solve #1071.

I'm not happy about how the invoice address panel is really wide now.
2018-11-12 10:38:22 +01:00
Raphael Michel
e953474138 Fix a few models.CASCADE 2018-11-11 16:23:37 +01:00
Tobias Kunze
0d438ad07c Remove outline when clicking on checkout progress (#1082) 2018-11-10 14:48:37 +01:00
Raphael Michel
e285b7cff0 Bump version to release 2.3.0.dev0 2018-11-09 16:45:43 +01:00
Raphael Michel
2bb2f30e66 Bump version to 2.2.0 2018-11-09 16:43:59 +01:00
Raphael Michel
9a8d23f582 Banktransfer: use proper formatting for IBANs 2018-11-09 16:42:43 +01:00
Raphael Michel
f37d12e056 Merge pull request #1079 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-11-09 16:26:35 +01:00
Maarten van den Berg
334ffc0be7 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2855 of 2855 strings)

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

powered by weblate
2018-11-09 14:18:13 +00:00
Maarten van den Berg
03f0da4ee6 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (66 of 66 strings)

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

powered by weblate
2018-11-09 14:18:13 +00:00
Raphael Michel
fbbd6eebc0 Refuse to update on old MySQL 2018-11-09 15:17:58 +01:00
Raphael Michel
584ced87db Add /me API endpoint 2018-11-09 11:34:40 +01:00
Raphael Michel
901953d988 Add a Retry-After to 409 responses 2018-11-09 11:13:24 +01:00
Raphael Michel
8c34a47138 Voucher API: Bulk creation 2018-11-09 10:50:21 +01:00
Raphael Michel
0fe3db634c Voucher API: Reduce number of event locks 2018-11-09 10:46:27 +01:00
Raphael Michel
d8d838fc4f Add note on celery-requirement for webhook retrials 2018-11-09 10:45:25 +01:00
Raphael Michel
9b94a1b3b2 Add documentation on rate limits 2018-11-09 10:00:55 +01:00
Raphael Michel
479abc1a65 Add missing screenshots to docs 2018-11-08 17:00:18 +01:00
Raphael Michel
1a17ba13ca Link to documentation 2018-11-08 16:57:30 +01:00
Raphael Michel
371c42b738 Merge pull request #1077 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-11-08 16:57:18 +01:00
Raphael Michel
ed85394845 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2855 of 2855 strings)

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

powered by weblate
2018-11-08 15:56:22 +00:00
Raphael Michel
a9a684a456 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (66 of 66 strings)

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

powered by weblate
2018-11-08 15:55:48 +00:00
Raphael Michel
d7d7792a4a Translated on translate.pretix.eu (German)
Currently translated at 100.0% (66 of 66 strings)

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

powered by weblate
2018-11-08 15:55:48 +00:00
Raphael Michel
c09587f5d3 Translated on translate.pretix.eu (German)
Currently translated at 99.8% (2848 of 2855 strings)

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

powered by weblate
2018-11-08 15:55:48 +00:00
Raphael Michel
23f719381c Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2855 of 2855 strings)

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

powered by weblate
2018-11-08 15:55:48 +00:00
Raphael Michel
d74d39d6e9 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (66 of 66 strings)

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

powered by weblate
2018-11-08 15:55:48 +00:00
Raphael Michel
5f2cf8d3ef Add documentation on webhooks 2018-11-08 16:53:25 +01:00
Raphael Michel
1843799345 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-11-08 16:40:19 +01:00
Raphael Michel
bd838b3b7c Fix #1074 -- More specific messages during asynctasks 2018-11-08 16:38:12 +01:00
Raphael Michel
c2d03f5e6b Fix #526 -- Add a webhook system (#1073)
- [x] Data model
- [x] UI
- [x] Fire hooks
- [x] Unit tests
- [x] Display logs
- [x] API to modify hooks
- [x] Documentation
- [x] More hooks!
2018-11-08 16:38:05 +01:00
Raphael Michel
74e8e73877 Stop testing against Py3.5 2018-11-08 15:44:56 +01:00
Raphael Michel
8830dc8f78 Fix tests for checkin list 2018-11-08 12:04:51 +01:00
Raphael Michel
ac877a7c0d Use 3 SQLite workers 2018-11-08 11:34:02 +01:00
Raphael Michel
0a442e712b Add company to checkin list 2018-11-08 11:01:29 +01:00
Raphael Michel
4477f8001e Adjust test for previous commit 2018-11-07 11:53:13 +01:00
Raphael Michel
152b94428f Make Item.allow_delete() more consistent 2018-11-07 11:19:23 +01:00
Raphael Michel
5390b0b191 API: Allow to sort orders by modification date 2018-11-07 10:29:41 +01:00
Raphael Michel
97de8cea08 Allow cart creation without attendee name 2018-11-06 18:16:54 +01:00
Raphael Michel
cd465c1aad Fix not passing an attendee name in the API 2018-11-06 17:42:18 +01:00
Raphael Michel
449dea41a8 Fix order list export 2018-11-06 14:57:35 +01:00
Raphael Michel
0b1a6e4745 Fix symmetry 2018-11-06 14:57:22 +01:00
Tobias Kunze
e49061e28c Don't check voucher quotas if they bypass quotas (#1070) 2018-11-06 11:01:05 +01:00
Raphael Michel
18cb29b425 Show date in event picker 2018-11-05 22:45:33 +01:00
Raphael Michel
994ff23719 Fix quick event switcher on mobile 2018-11-05 22:24:26 +01:00
Raphael Michel
15d077df6e Add explanation tooltips to invoice regeneration buttons 2018-11-05 21:46:16 +01:00
Raphael Michel
b490aa7f5d Add scheme to sample names 2018-11-05 21:35:44 +01:00
Raphael Michel
ca6b3badde Fix reference to removed field 2018-11-05 21:20:17 +01:00
Raphael Michel
1f200271af Allow rich text in question help texts 2018-11-05 18:07:15 +01:00
Raphael Michel
894a60d016 Merge pull request #1069 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-11-05 16:56:15 +01:00
Raphael Michel
4a2219134b Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2825 of 2825 strings)

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

powered by weblate
2018-11-05 15:55:43 +00:00
Raphael Michel
7d38fc5c03 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2825 of 2825 strings)

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

powered by weblate
2018-11-05 15:50:02 +00:00
Raphael Michel
ef5de187b9 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2825 of 2825 strings)

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

powered by weblate
2018-11-05 15:46:16 +00:00
Raphael Michel
a1c424266b Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-11-05 16:36:29 +01:00
Raphael Michel
557b4b7b6f Merge pull request #1060 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-11-05 16:36:02 +01:00
oocf
98be21253d Translated on translate.pretix.eu (Spanish)
Currently translated at 98.4% (2750 of 2794 strings)

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

powered by weblate
2018-11-05 15:24:40 +00:00
Maarten van den Berg
e5a04ada94 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2794 of 2794 strings)

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

powered by weblate
2018-11-05 15:24:40 +00:00
Maarten van den Berg
9b8b3090e6 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2794 of 2794 strings)

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

powered by weblate
2018-11-05 15:24:40 +00:00
Raphael Michel
e622c3948d Fix buggy migration 2018-11-05 16:24:30 +01:00
Raphael Michel
94be46ffdb Fix #978 -- Allow to split names (#1049)
- [x] attendee names
- [x] Invoice address names
- [x] Data migration
- [x] API serializers
  - [x] orderposition
  - [x] cartposition
  - [x] invoiceaddress
  - [x] checkinlistposition
- [x] position API search
- [x] invoice API search
- [x] business/individual required toggle
- [x] Split columns in CSV exports
- [x] ticket editor
- [x] shredder
- [x] ticket/invoice sample data
- [x] order search
- [x] Handle changed naming scheme
- [x] tests
- [x] make use in:
  - [x] Boabee
  - [x] Certificate download order
  - [x] Badge download order
  - [x] Ticket download order
- [x] Document new MySQL requirement
- [x] Plugins
2018-11-05 15:43:21 +01:00
Raphael Michel
7039374588 Allow to anonymize users 2018-11-05 11:11:43 +01:00
Raphael Michel
0a5347c08b Allow to delete organizers 2018-11-05 11:11:43 +01:00
Raphael Michel
87f3318431 Merge pull request #1006 from pretix/empty_vouchers
Allow to show all vouchers with empty tags
2018-11-05 10:35:22 +01:00
Tobias Kunze
2557a8e4ec Allow to show all vouchers with empty tags 2018-11-05 10:34:25 +01:00
Raphael Michel
aff7094cb0 Fix #1021 -- Bugs in SMTP test 2018-11-05 10:19:29 +01:00
Raphael Michel
5a29b4bf70 Allow to choose French and Spanish 2018-10-31 15:38:38 +01:00
Raphael Michel
e618183b49 Merge pull request #1059 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-10-31 15:38:01 +01:00
Raphael Michel
a18236b12d Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (65 of 65 strings)

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

powered by weblate
2018-10-31 14:34:07 +00:00
Raphael Michel
b5da4e89a6 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2794 of 2794 strings)

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

powered by weblate
2018-10-31 14:33:21 +00:00
Raphael Michel
1da2737427 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2794 of 2794 strings)

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

powered by weblate
2018-10-31 14:30:18 +00:00
Raphael Michel
032fdadc3c Translated on translate.pretix.eu (German)
Currently translated at 100.0% (65 of 65 strings)

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

powered by weblate
2018-10-31 14:26:13 +00:00
Raphael Michel
8ae3ff3fe6 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-10-31 15:21:52 +01:00
Raphael Michel
b8669503fa Only allow restricting payment countries if invoice address is obligatory 2018-10-31 15:21:26 +01:00
Raphael Michel
863165caaa Gracefully handle PayPal exceptions 2018-10-31 15:21:26 +01:00
Raphael Michel
b885f30789 Update from Weblate. (#1039) 2018-10-31 15:18:40 +01:00
Mattias de Hollander
461b62bd51 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (65 of 65 strings)

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

powered by weblate
2018-10-31 09:43:05 +00:00
Maarten van den Berg
23776db3b6 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2773 of 2773 strings)

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

powered by weblate
2018-10-31 09:43:05 +00:00
Arnaud Vergnet
19e91a6c7c Translated on translate.pretix.eu (French)
Currently translated at 100.0% (65 of 65 strings)

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

powered by weblate
2018-10-31 09:43:05 +00:00
Samir C. Costa
6f40325d3f Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 100.0% (65 of 65 strings)

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

powered by weblate
2018-10-31 09:43:05 +00:00
Samir C. Costa
1987bff4b1 Translated on translate.pretix.eu (Portuguese (Brazil))
Currently translated at 100,0% (65 of 65 strings)

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

powered by weblate
2018-10-31 09:43:05 +00:00
Maarten van den Berg
5aa0d55d47 Translated on translate.pretix.eu (Dutch)
Currently translated at 99.0% (2746 of 2773 strings)

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

powered by weblate
2018-10-31 09:43:05 +00:00
Raphael Michel
a28196e930 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2773 of 2773 strings)

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

powered by weblate
2018-10-31 09:43:05 +00:00
Raphael Michel
c55387819d Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2773 of 2773 strings)

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

powered by weblate
2018-10-31 09:43:05 +00:00
Raphael Michel
c8cc527aee OrderChangeManager: Do not mark order pending when adjusting price to actual payment 2018-10-31 10:42:44 +01:00
Raphael Michel
a39b207ad5 Mark manual payment failed if nothing happened 2018-10-31 10:33:23 +01:00
Raphael Michel
ea63b50f2e Bank transfer: consider payments valid even without quota 2018-10-31 10:31:40 +01:00
Raphael Michel
b101251aa4 Log confirmed payments that could not mark the order as paid 2018-10-31 10:31:21 +01:00
Raphael Michel
c9ba72ebc5 Fix a typo 2018-10-31 10:27:14 +01:00
Raphael Michel
4a1c3088a9 Locking in OrderChangeManager caused unexpected problems 2018-10-31 10:04:53 +01:00
Raphael Michel
a480ca1142 Add reverse charge flag to invoices 2018-10-30 10:57:29 +01:00
Tobias Kunze
a928fbfafe Config files (#1057)
* Move coveragerc to setup.cfg

* Move pytest.ini to setup.cfg

Closes #1027
2018-10-30 10:12:17 +01:00
Tobias Kunze
3bf3ff1ee2 Allow empty plugin responses (#1056)
While plugin developers are supposed to return an empty dictionary, it's
conceivable that they might just put in a `return` if their field is not
needed, and pretix being generous about this would be cool.
2018-10-30 10:11:39 +01:00
Raphael Michel
9647cc6cf2 Add more favicons for browser shortcuts 2018-10-30 10:05:48 +01:00
Raphael Michel
df2d8925ed Prevent some race conditions 2018-10-29 17:27:12 +01:00
Raphael Michel
7a945daefc Fix #957 -- Integrate BezahlCode and GiroCode 2018-10-29 12:57:26 +01:00
Raphael Michel
409e77cf2f Stop pinning setuptools 2018-10-29 12:42:07 +01:00
Raphael Michel
552f99a63b Read PDF backgrounds with strict=False 2018-10-29 11:41:26 +01:00
Raphael Michel
0842311451 PDF renderer: Do not break on wrong poweredby-styles 2018-10-29 11:41:26 +01:00
Raphael Michel
4d4b498636 Resolve bug in event copy signals of pdf output and badges 2018-10-29 11:41:26 +01:00
Raphael Michel
d08cc12240 Do not break on short VAT IDs 2018-10-29 11:41:26 +01:00
Raphael Michel
237442872e Reliably delete addons when deleting cart positions 2018-10-29 11:41:25 +01:00
Raphael Michel
16983826fb Allow to store structured invoice addresses 2018-10-24 01:37:18 +02:00
Raphael Michel
e60ff6b777 Allow to store strucutred SEPA bank transfer details 2018-10-24 00:21:33 +02:00
Raphael Michel
3a0ef3760c Make logging for payment changes consistent with OrderPayment handling 2018-10-22 22:28:42 +02:00
Raphael Michel
bc0bc78219 Try to fix invoice export 2018-10-22 06:23:09 +02:00
Tobias Kunze
d3137505a1 Don't offer to check empty VAT ID (#1044) 2018-10-17 01:50:24 +02:00
Flavia Bastos
a2acd336eb Fix #970 -- invoice info consistency (#1043)
resolves: Issue #970
2018-10-17 01:50:00 +02:00
Raphael Michel
6e4750336b Fix test case for previous commit 2018-10-12 11:44:46 +02:00
Raphael Michel
ddefeeaf02 Waiting list should send things out even if waiting list is disabled 2018-10-12 10:45:33 +02:00
Raphael Michel
250e0a930d Prevent huge invoice files if a JPEG logo is used 2018-10-09 10:56:11 +02:00
Tobias Kunze
51c6d60760 Use http_date instead of cookie_date (#1042)
http_date is deprecated as of Django 2.1
2018-10-09 10:50:25 +02:00
Raphael Michel
db513b21f8 Fix Apple Pay verification for organizer domains 2018-10-09 09:22:12 +02:00
Raphael Michel
ab336678ce Allow to change slug in admin sessions 2018-10-09 09:19:36 +02:00
Raphael Michel
3eea4d6945 Show suebvent in addons view 2018-10-08 12:17:30 +02:00
Raphael Michel
d091d3fd17 Show subevent in questions form 2018-10-08 11:20:15 +02:00
Raphael Michel
fc71f484ad Fix urlconf definition 2018-10-05 10:41:35 +02:00
Raphael Michel
bd772bf900 Never fail to send an email because of missing attachments 2018-10-05 09:33:40 +02:00
Raphael Michel
14db654681 Fix Apple Pay for custom domains 2018-10-05 09:31:23 +02:00
Raphael Michel
a85b96ea89 Allow plugins to have organizer_patterns 2018-10-05 09:31:14 +02:00
Raphael Michel
c2b5e876bc Bump version to 2.2.0.dev0 2018-10-04 11:35:00 +02:00
Raphael Michel
91c02dc0b3 Bump version to 2.1.0 2018-10-04 11:33:09 +02:00
Raphael Michel
f78ec830b5 Fix pretix-stripe.js 2018-10-03 17:31:06 +02:00
Raphael Michel
9f0e508ab3 Do not require meta_noindex 2018-10-03 12:52:37 +02:00
Raphael Michel
4ca50d750b Merge pull request #1037 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-10-03 12:44:14 +02:00
Raphael Michel
07c1b1b7f3 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2773 of 2773 strings)

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

powered by weblate
2018-10-03 10:43:50 +00:00
Raphael Michel
3e95dd52cf Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2773 of 2773 strings)

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

powered by weblate
2018-10-03 10:43:35 +00:00
Raphael Michel
80ef2f6b0e Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2773 of 2773 strings)

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

powered by weblate
2018-10-03 10:38:42 +00:00
Raphael Michel
53a8cda310 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-10-03 12:25:02 +02:00
Raphael Michel
63de49104c Merge pull request #1016 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-10-03 12:24:28 +02:00
Maarten van den Berg
8aa80bcb84 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2727 of 2727 strings)

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

powered by weblate
2018-10-03 10:15:55 +00:00
oocf
95115a7c5e Translated on translate.pretix.eu (Spanish)
Currently translated at 99.9% (2725 of 2727 strings)

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

powered by weblate
2018-10-03 10:15:55 +00:00
oocf
ce2967fd02 Translated on translate.pretix.eu (Spanish)
Currently translated at 99.9% (2725 of 2727 strings)

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

powered by weblate
2018-10-03 10:15:55 +00:00
oocf
399fb87d20 Translated on translate.pretix.eu (Spanish)
Currently translated at 99.7% (2719 of 2727 strings)

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

powered by weblate
2018-10-03 10:15:55 +00:00
oocf
c4bd5ac5df Translated on translate.pretix.eu (Spanish)
Currently translated at 99.7% (2719 of 2727 strings)

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

powered by weblate
2018-10-03 10:15:55 +00:00
Maarten van den Berg
123c2d6c02 Translated on translate.pretix.eu (Dutch)
Currently translated at 99.4% (2711 of 2727 strings)

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

powered by weblate
2018-10-03 10:15:55 +00:00
Maarten van den Berg
6954e9c984 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (65 of 65 strings)

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

powered by weblate
2018-10-03 10:15:55 +00:00
Yunus Fırat Pişkin
fc573e4e48 Translated on translate.pretix.eu (Turkish)
Currently translated at 100.0% (2727 of 2727 strings)

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

powered by weblate
2018-10-03 10:15:55 +00:00
Raphael Michel
0dbcfdc5ac Allow to enable ticket downloads for pending orders 2018-10-03 12:15:43 +02:00
Raphael Michel
4b8d4b4792 Allow to bulk-delete vouchers 2018-10-03 11:32:55 +02:00
Raphael Michel
d798da33ef Add option to add robots=noindex meta tag 2018-10-03 11:15:59 +02:00
Raphael Michel
d99517c8d1 Fix #917 -- Attach tickets to emails (#1034) 2018-10-03 11:06:50 +02:00
Raphael Michel
0787adcb8e Fix AttributeError in paypal module 2018-10-02 17:12:26 +02:00
Raphael Michel
f848561d25 Expose log details for admins 2018-10-01 14:13:44 +02:00
Raphael Michel
efbddc2486 Log failed payments 2018-10-01 13:48:47 +02:00
Raphael Michel
e6a138d8f2 Bank transfer: Use correct attribute 2018-10-01 13:05:17 +02:00
Raphael Michel
5b7a578307 Improve display of stripe transaction data 2018-10-01 12:47:36 +02:00
Raphael Michel
737738de93 Fix control display of bank transfers 2018-10-01 12:43:12 +02:00
Raphael Michel
eb3951ce13 Fix waiting list action view without return value 2018-10-01 12:43:12 +02:00
Raphael Michel
c2b7d9a257 Fix transaction handling in invite form 2018-09-30 14:07:14 +02:00
Raphael Michel
4738aa2771 Fix contextual table styles 2018-09-30 13:11:33 +02:00
Raphael Michel
29ac0af55e Improve Device.__str__ method 2018-09-28 16:33:15 +02:00
Raphael Michel
96bc64c456 Do not break invoices if order has no locale 2018-09-27 17:15:49 +02:00
Raphael Michel
0369deb72d Fix permission for access to root event resource 2018-09-27 10:01:57 +02:00
Raphael Michel
6e53990845 Make last commit more resilient 2018-09-25 18:20:40 +02:00
Raphael Michel
feb262644e Orders API: Reduce query load imposed by ?pdf_data=true by multiple orders of magnitude 2018-09-25 17:39:58 +02:00
Raphael Michel
abd679820f Merge pull request #1017 from pretix/deviceauth
Authentication scheme for devices
2018-09-25 14:36:23 +02:00
Raphael Michel
cd3ce848d1 Document permissions 2018-09-25 12:30:15 +02:00
Raphael Michel
63ba393c12 Proper permission handling and testing 2018-09-25 12:29:05 +02:00
Raphael Michel
23fdf8c457 Add compatibility note 2018-09-25 12:12:33 +02:00
Raphael Michel
304ad4e3db Restrict list of events 2018-09-25 10:54:36 +02:00
Raphael Michel
ec58ab07b6 Add tests for control 2018-09-25 10:28:07 +02:00
Raphael Michel
1ba4047b1b API-level tests 2018-09-25 10:28:07 +02:00
Raphael Michel
0bab8adc41 Add documentation on auth 2018-09-25 10:28:07 +02:00
Raphael Michel
17e09c601e Revoke + Logging 2018-09-25 10:28:07 +02:00
Raphael Michel
1aca5fb6ff Fix wrong action parameter 2018-09-25 10:28:07 +02:00
Raphael Michel
7860d690fa Add endpoints to update, roll and revoke devices 2018-09-25 10:28:07 +02:00
Raphael Michel
6d01c99d38 Auth mechanism 2018-09-25 10:28:07 +02:00
Raphael Michel
ddb645aeea Creating device objects 2018-09-25 10:28:07 +02:00
Raphael Michel
f08e4b41c4 Data model 2018-09-25 10:28:07 +02:00
Raphael Michel
1e23624955 Fix #1032 -- Workaround for markdown version 2018-09-24 14:07:11 +02:00
Raphael Michel
ee951a7448 API: Add subevent list on organizer level 2018-09-24 12:59:44 +02:00
Raphael Michel
9935ba370d Event list API: Do not show events without any access permissions 2018-09-24 12:44:45 +02:00
Raphael Michel
e815cce143 Event list API: Add filters 2018-09-24 12:36:12 +02:00
Raphael Michel
cea1032180 SplitDateTimeField: Adjust placeholders to actual locale 2018-09-21 16:54:22 +02:00
Raphael Michel
5695e1d9c8 SplitDateTimeField: Consider field empty if only a time is given 2018-09-21 16:54:22 +02:00
Raphael Michel
fd317afd01 Improve accessibility of payment selection 2018-09-21 16:54:22 +02:00
Raphael Michel
ccddd2a96f Activate passbook by default if installed 2018-09-21 16:54:22 +02:00
Raphael Michel
513d3034d8 Remove deprecated template part 2018-09-20 21:12:49 +02:00
Raphael Michel
51495187fa Merge pull request #1028 from chrko/error_pages_html
Fix outside of body script element
2018-09-20 10:08:11 +02:00
Christian Kohlstedde
2bd53f7b9f Fix outside of body script element
Signed-off-by: Christian Kohlstedde <christian@kohlsted.de>
2018-09-20 10:00:55 +02:00
Raphael Michel
06d9c48ed4 Allow to restrict payment methods by invoice address country 2018-09-19 16:10:40 +02:00
Raphael Michel
1155d18b7f Show waiting list options even when waiting list is disabled 2018-09-19 15:44:17 +02:00
Raphael Michel
6e14592c78 Delete check-ins when deleting a check-in list 2018-09-19 15:41:49 +02:00
Raphael Michel
55feaf2d2c Invoices: Your reference → Customer reference 2018-09-19 15:40:50 +02:00
Raphael Michel
c487036c8b Fix bug in thumbnail generation of small images 2018-09-19 15:38:12 +02:00
Raphael Michel
853ebf8c70 Fix Sphinx warnings 2018-09-19 14:00:01 +02:00
Raphael Michel
1c695c1cf9 Remove unused resource from docs 2018-09-19 13:59:15 +02:00
Raphael Michel
bd5687d169 Remove lock when paying a pending order 2018-09-17 13:04:49 +02:00
Raphael Michel
b384f71b64 Fail silently if attachment could not be found 2018-09-13 12:58:08 +02:00
Raphael Michel
10dd5278e7 Fix bug in previous commit 2018-09-13 12:32:07 +02:00
Raphael Michel
befa6527e4 Attach invoice to order approval email 2018-09-13 12:19:30 +02:00
Raphael Michel
00497630cb Merge pull request #1015 from thegcat/patch-1
Correct typo
2018-09-12 08:38:56 +02:00
Felix Schäfer
95cd457de1 Correct typo
The Header is `Content-Type` not `Content`.
2018-09-11 22:11:48 +02:00
Raphael Michel
7518c9e3e0 Do not install python34.txt on release CI 2018-09-11 18:24:00 +02:00
Raphael Michel
6a999835e2 Bump version 2018-09-11 18:23:10 +02:00
Raphael Michel
41d099c1be Bump version 2018-09-11 18:16:50 +02:00
Raphael Michel
ff306ce2c5 Fix isort 2018-09-11 18:07:27 +02:00
Raphael Michel
c7abc82055 Add squashed migrations 2018-09-11 17:18:20 +02:00
Raphael Michel
041d91dd3c Merge pull request #1013 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-09-11 17:18:13 +02:00
Raphael Michel
387f56ed9b Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2727 of 2727 strings)

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

powered by weblate
2018-09-11 14:59:05 +00:00
Raphael Michel
3181323c1f Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2727 of 2727 strings)

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

powered by weblate
2018-09-11 14:58:17 +00:00
Raphael Michel
ecf84150c1 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-09-11 16:43:17 +02:00
Raphael Michel
5b5025c776 Allow to manually revert check-ins on a check-in list 2018-09-11 15:21:22 +02:00
Raphael Michel
e47dd3058b Adjust tests to error messages 2018-09-11 09:02:08 +02:00
Raphael Michel
71f1dcd475 Fix #1009 -- Fix missing file include in MANIFEST.in 2018-09-11 08:58:32 +02:00
Raphael Michel
941856932c Documentation improvements 2018-09-11 08:58:14 +02:00
Raphael Michel
c51fde52e7 Merge pull request #1008 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-09-10 18:16:00 +02:00
Raphael Michel
c5362e3bde Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2724 of 2724 strings)

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

powered by weblate
2018-09-10 16:15:30 +00:00
Raphael Michel
a113703451 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2724 of 2724 strings)

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

powered by weblate
2018-09-10 16:14:40 +00:00
Raphael Michel
55ecb918e9 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-09-10 18:04:58 +02:00
Raphael Michel
3a870e2f8b Merge pull request #1004 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-09-10 18:04:20 +02:00
oocf
734231a4f1 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (65 of 65 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
oocf
223d6b29f4 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (2720 of 2720 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
oocf
4f41ec0a97 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (2720 of 2720 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
oocf
347a53297d Translated on translate.pretix.eu (Spanish)
Currently translated at 99.7% (2712 of 2720 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
oocf
820766abcb Translated on translate.pretix.eu (Spanish)
Currently translated at 23.5% (640 of 2720 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
oocf
4974fa1fed Translated on translate.pretix.eu (Spanish)
Currently translated at 17.2% (468 of 2720 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
Yunus Fırat Pişkin
7e829fa204 Translated on translate.pretix.eu (Turkish)
Currently translated at 100.0% (2720 of 2720 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
Yunus Fırat Pişkin
f6c7caa48d Translated on translate.pretix.eu (Turkish)
Currently translated at 100.0% (65 of 65 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
Yunus Fırat Pişkin
0dd9d252fd Translated on translate.pretix.eu (Turkish)
Currently translated at 100.0% (2720 of 2720 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
Yunus Fırat Pişkin
39f67a241c Translated on translate.pretix.eu (Turkish)
Currently translated at 96.4% (2623 of 2720 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
Yunus Fırat Pişkin
5706b08366 Translated on translate.pretix.eu (Turkish)
Currently translated at 96.9% (63 of 65 strings)

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

powered by weblate
2018-09-10 16:00:42 +00:00
Raphael Michel
81de9695e2 Add a more specific message on locked vouchers 2018-09-10 17:54:54 +02:00
Raphael Michel
589fb25fe3 Warn about variations without quota 2018-09-10 17:44:50 +02:00
Raphael Michel
61e5c6b468 Fix bug editing addon products 2018-09-10 17:40:56 +02:00
Raphael Michel
087ceb3687 Fix waiting list widgets with infinite quotas 2018-09-04 13:59:40 +02:00
Raphael Michel
0a2cd208b2 Fix invalid tests 2018-09-03 16:55:04 +02:00
Raphael Michel
678a936897 Fix #999 -- Clarify definition of overpaid 2018-09-03 16:30:23 +02:00
Raphael Michel
7c72ca089b Do not allow to mark an order as unpaid 2018-09-03 15:41:18 +02:00
Raphael Michel
21530f315f Properly restrict refunds to full payment amount 2018-09-03 15:41:05 +02:00
Raphael Michel
7274905a92 Ensure correct order of refund log 2018-09-03 15:25:28 +02:00
Raphael Michel
6c5cff6162 Stripe: Do not duplicate refunds of migrated payments 2018-09-03 15:20:05 +02:00
Raphael Michel
cf6b6c129a Stripe: Store refund details 2018-09-03 15:19:56 +02:00
Raphael Michel
74491d16ae Fix a resolver error 2018-09-02 19:54:36 +02:00
Raphael Michel
c1ab6e4eb4 Merge pull request #1003 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-09-02 16:28:17 +02:00
Raphael Michel
18c9ae235a Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2720 of 2720 strings)

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

powered by weblate
2018-09-02 14:27:44 +00:00
Raphael Michel
5c69d5fb88 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2720 of 2720 strings)

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

powered by weblate
2018-09-02 14:26:06 +00:00
Raphael Michel
90f0bda879 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-09-02 15:50:07 +02:00
Martin Gross
1b5c4a21bb Show download-provider specific icons where defined 2018-09-02 15:49:35 +02:00
Raphael Michel
08ee37112f Merge pull request #995 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-09-02 15:48:48 +02:00
Yunus Fırat Pişkin
cfbc88d3d6 Translated on translate.pretix.eu (Turkish)
Currently translated at 92.8% (2517 of 2712 strings)

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

powered by weblate
2018-09-02 13:46:46 +00:00
Raphael Michel
79f5529a5a Translated on translate.pretix.eu (Turkish)
Currently translated at 92.8% (2517 of 2712 strings)

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

powered by weblate
2018-09-02 13:46:36 +00:00
Yunus Fırat Pişkin
11ed0abd18 Translated on translate.pretix.eu (Turkish)
Currently translated at 92.8% (2517 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
01830d9910 Translated on translate.pretix.eu (Turkish)
Currently translated at 92.0% (2495 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
0f573805f2 Translated on translate.pretix.eu (Turkish)
Currently translated at 85.2% (2310 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
93b1d81a48 Translated on translate.pretix.eu (Turkish)
Currently translated at 83.8% (2274 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
e28d13b910 Translated on translate.pretix.eu (Turkish)
Currently translated at 81.6% (2212 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
8731e343c4 Translated on translate.pretix.eu (Turkish)
Currently translated at 75.6% (2051 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
oocf
605eca8cd7 Translated on translate.pretix.eu (Spanish)
Currently translated at 17.1% (464 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
oocf
5a8ddf5e4a Translated on translate.pretix.eu (Spanish)
Currently translated at 16.2% (438 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
f6d5d575fc Translated on translate.pretix.eu (Turkish)
Currently translated at 73.3% (1989 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
d5c344e3ac Translated on translate.pretix.eu (Turkish)
Currently translated at 71.6% (1941 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
oocf
18ba326cea Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (64 of 64 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
oocf
1a1473d3ba Translated on translate.pretix.eu (Spanish)
Currently translated at 14.9% (403 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Dimas 3r1ck Rivas
72804a09ec Translated on translate.pretix.eu (Spanish)
Currently translated at 13.0% (353 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
oocf
c1ce0a514c Translated on translate.pretix.eu (Spanish)
Currently translated at 13.0% (353 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
oocf
bd479312b5 Translated on translate.pretix.eu (Spanish)
Currently translated at 12.9% (350 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
oocf
469da540d2 Translated on translate.pretix.eu (Spanish)
Currently translated at 9.2% (250 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
oocf
69edaa974f Translated on translate.pretix.eu (Spanish)
Currently translated at 6.6% (178 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
oocf
ff56963040 Translated on translate.pretix.eu (Spanish)
Currently translated at 100.0% (64 of 64 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
266aeaef50 Translated on translate.pretix.eu (Turkish)
Currently translated at 66.9% (1813 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
fc660cfb1f Translated on translate.pretix.eu (Turkish)
Currently translated at 66.4% (1801 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Maarten van den Berg
27d343bdea Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (64 of 64 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Maarten van den Berg
a04b0da54a Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2712 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Maarten van den Berg
b15a6bfa98 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (64 of 64 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Maarten van den Berg
dcc638c12f Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (2712 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
84ea96a5ad Translated on translate.pretix.eu (Turkish)
Currently translated at 66.0% (1789 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
ae1bf85740 Translated on translate.pretix.eu (Turkish)
Currently translated at 98.4% (63 of 64 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Yunus Fırat Pişkin
1612d713c9 Translated on translate.pretix.eu (Turkish)
Currently translated at 56.0% (1519 of 2712 strings)

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

powered by weblate
2018-09-02 13:25:51 +00:00
Raphael Michel
6a4a8af731 Improve cookie detection and handling 2018-09-02 15:25:33 +02:00
Raphael Michel
e18375ca6d Avoid conflict in saving objects 2018-08-31 14:05:25 +02:00
Raphael Michel
e537e4538a Fix limits for manual payment 2018-08-31 13:06:13 +02:00
Raphael Michel
1ae97f5477 API: Allow to filter order positions by voucher 2018-08-31 12:53:37 +02:00
Raphael Michel
cc0083c6e5 Allow to search by voucher in check-in list 2018-08-31 12:50:50 +02:00
Raphael Michel
43e6ed2da9 Check-in list PDF: Deal with very long questions and answers 2018-08-31 12:35:28 +02:00
Raphael Michel
27bb3a948b Fix descending sorting of subevents 2018-08-31 12:22:31 +02:00
Raphael Michel
7c155d307b Return 404 for invalid check-in list ID 2018-08-31 12:16:15 +02:00
Raphael Michel
d789beddd0 Fix ValueError on change of payment method
Fixes Sentry issue PRETIXEU-KX
2018-08-31 11:15:59 +02:00
Raphael Michel
f790148ad3 Statistics: Fix AttributeError with subevents 2018-08-31 11:05:17 +02:00
Lukas Bockstaller
a643abe293 Prevent email enumeration (#1000)
Here is my attempt to prevent user enumeration. 
I've made the following changes:

**Application:**
- replaces success and failure messages in the form with two (with/without redis) information messages 
- adds logging for attempted password resets of unknown users
- adds logging for failing emails

**Tests:**
- test_unknown asserts a redirect instead of a ok
- adds test_email_reset_twice_redis to assert the correct logging of a twice reset email 
- adds a FakeRedis class similiar to the one implemented in test_metrics.py. I could refactor them into the testutils folder if prefered. 

Please excuse the commit mess. I am currently fighting with my tooling.
2018-08-31 10:28:39 +02:00
Raphael Michel
099b08f009 Move redis depencency to production.txt 2018-08-31 09:16:01 +02:00
Raphael Michel
35ddf6790e Add mark_refunded parameter to refund creation 2018-08-21 15:48:22 +02:00
Raphael Michel
6502fdb1f5 Allow to switch to admin mode on 404 and 403 page 2018-08-21 15:13:00 +02:00
Raphael Michel
b5cd3bf0af Do not send paid email for free orders 2018-08-21 11:03:56 +02:00
Felix Rindt
8183648902 Rename module async to tasks (#994)
Fixes #993
2018-08-21 10:53:40 +02:00
Raphael Michel
0e1159b01e Allow to disable plugins system-wide 2018-08-19 15:12:58 +02:00
Raphael Michel
625ef3da8a Round decimal on dashboard 2018-08-19 15:09:59 +02:00
Raphael Michel
10c7d9a6e1 Statistics: Prevent issues with async script loading 2018-08-17 11:12:19 +02:00
Raphael Michel
85952ce6b7 Widget: Put overlay directly in <body> to avoid z-index problems 2018-08-17 11:02:44 +02:00
Raphael Michel
bf9ce68d8b Fix test for free to paid 2018-08-17 09:18:05 +02:00
Raphael Michel
08c5992447 OrderChangeManager: Allow free→paid 2018-08-17 09:16:48 +02:00
Raphael Michel
dfc7f7c827 Widget: Pass cart IDs around in some more places 2018-08-16 18:40:21 +02:00
Raphael Michel
efdbbc6098 Do not pass signature in plaintext to renderer 2018-08-16 13:48:37 +02:00
Raphael Michel
185cf90d4c Fix the readthedocs build 2018-08-16 12:04:07 +02:00
Raphael Michel
4db4790270 Custom HTML email renderers and new email style (#991)
* Custom HTML email renderers

* Move inline_css call

* Small fixes

* New HTML mail style for pretix

* Thumbs

* Inlinestyle for notifications

* Documentation

* Set line-height
2018-08-16 12:01:23 +02:00
Raphael Michel
be3b890e2f PayPal error handling 2018-08-16 09:36:16 +02:00
Raphael Michel
4536f96493 Only mark apple pay domains as stored in live mode 2018-08-15 14:14:35 +02:00
Raphael Michel
a598c3e7a8 Stripe: Catch exceptions when filling countries 2018-08-15 11:25:47 +02:00
Raphael Michel
d9f5ee9d76 Stripe: Smoother animation 2018-08-15 09:55:39 +02:00
Martin Gross
a4ced609cd Stripe: ApplePay/Payment Request Button (#988)
As discussed, this is a WIP for integrating Stripe's Payment Request Buttons (with also includes the ApplePay-Button on iOS-devices).

Todos:
- [x] Payment Request Button is still displayed, even when a card has already been tokenized (when going back in the order-flow)
- [x] The domains used need to be verified using the Stripe API to enable ApplePay: https://stripe.com/docs/stripe-js/elements/payment-request-button#verifying-your-domain-with-apple-pay
- [x] Migration: Get the account-country for existing Stripe Connect users
- [x] Migration: Verify the domains using the above mentioned API for existing users
- [x] Converting the chargeable amount is not right for non-decimal currencies like JPY

Other considerations:
- On iOS-devices using Safari (probably also on MacBooks, etc. - not tested), the [regular payment request button](https://user-images.githubusercontent.com/157270/38515749-f53f8392-3be9-11e8-8917-61ef78dd354a.png) is automatically replaced with a [buy with Apple Pay button](https://docs-assets.developer.apple.com/published/094d0eb90e/988c36a8-a43c-4ff9-85ef-beda16c4b7c9.png).
- On all other platforms, the generic payment request button is displayed. Even if the device supports a specific payment provider like Google Pay, Microsoft Wallet, Samsung Pay, etc., the generic button will first offer the cards saved within the webbrowser in addition to the other payment methods. Only upon selecting the specific payment provider like GPay, the corresponding payment flow is started.
- Right now, the rendering of the payment button is completely in the hands of Stripe. Once pretix takes on the task of doing this, we should try to detect if the browser supports well known payment methods like GPay in addition to the browser-saved cards. If that's the case, we should add the corresponding marks onto the "Pay Now"-Button (like [this](https://developers.google.com/pay/api/images/brand-guidelines/google-pay-mark.png), [this](https://assets.pcmag.com/media/images/490984-samsung-pay.png?width=1600&height=900), or [this](https://www.firstffcu.com/images/MS-Wallet_stacked_rgb_grey.png)), so the customer can identify the purpose of the button easier.

- [x] Also, all of this is still based against the pretix 1.x codebase ;-)
2018-08-15 09:22:31 +02:00
Raphael Michel
673a4e6805 Fix locale-dependent test 2018-08-14 18:48:13 +02:00
Raphael Michel
d017ccfbd4 Merge pull request #987 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-08-14 18:10:02 +02:00
Raphael Michel
1f52ed2e83 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2712 of 2712 strings)

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

powered by weblate
2018-08-14 16:08:34 +00:00
Raphael Michel
08e83f616c Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2712 of 2712 strings)

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

powered by weblate
2018-08-14 15:57:40 +00:00
Raphael Michel
51edc4652e Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2712 of 2712 strings)

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

powered by weblate
2018-08-14 15:51:19 +00:00
Raphael Michel
a3c6f38642 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2674 of 2674 strings)

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

powered by weblate
2018-08-14 15:13:17 +00:00
Raphael Michel
a1db53f50b Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-08-14 17:13:11 +02:00
Raphael Michel
9e1046fde3 Merge pull request #989 from pretix/approvals
Require approval for orders of specific products
2018-08-14 17:12:32 +02:00
Raphael Michel
17173f72e0 Fix incorrect sum calculation 2018-08-14 14:16:14 +02:00
Raphael Michel
f60a99c357 Tests 2018-08-14 11:31:41 +02:00
Raphael Michel
1d763f1bc9 Widget: Fix voucher code argument 2018-08-14 11:11:46 +02:00
Raphael Michel
248b94c296 Approvals 2018-08-14 10:46:55 +02:00
Raphael Michel
f52447ff58 Model field 2018-08-14 10:46:32 +02:00
Raphael Michel
0cbacbb959 Fix checking in something that is checked in multiple times 2018-08-14 08:56:53 +02:00
Raphael Michel
a01edecaef Fix incorrect test 2018-08-13 18:10:43 +02:00
Raphael Michel
779756f1ab API: Allow to delete order positions 2018-08-13 18:09:10 +02:00
Raphael Michel
723fedc066 Widget button: Fall back to front page if no items are specified 2018-08-13 14:35:13 +02:00
Raphael Michel
a83bb23540 Widget: Allow to disable voucher input 2018-08-13 14:31:57 +02:00
Raphael Michel
5d68a5133e Add pseudonymization_id filter to API 2018-08-13 08:55:57 +02:00
Raphael Michel
8ca629151d Order list exporter: Fix payment date and format localization 2018-08-12 19:52:20 +02:00
Raphael Michel
693965af28 Add signal html_page_start 2018-08-11 12:52:46 +02:00
Raphael Michel
e645a350f2 Stripe: Support for pretix.eu 2018-08-11 12:31:44 +02:00
Raphael Michel
85e9808550 Fix quirk in data-display-dependency 2018-08-11 10:48:48 +02:00
Raphael Michel
0ce1c4565e Merge pull request #986 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-08-11 10:20:55 +02:00
Raphael Michel
478964ad30 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (64 of 64 strings)

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

powered by weblate
2018-08-11 08:20:11 +00:00
Raphael Michel
74a04e3b35 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2674 of 2674 strings)

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

powered by weblate
2018-08-11 08:18:49 +00:00
Raphael Michel
a48992ed9d Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2674 of 2674 strings)

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

powered by weblate
2018-08-11 08:18:10 +00:00
Raphael Michel
9a6ea8c9bb Translated on translate.pretix.eu (German)
Currently translated at 100.0% (64 of 64 strings)

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

powered by weblate
2018-08-11 08:17:39 +00:00
Raphael Michel
51b05cb128 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-08-11 10:14:23 +02:00
Raphael Michel
de33d6d44c Check-in list PDF: Proper word wrapping 2018-08-10 16:43:27 +02:00
Raphael Michel
3d5cc98df5 Add option to require company name 2018-08-10 16:05:20 +02:00
Raphael Michel
13f3b54393 Refactor order overview and hide empty fees section 2018-08-09 18:04:58 +02:00
Raphael Michel
f17f7b2272 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-08-09 17:24:04 +02:00
Raphael Michel
f61dc7197a Widget: Reload information after closing the iFrame 2018-08-09 16:08:41 +02:00
Raphael Michel
0534508bc3 Widget: Redirect to front page if a cart already exists 2018-08-09 16:08:41 +02:00
Raphael Michel
446c7ffd6a Widget: Fix opening voucher redemption page in widget 2018-08-09 16:08:41 +02:00
Raphael Michel
79e6216669 Widget: Clear CTA in active-cart message 2018-08-09 16:08:41 +02:00
Raphael Michel
5047e48de5 Check-in list export: Default to question answers of parent 2018-08-08 16:59:46 +02:00
Raphael Michel
bd48112bf9 Refs #710 -- Remove monkeypatch for django-hijack 2018-08-08 09:24:52 +02:00
Raphael Michel
5dc100d900 Move dangerous order clause 2018-08-08 09:00:44 +02:00
Raphael Michel
9f2ecb67d4 Do not use copy to copy models 2018-08-07 16:53:09 +02:00
Raphael Michel
5e4f45826e Merge pull request #983 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-08-07 15:58:43 +02:00
Raphael Michel
be6ff21184 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2672 of 2672 strings)

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

powered by weblate
2018-08-07 13:58:10 +00:00
Raphael Michel
5c660fbe7f Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2672 of 2672 strings)

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

powered by weblate
2018-08-07 13:57:25 +00:00
Raphael Michel
108718f275 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (63 of 63 strings)

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

powered by weblate
2018-08-07 13:48:54 +00:00
Raphael Michel
ab53a0b403 Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (63 of 63 strings)

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

powered by weblate
2018-08-07 13:48:04 +00:00
Raphael Michel
49b815bc98 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-08-07 15:45:52 +02:00
Raphael Michel
c702814203 Do not use deepcopy on models 2018-08-07 15:45:22 +02:00
Raphael Michel
0c0172a0b6 Fix inconsistent checks in order offsetting 2018-08-07 15:45:22 +02:00
Tobias Kunze
a8266c22f6 Make fields disabled if presale is over 2018-08-07 14:48:29 +02:00
Tobias Kunze
532c7fbc8f Add helpful title text to numeric inputs 2018-08-07 14:48:29 +02:00
Tobias Kunze
23ed381859 Center free price input for consistency
Looks better in Firefox, too
2018-08-07 14:48:29 +02:00
Raphael Michel
1ad11b0c58 Global Banner message: Only show Read more if appropriate 2018-08-07 12:39:14 +02:00
Raphael Michel
18cca916a0 Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-08-07 12:36:37 +02:00
Raphael Michel
97012082de Fix #972 -- Clarify sum in quota detail 2018-08-07 12:34:35 +02:00
Raphael Michel
423810cf61 Use a defaultdict for log rendering 2018-08-07 12:19:00 +02:00
Raphael Michel
a5159ce8e1 Do not show zeros in order overview 2018-08-07 11:37:14 +02:00
Raphael Michel
4dd3952c19 Fix waiting list tests 2018-08-07 11:36:19 +02:00
Raphael Michel
1e26b5c5f1 Add test case for working list priority 2018-08-07 10:56:48 +02:00
Raphael Michel
67897dfcc0 Fix #406 -- Allow moving waiting list entries to the top or bottom 2018-08-07 10:53:07 +02:00
Raphael Michel
0100604798 Allow to download waiting list 2018-08-07 10:38:26 +02:00
Raphael Michel
47afe01721 Improve waiting list filters 2018-08-07 10:28:37 +02:00
Raphael Michel
a2e12b795f Event settings: Fix custom widget 2018-08-07 10:09:34 +02:00
Raphael Michel
806ab3438e Fix rebuild command 2018-08-06 16:28:54 +02:00
Raphael Michel
f4be90fdd0 Fix overpaid queries 2018-08-06 16:16:19 +02:00
Raphael Michel
dd46767ee3 Merge pull request #981 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-08-06 16:12:52 +02:00
Raphael Michel
a2c712e5b3 Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2656 of 2656 strings)

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

powered by weblate
2018-08-06 14:10:01 +00:00
Raphael Michel
35f3a0077a Translated on translate.pretix.eu (German (informal))
Currently translated at 100.0% (2656 of 2656 strings)

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

powered by weblate
2018-08-06 14:09:46 +00:00
Raphael Michel
bc4195942a Translated on translate.pretix.eu (German)
Currently translated at 100.0% (2656 of 2656 strings)

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

powered by weblate
2018-08-06 13:19:08 +00:00
Raphael Michel
03baca2ed7 Translated on translate.pretix.eu (German)
Currently translated at 96.4% (2560 of 2656 strings)

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

powered by weblate
2018-08-06 12:28:50 +00:00
Raphael Michel
54a9c31a1a Fix setup.py management calls 2018-08-06 14:28:38 +02:00
Raphael Michel
db5073223d Update po files
[CI skip]

Signed-off-by: Raphael Michel <mail@raphaelmichel.de>
2018-08-06 12:49:09 +02:00
Raphael Michel
afd766999c Upgrade to Django 2.1 (#710)
* Upgrade to Django 2.0

* more models

* i18n foo

* Update setup.py

* Fix Sentry exception PRETIXEU-JC

* Enforce slug uniqueness

* Import sorting

* Upgrade to Django 2.1

* Travis config

* Try to fix PostgreSQL failure

* Smaller test matrix

* staticfiles→static

* Include request in all authenticate() calls
2018-08-06 12:48:46 +02:00
Raphael Michel
0637490216 Merge pull request #969 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-08-06 12:48:15 +02:00
TRIXHosting
6a3ba87b22 Translated on translate.pretix.eu (Spanish)
Currently translated at 3.3% (84 of 2563 strings)

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

powered by weblate
2018-08-06 10:24:41 +00:00
Muhammad Hewedy
20b287da52 Translated on translate.pretix.eu (Arabic)
Currently translated at 0.2% (5 of 2563 strings)

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

powered by weblate
2018-08-06 10:24:41 +00:00
Raphael Michel
18a378976b Fix #571 -- Partial payments and refunds 2018-08-06 12:24:36 +02:00
Raphael Michel
8e7af49206 Merge pull request #980 from johan12345/widget-default1
Widget: set default number to 1 if there is only one product
2018-08-05 16:38:47 +02:00
Raphael Michel
edeab082d4 Merge migration for compatibility with backport 2018-08-05 16:36:42 +02:00
Raphael Michel
7b76baaacf Backport a migration 2018-08-05 16:36:13 +02:00
Raphael Michel
053365cb67 Create a new migration for last commit 2018-08-05 16:32:18 +02:00
Raphael Michel
8301120a95 Delete old and unused settings entries 2018-08-05 16:27:30 +02:00
Raphael Michel
f15f0a6226 Update widget.js 2018-08-05 12:53:42 +02:00
Raphael Michel
0cfcadf5fa Fix test cases 2018-08-05 12:52:00 +02:00
Johan von Forstner
435c4acba6 Widget: set default number to 1 if there is only one product 2018-08-03 10:19:20 +02:00
Raphael Michel
edb913855d Add a CSS class to slug widgets 2018-07-29 15:39:31 +02:00
Raphael Michel
24739e1638 Hide waiting list vouchers in voucher list 2018-07-29 15:39:31 +02:00
Raphael Michel
54b906addb Force migration order 2018-07-23 15:04:21 +02:00
Raphael Michel
4a7a8df8a4 Small refactoring on ClassicInvoiceRenderer 2018-07-21 12:34:46 +02:00
Raphael Michel
f1dd62c936 Enable language tr 2018-07-20 11:07:43 +02:00
Raphael Michel
80cc7b0d64 Merge pull request #965 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-07-19 16:46:07 +02:00
Raphael Michel
eb4fbf3c0b Translated on translate.pretix.eu (French)
Currently translated at 94.3% (2416 of 2563 strings)

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

powered by weblate
2018-07-19 14:45:44 +00:00
Raphael Michel
c1cf1206fc Translated on translate.pretix.eu (Turkish)
Currently translated at 100.0% (1464 of 1464 strings)

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

powered by weblate
2018-07-19 14:45:33 +00:00
Yunus Fırat Pişkin
efebc02d24 Translated on translate.pretix.eu (Turkish)
Currently translated at 100.0% (1464 of 1464 strings)

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

powered by weblate
2018-07-19 14:10:45 +00:00
Raphael Michel
21dca8c17f Merge pull request #961 from pretix-translations/weblate-pretix-pretix
Update from Weblate.
2018-07-19 16:10:41 +02:00
Yunus Fırat Pişkin
4eb9839f77 Translated on translate.pretix.eu (Turkish)
Currently translated at 100.0% (1464 of 1464 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Yunus Fırat Pişkin
3b7906ea04 Translated on translate.pretix.eu (Turkish)
Currently translated at 99.0% (1449 of 1464 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Yunus Fırat Pişkin
9d17858500 Translated on translate.pretix.eu (Turkish)
Currently translated at 100.0% (62 of 62 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Yunus Fırat Pişkin
d5ceb5f465 Translated on translate.pretix.eu (Turkish)
Currently translated at 100.0% (62 of 62 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Yunus Fırat Pişkin
7dd2a0bbb4 Translated on translate.pretix.eu (Turkish)
Currently translated at 68.0% (996 of 1464 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Yunus Fırat Pişkin
13284fb3b9 Translated on translate.pretix.eu (Turkish)
Currently translated at 58.1% (36 of 62 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Yunus Fırat Pişkin
f42c5ec0ce Translated on translate.pretix.eu (Turkish)
Currently translated at 27.1% (397 of 1464 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Maarten van den Berg
6b269839cb Translated on translate.pretix.eu (Dutch)
Currently translated at 99.5% (2549 of 2563 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Raphael Michel
2eb3e0a278 Added translation on translate.pretix.eu (Turkish) 2018-07-19 08:07:53 +00:00
Raphael Michel
183a437678 Added translation on translate.pretix.eu (Turkish) 2018-07-19 08:07:53 +00:00
Claude
116b8171f8 Translated on translate.pretix.eu (French)
Currently translated at 94.3% (2416 of 2563 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Claude
c8c723bf4a Translated on translate.pretix.eu (French)
Currently translated at 100.0% (62 of 62 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Claude
d01cf018ce Translated on translate.pretix.eu (French)
Currently translated at 91.1% (2335 of 2563 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Claude
c701ab0776 Translated on translate.pretix.eu (French)
Currently translated at 100.0% (62 of 62 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Claude
180269d6b0 Translated on translate.pretix.eu (French)
Currently translated at 83.1% (2130 of 2563 strings)

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

powered by weblate
2018-07-19 08:07:53 +00:00
Raphael Michel
645c604fd4 Fix TypeError in price_too_high detection 2018-07-19 10:07:35 +02:00
Raphael Michel
de210db90d Fix error condition in event cloning 2018-07-19 09:44:53 +02:00
Raphael Michel
beddf1c772 Fix event meta deletion 2018-07-19 09:43:40 +02:00
Raphael Michel
75e618ee4a Throw cart error for price_too_high 2018-07-19 09:41:14 +02:00
Raphael Michel
d2a3ba182b Fix KeyError when accessing settings for disabled payment provider 2018-07-19 09:32:08 +02:00
Raphael Michel
427f78b14d OrderCreateSerializer: Do not crash on optional fields missing 2018-07-19 09:27:36 +02:00
Raphael Michel
febcf237ca Prevent a KeyError during form validation 2018-07-19 09:27:17 +02:00
Raphael Michel
5e158c3bd7 Prevent a KeyError with invalid add-on configuration 2018-07-19 09:27:17 +02:00
Raphael Michel
b4c9c86ba6 Prevent ValueError with invalid state of relative date 2018-07-19 09:27:17 +02:00
Raphael Michel
7c00853f5d Fix field that was accidentally required 2018-07-13 18:19:40 +02:00
Raphael Michel
a0fcb116f5 Bank transfer: Option to remove hyphen from reference 2018-07-13 16:39:55 +02:00
Raphael Michel
e46b33544d Fix race condition in formset validation 2018-07-11 14:57:31 +02:00
Raphael Michel
6b9c3ad4e7 PDF Layout: Make pretix logo a layout element, not a background element 2018-07-10 13:24:27 +02:00
702 changed files with 304597 additions and 49527 deletions

View File

@@ -17,7 +17,7 @@ pypi:
- virtualenv env
- source env/bin/activate
- pip install -U pip wheel setuptools
- XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt -r src/requirements/py34.txt
- XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt
- cd src
- python setup.py sdist
- pip install dist/pretix-*.tar.gz

View File

@@ -1,2 +1 @@
-r src/requirements/py34.txt
-r doc/requirements.txt

View File

@@ -11,17 +11,16 @@ fi
if [ "$PRETIX_CONFIG_FILE" == "tests/travis_postgres.cfg" ]; then
psql -c 'create database travis_ci_test;' -U postgres
pip3 install -Ur src/requirements/postgres.txt
fi
if [ "$1" == "style" ]; then
XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt -r src/requirements/py34.txt
XDG_CACHE_HOME=/cache pip3 install -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 -r src/requirements/py34.txt
XDG_CACHE_HOME=/cache pip3 install -Ur doc/requirements.txt
cd doc
make doctest
fi
@@ -39,21 +38,21 @@ if [ "$1" == "translation-spelling" ]; then
potypo
fi
if [ "$1" == "tests" ]; then
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt -r src/requirements/py34.txt pytest-xdist
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt
cd src
python manage.py check
make all compress
py.test --reruns 5 -n 2 tests
py.test --reruns 5 -n 3 tests
fi
if [ "$1" == "tests-cov" ]; then
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt -r src/requirements/py34.txt
pip3 install -r src/requirements.txt -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 -r src/requirements/py34.txt
pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt
cd src
python setup.py develop
make all compress

View File

@@ -1,7 +1,8 @@
language: python
dist: xenial
sudo: false
install:
- pip install -U pip wheel setuptools==28.6.1
- pip install -U pip wheel setuptools
script:
- bash .travis.sh $JOB
cache:
@@ -12,41 +13,35 @@ services:
- postgresql
matrix:
include:
- python: 3.6
- python: 3.7
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg
- python: 3.6
- python: 3.7
env: JOB=tests-cov PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
- python: 3.6
- python: 3.7
env: JOB=style
- python: 3.4
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg
- python: 3.5
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg
- python: 3.4
- python: 3.7
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
- python: 3.5
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
- python: 3.6
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
- python: 3.4
- python: 3.7
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
- python: 3.5
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
- python: 3.6
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
- python: 3.6
- python: 3.7
env: JOB=plugins
- python: 3.6
- python: 3.7
env: JOB=doc-spelling
- python: 3.6
- python: 3.7
env: JOB=translation-spelling
addons:
postgresql: "9.4"
mariadb: '10.3'
apt:
packages:
- enchant
- myspell-de-de
- aspell-en
- sqlite3
sources:
- travis-ci/sqlite3
branches:
except:
- /^weblate-.*/

View File

@@ -1,10 +1,26 @@
FROM python:3.6
RUN apt-get update && \
apt-get install -y git libxml2-dev libxslt1-dev python-dev python-virtualenv locales \
libffi-dev build-essential python3-dev zlib1g-dev libssl-dev gettext libpq-dev \
default-libmysqlclient-dev libmemcached-dev libjpeg-dev supervisor nginx sudo \
--no-install-recommends && \
apt-get install -y --no-install-recommends \
build-essential \
default-libmysqlclient-dev \
gettext \
git \
libffi-dev \
libjpeg-dev \
libmemcached-dev \
libpq-dev \
libssl-dev \
libxml2-dev \
libxslt1-dev \
locales \
nginx \
python-dev \
python-virtualenv \
python3-dev \
sudo \
supervisor \
zlib1g-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
dpkg-reconfigure locales && \
@@ -19,6 +35,22 @@ RUN apt-get update && \
ENV LC_ALL=C.UTF-8 \
DJANGO_SETTINGS_MODULE=production_settings
# To copy only the requirements files needed to install from PIP
COPY src/requirements /pretix/src/requirements
COPY src/requirements.txt /pretix/src
RUN pip3 install -U \
pip \
setuptools \
wheel && \
cd /pretix/src && \
pip3 install \
-r requirements.txt \
-r requirements/memcached.txt \
-r requirements/mysql.txt \
-r requirements/redis.txt \
gunicorn && \
rm -rf ~/.cache/pip
COPY deployment/docker/pretix.bash /usr/local/bin/pretix
COPY deployment/docker/supervisord.conf /etc/supervisord.conf
COPY deployment/docker/nginx.conf /etc/nginx/nginx.conf
@@ -27,11 +59,8 @@ COPY src /pretix/src
RUN chmod +x /usr/local/bin/pretix && \
rm /etc/nginx/sites-enabled/default && \
pip3 install -U pip wheel setuptools && \
cd /pretix/src && \
rm -f pretix.cfg && \
pip3 install -r requirements.txt -r requirements/mysql.txt -r requirements/postgres.txt \
-r requirements/memcached.txt -r requirements/redis.txt gunicorn && \
mkdir -p data && \
chown -R pretixuser:pretixuser /pretix /data data && \
sudo -u pretixuser make production

View File

@@ -53,6 +53,10 @@ Example::
A comma-separated list of plugins that are enabled by default for all new events.
Defaults to ``pretix.plugins.sendmail,pretix.plugins.statistics``.
``plugins_exclude``
A comma-separated list of plugins that are not available even though they are installed.
Defaults to an empty string.
``cookie_domain``
The cookie domain to be set. Defaults to ``None``.
@@ -121,6 +125,27 @@ Example::
Indicates if the database backend is a MySQL/MariaDB Galera cluster and
turns on some optimizations/special case handlers. Default: ``False``
.. _`config-replica`:
Database replica settings
-------------------------
If you use a replicated database setup, pretix expects that the default database connection always points to the primary database node.
Routing read queries to a replica on database layer is **strongly** discouraged since this can lead to inaccurate such as more tickets
being sold than are actually available.
However, pretix can still make use of a database replica to keep some expensive queries with that can tolerate some latency from your
primary database, such as backend search queries. The ``replica`` configuration section can have the same settings as the ``database``
section (except for the ``backend`` setting) and will default back to the ``database`` settings for all values that are not given. This
way, you just need to specify the settings that are different for the replica.
Example::
[replica]
host=192.168.0.2
.. _`config-urls`:
URLs
----
@@ -291,5 +316,13 @@ various places like order codes, secrets in the ticket QR codes, etc. Example::
; Voucher code needs to be < 255 characters, default is 16
voucher_code=16
External tools
--------------
pretix can make use of some external tools if they are installed. Currently, they are all optional. Example::
[tools]
pdftk=/usr/bin/pdftk
.. _Python documentation: https://docs.python.org/3/library/configparser.html?highlight=configparser#supported-ini-file-structure
.. _Celery documentation: http://docs.celeryproject.org/en/latest/userguide/configuration.html

View File

@@ -11,3 +11,4 @@ This documentation is for everyone who wants to install pretix on a server.
installation/index
config
maintainance
scaling

View File

@@ -0,0 +1,37 @@
.. highlight:: none
Installing a development version
================================
If you want to use a feature of pretix that is not yet contained in the last monthly release, you can also
install a development version with pretix.
.. warning:: When in production, we strongly recommend only installing released versions. Development versions might
be broken, incompatible to plugins, or in rare cases incompatible to upgrade later on.
Manual installation
-------------------
You can use ``pip`` to update pretix directly to the development branch. Then, upgrade as usual::
$ source /var/pretix/venv/bin/activate
(venv)$ pip3 install -U "git+https://github.com/pretix/pretix.git#egg=pretix&subdirectory=src"
(venv)$ python -m pretix migrate
(venv)$ python -m pretix rebuild
(venv)$ python -m pretix updatestyles
# systemctl restart pretix-web pretix-worker
Docker installation
-------------------
To use the latest development version with Docker, first pull it from Docker Hub::
$ docker pull pretix/standalone:latest
Then change your ``/etc/systemd/system/pretix.service`` file to use the ``:latest`` tag instead of ``:stable`` as well
and upgrade as usual::
$ systemctl restart pretix.service
$ docker exec -it pretix.service pretix upgrade

View File

@@ -26,7 +26,7 @@ installation guides):
* `Docker`_
* A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for
* A HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections
* A `MySQL`_ or `PostgreSQL`_ database server
* A `PostgreSQL`_, `MySQL`_ 5.7+, or MariaDB 10.2.7+ database server
* A `redis`_ server
We also recommend that you use a firewall, although this is not a pretix-specific recommendation. If you're new to
@@ -36,6 +36,9 @@ Linux and firewalls, we recommend that you start with `ufw`_.
SSL certificates can be obtained for free these days. We also *do not* provide support for HTTP-only
installations except for evaluation purposes.
.. warning:: We recommend **PostgreSQL**. If you go for MySQL, make sure you run **MySQL 5.7 or newer** or
**MariaDB 10.2.7 or newer**.
On this guide
-------------
@@ -55,16 +58,29 @@ Database
--------
Next, we need a database and a database user. We can create these with any kind of database managing tool or directly on
our database's shell, e.g. for MySQL::
our database's shell. For PostgreSQL, we would do::
$ mysql -u root -p
mysql> CREATE DATABASE pretix DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
mysql> GRANT ALL PRIVILEGES ON pretix.* TO pretix@'localhost' IDENTIFIED BY '*********';
mysql> FLUSH PRIVILEGES;
# sudo -u postgres createuser -P pretix
# sudo -u postgres createdb -O pretix pretix
Replace the asterisks with a password of your own. For MySQL, we will use a unix domain socket to connect to the
database. For PostgreSQL, be sure to configure the interface binding and your firewall so that the docker container
can reach PostgreSQL.
Make sure that your database listens on the network. If PostgreSQL on the same same host as docker, but not inside a docker container, we recommend that you just listen on the Docker interface by changing the following line in ``/etc/postgresql/<version>/main/postgresql.conf``::
listen_addresses = 'localhost,172.17.0.1'
You also need to add a new line to ``/etc/postgresql/<version>/main/pg_hba.conf`` to allow network connections to this user and database::
host pretix pretix 172.17.0.1/16 md5
Restart PostgreSQL after you changed these files::
# systemctl restart postgresql
If you have a firewall running, you should also make sure that port 5432 is reachable from the ``172.17.0.1/16`` subnet.
For MySQL, you can either also use network-based connections or mount the ``/var/run/mysqld/mysqld.sock`` socket into the docker container.
When using MySQL, make sure you set the character set of the database to ``utf8mb4``, e.g. like this::
mysql > CREATE DATABASE pretix DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
Redis
-----
@@ -111,13 +127,16 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
datadir=/data
[database]
; Replace mysql with postgresql_psycopg2 for PostgreSQL
backend=mysql
; Replace postgresql with mysql for MySQL
backend=postgresql
name=pretix
user=pretix
; Replace with the password you chose above
password=*********
; Replace with host IP address for PostgreSQL
host=/var/run/mysqld/mysqld.sock
; In most docker setups, 172.17.0.1 is the address of the docker host. Adjuts
; this to wherever your database is running, e.g. the name of a linked container
; or of a mounted MySQL socket.
host=172.17.0.1
[mail]
; See config file documentation for more options
@@ -161,14 +180,15 @@ 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 \
-v /var/run/mysqld:/var/run/mysqld \
pretix/standalone:stable all
ExecStop=/usr/bin/docker stop %n
[Install]
WantedBy=multi-user.target
You can leave the MySQL socket volume out if you're using PostgreSQL. You can now run the following commands
When using MySQL and socket mounting, you'll need the additional flag ``-v /var/run/mysqld:/var/run/mysqld`` in the command.
You can now run the following commands
to enable and start the service::
# systemctl daemon-reload

View File

@@ -0,0 +1,84 @@
.. highlight:: none
Installing pretix Enterprise plugins
====================================
If you want to use a feature of pretix that is part of our commercial offering pretix Enterprise, you need to follow
some extra steps. Installation works similar to normal pretix plugins, but involves a few extra steps.
Buying the license
------------------
To obtain a license, please get in touch at sales@pretix.eu. Please let us know how many tickets you roughly intend
to sell per year and how many servers you want to use the plugin on. We recommend having a look at our `price list`_
first.
Manual installation
-------------------
First, generate an SSH key for the system user that you install pretix as. In our tutorial, that would be the user
``pretix``. Choose an empty passphrase::
# su pretix
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/var/pretix/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /var/pretix/.ssh/id_rsa.
Your public key has been saved in /var/pretix/.ssh/id_rsa.pub.
Next, send the content of the *public* key to your sales representative at pretix::
$ cat /var/pretix/.ssh/id_rsa.pub
ssh-rsa AAAAB3N...744HZawHlD pretix@foo
After we configured your key in our system, you can install the plugin directly using ``pip`` from the URL we told
you, for example::
$ source /var/pretix/venv/bin/activate
(venv)$ pip3 install -U "git+ssh://git@code.rami.io:10022/pretix/pretix-slack.git@stable#egg=pretix-slack"
(venv)$ python -m pretix migrate
(venv)$ python -m pretix rebuild
# systemctl restart pretix-web pretix-worker
Docker installation
-------------------
To install a plugin, you need to build your own docker image. To do so, create a new directory to work in. As a first
step, generate a new SSH key in that directory to use for authentication with us::
$ cd /home/me/mypretixdocker
$ ssh-keygen -N "" -f id_pretix_enterprise
Next, send the content of the *public* key to your sales representative at pretix::
$ cat id_pretix_enterprise.pub
ssh-rsa AAAAB3N...744HZawHlD pretix@foo
After we configured your key in our system, you can add a ``Dockerfile`` in your directory that includes the newly
generated key and installs the plugin from the URL we told you::
FROM pretix/standalone:stable
USER root
COPY id_pretix_enterprise /root/.ssh/id_rsa
COPY id_pretix_enterprise.pub /root/.ssh/id_rsa.pub
RUN chmod -R 0600 /root/.ssh && \
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" && \
cd /pretix/src && \
sudo -u pretixuser make production
USER pretixuser
Then, build the image for docker::
$ docker build -t mypretix
You can now use that image ``mypretix`` instead of ``pretix/standalone:stable`` in your ``/etc/systemd/system/pretix.service``
service file. Be sure to re-build your custom image after you pulled ``pretix/standalone`` if you want to perform an
update to a new version of pretix.
.. _price list: https://pretix.eu/about/en/pricing

View File

@@ -21,6 +21,9 @@ To use pretix, you will need the following things:
.. warning:: Do not ever use SQLite in production. It will break.
.. warning:: We recommend **PostgreSQL**. If you go for MySQL, make sure you run **MySQL 5.7 or newer** or
**MariaDB 10.2.7 or newer**.
* A **reverse proxy**. pretix needs to deliver some static content to your users (e.g. CSS, images, ...). While pretix
is capable of doing this, having this handled by a proper web server like **nginx** or **Apache** will be much
faster. Also, you need a proxying web server in front to provide SSL encryption.

View File

@@ -1,3 +1,5 @@
.. _`installation`:
Installation guide
==================
@@ -10,3 +12,5 @@ for your needs.
general
docker_smallscale
manual_smallscale
dev_version
enterprise

View File

@@ -23,7 +23,7 @@ installation guides):
* A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for
* A HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections
* A `MySQL`_ or `PostgreSQL`_ database server
* A `PostgreSQL`_, `MySQL`_ 5.7+, or MariaDB 10.2.7+ database server
* A `redis`_ server
We also recommend that you use a firewall, although this is not a pretix-specific recommendation. If you're new to
@@ -33,6 +33,9 @@ Linux and firewalls, we recommend that you start with `ufw`_.
SSL certificates can be obtained for free these days. We also *do not* provide support for HTTP-only
installations except for evaluation purposes.
.. warning:: We recommend **PostgreSQL**. If you go for MySQL, make sure you run **MySQL 5.7 or newer** or
**MariaDB 10.2.7 or newer**.
Unix user
---------
@@ -47,21 +50,23 @@ Database
--------
Having the database server installed, we still need a database and a database user. We can create these with any kind
of database managing tool or directly on our database's shell, e.g. for MySQL::
of database managing tool or directly on our database's shell. For PostgreSQL, we would do::
$ mysql -u root -p
mysql> CREATE DATABASE pretix DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
mysql> GRANT ALL PRIVILEGES ON pretix.* TO pretix@'localhost' IDENTIFIED BY '*********';
mysql> FLUSH PRIVILEGES;
# sudo -u postgres createuser pretix
# sudo -u postgres createdb -O pretix pretix
When using MySQL, make sure you set the character set of the database to ``utf8mb4``, e.g. like this::
mysql > CREATE DATABASE pretix DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
Package dependencies
--------------------
To build and run pretix, you will need the following debian packages::
# apt-get install git build-essential python-dev python-virtualenv python3 python3-pip \
# apt-get install git build-essential python-dev python3-venv python3 python3-pip \
python3-dev libxml2-dev libxslt1-dev libffi-dev zlib1g-dev libssl-dev \
gettext libpq-dev libmysqlclient-dev libjpeg-dev
gettext libpq-dev libmariadbclient-dev libjpeg-dev libopenjp2-7-dev
Config file
-----------
@@ -82,13 +87,18 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
datadir=/var/pretix/data
[database]
; Replace mysql with postgresql_psycopg2 for PostgreSQL
backend=mysql
; For MySQL, replace with "mysql"
backend=postgresql
name=pretix
user=pretix
password=*********
; Replace with host IP address for PostgreSQL
host=/var/run/mysqld/mysqld.sock
; For MySQL, enter the user password. For PostgreSQL on the same host,
; we don't need one because we can use peer authentification if our
; PostgreSQL user matches our unix user.
password=
; For MySQL, use local socket, e.g. /var/run/mysqld/mysqld.sock
; For a remote host, supply an IP address
; For local postgres authentication, you can leave it empty
host=
[mail]
; See config file documentation for more options
@@ -112,17 +122,16 @@ Now we will install pretix itself. The following steps are to be executed as the
actually install pretix, we will create a virtual environment to isolate the python packages from your global
python installation::
$ virtualenv -p python3 /var/pretix/venv
$ python3 -m venv /var/pretix/venv
$ source /var/pretix/venv/bin/activate
(venv)$ pip3 install -U pip setuptools wheel
We now install pretix, its direct dependencies and gunicorn. Replace ``mysql`` with ``postgres`` in the following
command if you're running PostgreSQL::
We now install pretix, its direct dependencies and gunicorn. Replace ``postgres`` with ``mysql`` in the following
command if you're running MySQL::
(venv)$ pip3 install "pretix[mysql]" gunicorn
(venv)$ pip3 install "pretix[postgres]" gunicorn
If you are running Python 3.4, you also need to ``pip3 install typing``. This is not required on 3.5 or newer.
You can find out your Python version using ``python -V``.
Note that you need Python 3.5 or newer. You can find out your Python version using ``python -V``.
We also need to create a data directory::
@@ -266,10 +275,10 @@ Updates
.. warning:: While we try hard not to break things, **please perform a backup before every upgrade**.
To upgrade to a new pretix release, pull the latest code changes and run the following commands (again, replace
``mysql`` with ``postgres`` if necessary)::
``postgres`` with ``mysql`` if necessary)::
$ source /var/pretix/venv/bin/activate
(venv)$ pip3 install -U pretix[mysql] gunicorn
(venv)$ pip3 install -U pretix[postgres] gunicorn
(venv)$ python -m pretix migrate
(venv)$ python -m pretix rebuild
(venv)$ python -m pretix updatestyles

236
doc/admin/scaling.rst Normal file
View File

@@ -0,0 +1,236 @@
.. _`scaling`:
Scaling guide
=============
Our :ref:`installation guide <installation>` only covers "small-scale" setups, by which we mostly mean
setups that run on a **single (virtual) machine** and do not encounter large traffic peaks.
We do not offer an installation guide for larger-scale setups of pretix, mostly because we believe that
there is no one-size-fits-all solution for this and the desired setup highly depends on your use case,
the platform you run pretix on, and your technical capabilities. We do not recommend trying set up pretix
in a multi-server environment if you do not already have experience with managing server clusters.
This document is intended to give you a general idea on what issues you will encounter when you scale up
and what you should think of.
.. tip::
If you require more help on this, we're happy to help. Our pretix Enterprise support team has built
and helped building, scaling and load-testing pretix installations at any scale and we're looking
forward to work with you on fine-tuning your system. If you intend to sell **more than a thousand
tickets in a very short amount of time**, we highly recommend reaching out and at least talking this
through. Just get in touch at sales@pretix.eu!
Scaling reasons
---------------
There's mainly two reasons to scale up a pretix installation beyond a single server:
* **Availability:** Distributing pretix over multiple servers can allow you to survive failure of one or more single machines, leading to a higher uptime and reliability of your system.
* **Traffic and throughput:** Distributing pretix over multiple servers can allow you to process more web requests and ticket sales at the same time.
You are very unlikely to require scaling for other reasons, such as having too much data in your database.
Components
----------
A pretix installation usually consists of the following components which run performance-relevant processes:
* ``pretix-web`` is the Django-based web application that serves all user interaction.
* ``pretix-worker`` is a Celery-based application that processes tasks that should be run asynchronously outside of the web application process.
* A **SQL database** keeps all the important data and processes the actual transactions. We recommend using PostgreSQL, but MySQL/MariaDB works as well.
* A **web server** that terminates TLS and HTTP connections and forwards them to ``pretix-web``. In some cases, e.g. when serving static files, the web servers might return a response directly. We recommend using ``nginx``.
* A **redis** server responsible for the communication between ``pretix-web`` and ``pretix-worker``, as well as for caching.
* A directory of **media files** such as user-uploaded files or generated files (tickets, invoices, …) that are created and used by ``pretix-web``, ``pretix-worker`` and the web server.
In the following, we will discuss the scaling behavior of every component individually. In general, you can run all of the components
on the same server, but you can just as well distribute every component to its own server, or even use multiple servers for some single
components.
.. warning::
When setting up your system, don't forget about security. In a multi-server environment,
you need to take special care to ensure that no unauthorized access to your database
is possible through the network and that it's not easy to wiretap your connections. We
recommend a rigorous use of firewalls and encryption on all communications. You can
ensure this either on an application level (such as using the TLS support in your
database) or on a network level with a VPN solution.
Web server
""""""""""
Your web server is at the very front of your installation. It will need to absorb all of the traffic, and it should be able to
at least show a decent error message, even when everything else fails. Luckily, web servers are really fast these days, so this
can be achieved without too much work.
We recommend reading up on tuning your web server for high concurrency. For nginx, this means thinking about the number of worker
processes and the number of connections each worker process accepts. Double-check that TLS session caching works, because TLS
handshakes can get really expensive.
During a traffic peak, your web server will be able to make us of more CPU resources, while memory usage will stay comparatively low,
so if you invest in more hardware here, invest in more and faster CPU cores.
Make sure that pretix' static files (such as CSS and JavaScript assets) as well as user-uploaded media files (event logos, etc)
are served directly by your web server and your web server caches them in-memory (nginx does it by default) and sets useful
headers for client-side caching. As an additional performance improvement, you can turn of access logging for these types of files.
If you want, you can even farm out serving static files to a different web server entirely and :ref:`configure pretix to reference
them from a different URL <config-urls>`.
.. tip::
If you expect *really high traffic* for your very popular event, you might want to do some rate limiting on this layer, or,
if you want to ensure a fair and robust first-come-first-served experience and prefer letting users wait over showing them
errors, consider a queuing solution. We're happy to provide you with such systems, just get in touch at sales@pretix.eu.
pretix-web
""""""""""
The ``pretix-web`` process does not carry any internal state can be easily started on as many machines as you like, and you can
use the load balancing features of your frontend web server to redirect to all of them.
You can adjust the number of processes in the ``gunicorn`` command line, and we recommend choosing roughly two times the number
of CPU cores available. Under load, the memory consumption of ``pretix-web`` will stay comparatively constant, while the CPU usage
will increase a lot. Therefore, if you can add more or faster CPU cores, you will be able to serve more users.
pretix-worker
"""""""""""""
The ``pretix-worker`` process performs all operations that are not directly executed in the request-response-cycle of ``pretix-web``.
Just like ``pretix-web`` you can easily start up as many instances as you want on different machines to share the work. As long as they
all talk to the same redis server, they will all receive tasks from ``pretix-web``, work on them and post their result back.
You can configure the number of threads that run tasks in parallel through the ``--concurrency`` command line option of ``celery``.
Just like ``pretix-web``, this process is mostly heavy on CPU, disk IO and network IO, although memory peaks can occur e.g. during the
generation of large PDF files, so we recommend having some reserves here.
``pretix-worker`` performs a variety of tasks which are of different importance.
Some of them are mission-critical and need to be run quickly even during high load (such as
creating a cart or an order), others are irrelevant and can easily run later (such as
distributing tickets on the waiting list). You can fine-tune the capacity you assign to each
of these tasks by running ``pretix-worker`` processes that only work on a specific **queue**.
For example, you could have three servers dedicated only to process order creations and one
server dedicated only to sending emails. This allows you to set priorities and also protects
you from e.g. a slow email server lowering your ticket throughput.
You can do so by specifying one or more queues on the ``celery`` command line of this process, such as ``celery -A pretix.celery_app worker -Q notifications,mail``. Currently,
the following queues exist:
* ``checkout`` -- This queue handles everything related to carts and orders and thereby everything required to process a sale. This includes adding and deleting items from carts as well as creating and canceling orders.
* ``mail`` -- This queue handles sending of outgoing emails.
* ``notifications`` -- This queue handles the processing of any outgoing notifications, such as email notifications to admin users (except for the actual sending) or API notifications to registered webhooks.
* ``background`` -- This queue handles tasks that are expected to take long or have no human waiting for their result immediately, such as refreshing caches, re-generating CSS files, assigning tickets on the waiting list or parsing bank data files.
* ``default`` -- This queue handles everything else with "medium" or unassigned priority, most prominently the generation of files for tickets, invoices, badges, admin exports, etc.
Media files
"""""""""""
Both ``pretix-web``, ``pretix-worker`` and in some cases your webserver need to work with
media files. Media files are all files generated *at runtime* by the software. This can
include files uploaded by the event organizers, such as the event logo, files uploaded by
ticket buyers (if you use such features) or files generated by the software, such as
ticket files, invoice PDFs, data exports or customized CSS files.
Those files are by default stored to the ``media/`` sub-folder of the data directory given
in the ``pretix.cfg`` configuration file. Inside that ``media/`` folder, you will find a
``pub/`` folder containing the subset of files that should be publicly accessible through
the web server. Everything else only needs to be accessible by ``pretix-web`` and
``pretix-worker`` themselves.
If you distribute ``pretix-web`` or ``pretix-worker`` across more than one machine, you
**must** make sure that they all have access to a shared storage to read and write these
files, otherwise you **will** run into errors with the user interface.
The easiest solution for this is probably to store them on a NFS server that you mount
on each of the other servers.
Since we use Django's file storage mechanism internally, you can in theory also use a object-storage solution like Amazon S3, Ceph, or Minio to store these files, although we currently do not expose this through pretix' configuration file and this would require you to ship your own variant of ``pretix/settings.py`` and reference it through the ``DJANGO_SETTINGS_MODULE`` environment variable.
At pretix.eu, we use a custom-built `object storage cluster`_.
SQL database
""""""""""""
One of the most critical parts of the whole setup is the SQL database -- and certainly the
hardest to scale. Tuning relational databases is an art form, and while there's lots of
material on it on the internet, there's not a single recipe that you can apply to every case.
As a general rule of thumb, the more resources you can give your databases, the better.
Most databases will happily use all CPU cores available, but only use memory up to an amount
you configure, so make sure to set this memory usage as high as you can afford. Having more
memory available allows your database to make more use of caching, which is usually good.
Scaling your database to multiple machines needs to be treated with great caution. It's a
good to have a replica of your database for availability reasons. In case your primary
database server fails, you can easily switch over to the replica and continue working.
However, using database replicas for performance gains is much more complicated. When using
replicated database systems, you are always trading in consistency or availability to get
additional performance and the consequences of this can be subtle and it is important
that you have a deep understanding of the semantics of your replication mechanism.
.. warning::
Using an off-the-shelf database proxy solution that redirects read queries to your
replicas and write queries to your primary database **will lead to very nasty bugs.**
As an example, if you buy a ticket, pretix first needs to calculate how many tickets
are left to sell. If this calculation is done on a database replica that lags behind
even for fractions of a second, the decision to allow selling the ticket will be made
on out-of-data data and you can end up with more tickets sold than configured. Similarly,
you could imagine situations leading to double payments etc.
If you do have a replica, you *can* tell pretix about it :ref:`in your configuration <config-replica>`.
This way, pretix can offload complex read-only queries to the replica when it is safe to do so.
As of pretix 2.7, this is mainly used for search queries in the backend and for rendering the
product list and event lists in the frontend, but we plan on expanding this in the future.
Therefore, for now our clear recommendation is: Try to scale your database vertically and put
it on the most powerful machine you have available.
redis
"""""
While redis is a very important part that glues together some of the components, it isn't used
heavily and can usually handle a fairly large pretix installation easily on a single modern
CPU core.
Having some memory available is good in case of e.g. lots of tasks queuing up during a traffic peak, but we wouldn't expect ever needing more than a gigabyte of it.
Feel free to set up a redis cluster for availability but you won't need it for performance in a long time.
The limitations
---------------
Up to a certain point, pretix scales really well. However, there are a few things that we consider
even more important than scalability, and those are correctness and reliability. We want you to be
able to trust that pretix will not sell more tickets than you intended or run into similar error
cases.
Combined with pretix' flexibility and complexity, especially around vouchers and quotas, this creates
some hard issues. In many cases, we need to fall back to event-global locking for some actions which
are likely to run with high concurrency and cause harm.
For every event, only one of these locking actions can be run at the same time. Examples for this are
adding products limited by a quota to a cart, adding items to a cart using a voucher or placing an order
consisting of cart positions that don't have a valid reservation for much longer. In these cases, it is
currently not realistically possible to exceed selling **approx. 500 orders per minute per event**, even
if you add more hardware.
If you have an unlimited number of tickets, we can apply fewer locking and we've reached **approx.
1500 orders per minute per event** in benchmarks, although even more should be possible.
We're working to reduce the number of cases in which this is relevant and thereby improve the possible
throughput. If you want to use pretix for an event with 10,000+ tickets that are likely to be sold out
within minutes, please get in touch to discuss possible solutions. We'll work something out for you!
.. _object storage cluster: https://behind.pretix.eu/2018/03/20/high-available-cdn/

9
doc/api/auth.rst Normal file
View File

@@ -0,0 +1,9 @@
Authentication
==============
.. toctree::
:maxdepth: 2
tokenauth
oauth
deviceauth

137
doc/api/deviceauth.rst Normal file
View File

@@ -0,0 +1,137 @@
.. _`rest-deviceauth`:
Device authentication
=====================
Initializing a new device
-------------------------
Users can create new devices in the "Device" section of their organizer settings. When creating
a new device, users can specify a list of events the device is allowed to access. After a new
device is created, users will be presented initialization instructions, consisting of an URL
and an initialization token. They will also be shown as a QR code with the following contents::
{"handshake_version": 1, "url": "https://pretix.eu", "token": "kpp4jn8g2ynzonp6"}
Your application should be able to scan a QR code of this type, or allow to enter the URL and the
initialization token manually. The handshake version is not used for manual initialization. When a
QR code is scanned with a higher handshake version than you support, you should reject the request
and prompt the user to update the client application.
After your application received the token, you need to call the initialization endpoint to obtain
a proper API token. At this point, you need to identify the name and version of your application,
as well as the type of underlying hardware. Example:
.. sourcecode:: http
POST /api/v1/device/initialize HTTP/1.1
Host: pretix.eu
Content-Type: application/json
{
"token": "kpp4jn8g2ynzonp6",
"hardware_brand": "Samsung",
"hardware_model": "Galaxy S",
"software_brand": "pretixdroid",
"software_version": "4.0.0"
}
Every initialization token can only be used once. On success, you will receive a response containing
information on your device as well as your API token:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{
"organizer": "foo",
"device_id": 5,
"unique_serial": "HHZ9LW9JWP390VFZ",
"api_token": "1kcsh572fonm3hawalrncam4l1gktr2rzx25a22l8g9hx108o9oi0rztpcvwnfnd",
"name": "Bar"
}
Please make sure that you store this ``api_token`` value. We also recommend storing your device ID, your assigned
``unique_serial``, and the ``organizer`` you have access to, but that's up to you.
In case of an error, the response will look like this:
.. sourcecode:: http
HTTP/1.1 400 Bad Request
Content-Type: application/json
{"token":["This initialization token has already been used."]}
Performing API requests
-----------------------
You need to include the API token with every request to pretix' API in the ``Authorization`` header
like the following:
.. sourcecode:: http
:emphasize-lines: 3
GET /api/v1/organizers/ HTTP/1.1
Host: pretix.eu
Authorization: Device 1kcsh572fonm3hawalrncam4l1gktr2rzx25a22l8g9hx108o9oi0rztpcvwnfnd
Updating the software version
-----------------------------
If your application is updated, we ask you to tell the server about the new version in use. You can do this at the
following endpoint:
.. sourcecode:: http
POST /api/v1/device/update HTTP/1.1
Host: pretix.eu
Content-Type: application/json
Authorization: Device 1kcsh572fonm3hawalrncam4l1gktr2rzx25a22l8g9hx108o9oi0rztpcvwnfnd
{
"hardware_brand": "Samsung",
"hardware_model": "Galaxy S",
"software_brand": "pretixdroid",
"software_version": "4.1.0"
}
Creating a new API key
----------------------
If you think your API key might have leaked or just want to be extra cautious, the API allows you to create a new key.
The old API key will be invalid immediately. A request for a new key looks like this:
.. sourcecode:: http
POST /api/v1/device/roll HTTP/1.1
Host: pretix.eu
Authorization: Device 1kcsh572fonm3hawalrncam4l1gktr2rzx25a22l8g9hx108o9oi0rztpcvwnfnd
The response will look like the response to the initialization request.
Removing a device
-----------------
If you want implement a way to to deprovision a device in your software, you can call the ``revoke`` endpoint to
invalidate your API key. There is no way to reverse this operation.
.. sourcecode:: http
POST /api/v1/device/revoke HTTP/1.1
Host: pretix.eu
Authorization: Device 1kcsh572fonm3hawalrncam4l1gktr2rzx25a22l8g9hx108o9oi0rztpcvwnfnd
This can also be done by the user through the web interface.
Permissions
-----------
Device authentication is currently hardcoded to grant the following permissions:
* View event meta data and products etc.
* View and change orders
Devices cannot change events or products and cannot access vouchers.

View File

@@ -9,44 +9,20 @@ with pretix' REST API, such as authentication, pagination and similar definition
Authentication
--------------
If you're building an application for end users, we strongly recommend that you use our
:ref:`OAuth-based authentication progress <rest-oauth>`. However, for simpler needs, you
can also go with static API tokens that you can create on a per-team basis (see below).
To access the API, you need to present valid authentication credentials. pretix currently
supports the following authorization schemes:
You need to include the API token with every request to pretix' API in the ``Authorization`` header
like the following:
.. sourcecode:: http
:emphasize-lines: 3
GET /api/v1/organizers/ HTTP/1.1
Host: pretix.eu
Authorization: Token e1l6gq2ye72thbwkacj7jbri7a7tvxe614ojv8ybureain92ocub46t5gab5966k
.. note:: The API currently also supports authentication via browser sessions, i.e. the
same way that you authenticate with pretix when using the browser interface.
Using this type of authentication is *not* officially supported for use by
third-party clients and might change or be removed at any time. We plan on
adding OAuth2 support in the future for user-level authentication. If you want
to use session authentication, be sure to comply with Django's `CSRF policies`_.
Obtaining an API token
----------------------
To authenticate your API requests, you need to obtain an API token. You can create a
token in the pretix web interface on the level of organizer teams. Create a new team
or choose an existing team that has the level of permissions the token should have and
create a new token using the form below the list of team members:
.. image:: img/token_form.png
:class: screenshot
You can enter a description for the token to distinguish from other tokens later on.
Once you click "Add", you will be provided with an API token in the success message.
Copy this token, as you won't be able to retrieve it again.
.. image:: img/token_success.png
:class: screenshot
* :ref:`rest-tokenauth`: This is the simplest way and recommended for server-side applications
that interact with pretix without user interaction.
* :ref:`rest-oauth`: This is the recommended way to use if you write a third-party application
that users can connect with their pretix account. It provides the best user experience, but
requires user interaction and slightly more implementation effort.
* :ref:`rest-deviceauth`: This is the recommended way if you build apps or hardware devices that can
connect to pretix, e.g. for processing check-ins or to sell tickets offline. It provides a way
to uniquely identify devices and allows for a quick configuration flow inside your software.
* Authentication using browser sessions: This is used by the pretix web interface and it is *not*
officially supported for use by third-party applications. It might change or be removed at any
time without prior notice. If you use it, you need to comply with Django's `CSRF policies`_.
Permissions
-----------
@@ -172,6 +148,7 @@ Field specific input errors include the name of the offending fields as keys in
{"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}
If you see errors of type ``429 Too Many Requests``, you should read our documentation on :ref:`rest-ratelimit`.
Data types
----------
@@ -204,4 +181,37 @@ as the string values ``true`` and ``false``.
If the ``ordering`` parameter is documented for a resource, you can use it to sort the result set by one of the allowed
fields. Prepend a ``-`` to the field name to reverse the sort order.
.. _CSRF policies: https://docs.djangoproject.com/en/1.11/ref/csrf/#ajax
Idempotency
-----------
Our API supports an idempotency mechanism to make sure you can safely retry operations without accidentally performing
them twice. This is useful if an API call experiences interruptions in transit, e.g. due to a network failure, and you
do not know if it completed successfully.
To perform an idempotent request, add a ``X-Idempotency-Key`` header with a random string value (we recommend a version
4 UUID) to your request. If we see a second request with the same ``X-Idempotency-Key`` and the same ``Authorization``
and ``Cookie`` headers, we will not perform the action for a second time but return the exact same response instead.
Please note that this also goes for most error responses. For example, if we returned you a ``403 Permission Denied``
error and you retry with the same ``X-Idempotency-Key``, you will get the same error again, even if you were granted
permission in the meantime! This includes internal server errors on our side that might have been fixed in the meantime.
There are only three exceptions to the rule:
* Responses with status code ``409 Conflict`` are not cached. If you send the request again, it will be executed as a
new request, since these responses are intended to be retried.
* Rate-limited responses with status code ``429 Too Many Requests`` are not cached and you can safely retry them.
* Responses with status code ``503 Service Unavailable`` are not cached and you can safely retry them.
If you send a request with an ``X-Idempotency-Key`` header that we have seen before but that has not yet received a
response, you will receive a response with status code ``409 Conflict`` and are asked to retry after five seconds.
We store idempotency keys for 24 hours, so you should never retry a request after a longer time period.
All ``POST``, ``PUT``, ``PATCH``, or ``DELETE`` api calls support idempotency keys. Adding an idempotency key to a
``GET``, ``HEAD``, or ``OPTIONS`` request has no effect.
.. _CSRF policies: https://docs.djangoproject.com/en/1.11/ref/csrf/#ajax

View File

@@ -14,5 +14,7 @@ in functionality over time.
:maxdepth: 2
fundamentals
oauth
auth
resources/index
ratelimit
webhooks

View File

@@ -1,7 +1,7 @@
.. _`rest-oauth`:
OAuth support / "Connect with pretix"
=====================================
OAuth authentication / "Connect with pretix"
============================================
In addition to static tokens, pretix supports `OAuth2`_-based authentication starting with
pretix 1.16. This allows you to put a "Connect with pretix" button into your website or tool
@@ -166,6 +166,42 @@ endpoint to revoke it.
If you want to revoke your client secret, you can generate a new one in the list of your managed applications in the
pretix user interface.
Fetching the user profile
-------------------------
If you need the user's meta data, you can fetch it here:
.. http:get:: /api/v1/me
Returns the profile of the authenticated user
**Example request**:
.. sourcecode:: http
GET /api/v1/me HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Authorization: Bearer i3ytqTSRWsKp16fqjekHXa4tdM4qNC
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
email: "admin@localhost",
fullname: "John Doe",
locale: "de",
timezone: "Europe/Berlin"
}
:statuscode 200: no error
:statuscode 401: Authentication failure
.. _OAuth2: https://en.wikipedia.org/wiki/OAuth
.. _OAuth2 Simplified: https://aaronparecki.com/oauth-2-simplified/
.. _HTTP Basic authentication: https://en.wikipedia.org/wiki/Basic_access_authentication
.. _HTTP Basic authentication: https://en.wikipedia.org/wiki/Basic_access_authentication

31
doc/api/ratelimit.rst Normal file
View File

@@ -0,0 +1,31 @@
.. _`rest-ratelimit`:
Rate limiting
=============
.. note:: This page only applies to the pretix Hosted service at pretix.eu. APIs of custom pretix installations do not
enforce any rate limiting by default.
All authenticated requests to pretix' API are rate limited. If you exceed the limits, you will receive a response
with HTTP status code ``429 Too Many Requests``. This response will have a ``Retry-After`` header, containing the number
of seconds you are supposed to wait until you try again. We expect that all API clients respect this. If you continue
to burst requests after a ``429`` status code, we might get in touch with you or, in extreme cases, disable your API
access.
Currently, the following rate limits apply:
.. rst-class:: rest-resource-table
===================================== =================================================================================
Authentication method Rate limit
===================================== =================================================================================
:ref:`rest-deviceauth` 360 requests per minute per device
:ref:`rest-tokenauth` 360 requests per minute per organizer account
:ref:`rest-oauth` 360 requests per minute per combination of accessed organizer and OAuth application
Session authentication *Not an officially supported authentication method for external access*
===================================== =================================================================================
If you require a higher rate limit, please get in touch at support@pretix.eu and tell us about your use case, we are
sure we can work something out.

View File

@@ -25,6 +25,7 @@ item integer ID of the item
variation integer ID of the variation (or ``null``)
price money (string) Price of this position
attendee_name string Specified attendee name for this position (or ``null``)
attendee_name_parts object of strings Composition of attendee name (i.e. first name, last name, …)
attendee_email string Specified attendee email address for this position (or ``null``)
voucher integer Internal ID of the voucher used for this position (or ``null``)
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
@@ -78,6 +79,7 @@ Cart position endpoints
"variation": null,
"price": "23.00",
"attendee_name": null,
"attendee_name_parts": {},
"attendee_email": null,
"voucher": null,
"addon_to": null,
@@ -122,6 +124,7 @@ Cart position endpoints
"variation": null,
"price": "23.00",
"attendee_name": null,
"attendee_name_parts": {},
"attendee_email": null,
"voucher": null,
"addon_to": null,
@@ -175,7 +178,7 @@ Cart position endpoints
* ``item``
* ``variation`` (optional)
* ``price``
* ``attendee_name`` (optional)
* ``attendee_name`` **or** ``attendee_name_parts`` (optional)
* ``attendee_email`` (optional)
* ``subevent`` (optional)
* ``expires`` (optional)
@@ -199,7 +202,10 @@ Cart position endpoints
"item": 1,
"variation": null,
"price": "23.00",
"attendee_name": "Peter",
"attendee_name_parts": {
"given_name": "Peter",
"family_name": "Miller"
},
"attendee_email": null,
"answers": [
{

View File

@@ -20,7 +20,7 @@ internal_name string An optional nam
description multi-lingual string A public description (might include markdown, can
be ``null``)
position integer An integer, used for sorting the categories
is_addon boolean If ``True``, items within this category are not on sale
is_addon boolean If ``true``, items within this category are not on sale
on their own but the category provides a source for
defining add-ons for other products.
===================================== ========================== =======================================================

View File

@@ -156,14 +156,14 @@ Endpoints
"checkin_count": 17,
"position_count": 42,
"event": {
"name": "Demo Converence",
"name": "Demo Conference"
},
"items": [
{
"name": "T-Shirt",
"id": 1,
"checkin_count": 1,
"admission": False,
"admission": false,
"position_count": 1,
"variations": [
{
@@ -184,7 +184,7 @@ Endpoints
"name": "Ticket",
"id": 2,
"checkin_count": 15,
"admission": True,
"admission": true,
"position_count": 22,
"variations": []
}
@@ -332,11 +332,28 @@ Order position endpoints
The ``.../redeem/`` endpoint has been added.
.. versionchanged:: 2.0
The order positions endpoint has been extended by the filter queries ``voucher`` and ``voucher__code``.
.. versionchanged:: 2.7
The resource now contains the new attributes ``require_attention`` and ``order__status`` and accepts the new
``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.
.. 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
the :ref:`order-position-resource`, with one important difference: the ``checkins`` value will only include
check-ins for the selected list.
the :ref:`order-position-resource`, with the following differences:
* The ``checkins`` value will only include check-ins for the selected list.
* An additional boolean property ``require_attention`` will inform you whether either the order or the item
have the ``checkin_attention`` flag set.
* If ``attendee_name`` is empty, it will automatically fall back to values from a parent product or from invoice
addresses.
**Example request**:
@@ -367,6 +384,9 @@ Order position endpoints
"variation": null,
"price": "23.00",
"attendee_name": "Peter",
"attendee_name_parts": {
"full_name": "Peter",
},
"attendee_email": null,
"voucher": null,
"tax_rate": "0.00",
@@ -400,6 +420,8 @@ Order position endpoints
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string ignore_status: If set to ``true``, results will be returned regardless of the state of
the order they belong to and you will need to do your own filtering by order status.
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``order__code``,
``order__datetime``, ``positionid``, ``attendee_name``, ``last_checked_in`` and ``order__email``. Default:
``attendee_name,positionid``
@@ -422,6 +444,8 @@ Order position endpoints
:query integer addon_to: Only return positions that are add-ons to the position with the given ID.
:query integer addon_to__in: Only return positions that are add-ons to one of the positions with the given
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.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param list: The ID of the check-in list to look for
@@ -433,8 +457,17 @@ Order position endpoints
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/(id)/
Returns information on one order position, identified by its internal ID.
The result format is the same as the :ref:`order-position-resource`, with one important difference: the
``checkins`` value will only include check-ins for the selected list.
The result is the same as the :ref:`order-position-resource`, with the following differences:
* The ``checkins`` value will only include check-ins for the selected list.
* An additional boolean property ``require_attention`` will inform you whether either the order or the item
have the ``checkin_attention`` flag set.
* If ``attendee_name`` is empty, it will automatically fall back to values from a parent product or from invoice
addresses.
**Instead of an ID, you can also use the ``secret`` field as the lookup parameter.**
**Example request**:
@@ -460,6 +493,9 @@ Order position endpoints
"variation": null,
"price": "23.00",
"attendee_name": "Peter",
"attendee_name_parts": {
"full_name": "Peter",
},
"attendee_email": null,
"voucher": null,
"tax_rate": "0.00",
@@ -504,6 +540,8 @@ Order position endpoints
Tries to redeem an order position, identified by its internal ID, i.e. checks the attendee in. This endpoint
accepts a number of optional requests in the body.
**Instead of an ID, you can also use the ``secret`` field as the lookup parameter.**
:<json boolean questions_supported: When this parameter is set to ``true``, handling of questions is supported. If
you do not implement question handling in your user interface, you **must**
set this to ``false``. In that case, questions will just be ignored. Defaults
@@ -512,7 +550,8 @@ Order position endpoints
:<json boolean force: Specifies that the check-in should succeed regardless of previous check-ins or required
questions that have not been filled. Defaults to ``false``.
:<json boolean ignore_unpaid: Specifies that the check-in should succeed even if the order is in pending state.
Defaults to ``false``.
Defaults to ``false`` and only works when ``include_pending`` is set on the check-in
list.
:<json string nonce: You can set this parameter to a unique random value to identify this check-in. If you're sending
this request twice with the same nonce, the second request will also succeed but will always
create only one check-in object even when the previous request was successful as well. This
@@ -549,7 +588,10 @@ Order position endpoints
Content-Type: application/json
{
"status": "ok"
"status": "ok",
"position": {
}
}
**Example response with required questions**:
@@ -560,7 +602,10 @@ Order position endpoints
Content-Type: text/json
{
"status": "incomplete"
"status": "incomplete",
"position": {
},
"questions": [
{
"id": 1,
@@ -605,6 +650,9 @@ Order position endpoints
{
"status": "error",
"reason": "unpaid",
"position": {
}
}
Possible error reasons:

View File

@@ -15,6 +15,7 @@ name multi-lingual string The event's ful
slug string A short form of the name, used e.g. in URLs.
live boolean If ``true``, the event ticket shop is publicly
available.
testmode boolean If ``true``, the ticket shop is in test mode.
currency string The currency this event is handled in.
date_from datetime The event's start date
date_to datetime The event's end date (or ``null``)
@@ -24,7 +25,7 @@ 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``)
has_subevents boolean ``True`` if the event series feature is active for this
has_subevents boolean ``true`` if the event series feature is active for this
event. Cannot change after event is created.
meta_data dict Values set for organizer-specific meta data parameters.
plugins list A list of package names of the enabled plugins for this
@@ -41,6 +42,14 @@ plugins list A list of packa
The ``plugins`` field has been added.
The operations POST, PATCH, PUT and DELETE have been added.
.. versionchanged:: 2.1
Filters have been added to the list of events.
.. versionchanged:: 2.5
The ``testmode`` attribute has been added.
Endpoints
---------
@@ -75,6 +84,7 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -96,6 +106,12 @@ Endpoints
}
:query page: The page number in case of a multi-page result set, default is 1
:query is_public: If set to ``true``/``false``, only events with a matching value of ``is_public`` are returned.
:query live: If set to ``true``/``false``, only events with a matching value of ``live`` are returned.
:query has_subevents: If set to ``true``/``false``, only events with a matching value of ``has_subevents`` are returned.
:query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned. Event series are never (always) returned.
:query is_past: If set to ``true`` (``false``), only events that are over are (not) returned. Event series are never (always) returned.
:query ends_after: If set to a date and time, only events that happen during of after the given time are returned. Event series are never returned.
:param organizer: The ``slug`` field of a valid organizer
:statuscode 200: no error
:statuscode 401: Authentication failure
@@ -127,6 +143,7 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -173,6 +190,7 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -201,6 +219,7 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -249,6 +268,7 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -277,6 +297,7 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -337,6 +358,7 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -357,7 +379,7 @@ Endpoints
:param organizer: The ``slug`` field of the organizer of the event to update
:param event: The ``slug`` field of the event to update
:statuscode 201: no error
:statuscode 200: no error
:statuscode 400: The event 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 this resource.

View File

@@ -11,6 +11,7 @@ Resources and endpoints
categories
items
item_variations
item_bundles
item_add-ons
questions
question_options
@@ -21,3 +22,4 @@ Resources and endpoints
checkinlists
waitinglist
carts
webhooks

View File

@@ -13,7 +13,7 @@ Field Type Description
===================================== ========================== =======================================================
number string Invoice number (with prefix)
order string Order code of the order this invoice belongs to
is_cancellation boolean ``True``, if this invoice is the cancellation of a
is_cancellation boolean ``true``, if this invoice is the cancellation of a
different invoice.
invoice_from string Sender address
invoice_to string Receiver address

View File

@@ -189,7 +189,7 @@ Endpoints
{
"min_count": 0,
"max_count": 10,
"max_count": 10
}
**Example response**:

View File

@@ -0,0 +1,242 @@
Item bundles
============
Resource description
--------------------
With bundles, you can specify products that are included within other products. There are two premier use cases of this:
* Package discounts. For example, you could offer a discounted package that includes three tickets but can only be
bought as a whole. With a bundle including three times the usual product, the package will automatically pull three
sub-items into the cart, making sure of correct quota calculation and issuance of the correct number of tickets.
* Tax splitting. For example, if your conference ticket includes a part that is subject to different taxation and that
you need to put on the invoice separately. When you putting a "designated price" on a bundled sub-item, pretix will
use that price to show a split taxation.
The bundles resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the bundling configuration
bundled_item integer Internal ID of the item that is included.
bundled_variation integer Internal ID of the variation of the item (or ``null``).
count integer Number of items included
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.
===================================== ========================== =======================================================
.. versionchanged:: 2.6
This resource has been added.
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/bundles/
Returns a list of all bundles for a given item.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/items/11/bundles/ 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": 2,
"next": null,
"previous": null,
"results": [
{
"id": 3,
"bundled_item": 3,
"bundled_variation": null,
"count": 1,
"designated_price": "0.00"
},
{
"id": 3,
"bundled_item": 3,
"bundled_variation": null,
"count": 2,
"designated_price": "1.50"
}
]
}
: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 event: The ``slug`` field of the event to fetch
:param item: The ``id`` field of the item to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/item does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/bundles/(id)/
Returns information on one bundle configuration, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/items/1/bundles/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": 3,
"bundled_item": 3,
"bundled_variation": null,
"count": 2,
"designated_price": "1.50"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param item: The ``id`` field of the item to fetch
:param id: The ``id`` field of the bundle to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
.. http:post:: /api/v1/organizers/bigevents/events/sampleconf/items/1/bundles/
Creates a new bundle configuration
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/(organizer)/events/(event)/items/(item)/bundles/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content: application/json
{
"bundled_item": 3,
"bundled_variation": null,
"count": 2,
"designated_price": "1.50"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 3,
"bundled_item": 3,
"bundled_variation": null,
"count": 2,
"designated_price": "1.50"
}
:param organizer: The ``slug`` field of the organizer of the event/item to create a bundle-configuration for
:param event: The ``slug`` field of the event to create a bundle configuration for
:param item: The ``id`` field of the item to create a bundle configuration for
:statuscode 201: no error
:statuscode 400: The bundle 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 this resource.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/bundles/(id)/
Update a bundle configuration. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all
fields of the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields
that you want to change.
You can change all fields of the resource except the ``id`` field.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/items/1/bundles/3/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"count": 2
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 3,
"bundled_item": 3,
"bundled_variation": null,
"count": 2,
"designated_price": "1.50"
}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param item: The ``id`` field of the item to modify
:param id: The ``id`` field of the bundle to modify
:statuscode 200: no error
:statuscode 400: The bundle configuration could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/items/(id)/bundles/(id)/
Delete a bundle configuration.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/items/1/bundles/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 item to modify
:param id: The ``id`` field of the bundle to delete
: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.

View File

@@ -18,12 +18,18 @@ default_price money (string) The price set d
price money (string) The price used for this variation. This is either the
same as ``default_price`` if that value is set or equal
to the item's ``default_price`` (read-only).
active boolean If ``False``, this variation will not be sold or shown.
original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
active boolean If ``false``, this variation will not be sold or shown.
description multi-lingual string A public description of the variation. May contain
Markdown syntax or can be ``null``.
position integer An integer, used for sorting
===================================== ========================== =======================================================
.. versionchanged:: 2.7
The attribute ``original_price`` has been added.
.. versionchanged:: 1.12
This resource has been added.
@@ -67,7 +73,8 @@ Endpoints
},
"position": 0,
"default_price": "223.00",
"price": 223.0
"price": 223.0,
"original_price": null,
},
{
"id": 3,
@@ -120,6 +127,7 @@ Endpoints
},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -167,6 +175,7 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -216,6 +225,7 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": false,
"description": null,
"position": 1

View File

@@ -21,32 +21,35 @@ default_price money (string) The item price
overwritten by variations or other options.
category integer The ID of the category this item belongs to
(or ``null``).
active boolean If ``False``, the item is hidden from all public lists
active boolean If ``false``, the item is hidden from all public lists
and will not be sold.
description multi-lingual string A public description of the item. May contain Markdown
syntax or can be ``null``.
free_price boolean If ``True``, customers can change the price at which
free_price boolean If ``true``, customers can change the price at which
they buy the product (however, the price can't be set
lower than the price defined by ``default_price`` or
otherwise).
tax_rate decimal (string) The VAT rate to be applied for this item.
tax_rate decimal (string) The VAT rate to be applied for this item (read-only,
set through ``tax_rule``).
tax_rule integer The internal ID of the applied tax rule (or ``null``).
admission boolean ``True`` for items that grant admission to the event
(such as primary tickets) and ``False`` for others
admission boolean ``true`` for items that grant admission to the event
(such as primary tickets) and ``false`` for others
(such as add-ons or merchandise).
position integer An integer, used for sorting
picture string A product picture to be displayed in the shop
(read-only).
(read-only, can be ``null``).
sales_channels list of strings Sales channels this product is available on, such as
``"web"`` or ``"resellers"``. Defaults to ``["web"]``.
available_from datetime The first date time at which this item can be bought
(or ``null``).
available_until datetime The last date time at which this item can be bought
(or ``null``).
require_voucher boolean If ``True``, this item can only be bought using a
require_voucher boolean If ``true``, this item can only be bought using a
voucher that is specifically assigned to this item.
hide_without_voucher boolean If ``True``, this item is only shown during the voucher
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
redemption process, but not in the normal shop
frontend.
allow_cancel boolean If ``False``, customers cannot cancel orders containing
allow_cancel boolean If ``false``, customers cannot cancel orders containing
this item.
min_per_order integer This product can only be bought if it is included at
least this many times in the order (or ``null`` for no
@@ -54,11 +57,21 @@ min_per_order integer This product ca
max_per_order integer This product can only be bought if it is included at
most this many times in the order (or ``null`` for no
limitation).
checkin_attention boolean If ``True``, the check-in app should show a warning
checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if such
a product is being scanned.
original_price money (string) An original price, shown for comparison, not used
for price calculations.
for price calculations (or ``null``).
require_approval boolean If ``true``, orders with this product will need to be
approved by the event organizer before they can be
paid.
require_bundling boolean If ``true``, this item is only available as part of bundles.
generate_tickets boolean If ``false``, tickets are never generated for this
product, regardless of other settings. If ``true``,
tickets are generated even if this is a
non-admission or add-on product, regardless of event
settings. If this is ``null``, regular ticketing
rules apply.
has_variations boolean Shows whether or not this item has variations.
variations list of objects A list with one object for each variation of this item.
Can be empty. Only writable during creation,
@@ -69,7 +82,9 @@ variations list of objects A list with one
├ price money (string) The price used for this variation. This is either the
same as ``default_price`` if that value is set or equal
to the item's ``default_price``.
active boolean If ``False``, this variation will not be sold or shown.
original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
├ active boolean If ``false``, this variation will not be sold or shown.
├ description multi-lingual string A public description of the variation. May contain
Markdown syntax or can be ``null``.
└ position integer An integer, used for sorting
@@ -80,10 +95,23 @@ addons list of objects Definition of a
chosen from.
├ min_count integer The minimal number of add-ons that need to be chosen.
├ max_count integer The maximal number of add-ons that can be chosen.
position integer An integer, used for sorting
position integer An integer, used for sorting
└ price_included boolean Adding this add-on to the item is free
bundles list of objects Definition of bundles that are included in this item.
Only writable during creation,
use separate endpoint to modify this later.
├ bundled_item integer Internal ID of the item that is included.
├ bundled_variation integer Internal ID of the variation of the item (or ``null``).
├ count integer Number of items included
└ 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.
===================================== ========================== =======================================================
.. versionchanged:: 2.7
The attribute ``original_price`` has been added for ``variations``.
.. versionchanged:: 1.7
The attribute ``tax_rule`` has been added. ``tax_rate`` is kept for compatibility. The attribute
@@ -96,17 +124,34 @@ addons list of objects Definition of a
.. versionchanged:: 1.16
The field ``internal_name`` and ``original_price`` fields have been added.
The ``internal_name`` and ``original_price`` fields have been added.
.. versionchanged:: 2.0
The field ``require_approval`` has been added.
.. versionchanged:: 2.3
The ``sales_channels`` attribute has been added.
.. versionchanged:: 2.4
The ``generate_tickets`` attribute has been added.
.. versionchanged:: 2.6
The ``bundles`` and ``require_bundling`` attributes have been added.
Notes
-----
Please note that an item either always has variations or never has. Once created with variations the item can never
change to an item without and vice versa. To create an item with variations ensure that you POST an item with at least
one variation.
Also note that ``variations`` and ``addons`` are only supported on ``POST``. To update/delete variations and add-ons please
use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT`` with nested
``variations`` and/or ``addons``.
Also note that ``variations``, ``bundles``, and ``addons`` are only supported on ``POST``. To update/delete variations,
bundles, and add-ons please use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT``
with nested ``variations``, ``bundles`` and/or ``addons``.
Endpoints
---------
@@ -140,6 +185,7 @@ Endpoints
"id": 1,
"name": {"en": "Standard ticket"},
"internal_name": "",
"sales_channels": ["web"],
"default_price": "23.00",
"original_price": null,
"category": null,
@@ -160,11 +206,15 @@ Endpoints
"max_per_order": null,
"checkin_attention": false,
"has_variations": false,
"generate_tickets": null,
"require_approval": false,
"require_bundling": false,
"variations": [
{
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -173,12 +223,14 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1
}
],
"addons": []
"addons": [],
"bundles": []
}
]
}
@@ -224,6 +276,7 @@ Endpoints
"id": 1,
"name": {"en": "Standard ticket"},
"internal_name": "",
"sales_channels": ["web"],
"default_price": "23.00",
"original_price": null,
"category": null,
@@ -240,15 +293,19 @@ Endpoints
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
"generate_tickets": null,
"min_per_order": null,
"max_per_order": null,
"checkin_attention": false,
"has_variations": false,
"require_approval": false,
"require_bundling": false,
"variations": [
{
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -257,12 +314,14 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1
}
],
"addons": []
"addons": [],
"bundles": []
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -289,6 +348,7 @@ Endpoints
"id": 1,
"name": {"en": "Standard ticket"},
"internal_name": "",
"sales_channels": ["web"],
"default_price": "23.00",
"original_price": null,
"category": null,
@@ -305,14 +365,18 @@ Endpoints
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
"generate_tickets": null,
"min_per_order": null,
"max_per_order": null,
"checkin_attention": false,
"require_approval": false,
"require_bundling": false,
"variations": [
{
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -321,12 +385,14 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1
}
],
"addons": []
"addons": [],
"bundles": []
}
**Example response**:
@@ -341,6 +407,7 @@ Endpoints
"id": 1,
"name": {"en": "Standard ticket"},
"internal_name": "",
"sales_channels": ["web"],
"default_price": "23.00",
"original_price": null,
"category": null,
@@ -359,13 +426,17 @@ Endpoints
"allow_cancel": true,
"min_per_order": null,
"max_per_order": null,
"generate_tickets": null,
"checkin_attention": false,
"has_variations": true,
"require_approval": false,
"require_bundling": false,
"variations": [
{
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -374,12 +445,14 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1
}
],
"addons": []
"addons": [],
"bundles": []
}
:param organizer: The ``slug`` field of the organizer of the event to create an item for
@@ -425,6 +498,7 @@ Endpoints
"id": 1,
"name": {"en": "Ticket"},
"internal_name": "",
"sales_channels": ["web"],
"default_price": "25.00",
"original_price": null,
"category": null,
@@ -440,16 +514,20 @@ Endpoints
"available_until": null,
"require_voucher": false,
"hide_without_voucher": false,
"generate_tickets": null,
"allow_cancel": true,
"min_per_order": null,
"max_per_order": null,
"checkin_attention": false,
"has_variations": true,
"require_approval": false,
"require_bundling": false,
"variations": [
{
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -458,12 +536,14 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1
}
],
"addons": []
"addons": [],
"bundles": []
}
:param organizer: The ``slug`` field of the organizer to modify

File diff suppressed because it is too large Load Diff

View File

@@ -128,7 +128,7 @@ Endpoints
POST /api/v1/organizers/bigevents/events/sampleconf/questions/1/options/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content: application/json
Content-Type: application/json
{
"identifier": "LVETRWVU",

View File

@@ -30,14 +30,17 @@ type string The expected ty
* ``D`` date
* ``H`` time
* ``W`` date and time
required boolean If ``True``, the question needs to be filled out.
* ``CC`` country code (ISO 3666-1 alpha-2)
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.
identifier string An arbitrary string that can be used for matching with
other sources.
ask_during_checkin boolean If ``True``, this question will not be asked while
ask_during_checkin boolean If ``true``, this question will not be asked while
buying the ticket, but will show up when redeeming
the ticket instead.
hidden boolean If ``true``, the question will only be shown in the
backend.
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.
@@ -46,6 +49,16 @@ options list of objects In case of ques
├ identifier string An arbitrary string that can be used for matching with
other sources.
└ answer multi-lingual string The displayed value of this option
dependency_question integer Internal ID of a different question. The current
question will only be shown if the question given in
this attribute is set to the value given in
``dependency_value``. This cannot be combined with
``ask_during_checkin``.
dependency_value string The value ``dependency_question`` needs to be set to.
If ``dependency_question`` is set to a boolean
question, this should be ``"true"`` or ``"false"``.
Otherwise, it should be the ``identifier`` of a
question option.
===================================== ========================== =======================================================
.. versionchanged:: 1.12
@@ -58,6 +71,10 @@ options list of objects In case of ques
Write methods have been added. The attribute ``identifier`` has been added to both the resource itself and the
options resource. The ``position`` attribute has been added to the options resource.
.. versionchanged:: 2.7
The attribute ``hidden`` and the question type ``CC`` have been added.
Endpoints
---------
@@ -100,6 +117,9 @@ Endpoints
"position": 1,
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"hidden": false,
"dependency_question": null,
"dependency_value": null,
"options": [
{
"id": 1,
@@ -165,6 +185,9 @@ Endpoints
"position": 1,
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"hidden": false,
"dependency_question": null,
"dependency_value": null,
"options": [
{
"id": 1,
@@ -214,6 +237,9 @@ Endpoints
"items": [1, 2],
"position": 1,
"ask_during_checkin": false,
"hidden": false,
"dependency_question": null,
"dependency_value": null,
"options": [
{
"answer": {"en": "S"}
@@ -245,6 +271,9 @@ Endpoints
"position": 1,
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"hidden": false,
"dependency_question": null,
"dependency_value": null,
"options": [
{
"id": 1,
@@ -314,6 +343,9 @@ Endpoints
"position": 2,
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"hidden": false,
"dependency_question": null,
"dependency_value": null,
"options": [
{
"id": 1,

View File

@@ -17,8 +17,11 @@ Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the sub-event
name multi-lingual string The sub-event's full name
event string The slug of the parent event
active boolean If ``true``, the sub-event ticket shop is publicly
available.
is_public boolean If ``true``, the sub-event ticket shop is publicly
shown in lists.
date_from datetime The sub-event's start date
date_to datetime The sub-event's end date (or ``null``)
date_admission datetime The sub-event's admission date (or ``null``)
@@ -40,6 +43,16 @@ meta_data dict Values set for
The ``meta_data`` field has been added.
.. versionchanged:: 2.1
The ``event`` field has been added, together with filters on the list of dates and an organizer-level list.
.. versionchanged:: 2.6
The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added.
.. versionchanged:: 2.7
The attribute ``is_public`` has been added.
Endpoints
---------
@@ -72,7 +85,9 @@ Endpoints
{
"id": 1,
"name": {"en": "First Sample Conference"},
"event": "sampleconf",
"active": false,
"is_public": true,
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
"date_admission": null,
@@ -92,12 +107,90 @@ Endpoints
}
:query page: The page number in case of a multi-page result set, default is 1
:query active: If set to ``true``/``false``, only events with a matching value of ``active`` are returned.
:query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned.
:query is_past: If set to ``true`` (``false``), only events that are over are (not) returned.
: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 event to fetch
:param event: The ``slug`` field of the main event
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/subevents/
Creates a new subevent.
Permission required: "Can create events"
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/subevents/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content: application/json
{
"name": {"en": "First Sample Conference"},
"active": false,
"is_public": true,
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
"date_admission": null,
"presale_start": null,
"presale_end": null,
"location": null,
"item_price_overrides": [
{
"item": 2,
"price": "12.00"
}
],
"variation_price_overrides": [],
"meta_data": {}
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": {"en": "First Sample Conference"},
"active": false,
"is_public": true,
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
"date_admission": null,
"presale_start": null,
"presale_end": null,
"location": null,
"item_price_overrides": [
{
"item": 2,
"price": "12.00"
}
],
"variation_price_overrides": [],
"meta_data": {}
}
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of the main event
:statuscode 201: no error
:statuscode 400: The sub-event 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:get:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/
Returns information on one sub-event, identified by its ID.
@@ -121,7 +214,9 @@ Endpoints
{
"id": 1,
"name": {"en": "First Sample Conference"},
"event": "sampleconf",
"active": false,
"is_public": true,
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
"date_admission": null,
@@ -138,9 +233,164 @@ Endpoints
"meta_data": {}
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``slug`` field of the sub-event to fetch
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of the main event
:param id: The ``id`` field of the sub-event to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view it.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/
Updates a sub-event, identified by its ID. 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.
Permission required: "Can change event settings"
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/subevents/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content: application/json
{
"name": {"en": "New Subevent Name"},
"item_price_overrides": [
{
"item": 2,
"price": "23.42"
}
],
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": {"en": "New Subevent Name"},
"event": "sampleconf",
"active": false,
"is_public": true,
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
"date_admission": null,
"presale_start": null,
"presale_end": null,
"location": null,
"item_price_overrides": [
{
"item": 2,
"price": "23.42"
}
],
"variation_price_overrides": [],
"meta_data": {}
}
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of the main event
:param id: The ``id`` field of the sub-event to update
:statuscode 200: no error
:statuscode 400: The sub-event could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/sub-event does not exist **or** you have no permission to update this resource.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/
Delete a sub-event. Note that events with orders cannot be deleted to ensure data integrity.
Permission required: "Can change event settings"
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/subevents/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 a valid organizer
:param event: The ``slug`` field of the main event
:param id: The ``id`` field of the sub-event to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/sub-event does not exist **or** you have no permission to delete this resource.
.. http:get:: /api/v1/organizers/(organizer)/subevents/
Returns a list of all sub-events of any event series you have access to within an organizer account.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/subevents/ 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": {"en": "First Sample Conference"},
"event": "sampleconf",
"active": false,
"is_public": true,
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
"date_admission": null,
"presale_start": null,
"presale_end": null,
"location": null,
"item_price_overrides": [
{
"item": 2,
"price": "12.00"
}
],
"variation_price_overrides": [],
"meta_data": {}
}
]
}
:query page: The page number in case of a multi-page result set, default is 1
:query active: If set to ``true``/``false``, only events with a matching value of ``active`` are returned.
:query event__live: If set to ``true``/``false``, only events with a matching value of ``live`` on the parent event are returned.
:query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned.
:query is_past: If set to ``true`` (``false``), only events that are over are (not) returned.
: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 event 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 it.

View File

@@ -18,8 +18,8 @@ max_usages integer The maximum num
redeemed integer The number of times this voucher already has been
redeemed.
valid_until datetime The voucher expiration date (or ``null``).
block_quota boolean If ``True``, quota is blocked for this voucher.
allow_ignore_quota boolean If ``True``, this voucher can be redeemed even if a
block_quota boolean If ``true``, quota is blocked for this voucher.
allow_ignore_quota boolean If ``true``, this voucher can be redeemed even if a
product is sold out and even if quota is not blocked
for this voucher.
price_mode string Determines how this voucher affects product prices.
@@ -231,6 +231,76 @@ Endpoints
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this resource.
:statuscode 409: The server was unable to acquire a lock and could not process your request. You can try again after a short waiting period.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/vouchers/batch_create/
Creates multiple new vouchers atomically.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/vouchers/batch_create/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 408
[
{
"code": "43K6LKM37FBVR2YG",
"max_usages": 1,
"valid_until": null,
"block_quota": false,
"allow_ignore_quota": false,
"price_mode": "set",
"value": "12.00",
"item": 1,
"variation": null,
"quota": null,
"tag": "testvoucher",
"comment": "",
"subevent": null
},
{
"code": "ASDKLJCYXCASDASD",
"max_usages": 1,
"valid_until": null,
"block_quota": false,
"allow_ignore_quota": false,
"price_mode": "set",
"value": "12.00",
"item": 1,
"variation": null,
"quota": null,
"tag": "testvoucher",
"comment": "",
"subevent": null
},
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
[
{
"id": 1,
"code": "43K6LKM37FBVR2YG",
}, …
}
:param organizer: The ``slug`` field of the organizer to create a vouchers for
:param event: The ``slug`` field of the event to create a vouchers for
:statuscode 201: no error
:statuscode 400: The vouchers 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 this resource.
:statuscode 409: The server was unable to acquire a lock and could not process your request. You can try again after a short waiting period.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/vouchers/(id)/
Update a voucher. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of

View File

@@ -0,0 +1,242 @@
.. _`rest-webhooks`:
Webhooks
========
.. note:: This page is about how to modify webhook settings themselves through the REST API. If you just want to know
how webhooks work, go here: :ref:`webhooks`
Resource description
--------------------
The webhook resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the webhook
enabled boolean If ``false``, this webhook will not receive any notifications
target_url string The URL to call
all_events boolean If ``true``, this webhook will receive notifications
on all events of this organizer
limit_events list of strings If ``all_events`` is ``false``, this is a list of
event slugs this webhook is active for
action_types list of strings A list of action type filters that limit the
notifications sent to this webhook. See below for
valid values
===================================== ========================== =======================================================
The following values for ``action_types`` are valid with pretix core:
* ``pretix.event.order.placed``
* ``pretix.event.order.paid``
* ``pretix.event.order.canceled``
* ``pretix.event.order.expired``
* ``pretix.event.order.modified``
* ``pretix.event.order.contact.changed``
* ``pretix.event.order.changed.*``
* ``pretix.event.order.refund.created.externally``
* ``pretix.event.order.approved``
* ``pretix.event.order.denied``
* ``pretix.event.checkin``
* ``pretix.event.checkin.reverted``
Installed plugins might register more valid values.
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/webhooks/
Returns a list of all webhooks within a given organizer.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/webhooks/ 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": 2,
"enabled": true,
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
}
]
}
: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)/webhooks/(id)/
Returns information on one webhook, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/webhooks/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": 2,
"enabled": true,
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
}
:param organizer: The ``slug`` field of the organizer to fetch
:param id: The ``id`` field of the webhook 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)/webhooks/
Creates a new webhook
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/webhooks/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content: application/json
{
"enabled": true,
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 3,
"enabled": true,
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
}
:param organizer: The ``slug`` field of the organizer to create a webhook for
:statuscode 201: no error
:statuscode 400: The webhook 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)/webhooks/(id)/
Update a webhook. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
You can change all fields of the resource except the ``id`` field.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/webhooks/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"enabled": false
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"enabled": false,
"target_url": "https://httpstat.us/200",
"all_events": false,
"limit_events": ["democon"],
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
}
:param organizer: The ``slug`` field of the organizer to modify
:param id: The ``id`` field of the webhook to modify
:statuscode 200: no error
:statuscode 400: The webhook 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)/webhook/(id)/
Delete a webhook. Currently, this will not delete but just disable the webhook.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/webhooks/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 id: The ``id`` field of the webhook to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to delete this resource.

36
doc/api/tokenauth.rst Normal file
View File

@@ -0,0 +1,36 @@
.. _`rest-tokenauth`:
Token-based authentication
==========================
Obtaining an API token
----------------------
To authenticate your API requests with Tokens, you need to obtain a team-level API token.
You can create a token in the pretix web interface on the level of organizer teams. Create
a new team or choose an existing team that has the level of permissions the token should
have and create a new token using the form below the list of team members:
.. image:: img/token_form.png
:class: screenshot
You can enter a description for the token to distinguish from other tokens later on.
Once you click "Add", you will be provided with an API token in the success message.
Copy this token, as you won't be able to retrieve it again.
.. image:: img/token_success.png
:class: screenshot
Using an API token
------------------
You need to include the API token with every request to pretix' API in the ``Authorization`` header
like the following:
.. sourcecode:: http
:emphasize-lines: 3
GET /api/v1/organizers/ HTTP/1.1
Host: pretix.eu
Authorization: Token e1l6gq2ye72thbwkacj7jbri7a7tvxe614ojv8ybureain92ocub46t5gab5966k

108
doc/api/webhooks.rst Normal file
View File

@@ -0,0 +1,108 @@
.. _`webhooks`:
Webhooks
========
pretix can send webhook calls to notify your application of any changes that happen inside pretix. This is especially
useful for everything triggered by an actual user, such as a new ticket sale or the arrival of a payment.
You can register any number of webhook URLs that pretix will notify any time one of the supported events occurs inside
your organizer account. A great example use case of webhooks would be to add the buyer to your mailing list every time
a new order comes in.
Configuring webhooks
--------------------
You can find the list of your active webhooks in the "Webhook" section of your organizer account:
.. thumbnail:: ../screens/organizer/webhook_list.png
:align: center
:class: screenshot
Click "Create webhook" if you want to add a new URL. You will then be able to enter the URL pretix shall call for
notifications. You need to select any number of notification types that you want to receive and you can optionally
filter the events you want to receive notifications for.
.. thumbnail:: ../screens/organizer/webhook_edit.png
:align: center
:class: screenshot
You can also configure webhooks :ref:`through the API itself <rest-webhooks>`.
Receiving webhooks
------------------
Creating a webhook endpoint on your server is no different from creating any other page on your website. If your
website is written in PHP, you might just create a new ``.php`` file on your server; if you use a web framework like
Symfony or Django, you would just create a new route with the desired URL.
We will call your URL with a HTTP ``POST`` request with a ``JSON`` body. In PHP, you can parse this like this::
$input = @file_get_contents('php://input');
$event_json = json_decode($input);
// Do something with $event_json
In Django, you would create a view like this::
def my_webhook_view(request):
event_json = json.loads(request.body)
# Do something with event_json
return HttpResponse(status=200)
More samples for the language of your choice are easy to find online.
The exact body of the request varies by notification type, but for the main types included with pretix core, such as
those related to changes of an order, it will look like this::
{
"notification_id": 123455,
"organizer": "acmecorp",
"event": "democon",
"code": "ABC23",
"action": "pretix.event.order.placed"
}
Notifications regarding a check-in will contain more details like ``orderposition_id``
and ``checkin_list``.
.. warning:: You should not trust data supplied to your webhook, but only use it as a trigger to fetch updated data.
Anyone could send data there if they guess the correct URL and you won't be able to tell. Therefore, we
only include the minimum amount of data necessary for you to fetch the changed objects from our
:ref:`rest-api` in an authenticated way.
If you want to further prevent others from accessing your webhook URL, you can also use `Basic authentication`_ and
supply the URL to us in the format of ``https://username:password@domain.com/path/``.
We recommend that you use HTTPS for your webhook URL and might require it in the future. If HTTPS is used, we require
that a valid certificate is in use.
.. note:: If you use a web framework that makes use of automatic CSRF protection, this protection might prevent us
from calling your webhook URL. In this case, we recommend that you turn of CSRF protection selectively
for that route. In Django, you can do this by putting the ``@csrf_exempt`` decorator on your view. In
Rails, you can pass an ``except`` parameter to ``protect_from_forgery``.
Responding to a webhook
-----------------------
If you successfully received a webhook call, your endpoint should return a HTTP status code between ``200`` and ``299``.
If any other status code is returned, we will assume you did not receive the call. This does mean that any redirection
or ``304 Not Modified`` response will be treated as a failure. pretix will not follow any ``301`` or ``302`` redirect
headers and pretix will ignore all other information in your response headers or body.
If we do not receive a status code in the range of ``200`` and ``299``, pretix will retry to deliver for up to three
days with an exponential back off. Therefore, we recommend that you implement your endpoint in a way where calling it
multiple times for the same event due to a perceived error does not do any harm.
There is only one exception: If status code ``410 Gone`` is returned, we will assume the
endpoint does not exist any more and automatically disable the webhook.
.. note:: If you use a self-hosted version of pretix (i.e. not our SaaS offering at pretix.eu) and you did not
configure a background task queue, failed webhooks will not be retried.
Debugging webhooks
------------------
If you want to debug your webhooks, you can view a log of all sent notifications and the responses of your server for
30 days right next to your configuration.
.. _Basic authentication: https://en.wikipedia.org/wiki/Basic_access_authentication

View File

@@ -64,7 +64,7 @@ Similarly, there is ``organizer_permission_required`` and ``OrganizerPermissionR
event-related views, there is also a signal that allows you to add the view to the event navigation like this::
from django.core.urlresolvers import resolve, reverse
from django.urls import resolve, reverse
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from pretix.control.signals import nav_event

View File

@@ -0,0 +1,109 @@
.. highlight:: python
:linenothreshold: 5
Writing an HTML e-mail renderer plugin
======================================
An email renderer class controls how the HTML part of e-mails sent by pretix is built.
The creation of such a plugin is very similar to creating an export output.
Please read :ref:`Creating a plugin <pluginsetup>` first, if you haven't already.
Output registration
-------------------
The email HTML renderer API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available email renderers. Your plugin
should listen for this signal and return the subclass of ``pretix.base.email.BaseHTMLMailRenderer``
that we'll provide in this plugin::
from django.dispatch import receiver
from pretix.base.signals import register_html_mail_renderers
@receiver(register_html_mail_renderers, dispatch_uid="renderer_custom")
def register_mail_renderers(sender, **kwargs):
from .email import MyMailRenderer
return MyMailRenderer
The renderer class
------------------
.. class:: pretix.base.email.BaseHTMLMailRenderer
The central object of each email renderer is the subclass of ``BaseHTMLMailRenderer``.
.. py:attribute:: BaseHTMLMailRenderer.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:: thumbnail_filename
This is an abstract attribute, you **must** override this!
.. autoattribute:: is_available
.. automethod:: render
This is an abstract method, you **must** implement this!
Helper class for template-base renderers
----------------------------------------
The email renderer that ships with pretix is based on Django templates to generate HTML.
In case you also want to render emails based on a template, we provided a ready-made base
class ``TemplateBasedMailRenderer`` that you can re-use to perform the following steps:
* Convert the body text and the signature to HTML using our markdown renderer
* Render the template
* Call `inlinestyler`_ to convert all ``<style>`` style sheets to inline ``style=""``
attributes for better compatibility
To use it, you just need to implement some variables::
class ClassicMailRenderer(TemplateBasedMailRenderer):
verbose_name = _('pretix default')
identifier = 'classic'
thumbnail_filename = 'pretixbase/email/thumb.png'
template_name = 'pretixbase/email/plainwrapper.html'
The template is passed the following context variables:
``site``
Name of the pretix installation (``settings.PRETIX_INSTANCE_NAME``)
``site_url``
Root URL of the pretix installation (``settings.SITE_URL``)
``body``
The body as markdown (render with ``{{ body|safe }}``)
``subject``
The email subject
``color``
The primary color of the event
``event``
The ``Event`` object
``signature`` (optional, only if configured)
The body as markdown (render with ``{{ signature|safe }}``)
``order`` (optional, only if applicable)
The ``Order`` object
.. _inlinestyler: https://pypi.org/project/inlinestyler/

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
item_copy_data, register_sales_channels, register_global_settings
Order events
""""""""""""
@@ -20,17 +20,13 @@ Order events
There are multiple signals that will be sent out in the ordering cycle:
.. automodule:: pretix.base.signals
:members: validate_cart, order_fee_calculation, order_paid, order_placed, order_fee_type_name, allow_ticket_download
:members: validate_cart, 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
Frontend
--------
.. automodule:: pretix.presale.signals
:members: html_head, html_footer, footer_links, front_page_top, front_page_bottom, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content
.. automodule:: pretix.presale.signals
:members: order_info, order_meta_from_request
: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, checkout_flow_steps, order_info, order_meta_from_request
Request flow
""""""""""""
@@ -48,7 +44,8 @@ Backend
-------
.. automodule:: pretix.control.signals
:members: nav_event, html_head, quota_detail_html, nav_topbar, nav_global, nav_organizer, nav_event_settings, order_info, event_settings_widget, oauth_application_registered
: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
.. automodule:: pretix.base.signals

View File

@@ -10,6 +10,8 @@ Contents:
exporter
ticketoutput
payment
payment_2.0
email
invoice
shredder
customview

View File

@@ -23,7 +23,7 @@ that we'll provide in this plugin::
@receiver(register_invoice_renderers, dispatch_uid="output_custom")
def register_infoice_renderers(sender, **kwargs):
def register_invoice_renderers(sender, **kwargs):
from .invoice import MyInvoiceRenderer
return MyInvoiceRenderer

View File

@@ -9,6 +9,10 @@ is very similar to creating an export output.
Please read :ref:`Creating a plugin <pluginsetup>` first, if you haven't already.
.. warning:: We changed our payment provider API a lot in pretix 2.x. Our documentation page on :ref:`payment2.0`
might be insightful even if you do not have a payment provider to port, as it outlines the rationale
behind the current design.
Provider registration
---------------------
@@ -31,7 +35,7 @@ that the plugin will provide::
The provider class
------------------
.. class:: pretix.base.payment.BasePaymentProvider
.. py:class:: pretix.base.payment.BasePaymentProvider
The central object of each payment provider is the subclass of ``BasePaymentProvider``.
@@ -54,58 +58,64 @@ The provider class
This is an abstract attribute, you **must** override this!
.. autoattribute:: is_enabled
.. autoattribute:: public_name
.. automethod:: calculate_fee
.. autoattribute:: is_enabled
.. autoattribute:: settings_form_fields
.. automethod:: settings_form_clean
.. automethod:: settings_content_render
.. automethod:: render_invoice_text
.. automethod:: is_allowed
.. automethod:: payment_form_render
.. automethod:: payment_form
.. automethod:: is_allowed
.. autoattribute:: payment_form_fields
.. automethod:: checkout_prepare
.. automethod:: payment_is_valid_session
.. automethod:: checkout_prepare
.. automethod:: checkout_confirm_render
This is an abstract method, you **must** override this!
.. automethod:: payment_perform
.. automethod:: execute_payment
.. automethod:: calculate_fee
.. automethod:: order_pending_mail_render
.. automethod:: order_pending_render
.. automethod:: payment_pending_render
This is an abstract method, you **must** override this!
.. autoattribute:: abort_pending_allowed
.. automethod:: render_invoice_text
.. automethod:: order_change_allowed
.. automethod:: order_can_retry
.. automethod:: payment_prepare
.. automethod:: order_prepare
.. automethod:: payment_control_render
.. automethod:: order_paid_render
.. automethod:: payment_refund_supported
.. automethod:: order_control_render
.. automethod:: payment_partial_refund_supported
.. automethod:: order_control_refund_render
.. automethod:: order_control_refund_perform
.. automethod:: is_implicit
.. automethod:: execute_refund
.. automethod:: shred_payment_info
.. autoattribute:: is_implicit
.. autoattribute:: is_meta
.. autoattribute:: test_mode_message
Additional views
----------------

View File

@@ -0,0 +1,129 @@
.. highlight:: python
:linenothreshold: 5
.. _`payment2.0`:
Porting a payment provider from pretix 1.x to pretix 2.x
========================================================
In pretix 2.x, we changed large parts of the payment provider API. This documentation details the changes we made
and shows you how you can make an existing pretix 1.x payment provider compatible with pretix 2.x
Conceptual overview
-------------------
In pretix 1.x, an order was always directly connected to a payment provider for the full life of an order. As long as
an order was unpaid, this could still be changed in some cases, but once an order was paid, no changes to the payment
provider were possible any more. Additionally, the internal state of orders allowed orders only to be fully paid or
not paid at all. This leads to a couple of consequences:
* Payment-related functions (like "execute payment" or "do a refund") always operated on full orders.
* Changing the total of an order was basically impossible once an order was paid, since there was no concept of
partial payments or partial refunds.
* Payment provider plugins needed to take complicated steps to detect cases that require human intervention, like e.g.
* An order has expired, no quota is left to revive it, but a payment has been received
* A payment has been received for a canceled order
* A payment has been received for an order that has already been paid with a different payment method
* An external payment service notified us of a refund/dispute
We noticed that we copied and repeated large portions of code in all our official payment provider plugins, just
to deal with some of these cases.
* Sometimes, there is the need to mark an order as refunded within pretix, without automatically triggering a refund
with an external API. Every payment method needed to implement a user interface for this independently.
* If a refund was not possible automatically, there was no way user to track which payments actually have been refunded
manually and which are still left to do.
* When the payment with one payment provider failed and the user changed to a different payment provider, all
information about the first payment was lost from the order object and could only be retrieved from order log data,
which also made it hard to design a data shredder API to get rid of this data.
In pretix 2.x, we introduced two new models, :py:class:`OrderPayment <pretix.base.models.OrderPayment>` and
:py:class:`OrderRefund <pretix.base.models.OrderRefund>`. Each instance of these is connected to an order and
represents one single attempt to pay or refund a specific amount of money. Each one of these has an individual state,
can individually fail or succeed, and carries an amount variable that can differ from the order total.
This has the following advantages:
* The system can now detect orders that are over- or underpaid, independent of the payment providers in use.
* Therefore, we can now allow partial payments, partial refunds, and changing paid orders, and automatically detect
the cases listed above and notify the user.
Payment providers now interact with those payment and refund objects more than with orders.
Your to-do list
---------------
Payment processing
""""""""""""""""""
* The method ``BasePaymentProvider.order_pending_render`` has been removed and replaced by a new
``BasePaymentProvider.payment_pending_render(request, payment)`` method that is passed an ``OrderPayment``
object instead of an ``Order``.
* The method ``BasePaymentProvider.payment_form_render`` now receives a new ``total`` parameter.
* The method ``BasePaymentProvider.payment_perform`` has been removed and replaced by a new method
``BasePaymentProvider.execute_payment(request, payment)`` that is passed an ``OrderPayment``
object instead of an ``Order``.
* The function ``pretix.base.services.mark_order_paid`` has been removed, instead call ``payment.confirm()``
on a pending ``OrderPayment`` object. If no further payments are required for this order, this will also
mark the order as paid automatically. Note that ``payment.confirm()`` can still throw a ``QuotaExceededException``,
however it will still mark the payment as complete (not the order!), so you should catch this exception and
inform the user, but not abort the transaction.
* A new property ``BasePaymentProvider.abort_pending_allowed`` has been introduced. Only if set, the user will
be able to retry a payment or switch the payment method when the order currently has a payment object in
state ``"pending"``. This replaces ``BasePaymentProvider.order_can_retry``, which no longer exists.
* The methods ``BasePaymentProvider.retry_prepare`` and ``BasePaymentProvider.order_prepare`` have both been
replaced by a new method ``BasePaymentProvider.payment_prepare(request, payment)`` that is passed an ``OrderPayment``
object instead of an ``Order``. **Keep in mind that this payment object might have an amount property that
differs from the order total, if the order is already partially paid.**
* The method ``BasePaymentProvider.order_paid_render`` has been removed.
* The method ``BasePaymentProvider.order_control_render`` has been removed and replaced by a new method
``BasePaymentProvider.payment_control_render(request, payment)`` that is passed an ``OrderPayment``
object instead of an ``Order``.
* There's no need to manually deal with excess payments or duplicate payments anymore, just setting the ``OrderPayment``
methods to the correct state will do the job.
Creating refunds
""""""""""""""""
* The methods ``BasePaymentProvider.order_control_refund_render`` and ``BasePaymentProvider.order_control_refund_perform``
have been removed.
* Two new boolean methods ``BasePaymentProvider.payment_refund_supported(payment)`` and ``BasePaymentProvider.payment_partial_refund_supported(payment)``
have been introduced. They should be set to return ``True`` if and only if the payment API allows to *automatically*
transfer the money back to the customer.
* A new method ``BasePaymentProvider.execute_refund(refund)`` has been introduced. This method is called using a
``OrderRefund`` object in ``"created"`` state and is expected to transfer the money back and confirm success with
calling ``refund.done()``. This will only ever be called if either ``BasePaymentProvider.payment_refund_supported(payment)``
or ``BasePaymentProvider.payment_partial_refund_supported(payment)`` return ``True``.
Processing external refunds
"""""""""""""""""""""""""""
* If e.g. a webhook API notifies you that a payment has been disputed or refunded with the external API, you are
expected to call ``OrderPayment.create_external_refund(self, amount, execution_date, info='{}')`` on this payment.
This will create and return an appropriate ``OrderRefund`` object and send out a notification. However, it will not
mark the order as refunded, but will ask the event organizer for a decision.
Data shredders
""""""""""""""
* The method ``BasePaymentProvider.shred_payment_info`` is no longer passed an order, but instead **either**
an ``OrderPayment`` **or** an ``OrderRefund``.

View File

@@ -49,15 +49,19 @@ description string A more verbose description of what your
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.
compatibility string Specifier for compatible pretix versions.
================== ==================== ===========================================================
A working example would be::
from django.apps import AppConfig
try:
from pretix.base.plugins import PluginConfig
except ImportError:
raise RuntimeError("Please use pretix 2.7 or above to run this plugin!")
from django.utils.translation import ugettext_lazy as _
class PaypalApp(AppConfig):
class PaypalApp(PluginConfig):
name = 'pretix_paypal'
verbose_name = _("PayPal")
@@ -68,6 +72,7 @@ A working example would be::
visible = True
restricted = False
description = _("This plugin allows you to receive payments via PayPal")
compatibility = "pretix>=2.7.0"
default_app_config = 'pretix_paypal.PaypalApp'
@@ -79,6 +84,9 @@ human-readable error messages. We recommend using the ``django.utils.functional.
decorator, as it might get called a lot. You can also implement ``compatibility_warnings``,
those will be displayed but not block the plugin execution.
The ``AppConfig`` class may implement a method ``is_available(event)`` that checks if a plugin
is available for a specific event. If not, it will not be shown in the plugin list of that event.
Plugin registration
-------------------

View File

@@ -82,6 +82,12 @@ Orders
^^^^^^
If a customer completes the checkout process, an **Order** will be created containing all the entered information.
An order can be in one of currently five states that are listed in the diagram below:
An order can be in one of currently four states that are listed in the diagram below:
.. image:: /images/order_states.png
There are additional "fake" states that are displayed like states but not represented as states in the system:
* An order is considered **canceled (with paid fee)** if it is in **paid** status but does not include any non-cancelled positions.
* An order is considered **requiring approval** if it is in **pending** status with the ``require_approval`` attribute set to ``True``.

View File

@@ -23,7 +23,7 @@ Organizers and events
:members:
.. autoclass:: pretix.base.models.Event
:members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running, cache, lock, get_plugins, get_mail_backend, payment_term_last, get_payment_providers, get_invoice_renderers, active_subevents, invoice_renderer, settings
:members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running, cache, lock, get_plugins, get_mail_backend, payment_term_last, get_payment_providers, get_invoice_renderers, invoice_renderer, settings
.. autoclass:: pretix.base.models.SubEvent
:members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running
@@ -86,6 +86,15 @@ Carts and Orders
.. autoclass:: pretix.base.models.OrderPosition
:members:
.. autoclass:: pretix.base.models.OrderFee
:members:
.. autoclass:: pretix.base.models.OrderPayment
:members:
.. autoclass:: pretix.base.models.OrderRefund
:members:
.. autoclass:: pretix.base.models.CartPosition
:members:

View File

@@ -18,7 +18,7 @@ External Dependencies
---------------------
Your should install the following on your system:
* Python 3.4 or newer
* Python 3.5 or newer
* ``pip`` for Python 3 (Debian package: ``python3-pip``)
* ``python-dev`` for Python 3 (Debian package: ``python3-dev``)
* ``libffi`` (Debian package: ``libffi-dev``)
@@ -54,10 +54,6 @@ The first thing you need are all the main application's dependencies::
cd src/
pip3 install -r requirements.txt -r requirements/dev.txt
If you are working with Python 3.4, you will also need (you can skip this for Python 3.5+)::
pip3 install -r requirements/py34.txt
Next, you need to copy the SCSS files from the source folder to the STATIC_ROOT directory::
python manage.py collectstatic --noinput

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -4,7 +4,6 @@ Pending: Order is expecting payment\nOrder reduces quotas
Expired: Payment period is over\nOrder does not affect quotas
Paid: Order was successful\nOrder reduces quotas
Canceled: Order has been canceled\nOrder does not affect quotas
Refunded: Order has been refunded\nOrder does not affect quotas
[*] --> Pending: customer\nplaces order
Pending --> Paid: successful payment
@@ -12,8 +11,9 @@ Pending --> Expired: automatically\nor manually\non admin action
Expired --> Paid: if payment is received\nonly if quota left
Expired --> Canceled
Expired --> Pending: manually\non admin action
Paid --> Refunded: manually on\nadmin action\nor if an external\npayment provider\nnotifies about a\npayment refund
Paid --> Canceled: manually on\nadmin action\nor if an external\npayment provider\nnotifies about a\npayment refund
Pending --> Canceled: on admin or\ncustomer action
Paid -> Pending: manually on admin action
[*] --> Paid: customer\nplaces free order
@enduml

View File

@@ -108,3 +108,43 @@ Endpoints
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view it.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/badgeitems/
Returns a list of all assignments of items to layouts
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/democon/badgeitems/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"layout": 2,
"item": 3,
}
]
}
: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 a valid event
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.

View File

@@ -316,7 +316,7 @@ uses to communicate with the pretix server.
"total": 42,
"version": 3,
"event": {
"name": "Demo Converence",
"name": "Demo Conference",
"slug": "democon",
"date_from": "2016-12-27T17:00:00Z",
"date_to": "2016-12-30T18:00:00Z",

View File

@@ -20,6 +20,7 @@ default boolean ``true`` if thi
layout object Layout specification for libpretixprint
background URL Background PDF file
item_assignments list of objects Products this layout is assigned to
├ sales_channel string Sales channel (defaults to ``web``).
└ item integer Item ID
===================================== ========================== =======================================================
@@ -27,6 +28,10 @@ item_assignments list of objects Products this l
This resource has been added.
.. versionchanged:: 2.3
The ``item_assignments.sales_channel`` field has been added.
Endpoints
---------
@@ -109,3 +114,44 @@ Endpoints
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view it.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/ticketlayoutitems/
Returns a list of all assignments of items to layouts
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/democon/ticketlayoutitems/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"layout": 2,
"item": 3,
"sales_channel": web
}
]
}
: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 a valid event
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,17 +1,21 @@
addon
addons
Analytics
anonymize
api
auditability
auth
autobuild
availabilities
backend
backends
banktransfer
Bcc
boolean
booleans
cancelled
casted
Ceph
checkbox
checksum
config
@@ -23,6 +27,7 @@ cronjob
cryptographic
debian
deduplication
deprovision
discoverable
django
dockerfile
@@ -49,6 +54,7 @@ linters
memcached
metadata
middleware
Minio
mixin
mixins
multi
@@ -64,6 +70,7 @@ ons
optimizations
overpayment
param
passphrase
percental
positionid
pre
@@ -88,7 +95,11 @@ regex
renderer
renderers
reportlab
SaaS
scalability
screenshot
scss
searchable
selectable
serializers
serializers
@@ -103,8 +114,11 @@ subdomains
subevent
subevents
submodule
subnet
subpath
Symfony
systemd
testmode
testutils
timestamp
tuples
@@ -113,6 +127,7 @@ unconfigured
unix
unprefixed
untrusted
uptime
username
url
versa

View File

@@ -21,11 +21,18 @@ Frontpage text
your product types, give more information on the event or for other general notices.
You can use :ref:`Markdown syntax <markdown-guide>` in this field.
Voucher explanation
This text will be shown above the voucher input box. You can use it to explain how to obtain a voucher and use it.
Show variations of a product expanded by default
If this is not checked, a product with variations will be shown as one row in the show by default and will expand
into multiple rows once it is clicked on. With this box checked, the variations will be shown as multiple rows
right from the beginning.
Ask search engines not to index the ticket shop
If this is checked, we will set a HTML meta attribute asking search engines by Google not to put this ticket shop
into their searchable index.
The lower part of the page contains settings that you can **either** set on organizer-level for all your events **or**
override for this single event individually. Those are:
@@ -35,6 +42,12 @@ Primary color
customers. We suggest not choosing something to light, since text in that color should be readable on a white
background and white text should be readable on a background of this color.
Accent color for success
This color will be used for success messages. We suggest to choose a dark shade of green.
Accent color for errors
This color will be used for error messages. We suggest to choose a dark shade of red.
Font
Choose one of multiple fonts to use for your web shop.

View File

@@ -8,8 +8,8 @@ event.
:align: center
:class: screenshot
The page is separated into three parts: "E-mail settings", "E-mail content" and "SMTP settings". We will explain all
of them in detail on this page.
The page is separated into four parts: "E-mail settings", "E-mail design", "E-mail content" and "SMTP settings".
We will explain all of them in detail on this page.
E-mail settings
---------------
@@ -30,10 +30,18 @@ Signature
This text will be appended to all e-mails in form of a signature. This might be useful e.g. to add your contact
details or any legal information that needs to be included with the e-mails.
Bcc address
This email address will receive a copy of every event-related email.
E-mail design
-------------
In this part, you can choose and preview the layout of your emails. More layouts can be added by pretix plugins.
E-mail content
--------------
The middle part of the page allows you to customize the exact texts of all e-mails sent by the system automatically.
The next part of the page allows you to customize the exact texts of all e-mails sent by the system automatically.
You can click on the different boxes to expand them and see the texts.
Within the texts, you can use placeholders that will later by replaced by values depending on the event or order. Below
@@ -45,6 +53,7 @@ Placeholder Description
============================== ===============================================================================
event The event name
total The order's total value
total_with_currency The order's total value with a localized currency sign
currency The currency used for the event (three-letter code)
payment_info Information text specific to the payment method (e.g. banking details)
url An URL pointing to the download/status page of the order
@@ -112,6 +121,22 @@ Reminder to download tickets
attendees to download their tickets. The e-mail should include a link to the ticket download. This e-mail will only
ever be sent if you specify a number of days.
Order approval process
If you configure one of your products to "require approval", orders of that product will not immediately be confirmed
but only after you approved them manually. In this case, the following e-mail templates will be sent out.
Received order
After an order has been received, this e-mail will be sent automatically instead of the "order placed" e-mail from
above.
Approved order
This e-mail will be sent after you manually approved an order. This should include instructions to pay for the order,
which is why this will only be used for a paid order. For a free order, the "free order" e-mail from above will
be sent.
Denied order
This e-mail will be sent out to customers when their order has been denied.
SMTP settings
-------------

View File

@@ -11,18 +11,6 @@ The settings at "Settings" → "Invoice" allow you to specify if and how pretix
In particular, you can configure the following things:
Ask for invoice address
If this checkbox is enabled, customers will be able to enter an invoice address during checkout. If you only enable
this box, the invoice address will be optional to fill in.
Require invoice address
If this checkbox is enabled, entering an invoice address will be obligatory for all customers and it will not be
able to create an order without entering an address.
Require customer name
If this checkbox is enabled, the street, city, and country fields of the invoice address will still be optional but
the name field will be obligatory.
Generate invoices
This field controls whether pretix should generate an invoice for an order. You have the following options:
@@ -51,6 +39,51 @@ Attach invoices to emails
"Automatically for all created orders" or to the payment confirmation e-mails if it is set to "Automatically on
payment".
Invoice number prefix
This is the prefix that will be prepended to all your invoice numbers. For example, if you set this to "Inv", your
invoices will be numbered Inv00001, Inv00002, etc. If you leave this field empty, your event slug will be used,
followed by a dash, e.g. DEMOCON-00001.
Within one organizer account, events with the same number prefix will share their number range. For example, if you
set this to "Inv" for all of your events, there will be only one invoice numbered Inv00007 across all your events
and the numbers will have gaps within one event.
Generate invoices with consecutive numbers
If enabled, invoices will be created with numerical invoice numbers in the order of their creation, i.e.
PREFIX-00001, PREFIX-00002, and so on. If disabled, invoice numbers will instead be generated from the order code,
i.e. PREFIX-YHASD-1. When in doubt, keep this option enabled since it might be legally required in your country,
but disabling it has the advantage that your customers can not estimate the number of tickets sold by looking at
the invoice numbers.
Invoice language
This setting allows you to configure the language of all invoices. You can either set it to one of your event
language or dynamically to the language used by the customer.
Show free products on invoices
If enabled, products that do not cost anything will still show up on invoices. Note that the order needs to contain
at least one non-free product in order to generate an invoice.
Show attendee names on invoices
If enabled, the attendee name will be printed on the invoice for admission tickets.
Ask for invoice address
If this checkbox is enabled, customers will be able to enter an invoice address during checkout. If you only enable
this box, the invoice address will be optional to fill in.
Require invoice address
If this checkbox is enabled, entering an invoice address will be obligatory for all customers and it will not be
able to create an order without entering an address.
Require customer name
If this checkbox is enabled, the street, city, and country fields of the invoice address will still be optional but
the name field will be obligatory.
Require a business address
If enabled, the invoice address form will require a company name and do not allow personal addresses.
Ask for beneficiary
If enabled, the invoice address form will contain an additional field to input the beneficiary of the transaction.
Ask for VAT ID
If enabled, the invoice address form will not only ask for a postal address, but also for a VAT ID. The VAT ID will
always be an optional field.
@@ -62,26 +95,13 @@ Generate invoices with consecutive numbers
but disabling it has the advantage that your customers can not estimate the number of tickets sold by looking at
the invoice numbers.
Invoice number prefix
This is the prefix that will be prepended to all your invoice numbers. For example, if you set this to "Inv", your
invoices will be numbered Inv00001, Inv00002, etc. If you leave this field empty, your event slug will be used,
followed by a dash, e.g. DEMOCON-00001.
Within one organizer account, events with the same number prefix will share their number range. For example, if you
set this to "Inv" for all of your events, there will be only one invoice numbered Inv00007 across all your events
and the numbers will have gaps within one event.
Show free products on invoices
If enabled, products that do not cost anything will still show up on invoices. Note that the order needs to contain
at least one non-free product in order to generate an invoice.
Show attendee names on invoices
If enabled, the attendee name will be printed on the invoice for admission tickets.
Your address
This should be set to the address of the entity issuing the invoice (read: you) and will be printed inside
Your invoice details
These fields should be set to the address of the entity issuing the invoice (read: you) and will be printed inside
the header of the invoice.
Invoice style
This setting allows you to choose the design of your invoice. Additional designs can be added by pretix plugins.
Introductory text
A free custom text that will be printed above the list of products on the invoice.

View File

@@ -0,0 +1,260 @@
Product structure guide
=======================
Between products, categories, variations, add-ons, bundles, and quotas, pretix provides a wide range of features that allow you to set up your event in the way you want it.
However, it is easy to get lost in the process or to get started with building your event right.
Often times, there are multiple ways to do something that come with different advantages and disadvantages.
This guide will walk you through a number of typical examples of pretix event structures and will explain how to set them up feel free to just skip ahead to a section relevant for you.
Terminology
-----------
Products
A product is a basic entity that can be bought. You can think of it as a ticket type, but it can be more things than just a ticket, it can also be a piece of merchandise, a parking slot, etc.
You might find some places where they are called "items" instead, but we're trying to get rid of that.
Product categories
Products can be sorted into categories. Each product can only be in one category. Category are mostly used for grouping related products together to make your event page easier to read for buyers. However, we'll need categories as well to set up some of the structures outlined below.
Product variations
During creation of a product, you can decide that your product should have multiple variations. Variations of a product can differ in price, description, and availability, but are otherwise the same. You could use this e.g. for differentiating between a regular ticket and a discounted ticket for students, or when selling merchandise to differentiate the different sizes of a t-shirt.
Product add-ons
Add-ons are products that are sold together with another product (which we will call the base product in this case). For example, you could have a base product "Conference ticket" and then define multiple workshops that can be chosen as an add-on.
Product bundles
Bundles work very similarly to add-ons, but are different in the way that they are always automatically included with the base product and cannot be optional. In contrast to add-on products, the same product can be included multiple times in a bundle.
Quotas
Quotas define the availability of products. A quota has a size (i.e. the number of products in the inventory) and is mapped to one or multiple products or variations.
Questions
Questions are user-defined form fields that buyers will need to fill out when purchasing a product.
Use case: Multiple price levels
-------------------------------
Imagine you're running a concert with general admission that sells a total of 200 tickets for two prices:
* Regular: € 25.00
* Students: € 19.00
You can either set up two different products called e.g. "Regular ticket" and "Student ticket" with the respective prices, or two variations within the same product. In this simple case, it really doesn't matter.
In addition, you will need quotas. If you do not care how many of your tickets are sold to students, you should set up just **one quota** of 200 called e.g. "General admission" that you link to **both products**.
If you want to limit the number of student tickets to 50 to ensure a certain minimum revenue, but do not want to limit the number of regular tickets artificially, we suggest you to create the same quota of 200 that is linked to both products, and then create a **second quota** of 50 that is only linked to the student ticket. This way, the system will reduce both quotas whenever a student ticket is sold and only the larger quota when a regular ticket is sold.
Use case: Early-bird tiers
--------------------------
Let's say you run a conference that has the following pricing scheme:
* 12 to 6 months before the event: € 450
* 6 to 3 months before the event: € 550
* closer than 3 months to the event: € 650
Of course, you could just set up one product and change its price at the given dates manually, but if you want to set this up automatically, here's how:
Create three products (e.g. "super early bird", "early bird", "regular ticket") with the respective prices and one shared quota of your total event capacity. Then, set the **available from** and **available until** configuration fields of the products to automatically turn them on and off based on the current date.
.. note::
pretix currently can't do early-bird tiers based on **ticket number** instead of time. We're planning this feature for later in 2019. For now, you'll need to monitor that manually.
Use case: Up-selling of ticket extras
-------------------------------------
Let's assume you're putting up a great music festival, and to save trouble with handling payments on-site, you want to sell parking spaces together with your ticket. By using our add-on feature, you can prompt all users to book the parking space (to make sure they see it) and ensure that only people with a ticket can book a parking space. You can set it up like this:
* Create a base product "Festival admission"
* Create a quota for the base product
* Create a category "Ticket extras" and check "Products in this category are add-on products"
* Create a product "Parking space" within that category
* Create a quota for the parking space product
* Go to the base product and select the tab "Add-Ons" at the top. Click "Add a new add-on" and choose the "Ticket extras" category. You can keep the numbers at 0 and 1.
During checkout, all buyers of the base product will now be prompted if they want to add the parking space.
.. tip::
You can also use add-on products for free things, just to keep tabs on capacity.
Use case: Conference with workshops
-----------------------------------
When running a conference, you might also organize a number of workshops with smaller capacity. To be able to plan, it would be great to know which workshops an attendee plans to attend.
Your first and simplest option is to just create a multiple-choice question. This has the upside of making it easy for users to change their mind later on, but will not allow you to restrict the number of attendees signing up for a given workshop or even charge extra for a given workshop.
The usually better option is to go with add-on products. Let's take for example the following conference schedule, in which the lecture can be attended by anyone, but the workshops only have space for 20 persons each:
==================== =================================== ===================================
Time Room A Room B
==================== =================================== ===================================
Wednesday morning Lecture
Wednesday afternoon Workshop A Workshop B
Thursday morning Workshop C Workshop D (20 € extra charge)
==================== =================================== ===================================
Assuming you already created one or more products for your general conference admission, we suggest that you additionally create:
* A category called "Workshops" with the checkbox "Products in this category are add-on products" activated
* A free product called "Wednesday afternoon" within the category "Workshops" and with two variations:
* Workshop A
* Workshop B
* A free product called "Thursday morning" within the category "Workshops" and with two variations:
* Workshop C
* Workshop D with a price of 20 €
* Four quotas for each of the workshops
* One add-on configuration on your base product that allows users to choose between 0 and 2 products from the category "Workshops"
Use case: Discounted packages
-----------------------------
Imagine you run a trade show that opens on three consecutive days and you want to have the following pricing:
* Single day: € 10
* Any two days: € 17
* All three days: € 25
In this case, there are multiple different ways you could set this up with pretix.
Option A: Combination products
""""""""""""""""""""""""""""""
With this option, you just set up all the different combinations someone could by as a separate product. In this case, you would need 7 products:
* Day 1 pass
* Day 2 pass
* Day 3 pass
* Day 1+2 pass
* Day 2+3 pass
* Day 1+3 pass
* All-day pass
Then, you create three quotas, each one with the maximum capacity of your venue on any given day:
* Day 1 quota, linked to "Day 1 pass", "Day 1+2 pass", "Day 1+3 pass", and "All-day pass"
* Day 2 quota, linked to "Day 2 pass", "Day 1+2 pass", "Day 2+3 pass", and "All-day pass"
* Day 3 quota, linked to "Day 3 pass", "Day 2+3 pass", "Day 1+3 pass", and "All-day pass"
This way, every person gets exactly one ticket that they can use for all days that they attend. You can later set up check-in lists appropriately to make sure only tickets valid for a certain day can be scanned on that day.
The benefit of this option is that your product structure and order structure stays very simple. However, the two-day packages scale badly when you need many products.
We recommend this setup for most setups in which the number of possible combinations does not exceed the number of parts (here: number of days) by much.
Option B: Add-ons and bundles
"""""""""""""""""""""""""""""
We can combine the two features "product add-ons" and "product bundles" to set this up in a different way. Here, you would create the following five products:
* Day 1 pass in a category called "Day passes"
* Day 2 pass in a category called "Day passes"
* Day 3 pass in a category called "Day passes"
* Two-day pass
* All-day pass
This time, you will need five quotas:
* Day 1 quota, linked to "Day 1 pass"
* Day 2 quota, linked to "Day 2 pass"
* Day 3 quota, linked to "Day 3 pass"
* Two-day pass quota, linked to "Two-day pass" (can be unlimited)
* All-day pass quota, linked to "All-day pass" (can be unlimited)
Then, you open the "Add-On" tab in the settings of the **Two-day pass** product and create a new add-on configuration specifying the following options:
* Category: "Day passes"
* Minimum number: 2
* Maximum number: 2
* Add-Ons are included in the price: Yes
This way, when buying a two-day pass, the user will be able to select *exactly* two days for free, which will then be added to the cart. Depending on your specific configuration, the user will now receive *two separate* tickets, one for each day.
For the all-day pass, you open the "Bundled products" tab in the settings of the **All-day pass** product and add **three** new bundled items with the following options:
* Bundled product: "Day 1/2/3"
* Bundled variation: None
* Count: 1
* Designated price: 0
This way, when buying an all-day pass, three free day passes will *automatically* be added to the cart. Depending on your specific configuration, the user will now receive *three separate* tickets, one for each day.
This approach makes your order data more complicated, since e.g. someone who buys an all-day pass now technically bought **four products**. However, this option allows for more flexibility when you have lots of options to choose from.
.. tip::
Depending on the packages you offer, you **might not need both the add-on and the bundle feature**, i.e. you only need the add-on feature for the two-day pass and only the bundle feature for the all-day pass. You could also set up the two-day pass like we showed here, but the all-day pass like in option A!
Use case: Group discounts
-------------------------
Often times, you want to give discounts for whole groups attending your event. pretix can't automatically discount based on volume, but there's still some ways you can set up group tickets.
Flexible group sizes
""""""""""""""""""""
If you want to give out discounted tickets to groups starting at a given size, but still billed per person, you can do so by creating a special **Group ticket** at the per-person price and set the **Minimum amount per order** option of the ticket to the minimal group size.
This way, your ticket can be bought an arbitrary number of times but no less than the given minimal amount per order.
Fixed group sizes
"""""""""""""""""
If you want to sell group tickets in fixed sizes, e.g. a table of eight at your gala dinner, you can use product bundles. Assuming you already set up a ticket for admission of single persons, you then set up a second product **Table (8 persons)** with a discounted full price. Then, head to the **Bundled products** tab of that product and add one bundle configuration to include the single admission product **eight times**. Next, create an unlimited quota mapped to the new product.
This way, the purchase of a table will automatically create eight tickets, leading to a correct calculation of your total quota and, as expected, eight persons on your check-in list. You can even ask for the individual names of the persons during checkout.
Use case: Restricted audience
-----------------------------
Not all events are for everyone. Sometimes, there is a good reason to restrict access to your event or parts of your event only to a specific, invited group. There's two ways to implement this with pretix:
Option A: Required voucher codes
""""""""""""""""""""""""""""""""
If you check the option "**This product can only be bought using a voucher**" of one or multiple products, only people holding an applicable voucher code will be able to buy the product.
You can then generate voucher codes for the respective product and send them out to the group of possible attendees. If the recipients should still be able to choose between different products, you can create an additional quota and map the voucher to that quota instead of the products themselves.
There's also the second option "**This product will only be shown if a voucher matching the product is redeemed**". In this case, the existence of the product won't even be shown before a voucher code is entered useful for a VIP option in a shop where you also sell other products to the general public. Please note that this option does **not** work with vouchers assigned to a quota, only with vouchers assigned directly to the product.
This option is appropriate if you know the group of people beforehand, e.g. members of a club, and you can mail them their access codes.
Option B: Order approvals
"""""""""""""""""""""""""
If you do not know your audience already, but still want to restrict it to a certain group, e.g. people with a given profession, you can check the "**Buying this product requires approval**" in the settings of your product. If a customer tries to buy such a product, they will be able to place their order but can not proceed to payment. Instead, you will be asked to approve or deny the order and only if you approve it, we will send a payment link to the customer.
This requires the customer to interact with the ticket shop twice (once for the order, once for the payment) which adds a little more friction, but gives you full control over who attends the event.
Use case: Mixed taxation
------------------------
Let's say you are a charitable organization in Germany and are allowed to charge a reduced tax rate of 7% for your educational event. However, your event includes a significant amount of food, you might need to charge a 19% tax rate on that portion. For example, your desired tax structure might then look like this:
* Conference ticket price: € 450 (incl. € 150 for food)
* incl. € 19.63 VAT at 7%
* incl. € 23.95 VAT at 19%
You can implement this in pretix using product bundles. In order to do so, you should create the following two products:
* Conference ticket at € 450 with a 7% tax rule
* Conference food at € 150 with a 19% tax rule and the option "**Only sell this product as part of a bundle**" set
In addition to your normal conference quota, you need to create an unlimited quota for the food product.
Then, head to the **Bundled products** tab of the "conference ticket" and add the "conference food" as a bundled product with a **designated price** of € 150.
Once a customer tries to buy the € 450 conference ticket, a sub-product will be added and the price will automatically be split into the two components, leading to a correct computation of taxes.

View File

@@ -25,6 +25,10 @@ Generate tickets for non-admission products
By default, tickets will only be generated for products that are marked as admission products. Enable this option to
generate tickets for all products instead.
Offer to download tickets even before an order is paid
By default, ticket download is only possible for paid orders. If you run an event where people usually pay only after
the event, you can check this box to enable ticket download even before.
Below these settings, the detail settings for the various ticket file formats are offered. They differ from format to
format and only share the common "Enable" setting that can be used to turn them on. By default, pretix ships with
a PDF output plugin that you can configure through a visual design editor.

View File

@@ -107,6 +107,42 @@ voucher's settings.
</div>
</noscript>
Disabling the voucher input
---------------------------
If you want to disable voucher input in the widget, you can pass the ``disable-vouchers`` attribute::
<pretix-widget event="https://pretix.eu/demo/democon/" disable-vouchers></pretix-widget>
Multi-event selection
---------------------
If you want to embed multiple events in a single widget, you can do so. If it's multiple dates of an event series, just leave off the ``series`` attribute::
<pretix-widget event="https://pretix.eu/demo/series/"></pretix-widget>
If you want to include all your public events, you can just reference your organizer::
<pretix-widget event="https://pretix.eu/demo/"></pretix-widget>
There is an optional ``style`` parameter that let's you choose between a calendar view and a list view. If you do not set it, the choice will be taken from your organizer settings::
<pretix-widget event="https://pretix.eu/demo/series/" style="list"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" style="calendar"></pretix-widget>
You can see an example here:
.. raw:: html
<pretix-widget event="https://pretix.eu/demo/series/" style="calendar"></pretix-widget>
<noscript>
<div class="pretix-widget">
<div class="pretix-widget-info-message">
JavaScript is disabled in your browser. To access our ticket shop without javascript, please <a target="_blank" href="https://pretix.eu/demo/series/">click here</a>.
</div>
</div>
</noscript>
pretix Button
-------------
@@ -136,14 +172,107 @@ resources. Then, instead of the ``pretix-widget`` tag, use the ``pretix-button``
As you can see, the ``pretix-button`` element takes an additional ``items`` attribute that specifies the items that
should be added to the cart. The syntax of this attribute is ``item_ITEMID=1,item_ITEMID=2,variation_ITEMID_VARID=4``
where ``ITEMID`` are the internal IDs of items to be added and ``VARID`` are the internal IDs of variations of those
items, if the items have variations.
items, if the items have variations. If you omit the ``items`` attribute, the general start page will be presented.
Just as the widget, the button supports the optional attributes ``voucher`` and ``skip-ssl-check``.
You can style the button using the ``pretix-button`` CSS class.
.. versionchanged:: 1.13
Dynamically loading the widget
------------------------------
The pretix Button has been added in version 1.13.
If you need to control the way or timing the widget loads, for example because you want to modify user data (see
below) dynamically via JavaScript, you can register a listener that we will call before creating the widget::
<script type="text/javascript">
window.pretixWidgetCallback = function () {
// Will be run before we create the widget.
}
</script>
If you want, you can suppress us loading the widget and/or modify the user data passed to the widget::
<script type="text/javascript">
window.pretixWidgetCallback = function () {
window.PretixWidget.build_widgets = false;
window.PretixWidget.widget_data["email"] = "test@example.org";
}
</script>
If you then later want to trigger loading the widgets, just call ``window.PretixWidget.buildWidgets()``.
Passing user data to the widget
-------------------------------
If you display the widget in a restricted area of your website and you want to pre-fill fields in the checkout process
with known user data to save your users some typing and increase conversions, you can pass additional data attributes
with that information::
<pretix-widget event="https://pretix.eu/demo/democon/"
data-attendee-name-given-name="John"
data-attendee-name-family-name="Doe"
data-invoice-address-name-given-name="John"
data-invoice-address-name-family-name="Doe"
data-email="test@example.org"
data-question-L9G8NG9M="Foobar">
</pretix-widget>
This works for the pretix Button as well. 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).
* ``data-question-IDENTIFIER`` will pre-fill the answer for the question with the given identifier. You can view and set
identifiers in the *Questions* section of the backend.
* Depending on the person name scheme configured in your event settings, you can pass one or more of
``data-attendee-name-full-name``, ``data-attendee-name-given-name``, ``data-attendee-name-family-name``,
``data-attendee-name-middle-name``, ``data-attendee-name-title``, ``data-attendee-name-calling-name``,
``data-attendee-name-latin-transcription``. If you don't know or don't care, you can also just pass a string as
``data-attendee-name``, which will pre-fill the last part of the name, whatever that is.
* ``data-invoice-address-FIELD`` will pre-fill the corresponding field of the invoice address. Possible values for
``FIELD`` are ``company``, ``street``, ``zipcode``, ``city`` and ``country``, as well as fields specified by the
naming scheme such as ``name-title`` or ``name-given-name`` (see above). ``country`` expects a two-character
country code.
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:
* If you use the campaigns plugin, you can pass a campaign ID as a value to ``data-campaign``. This way, all orders
made through this widget will be counted towards this campaign.
* If you use the tracking plugin, you can pass a Google Analytics User ID to enable cross-domain tracking. This will
require you to dynamically load the widget, like this::
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXX-1', 'auto');
ga('send', 'pageview');
window.pretixWidgetCallback = function () {
window.PretixWidget.build_widgets = false;
window.addEventListener('load', function() { // Wait for GA to be loaded
if(window.ga && ga.create) {
ga(function(tracker) {
window.PretixWidget.widget_data["tracking-ga-id"] = tracker.get('clientId');
window.PretixWidget.buildWidgets()
});
} else { // Tracking is probably blocked
window.PretixWidget.buildWidgets()
}
});
};
</script>
.. 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.
.. _Let's Encrypt: https://letsencrypt.org/

View File

@@ -4,22 +4,10 @@ FAQ and Troubleshooting
How can I test my shop before taking it live?
---------------------------------------------
There are multiple ways to do this.
First, you could just create some orders in your real shop and cancel/refund them later. If you don't want to process
real payments for the tests, you can either use a "manual" payment method like bank transfer and just mark the orders
as paid with the button in the backend, or if you want to use e.g. Stripe, you can configure pretix to use your keys
for the Stripe test system and use their test credit cars. Read our :ref:`Stripe documentation <stripe>` for more
information.
Second, you could create a separate event, just for testing. In the last step of the :ref:`event creation process <event_create>`,
you can specify that you want to copy all settings from your real event, so you don't have to do all of it twice.
We are planning to add a dedicated test mode in a later version of pretix.
If you are using the hosted service at pretix.eu and want to get rid of the test orders completely, contact us at
support@pretix.eu and we can remove them for you. Please note that we only are able to do that *before* you have
received any real orders (i.e. taken the shop public). We won't charge any fees for test orders or test events.
On your event dashboard, click on the first tile that shows your shop status. On the lower part of this page, you can
place your event into "test mode". In "test mode", everything behaves the same, but orders created during test mode can
later be fully deleted. Be sure to actually delete them when or after you turn off test mode, since test mode orders
still count toward your quotas and are included in your reports.
How do I delete an event?
-------------------------

View File

@@ -10,6 +10,7 @@ wanting to use pretix to sell tickets.
organizers/index
events/create
events/settings
events/structureguide
events/widget
faq
markdown

View File

@@ -3,6 +3,13 @@
PayPal
======
.. note::
If you use pretix Hosted, you do not longer need to go through this tedious process! You can
just open the PayPal settings in the payment section of your event, click "Connect to PayPal"
and log in to your PayPal account. The following guide is only required for self-hosted
versions of pretix.
To integrate PayPal with pretix, you first need to have an active PayPal merchant account. If you do not already have a
PayPal account, you can create one on `paypal.com`_.
If you look into pretix' settings, you are required to fill in two keys:

6
readthedocs.yml Normal file
View File

@@ -0,0 +1,6 @@
build:
image: latest
python:
version: 3.6

View File

@@ -1,12 +0,0 @@
[run]
source = pretix
omit = */migrations/*,*/urls.py,*/tests/*,*/testdummy/*,*/admin.py,pretix/wsgi.py,pretix/settings.py
[report]
exclude_lines =
pragma: no cover
def __str__
der __repr__
if settings.DEBUG
NOQA
NotImplementedError

View File

@@ -34,4 +34,5 @@ git push
# Unlock Weblate
for c in $COMPONENTS; do
wlc unlock $c;
wlc pull $c;
done

View File

@@ -8,6 +8,8 @@ recursive-include pretix/control/templates *
recursive-include pretix/presale/templates *
recursive-include pretix/plugins/banktransfer/templates *
recursive-include pretix/plugins/banktransfer/static *
recursive-include pretix/plugins/manualpayment/templates *
recursive-include pretix/plugins/manualpayment/static *
recursive-include pretix/plugins/paypal/templates *
recursive-include pretix/plugins/pretixdroid/templates *
recursive-include pretix/plugins/pretixdroid/static *

View File

@@ -1 +1 @@
__version__ = "2.0.0.dev0"
__version__ = "2.7.0"

View File

@@ -5,5 +5,8 @@ class PretixApiConfig(AppConfig):
name = 'pretix.api'
label = 'pretixapi'
def ready(self):
from . import signals, webhooks # noqa
default_app_config = 'pretix.api.PretixApiConfig'

View File

@@ -0,0 +1,25 @@
from django.contrib.auth.models import AnonymousUser
from rest_framework import exceptions
from rest_framework.authentication import TokenAuthentication
from pretix.base.models import Device
class DeviceTokenAuthentication(TokenAuthentication):
model = Device
keyword = 'Device'
def authenticate_credentials(self, key):
model = self.get_model()
try:
device = model.objects.select_related('organizer').get(api_token=key)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
if not device.initialized:
raise exceptions.AuthenticationFailed('Device has not been initialized.')
if device.revoked:
raise exceptions.AuthenticationFailed('Device access has been revoked.')
return AnonymousUser(), device

View File

@@ -1,7 +1,8 @@
from rest_framework.permissions import SAFE_METHODS, BasePermission
from pretix.api.models import OAuthAccessToken
from pretix.base.models import Event
from pretix.base.models import Device, Event, User
from pretix.base.models.auth import SuperuserPermissionSet
from pretix.base.models.organizer import Organizer, TeamAPIToken
from pretix.helpers.security import (
SessionInvalid, SessionReauthRequired, assert_session_valid,
@@ -9,10 +10,9 @@ from pretix.helpers.security import (
class EventPermission(BasePermission):
model = TeamAPIToken
def has_permission(self, request, view):
if not request.user.is_authenticated and not isinstance(request.auth, TeamAPIToken):
if not request.user.is_authenticated and not isinstance(request.auth, (Device, TeamAPIToken)):
return False
if request.method not in SAFE_METHODS and hasattr(view, 'write_permission'):
@@ -31,17 +31,20 @@ class EventPermission(BasePermission):
except SessionReauthRequired:
return False
perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken)
perm_holder = (request.auth if isinstance(request.auth, (Device, TeamAPIToken))
else request.user)
if 'event' in request.resolver_match.kwargs and 'organizer' in request.resolver_match.kwargs:
request.event = Event.objects.filter(
slug=request.resolver_match.kwargs['event'],
organizer__slug=request.resolver_match.kwargs['organizer'],
).select_related('organizer').first()
if not request.event or not perm_holder.has_event_permission(request.event.organizer, request.event):
if not request.event or not perm_holder.has_event_permission(request.event.organizer, request.event, request=request):
return False
request.organizer = request.event.organizer
request.eventpermset = perm_holder.get_event_permission_set(request.organizer, request.event)
if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key):
request.eventpermset = SuperuserPermissionSet()
else:
request.eventpermset = perm_holder.get_event_permission_set(request.organizer, request.event)
if required_permission and required_permission not in request.eventpermset:
return False
@@ -50,9 +53,12 @@ class EventPermission(BasePermission):
request.organizer = Organizer.objects.filter(
slug=request.resolver_match.kwargs['organizer'],
).first()
if not request.organizer or not perm_holder.has_organizer_permission(request.organizer):
if not request.organizer or not perm_holder.has_organizer_permission(request.organizer, request=request):
return False
request.orgapermset = perm_holder.get_organizer_permission_set(request.organizer)
if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key):
request.orgapermset = SuperuserPermissionSet()
else:
request.orgapermset = perm_holder.get_organizer_permission_set(request.organizer)
if required_permission and required_permission not in request.orgapermset:
return False
@@ -76,7 +82,7 @@ class EventCRUDPermission(EventPermission):
return False
elif view.action == 'destroy' and 'can_change_event_settings' not in request.eventpermset:
return False
elif view.action in ['retrieve', 'update', 'partial_update'] \
elif view.action in ['update', 'partial_update'] \
and 'can_change_event_settings' not in request.eventpermset:
return False

View File

@@ -10,7 +10,10 @@ def custom_exception_handler(exc, context):
if isinstance(exc, LockTimeoutException):
response = Response(
{'detail': 'The server was too busy to process your request. Please try again.'},
status=status.HTTP_409_CONFLICT
status=status.HTTP_409_CONFLICT,
headers={
'Retry-After': 5
}
)
return response

View File

@@ -0,0 +1,91 @@
import json
from hashlib import sha1
from django.conf import settings
from django.db import transaction
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.utils.timezone import now
from rest_framework import status
from pretix.api.models import ApiCall
class IdempotencyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request: HttpRequest):
if request.method in ('GET', 'HEAD', 'OPTIONS'):
return self.get_response(request)
if not request.path.startswith('/api/'):
return self.get_response(request)
if not request.headers.get('X-Idempotency-Key'):
return self.get_response(request)
auth_hash_parts = '{}:{}'.format(
request.headers.get('Authorization', ''),
request.COOKIES.get(settings.SESSION_COOKIE_NAME, '')
)
auth_hash = sha1(auth_hash_parts.encode()).hexdigest()
idempotency_key = request.headers.get('X-Idempotency-Key', '')
with transaction.atomic():
call, created = ApiCall.objects.select_for_update().get_or_create(
auth_hash=auth_hash,
idempotency_key=idempotency_key,
defaults={
'locked': now(),
'request_method': request.method,
'request_path': request.path,
'response_code': 0,
'response_headers': '{}',
'response_body': b''
}
)
if created:
resp = self.get_response(request)
with transaction.atomic():
if resp.status_code in (409, 429, 503):
# This is the exception: These calls are *meant* to be retried!
call.delete()
else:
call.response_code = resp.status_code
if isinstance(resp.content, str):
call.response_body = resp.content.encode()
elif isinstance(resp.content, memoryview):
call.response_body = resp.content.tobytes()
elif isinstance(resp.content, bytes):
call.response_body = resp.content
elif hasattr(resp.content, 'read'):
call.response_body = resp.read()
elif hasattr(resp, 'data'):
call.response_body = json.dumps(resp.data)
else:
call.response_body = repr(resp).encode()
call.response_headers = json.dumps(resp._headers)
call.locked = None
call.save(update_fields=['locked', 'response_code', 'response_headers',
'response_body'])
return resp
else:
if call.locked:
r = JsonResponse(
{'detail': 'Concurrent request with idempotency key.'},
status=status.HTTP_409_CONFLICT,
)
r['Retry-After'] = 5
return r
content = call.response_body
if isinstance(content, memoryview):
content = content.tobytes()
r = HttpResponse(
content=content,
status=call.response_code,
)
for k, v in json.loads(call.response_headers).values():
r[k] = v
return r

View File

@@ -46,7 +46,7 @@ class Migration(migrations.Migration):
('updated', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='Application name')),
('redirect_uris', models.TextField(help_text='Allowed URIs list, space separated',
validators=[oauth2_provider.validators.validate_uris],
validators=[oauth2_provider.validators.URIValidator],
verbose_name='Redirection URIs')),
('client_id',
models.CharField(db_index=True, default=oauth2_provider.generators.generate_client_id, max_length=100,

View File

@@ -0,0 +1,79 @@
# Generated by Django 2.1.1 on 2018-11-07 10:46
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0102_auto_20181017_0024'),
('pretixapi', '0002_auto_20180604_1120'),
]
operations = [
migrations.CreateModel(
name='WebHook',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('enabled', models.BooleanField(default=True, verbose_name='Enable webhook')),
('target_url', models.URLField(verbose_name='Target URL')),
('all_events', models.BooleanField(default=False, verbose_name='All events (including newly created ones)')),
('limit_events', models.ManyToManyField(blank=True, to='pretixbase.Event', verbose_name='Limit to events')),
('organizer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Organizer')),
],
),
migrations.CreateModel(
name='WebHookCall',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('datetime', models.DateTimeField(auto_now_add=True)),
('target_url', models.URLField()),
('is_retry', models.BooleanField(default=False)),
('execution_time', models.FloatField(null=True)),
('return_code', models.PositiveIntegerField(default=0)),
('payload', models.TextField()),
('response_body', models.TextField()),
('webhook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixapi.WebHook')),
],
),
migrations.CreateModel(
name='WebHookEventListener',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('action_type', models.CharField(max_length=255)),
('webhook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixapi.WebHook')),
],
),
migrations.AddField(
model_name='webhookcall',
name='success',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='webhook',
name='all_events',
field=models.BooleanField(default=True, verbose_name='All events (including newly created ones)'),
),
migrations.AlterField(
model_name='webhook',
name='organizer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='webhooks', to='pretixbase.Organizer'),
),
migrations.AlterField(
model_name='webhookcall',
name='webhook',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='calls', to='pretixapi.WebHook'),
),
migrations.AlterField(
model_name='webhookeventlistener',
name='webhook',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='listeners', to='pretixapi.WebHook'),
),
migrations.AddField(
model_name='webhookcall',
name='action_type',
field=models.CharField(default='', max_length=255),
preserve_default=False,
),
]

View File

@@ -0,0 +1,44 @@
# Generated by Django 2.1.5 on 2019-04-05 10:48
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('pretixbase', '0116_auto_20190402_0722'),
('pretixapi', '0003_webhook_webhookcall_webhookeventlistener'),
]
operations = [
migrations.CreateModel(
name='ApiCall',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('idempotency_key', models.CharField(db_index=True, max_length=190)),
('auth_hash', models.CharField(db_index=True, max_length=190)),
('created', models.DateTimeField(auto_now_add=True)),
('locked', models.DateTimeField(null=True)),
('request_method', models.CharField(max_length=20)),
('request_path', models.CharField(max_length=255)),
('response_code', models.PositiveIntegerField()),
('response_headers', models.TextField()),
('response_body', models.BinaryField()),
],
),
migrations.AlterModelOptions(
name='webhookcall',
options={'ordering': ('-datetime',)},
),
migrations.AlterModelOptions(
name='webhookeventlistener',
options={'ordering': ('action_type',)},
),
migrations.AlterUniqueTogether(
name='apicall',
unique_together={('idempotency_key', 'auth_hash')},
),
]

View File

@@ -11,13 +11,13 @@ from oauth2_provider.models import (
AbstractAccessToken, AbstractApplication, AbstractGrant,
AbstractRefreshToken,
)
from oauth2_provider.validators import validate_uris
from oauth2_provider.validators import URIValidator
class OAuthApplication(AbstractApplication):
name = models.CharField(verbose_name=_("Application name"), max_length=255, blank=False)
redirect_uris = models.TextField(
blank=False, validators=[validate_uris],
blank=False, validators=[URIValidator],
verbose_name=_("Redirection URIs"),
help_text=_("Allowed URIs list, space separated")
)
@@ -68,3 +68,61 @@ class OAuthRefreshToken(AbstractRefreshToken):
OAuthAccessToken, on_delete=models.SET_NULL, blank=True, null=True,
related_name="refresh_token"
)
class WebHook(models.Model):
organizer = models.ForeignKey('pretixbase.Organizer', on_delete=models.CASCADE, related_name='webhooks')
enabled = models.BooleanField(default=True, verbose_name=_("Enable webhook"))
target_url = models.URLField(verbose_name=_("Target URL"))
all_events = models.BooleanField(default=True, verbose_name=_("All events (including newly created ones)"))
limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True)
class Meta:
ordering = ('id',)
@property
def action_types(self):
return [
l.action_type for l in self.listeners.all()
]
class WebHookEventListener(models.Model):
webhook = models.ForeignKey('WebHook', on_delete=models.CASCADE, related_name='listeners')
action_type = models.CharField(max_length=255)
class Meta:
ordering = ("action_type",)
class WebHookCall(models.Model):
webhook = models.ForeignKey('WebHook', on_delete=models.CASCADE, related_name='calls')
datetime = models.DateTimeField(auto_now_add=True)
target_url = models.URLField()
action_type = models.CharField(max_length=255)
is_retry = models.BooleanField(default=False)
execution_time = models.FloatField(null=True)
return_code = models.PositiveIntegerField(default=0)
success = models.BooleanField(default=False)
payload = models.TextField()
response_body = models.TextField()
class Meta:
ordering = ("-datetime",)
class ApiCall(models.Model):
idempotency_key = models.CharField(max_length=190, db_index=True)
auth_hash = models.CharField(max_length=190, db_index=True)
created = models.DateTimeField(auto_now_add=True)
locked = models.DateTimeField(null=True)
request_method = models.CharField(max_length=20)
request_path = models.CharField(max_length=255)
response_code = models.PositiveIntegerField()
response_headers = models.TextField()
response_body = models.BinaryField()
class Meta:
unique_together = (('idempotency_key', 'auth_hash'),)

View File

@@ -19,18 +19,19 @@ class CartPositionSerializer(I18nAwareModelSerializer):
class Meta:
model = CartPosition
fields = ('id', 'cart_id', 'item', 'variation', 'price', 'attendee_name', 'attendee_email',
'voucher', 'addon_to', 'subevent', 'datetime', 'expires', 'includes_tax',
fields = ('id', 'cart_id', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'attendee_email', 'voucher', 'addon_to', 'subevent', 'datetime', 'expires', 'includes_tax',
'answers',)
class CartPositionCreateSerializer(I18nAwareModelSerializer):
answers = AnswerCreateSerializer(many=True, required=False)
expires = serializers.DateTimeField(required=False)
attendee_name = serializers.CharField(required=False, allow_null=True)
class Meta:
model = CartPosition
fields = ('cart_id', 'item', 'variation', 'price', 'attendee_name', 'attendee_email',
fields = ('cart_id', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'subevent', 'expires', 'includes_tax', 'answers',)
def create(self, validated_data):
@@ -65,6 +66,11 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
quota.name
)
)
attendee_name = validated_data.pop('attendee_name', '')
if attendee_name and not validated_data.get('attendee_name_parts'):
validated_data['attendee_name_parts'] = {
'_legacy': attendee_name
}
cp = CartPosition.objects.create(event=self.context['event'], **validated_data)
for answ_data in answers_data:
@@ -118,4 +124,8 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
raise ValidationError(
'You cannot specify a variation for this item.'
)
if data.get('attendee_name') and data.get('attendee_name_parts'):
raise ValidationError(
{'attendee_name': ['Do not specify attendee_name if you specified attendee_name_parts.']}
)
return data

View File

@@ -1,9 +1,11 @@
from django.conf import settings
from django.core.exceptions import ValidationError
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 rest_framework.relations import SlugRelatedField
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import Event, TaxRule
@@ -29,10 +31,10 @@ class PluginsField(Field):
def to_representation(self, obj):
from pretix.base.plugins import get_all_plugins
return {
return sorted([
p.module for p in get_all_plugins()
if not p.name.startswith('.') and getattr(p, 'visible', True) and p.module in obj.get_plugins()
}
])
def to_internal_value(self, data):
return {
@@ -46,7 +48,7 @@ class EventSerializer(I18nAwareModelSerializer):
class Meta:
model = Event
fields = ('name', 'slug', 'live', 'currency', 'date_from',
fields = ('name', 'slug', 'live', 'testmode', 'currency', 'date_from',
'date_to', 'date_admission', 'is_public', 'presale_start',
'presale_end', 'location', 'has_subevents', 'meta_data', 'plugins')
@@ -94,7 +96,7 @@ class EventSerializer(I18nAwareModelSerializer):
from pretix.base.plugins import get_all_plugins
plugins_available = {
p.module for p in get_all_plugins()
p.module for p in get_all_plugins(self.instance)
if not p.name.startswith('.') and getattr(p, 'visible', True)
}
@@ -107,7 +109,7 @@ class EventSerializer(I18nAwareModelSerializer):
@transaction.atomic
def create(self, validated_data):
meta_data = validated_data.pop('meta_data', None)
plugins = validated_data.pop('plugins', None)
plugins = validated_data.pop('plugins', settings.PRETIX_PLUGINS_DEFAULT.split(','))
event = super().create(validated_data)
# Meta data
@@ -121,6 +123,7 @@ class EventSerializer(I18nAwareModelSerializer):
# Plugins
if plugins is not None:
event.set_active_plugins(plugins)
event.save(update_fields=['plugins'])
return event
@@ -188,16 +191,114 @@ class SubEventItemVariationSerializer(I18nAwareModelSerializer):
class SubEventSerializer(I18nAwareModelSerializer):
item_price_overrides = SubEventItemSerializer(source='subeventitem_set', many=True)
variation_price_overrides = SubEventItemVariationSerializer(source='subeventitemvariation_set', many=True)
item_price_overrides = SubEventItemSerializer(source='subeventitem_set', many=True, required=False)
variation_price_overrides = SubEventItemVariationSerializer(source='subeventitemvariation_set', many=True, required=False)
event = SlugRelatedField(slug_field='slug', read_only=True)
meta_data = MetaDataField(source='*')
class Meta:
model = SubEvent
fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission',
'presale_start', 'presale_end', 'location',
'presale_start', 'presale_end', 'location', 'event', 'is_public',
'item_price_overrides', 'variation_price_overrides', 'meta_data')
def validate(self, data):
data = super().validate(data)
event = self.context['request'].event
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
Event.clean_dates(data.get('date_from'), data.get('date_to'))
Event.clean_presale(data.get('presale_start'), data.get('presale_end'))
SubEvent.clean_items(event, [item['item'] for item in full_data.get('subeventitem_set', [])])
SubEvent.clean_variations(event, [item['variation'] for item in full_data.get('subeventitemvariation_set', [])])
return data
def validate_item_price_overrides(self, data):
return list(filter(lambda i: 'item' in i, data))
def validate_variation_price_overrides(self, data):
return list(filter(lambda i: 'variation' in i, data))
@cached_property
def meta_properties(self):
return {
p.name: p for p in self.context['request'].organizer.meta_properties.all()
}
def validate_meta_data(self, value):
for key in value['meta_data'].keys():
if key not in self.meta_properties:
raise ValidationError(_('Meta data property \'{name}\' does not exist.').format(name=key))
return value
@transaction.atomic
def create(self, validated_data):
item_price_overrides_data = validated_data.pop('subeventitem_set') if 'subeventitem_set' in validated_data else {}
variation_price_overrides_data = validated_data.pop('subeventitemvariation_set') if 'subeventitemvariation_set' in validated_data else {}
meta_data = validated_data.pop('meta_data', None)
subevent = super().create(validated_data)
for item_price_override_data in item_price_overrides_data:
SubEventItem.objects.create(subevent=subevent, **item_price_override_data)
for variation_price_override_data in variation_price_overrides_data:
SubEventItemVariation.objects.create(subevent=subevent, **variation_price_override_data)
# Meta data
if meta_data is not None:
for key, value in meta_data.items():
subevent.meta_values.create(
property=self.meta_properties.get(key),
value=value
)
return subevent
@transaction.atomic
def update(self, instance, validated_data):
item_price_overrides_data = validated_data.pop('subeventitem_set') if 'subeventitem_set' in validated_data else {}
variation_price_overrides_data = validated_data.pop('subeventitemvariation_set') if 'subeventitemvariation_set' in validated_data else {}
meta_data = validated_data.pop('meta_data', None)
subevent = super().update(instance, validated_data)
existing_item_overrides = {item.item: item.id for item in SubEventItem.objects.filter(subevent=subevent)}
for item_price_override_data in item_price_overrides_data:
id = existing_item_overrides.pop(item_price_override_data['item'], None)
SubEventItem(id=id, subevent=subevent, **item_price_override_data).save()
SubEventItem.objects.filter(id__in=existing_item_overrides.values()).delete()
existing_variation_overrides = {item.variation: item.id for item in SubEventItemVariation.objects.filter(subevent=subevent)}
for variation_price_override_data in variation_price_overrides_data:
id = existing_variation_overrides.pop(variation_price_override_data['variation'], None)
SubEventItemVariation(id=id, subevent=subevent, **variation_price_override_data).save()
SubEventItemVariation.objects.filter(id__in=existing_variation_overrides.values()).delete()
# Meta data
if meta_data is not None:
current = {mv.property: mv for mv in subevent.meta_values.select_related('property')}
for key, value in meta_data.items():
prop = self.meta_properties.get(key)
if prop in current:
current[prop].value = value
current[prop].save()
else:
subevent.meta_values.create(
property=self.meta_properties.get(key),
value=value
)
for prop, current_object in current.items():
if prop.name not in meta_data:
current_object.delete()
return subevent
class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
class Meta:

View File

@@ -15,13 +15,20 @@ class I18nField(Field):
super().__init__(**kwargs)
def to_representation(self, value):
if value is None or value.data is None:
if hasattr(value, 'data'):
if isinstance(value.data, dict):
return value.data
elif value.data is None:
return None
else:
return {
settings.LANGUAGE_CODE: str(value.data)
}
elif value is None:
return None
if isinstance(value.data, dict):
return value.data
else:
return {
settings.LANGUAGE_CODE: str(value.data)
settings.LANGUAGE_CODE: str(value)
}
def to_internal_value(self, data):

View File

@@ -7,23 +7,36 @@ from rest_framework import serializers
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import (
Item, ItemAddOn, ItemCategory, ItemVariation, Question, QuestionOption,
Quota,
Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation, Question,
QuestionOption, Quota,
)
class InlineItemVariationSerializer(I18nAwareModelSerializer):
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
coerce_to_string=True)
class Meta:
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price')
'position', 'default_price', 'price', 'original_price')
class ItemVariationSerializer(I18nAwareModelSerializer):
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
coerce_to_string=True)
class Meta:
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price')
'position', 'default_price', 'price', 'original_price')
class InlineItemBundleSerializer(serializers.ModelSerializer):
class Meta:
model = ItemBundle
fields = ('bundled_item', 'bundled_variation', 'count',
'designated_price')
class InlineItemAddOnSerializer(serializers.ModelSerializer):
@@ -33,6 +46,31 @@ class InlineItemAddOnSerializer(serializers.ModelSerializer):
'position', 'price_included')
class ItemBundleSerializer(serializers.ModelSerializer):
class Meta:
model = ItemBundle
fields = ('id', 'bundled_item', 'bundled_variation', 'count',
'designated_price')
def validate(self, data):
data = super().validate(data)
event = self.context['event']
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
ItemBundle.clean_itemvar(event, full_data.get('bundled_item'), full_data.get('bundled_variation'))
item = self.context['item']
if item == full_data.get('bundled_item'):
raise ValidationError(_("The bundled item must not be the same item as the bundling one."))
if full_data.get('bundled_item'):
if full_data['bundled_item'].bundles.exists():
raise ValidationError(_("The bundled item must not have bundles on its own."))
return data
class ItemAddOnSerializer(serializers.ModelSerializer):
class Meta:
model = ItemAddOn
@@ -69,17 +107,18 @@ class ItemTaxRateField(serializers.Field):
class ItemSerializer(I18nAwareModelSerializer):
addons = InlineItemAddOnSerializer(many=True, required=False)
bundles = InlineItemBundleSerializer(many=True, required=False)
variations = InlineItemVariationSerializer(many=True, required=False)
tax_rate = ItemTaxRateField(source='*', read_only=True)
class Meta:
model = Item
fields = ('id', 'category', 'name', 'internal_name', 'active', 'description',
fields = ('id', 'category', 'name', 'internal_name', 'active', 'sales_channels', 'description',
'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission',
'position', 'picture', 'available_from', 'available_until',
'require_voucher', 'hide_without_voucher', 'allow_cancel',
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations',
'variations', 'addons', 'original_price')
'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')
read_only_fields = ('has_variations', 'picture')
def get_serializer_context(self):
@@ -87,8 +126,8 @@ class ItemSerializer(I18nAwareModelSerializer):
def validate(self, data):
data = super().validate(data)
if self.instance and ('addons' in data or 'variations' in data):
raise ValidationError(_('Updating add-ons or variations via PATCH/PUT is not supported. Please use the '
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data):
raise ValidationError(_('Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use the '
'dedicated nested endpoint.'))
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order'))
@@ -104,6 +143,12 @@ class ItemSerializer(I18nAwareModelSerializer):
Item.clean_tax_rule(value, self.context['event'])
return value
def validate_bundles(self, value):
if not self.instance:
for b_data in value:
ItemBundle.clean_itemvar(self.context['event'], b_data['bundled_item'], b_data['bundled_variation'])
return value
def validate_addons(self, value):
if not self.instance:
for addon_data in value:
@@ -117,11 +162,14 @@ class ItemSerializer(I18nAwareModelSerializer):
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 {}
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)
return item
@@ -159,12 +207,21 @@ class QuestionSerializer(I18nAwareModelSerializer):
class Meta:
model = Question
fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position',
'ask_during_checkin', 'identifier')
'ask_during_checkin', 'identifier', 'dependency_question', 'dependency_value',
'hidden')
def validate_identifier(self, value):
Question._clean_identifier(self.context['event'], value, self.instance)
return value
def validate_dependency_question(self, value):
if value:
if value.type not in (Question.TYPE_CHOICE, Question.TYPE_BOOLEAN, Question.TYPE_CHOICE_MULTIPLE):
raise ValidationError('Question dependencies can only be set to boolean or choice questions.')
if value == self.instance:
raise ValidationError('A question cannot depend on itself.')
return value
def validate(self, data):
data = super().validate(data)
if self.instance and 'options' in data:
@@ -176,6 +233,18 @@ class QuestionSerializer(I18nAwareModelSerializer):
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
if full_data.get('ask_during_checkin') and full_data.get('dependency_question'):
raise ValidationError('Dependencies are not supported during check-in.')
dep = full_data.get('dependency_question')
if dep:
seen_ids = {self.instance.pk} if self.instance else set()
while dep:
if dep.pk in seen_ids:
raise ValidationError(_('Circular dependency between questions detected.'))
seen_ids.add(dep.pk)
dep = dep.dependency_question
Question.clean_items(event, full_data.get('items'))
return data

View File

@@ -7,14 +7,19 @@ from django.utils.translation import ugettext_lazy
from django_countries.fields import Country
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.relations import SlugRelatedField
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.i18n import language
from pretix.base.models import (
Checkin, Invoice, InvoiceAddress, InvoiceLine, Order, OrderPosition,
Question, QuestionAnswer,
Checkin, Invoice, InvoiceAddress, InvoiceLine, Item, ItemVariation, Order,
OrderPosition, Question, QuestionAnswer, SubEvent,
)
from pretix.base.models.orders import (
CartPosition, OrderFee, OrderPayment, OrderRefund,
)
from pretix.base.models.orders import CartPosition, OrderFee
from pretix.base.pdf import get_variables
from pretix.base.signals import register_ticket_outputs
@@ -32,11 +37,12 @@ class CompatibleCountryField(serializers.Field):
class InvoiceAddressSerializer(I18nAwareModelSerializer):
country = CompatibleCountryField(source='*')
name = serializers.CharField(required=False)
class Meta:
model = InvoiceAddress
fields = ('last_modified', 'is_business', 'company', 'name', 'street', 'zipcode', 'city', 'country', 'vat_id',
'vat_id_validated', 'internal_reference')
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')
def __init__(self, *args, **kwargs):
@@ -45,6 +51,15 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
v.required = False
v.allow_blank = True
def validate(self, data):
if data.get('name') and data.get('name_parts'):
raise ValidationError(
{'name': ['Do not specify name if you specified name_parts.']}
)
if data.get('name_parts') and '_scheme' not in data.get('name_parts'):
data['name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
return data
class AnswerQuestionIdentifierField(serializers.Field):
def to_representation(self, instance: QuestionAnswer):
@@ -74,7 +89,8 @@ class CheckinSerializer(I18nAwareModelSerializer):
class OrderDownloadsField(serializers.Field):
def to_representation(self, instance: Order):
if instance.status != Order.STATUS_PAID:
return []
if instance.status != Order.STATUS_PENDING or instance.require_approval or not instance.event.settings.ticket_download_pending:
return []
request = self.context['request']
res = []
@@ -97,10 +113,9 @@ class OrderDownloadsField(serializers.Field):
class PositionDownloadsField(serializers.Field):
def to_representation(self, instance: OrderPosition):
if instance.order.status != Order.STATUS_PAID:
return []
if instance.addon_to_id and not instance.order.event.settings.ticket_download_addons:
return []
if not instance.item.admission and not instance.order.event.settings.ticket_download_nonadm:
if instance.order.status != Order.STATUS_PENDING or instance.order.require_approval or not instance.order.event.settings.ticket_download_pending:
return []
if not instance.generate_ticket:
return []
request = self.context['request']
@@ -126,13 +141,21 @@ class PdfDataSerializer(serializers.Field):
res = {}
ev = instance.subevent or instance.order.event
with language(instance.order.locale):
# This needs to have some extra performance improvements to avoid creating hundreds of queries when
# we serialize a list.
pdfvars = get_variables(instance.order.event)
for k, f in pdfvars.items():
res[k] = f['evaluate'](instance, instance.order, ev)
if 'vars' not in self.context:
self.context['vars'] = get_variables(self.context['request'].event)
for k, v in ev.meta_data.items():
res['meta:' + k] = v
for k, f in self.context['vars'].items():
res[k] = f['evaluate'](instance, instance.order, ev)
if not hasattr(ev, '_cached_meta_data'):
ev._cached_meta_data = ev.meta_data
for k, v in ev._cached_meta_data.items():
res['meta:' + k] = v
return res
@@ -146,9 +169,9 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderPosition
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_email',
'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins', 'downloads',
'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data')
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')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -156,29 +179,180 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
self.fields.pop('pdf_data')
class RequireAttentionField(serializers.Field):
def to_representation(self, instance: OrderPosition):
return instance.order.checkin_attention or instance.item.checkin_attention
class AttendeeNameField(serializers.Field):
def to_representation(self, instance: OrderPosition):
an = instance.attendee_name
if not an:
if instance.addon_to_id:
an = instance.addon_to.attendee_name
if not an:
try:
an = instance.order.invoice_address.name
except InvoiceAddress.DoesNotExist:
pass
return an
class AttendeeNamePartsField(serializers.Field):
def to_representation(self, instance: OrderPosition):
an = instance.attendee_name
p = instance.attendee_name_parts
if not an:
if instance.addon_to_id:
an = instance.addon_to.attendee_name
p = instance.addon_to.attendee_name_parts
if not an:
try:
p = instance.order.invoice_address.name_parts
except InvoiceAddress.DoesNotExist:
pass
return p
class CheckinListOrderPositionSerializer(OrderPositionSerializer):
require_attention = RequireAttentionField(source='*')
attendee_name = AttendeeNameField(source='*')
attendee_name_parts = AttendeeNamePartsField(source='*')
order__status = serializers.SlugRelatedField(read_only=True, slug_field='status', source='order')
class Meta:
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', 'require_attention',
'order__status')
class OrderPaymentTypeField(serializers.Field):
# TODO: Remove after pretix 2.2
def to_representation(self, instance: Order):
t = None
for p in instance.payments.all():
t = p.provider
return t
class OrderPaymentDateField(serializers.DateField):
# TODO: Remove after pretix 2.2
def to_representation(self, instance: Order):
t = None
for p in instance.payments.all():
t = p.payment_date or t
if t:
return super().to_representation(t.date())
class OrderFeeSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderFee
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule')
class OrderPaymentSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderPayment
fields = ('local_id', 'state', 'amount', 'created', 'payment_date', 'provider')
class OrderRefundSerializer(I18nAwareModelSerializer):
payment = SlugRelatedField(slug_field='local_id', read_only=True)
class Meta:
model = OrderRefund
fields = ('local_id', 'state', 'source', 'amount', 'payment', 'created', 'execution_date', 'provider')
class OrderSerializer(I18nAwareModelSerializer):
invoice_address = InvoiceAddressSerializer()
positions = OrderPositionSerializer(many=True)
fees = OrderFeeSerializer(many=True)
downloads = OrderDownloadsField(source='*')
invoice_address = InvoiceAddressSerializer(allow_null=True)
positions = OrderPositionSerializer(many=True, read_only=True)
fees = OrderFeeSerializer(many=True, read_only=True)
downloads = OrderDownloadsField(source='*', read_only=True)
payments = OrderPaymentSerializer(many=True, read_only=True)
refunds = OrderRefundSerializer(many=True, read_only=True)
payment_date = OrderPaymentDateField(source='*', read_only=True)
payment_provider = OrderPaymentTypeField(source='*', read_only=True)
class Meta:
model = Order
fields = ('code', 'status', 'secret', 'email', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'last_modified')
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'
)
read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'positions', 'downloads',
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.context['request'].query_params.get('pdf_data', 'false') == 'true':
self.fields['positions'].child.fields.pop('pdf_data')
def validate_locale(self, l):
if l not in set(k for k in self.instance.event.settings.locales):
raise ValidationError('"{}" is not a supported locale for this event.'.format(l))
return l
def update(self, instance, validated_data):
# Even though all fields that shouldn't be edited are marked as read_only in the serializer
# (hopefully), we'll be extra careful here and be explicit about the model fields we update.
update_fields = ['comment', 'checkin_attention', 'email', 'locale']
print(validated_data)
if 'invoice_address' in validated_data:
iadata = validated_data.pop('invoice_address')
if not iadata:
try:
instance.invoice_address.delete()
except InvoiceAddress.DoesNotExist:
pass
else:
name = iadata.pop('name', '')
if name and not iadata.get('name_parts'):
iadata['name_parts'] = {
'_legacy': name
}
try:
ia = instance.invoice_address
if iadata.get('vat_id') != ia.vat_id:
ia.vat_id_validated = False
self.fields['invoice_address'].update(ia, iadata)
except InvoiceAddress.DoesNotExist:
InvoiceAddress.objects.create(order=instance, **iadata)
for attr, value in validated_data.items():
if attr in update_fields:
setattr(instance, attr, value)
instance.save(update_fields=update_fields)
return instance
class PriceCalcSerializer(serializers.Serializer):
item = serializers.PrimaryKeyRelatedField(queryset=Item.objects.none(), required=False, allow_null=True)
variation = serializers.PrimaryKeyRelatedField(queryset=ItemVariation.objects.none(), required=False, allow_null=True)
subevent = serializers.PrimaryKeyRelatedField(queryset=SubEvent.objects.none(), required=False, allow_null=True)
locale = serializers.CharField(allow_null=True, required=False)
def __init__(self, *args, **kwargs):
event = kwargs.pop('event')
super().__init__(*args, **kwargs)
self.fields['item'].queryset = event.items.all()
self.fields['variation'].queryset = ItemVariation.objects.filter(item__event=event)
if event.has_subevents:
self.fields['subevent'].queryset = event.subevents.all()
else:
del self.fields['subevent']
class AnswerCreateSerializer(I18nAwareModelSerializer):
@@ -255,14 +429,15 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
answers = AnswerCreateSerializer(many=True, required=False)
addon_to = serializers.IntegerField(required=False, allow_null=True)
secret = serializers.CharField(required=False)
attendee_name = serializers.CharField(required=False, allow_null=True)
class Meta:
model = OrderPosition
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_email',
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'secret', 'addon_to', 'subevent', 'answers')
def validate_secret(self, secret):
if secret and OrderPosition.objects.filter(order__event=self.context['event'], secret=secret).exists():
if secret and OrderPosition.all.filter(order__event=self.context['event'], secret=secret).exists():
raise ValidationError(
'You cannot assign a position secret that already exists.'
)
@@ -309,6 +484,12 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
raise ValidationError(
{'variation': ['You cannot specify a variation for this item.']}
)
if data.get('attendee_name') and data.get('attendee_name_parts'):
raise ValidationError(
{'attendee_name': ['Do not specify attendee_name if you specified attendee_name_parts.']}
)
if data.get('attendee_name_parts') and '_scheme' not in data.get('attendee_name_parts'):
data['attendee_name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
return data
@@ -342,17 +523,24 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
payment_provider = serializers.CharField(required=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)
class Meta:
model = Order
fields = ('code', 'status', 'email', 'locale', 'payment_provider', 'fees', 'comment',
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'consume_carts')
fields = ('code', 'status', 'testmode', 'email', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts', 'force')
def validate_payment_provider(self, pp):
if pp not in self.context['event'].get_payment_providers():
raise ValidationError('The given payment provider is not known.')
return pp
def validate_sales_channel(self, channel):
if channel not in get_all_sales_channels():
raise ValidationError('Unknown sales channel.')
return channel
def validate_code(self, code):
if code and Order.objects.filter(event__organizer=self.context['event'].organizer, code=code).exists():
raise ValidationError(
@@ -410,8 +598,19 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
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_info = validated_data.pop('payment_info', '{}')
payment_date = validated_data.pop('payment_date', now())
force = validated_data.pop('force', False)
if 'invoice_address' in validated_data:
ia = InvoiceAddress(**validated_data.pop('invoice_address'))
iadata = validated_data.pop('invoice_address')
name = iadata.pop('name', '')
if name and not iadata.get('name_parts'):
iadata['name_parts'] = {
'_legacy': name
}
ia = InvoiceAddress(**iadata)
else:
ia = None
@@ -436,52 +635,79 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
errs = [{} for p in positions_data]
for i, pos_data in enumerate(positions_data):
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')))
if len(new_quotas) == 0:
errs[i]['item'] = [ugettext_lazy('The product "{}" is not assigned to a quota.').format(
str(pos_data.get('item'))
)]
else:
for quota in new_quotas:
if quota not in quota_avail_cache:
quota_avail_cache[quota] = list(quota.availability())
if not force:
for i, pos_data in enumerate(positions_data):
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')))
if len(new_quotas) == 0:
errs[i]['item'] = [ugettext_lazy('The product "{}" is not assigned to a quota.').format(
str(pos_data.get('item'))
)]
else:
for quota in new_quotas:
if quota not in quota_avail_cache:
quota_avail_cache[quota] = list(quota.availability())
if quota_avail_cache[quota][1] is not None:
quota_avail_cache[quota][1] -= 1
if quota_avail_cache[quota][1] < 0:
errs[i]['item'] = [
ugettext_lazy('There is not enough quota available on quota "{}" to perform the operation.').format(
quota.name
)
]
if quota_avail_cache[quota][1] is not None:
quota_avail_cache[quota][1] -= 1
if quota_avail_cache[quota][1] < 0:
errs[i]['item'] = [
ugettext_lazy('There is not enough quota available on quota "{}" to perform the operation.').format(
quota.name
)
]
quotadiff.update(new_quotas)
quotadiff.update(new_quotas)
if any(errs):
raise ValidationError({'positions': errs})
if validated_data.get('locale', None) is None:
validated_data['locale'] = self.context['event'].settings.locale
order = Order(event=self.context['event'], **validated_data)
order.set_expires(subevents=[p['subevent'] for p in positions_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 = "{}"
if order.total == Decimal('0.00') and validated_data.get('status') != Order.STATUS_PAID:
order.payment_provider = 'free'
order.status = Order.STATUS_PAID
elif order.payment_provider == "free" and order.total != Decimal('0.00'):
raise ValidationError('You cannot use the "free" payment provider for non-free orders.')
if validated_data.get('status') == Order.STATUS_PAID:
order.payment_date = now()
order.save()
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:
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()
pos_map = {}
for pos_data in positions_data:
answers_data = pos_data.pop('answers')
addon_to = pos_data.pop('addon_to')
answers_data = pos_data.pop('answers', [])
addon_to = pos_data.pop('addon_to', None)
attendee_name = pos_data.pop('attendee_name', '')
if attendee_name and not pos_data.get('attendee_name_parts'):
pos_data['attendee_name_parts'] = {
'_legacy': attendee_name
}
pos = OrderPosition(**pos_data)
pos.order = order
pos._calculate_tax()
@@ -490,7 +716,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
pos.save()
pos_map[pos.positionid] = pos
for answ_data in answers_data:
options = answ_data.pop('options')
options = answ_data.pop('options', [])
answ = pos.answers.create(**answ_data)
answ.options.add(*options)
@@ -522,3 +748,27 @@ class InvoiceSerializer(I18nAwareModelSerializer):
'introductory_text', 'additional_text', 'payment_provider_text', 'footer_text', 'lines',
'foreign_currency_display', 'foreign_currency_rate', 'foreign_currency_rate_date',
'internal_reference')
class OrderRefundCreateSerializer(I18nAwareModelSerializer):
payment = serializers.IntegerField(required=False, allow_null=True)
provider = serializers.CharField(required=True, allow_null=False, allow_blank=False)
info = CompatibleJSONField(required=False)
class Meta:
model = OrderRefund
fields = ('state', 'source', 'amount', 'payment', 'execution_date', 'provider', 'info')
def create(self, validated_data):
pid = validated_data.pop('payment', None)
if pid:
try:
p = self.context['order'].payments.get(local_id=pid)
except OrderPayment.DoesNotExist:
raise ValidationError('Unknown payment ID.')
else:
p = None
order = OrderRefund(order=self.context['order'], payment=p, **validated_data)
order.save()
return order

View File

@@ -1,7 +1,27 @@
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import Voucher
class VoucherListSerializer(serializers.ListSerializer):
def create(self, validated_data):
codes = set()
errs = []
err = False
for voucher_data in validated_data:
if voucher_data['code'] in codes:
err = True
errs.append({'code': ['Duplicate voucher code in request.']})
else:
codes.add(voucher_data['code'])
errs.append({})
if err:
raise ValidationError(errs)
return super().create(validated_data)
class VoucherSerializer(I18nAwareModelSerializer):
class Meta:
model = Voucher
@@ -9,6 +29,7 @@ class VoucherSerializer(I18nAwareModelSerializer):
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
'tag', 'comment', 'subevent')
read_only_fields = ('id', 'redeemed')
list_serializer_class = VoucherListSerializer
def validate(self, data):
data = super().validate(data)

View File

@@ -8,7 +8,7 @@ class WaitingListSerializer(I18nAwareModelSerializer):
class Meta:
model = WaitingListEntry
fields = ('id', 'created', 'email', 'voucher', 'item', 'variation', 'locale', 'subevent')
fields = ('id', 'created', 'email', 'voucher', 'item', 'variation', 'locale', 'subevent', 'priority')
read_only_fields = ('id', 'created', 'voucher')
def validate(self, data):

View File

@@ -0,0 +1,71 @@
from django.core.exceptions import ValidationError
from rest_framework import serializers
from pretix.api.models import WebHook
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.webhooks import get_all_webhook_events
from pretix.base.models import Event
class EventRelatedField(serializers.SlugRelatedField):
def get_queryset(self):
return self.context['organizer'].events.all()
class ActionTypesField(serializers.Field):
def to_representation(self, instance: WebHook):
return instance.action_types
def to_internal_value(self, data):
types = get_all_webhook_events()
for d in data:
if d not in types:
raise ValidationError('Invalid action type "%s".' % d)
return {'action_types': data}
class WebHookSerializer(I18nAwareModelSerializer):
limit_events = EventRelatedField(
slug_field='slug',
queryset=Event.objects.none(),
many=True
)
action_types = ActionTypesField(source='*')
class Meta:
model = WebHook
fields = ('id', 'enabled', 'target_url', 'all_events', 'limit_events', 'action_types')
def validate(self, data):
data = super().validate(data)
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
for event in full_data.get('limit_events'):
if self.context['organizer'] != event.organizer:
raise ValidationError('One or more events do not belong to this organizer.')
if full_data.get('limit_events') and full_data.get('all_events'):
raise ValidationError('You can set either limit_events or all_events.')
return data
def create(self, validated_data):
action_types = validated_data.pop('action_types')
inst = super().create(validated_data)
for l in action_types:
inst.listeners.create(action_type=l)
return inst
def update(self, instance, validated_data):
action_types = validated_data.pop('action_types', None)
instance = super().update(instance, validated_data)
if action_types is not None:
current_listeners = set(instance.listeners.values_list('action_type', flat=True))
new_listeners = set(action_types)
for l in current_listeners - new_listeners:
instance.listeners.filter(action_type=l).delete()
for l in new_listeners - current_listeners:
instance.listeners.create(action_type=l)
return instance

26
src/pretix/api/signals.py Normal file
View File

@@ -0,0 +1,26 @@
from datetime import timedelta
from django.dispatch import Signal, receiver
from django.utils.timezone import now
from pretix.api.models import ApiCall, WebHookCall
from pretix.base.signals import periodic_task
register_webhook_events = Signal(
providing_args=[]
)
"""
This signal is sent out to get all known webhook events. Receivers should return an
instance of a subclass of pretix.api.webhooks.WebhookEvent or a list of such
instances.
"""
@receiver(periodic_task)
def cleanup_webhook_logs(sender, **kwargs):
WebHookCall.objects.filter(datetime__lte=now() - timedelta(days=30)).delete()
@receiver(periodic_task)
def cleanup_api_logs(sender, **kwargs):
ApiCall.objects.filter(created__lte=now() - timedelta(hours=24)).delete()

View File

@@ -7,7 +7,8 @@ from rest_framework import routers
from pretix.api.views import cart
from .views import (
checkin, event, item, oauth, order, organizer, voucher, waitinglist,
checkin, device, event, item, oauth, order, organizer, user, voucher,
waitinglist, webhooks,
)
router = routers.DefaultRouter()
@@ -15,6 +16,8 @@ router.register(r'organizers', organizer.OrganizerViewSet)
orga_router = routers.DefaultRouter()
orga_router.register(r'events', event.EventViewSet)
orga_router.register(r'subevents', event.SubEventViewSet)
orga_router.register(r'webhooks', webhooks.WebHookViewSet)
event_router = routers.DefaultRouter()
event_router.register(r'subevents', event.SubEventViewSet)
@@ -41,6 +44,11 @@ question_router.register(r'options', item.QuestionOptionViewSet)
item_router = routers.DefaultRouter()
item_router.register(r'variations', item.ItemVariationViewSet)
item_router.register(r'addons', item.ItemAddOnViewSet)
item_router.register(r'bundles', item.ItemBundleViewSet)
order_router = routers.DefaultRouter()
order_router.register(r'payments', order.PaymentViewSet)
order_router.register(r'refunds', order.RefundViewSet)
# Force import of all plugins to give them a chance to register URLs with the router
for app in apps.get_app_configs():
@@ -57,7 +65,13 @@ urlpatterns = [
include(question_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/checkinlists/(?P<list>[^/]+)/',
include(checkinlist_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/orders/(?P<order>[^/]+)/', include(order_router.urls)),
url(r"^oauth/authorize$", oauth.AuthorizationView.as_view(), name="authorize"),
url(r"^oauth/token$", oauth.TokenView.as_view(), name="token"),
url(r"^oauth/revoke_token$", oauth.RevokeTokenView.as_view(), name="revoke-token"),
url(r"^device/initialize$", device.InitializeView.as_view(), name="device.initialize"),
url(r"^device/update$", device.UpdateView.as_view(), name="device.update"),
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"),
]

View File

@@ -31,12 +31,15 @@ class RichOrderingFilter(OrderingFilter):
class ConditionalListView:
def list(self, request, **kwargs):
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
if_modified_since = request.headers.get('If-Modified-Since')
if if_modified_since:
if_modified_since = parse_http_date_safe(if_modified_since)
if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
if_unmodified_since = request.headers.get('If-Unmodified-Since')
if if_unmodified_since:
if_unmodified_since = parse_http_date_safe(if_unmodified_since)
if not hasattr(request, 'event'):
return super().list(request, **kwargs)
lmd = request.event.logentry_set.filter(
content_type__model=self.queryset.model._meta.model_name,
content_type__app_label=self.queryset.model._meta.app_label,

View File

@@ -1,21 +1,24 @@
from django.core.exceptions import ValidationError
from django.db.models import Count, F, Max, OuterRef, Prefetch, Subquery
from django.db.models import Count, F, Max, OuterRef, Prefetch, Q, Subquery
from django.db.models.functions import Coalesce
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import viewsets
from rest_framework.decorators import detail_route
from rest_framework.decorators import action
from rest_framework.fields import DateTimeField
from rest_framework.response import Response
from pretix.api.serializers.checkin import CheckinListSerializer
from pretix.api.serializers.item import QuestionSerializer
from pretix.api.serializers.order import OrderPositionSerializer
from pretix.api.serializers.order import CheckinListOrderPositionSerializer
from pretix.api.views import RichOrderingFilter
from pretix.api.views.order import OrderPositionFilter
from pretix.base.models import Checkin, CheckinList, Order, OrderPosition
from pretix.base.models import (
Checkin, CheckinList, Event, Order, OrderPosition,
)
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, perform_checkin,
)
@@ -32,7 +35,7 @@ class CheckinListViewSet(viewsets.ModelViewSet):
serializer_class = CheckinListSerializer
queryset = CheckinList.objects.none()
filter_backends = (DjangoFilterBackend,)
filter_class = CheckinListFilter
filterset_class = CheckinListFilter
permission = 'can_view_orders'
write_permission = 'can_change_event_settings'
@@ -74,7 +77,7 @@ class CheckinListViewSet(viewsets.ModelViewSet):
)
super().perform_destroy(instance)
@detail_route(methods=['GET'])
@action(detail=True, methods=['GET'])
def status(self, *args, **kwargs):
clist = self.get_object()
cqs = Checkin.objects.filter(
@@ -150,10 +153,10 @@ class CheckinOrderPositionFilter(OrderPositionFilter):
class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPositionSerializer
serializer_class = CheckinListOrderPositionSerializer
queryset = OrderPosition.objects.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
ordering = ('attendee_name', 'positionid')
ordering = ('attendee_name_cached', 'positionid')
ordering_fields = (
'order__code', 'order__datetime', 'positionid', 'attendee_name',
'last_checked_in', 'order__email',
@@ -161,11 +164,11 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
ordering_custom = {
'attendee_name': {
'_order': F('display_name').asc(nulls_first=True),
'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')
'display_name': Coalesce('attendee_name_cached', 'addon_to__attendee_name_cached')
},
'-attendee_name': {
'_order': F('display_name').desc(nulls_last=True),
'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')
'display_name': Coalesce('attendee_name_cached', 'addon_to__attendee_name_cached')
},
'last_checked_in': {
'_order': FixedOrderBy(F('last_checked_in'), nulls_first=True),
@@ -175,15 +178,18 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
},
}
filter_class = CheckinOrderPositionFilter
filterset_class = CheckinOrderPositionFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
@cached_property
def checkinlist(self):
return get_object_or_404(CheckinList, event=self.request.event, pk=self.kwargs.get("list"))
try:
return get_object_or_404(CheckinList, event=self.request.event, pk=self.kwargs.get("list"))
except ValueError:
raise Http404()
def get_queryset(self):
def get_queryset(self, ignore_status=False):
cqs = Checkin.objects.filter(
position_id=OuterRef('pk'),
list_id=self.checkinlist.pk
@@ -193,28 +199,59 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
qs = OrderPosition.objects.filter(
order__event=self.request.event,
order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if self.checkinlist.include_pending else [Order.STATUS_PAID],
subevent=self.checkinlist.subevent
).annotate(
last_checked_in=Subquery(cqs)
).prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id=self.checkinlist.pk)
)
if self.request.query_params.get('ignore_status', 'false') != 'true' and not ignore_status:
qs = qs.filter(
order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if self.checkinlist.include_pending else [Order.STATUS_PAID]
)
).select_related('item', 'variation', 'order', 'addon_to')
if self.request.query_params.get('pdf_data', 'false') == 'true':
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id=self.checkinlist.pk)
),
'checkins', 'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
'event',
Event.objects.select_related('organizer')
),
Prefetch(
'positions',
OrderPosition.objects.prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
))
).select_related(
'item', 'variation', 'item__category', 'addon_to', 'order', 'order__invoice_address'
)
else:
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id=self.checkinlist.pk)
),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order')
if not self.checkinlist.all_products:
qs = qs.filter(item__in=self.checkinlist.limit_products.values_list('id', flat=True))
return qs
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def redeem(self, *args, **kwargs):
force = bool(self.request.data.get('force', False))
ignore_unpaid = bool(self.request.data.get('ignore_unpaid', False))
nonce = self.request.data.get('nonce')
op = self.get_object()
op = self.get_object(ignore_status=True)
if 'datetime' in self.request.data:
dt = DateTimeField().to_internal_value(self.request.data.get('datetime'))
@@ -240,11 +277,15 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
ignore_unpaid=ignore_unpaid,
nonce=nonce,
datetime=dt,
questions_supported=self.request.data.get('questions_supported', True)
questions_supported=self.request.data.get('questions_supported', True),
user=self.request.user,
auth=self.request.auth,
)
except RequiredQuestionsError as e:
return Response({
'status': 'incomplete',
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=self.get_serializer_context()).data,
'questions': [
QuestionSerializer(q).data for q in e.questions
]
@@ -252,9 +293,21 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
except CheckInError as e:
return Response({
'status': 'error',
'reason': e.code
'reason': e.code,
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=self.get_serializer_context()).data
}, status=400)
else:
return Response({
'status': 'ok',
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=self.get_serializer_context()).data
}, status=201)
def get_object(self, ignore_status=False):
queryset = self.filter_queryset(self.get_queryset(ignore_status=ignore_status))
if self.kwargs['pk'].isnumeric():
obj = get_object_or_404(queryset, Q(pk=self.kwargs['pk']) | Q(secret=self.kwargs['pk']))
else:
obj = get_object_or_404(queryset, secret=self.kwargs['pk'])
return obj

View File

@@ -0,0 +1,113 @@
import logging
from django.utils.timezone import now
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from rest_framework.views import APIView
from pretix.api.auth.device import DeviceTokenAuthentication
from pretix.base.models import Device
from pretix.base.models.devices import generate_api_token
logger = logging.getLogger(__name__)
class InitializationRequestSerializer(serializers.Serializer):
token = serializers.CharField(max_length=190)
hardware_brand = serializers.CharField(max_length=190)
hardware_model = serializers.CharField(max_length=190)
software_brand = serializers.CharField(max_length=190)
software_version = serializers.CharField(max_length=190)
class UpdateRequestSerializer(serializers.Serializer):
hardware_brand = serializers.CharField(max_length=190)
hardware_model = serializers.CharField(max_length=190)
software_brand = serializers.CharField(max_length=190)
software_version = serializers.CharField(max_length=190)
class DeviceSerializer(serializers.ModelSerializer):
organizer = serializers.SlugRelatedField(slug_field='slug', read_only=True)
class Meta:
model = Device
fields = [
'organizer', 'device_id', 'unique_serial', 'api_token',
'name'
]
class InitializeView(APIView):
authentication_classes = tuple()
permission_classes = tuple()
def post(self, request, format=None):
serializer = InitializationRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
device = Device.objects.get(initialization_token=serializer.validated_data.get('token'))
except Device.DoesNotExist:
raise ValidationError({'token': ['Unknown initialization token.']})
if device.initialized:
raise ValidationError({'token': ['This initialization token has already been used.']})
device.initialized = now()
device.hardware_brand = serializer.validated_data.get('hardware_brand')
device.hardware_model = serializer.validated_data.get('hardware_model')
device.software_brand = serializer.validated_data.get('software_brand')
device.software_version = serializer.validated_data.get('software_version')
device.api_token = generate_api_token()
device.save()
device.log_action('pretix.device.initialized', data=serializer.validated_data, auth=device)
serializer = DeviceSerializer(device)
return Response(serializer.data)
class UpdateView(APIView):
authentication_classes = (DeviceTokenAuthentication,)
def post(self, request, format=None):
serializer = UpdateRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
device = request.auth
device.hardware_brand = serializer.validated_data.get('hardware_brand')
device.hardware_model = serializer.validated_data.get('hardware_model')
device.software_brand = serializer.validated_data.get('software_brand')
device.software_version = serializer.validated_data.get('software_version')
device.save()
device.log_action('pretix.device.updated', data=serializer.validated_data, auth=device)
serializer = DeviceSerializer(device)
return Response(serializer.data)
class RollKeyView(APIView):
authentication_classes = (DeviceTokenAuthentication,)
def post(self, request, format=None):
device = request.auth
device.api_token = generate_api_token()
device.save()
device.log_action('pretix.device.keyroll', auth=device)
serializer = DeviceSerializer(device)
return Response(serializer.data)
class RevokeKeyView(APIView):
authentication_classes = (DeviceTokenAuthentication,)
def post(self, request, format=None):
device = request.auth
device.revoked = True
device.save()
device.log_action('pretix.device.revoked', auth=device)
serializer = DeviceSerializer(device)
return Response(serializer.data)

View File

@@ -1,5 +1,7 @@
import django_filters
from django.db import transaction
from django.db.models import ProtectedError
from django.db.models import ProtectedError, Q
from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import filters, viewsets
from rest_framework.exceptions import PermissionDenied
@@ -10,20 +12,79 @@ from pretix.api.serializers.event import (
TaxRuleSerializer,
)
from pretix.api.views import ConditionalListView
from pretix.base.models import Event, ItemCategory, TaxRule
from pretix.base.models import (
CartPosition, Device, Event, ItemCategory, TaxRule, TeamAPIToken,
)
from pretix.base.models.event import SubEvent
from pretix.helpers.dicts import merge_dicts
class EventFilter(FilterSet):
is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs')
is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs')
ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs')
class Meta:
model = Event
fields = ['is_public', 'live', 'has_subevents']
def ends_after_qs(self, queryset, name, value):
expr = (
Q(has_subevents=False) &
Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=value))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=value))
)
)
return queryset.filter(expr)
def is_past_qs(self, queryset, name, value):
expr = (
Q(has_subevents=False) &
Q(
Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__lt=now()))
)
)
if value:
return queryset.filter(expr)
else:
return queryset.exclude(expr)
def is_future_qs(self, queryset, name, value):
expr = (
Q(has_subevents=False) &
Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=now()))
)
)
if value:
return queryset.filter(expr)
else:
return queryset.exclude(expr)
class EventViewSet(viewsets.ModelViewSet):
serializer_class = EventSerializer
queryset = Event.objects.none()
lookup_field = 'slug'
lookup_url_kwarg = 'event'
permission_classes = (EventCRUDPermission,)
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
filterset_class = EventFilter
def get_queryset(self):
return self.request.organizer.events.prefetch_related('meta_values', 'meta_values__property')
if isinstance(self.request.auth, (TeamAPIToken, Device)):
qs = self.request.auth.get_events_with_any_permission()
elif self.request.user.is_authenticated:
qs = self.request.user.get_events_with_any_permission(self.request).filter(
organizer=self.request.organizer
)
return qs.prefetch_related(
'meta_values', 'meta_values__property'
)
def perform_update(self, serializer):
current_live_value = serializer.instance.live
@@ -120,22 +181,104 @@ class CloneEventViewSet(viewsets.ModelViewSet):
class SubEventFilter(FilterSet):
is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs')
is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs')
ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs')
class Meta:
model = SubEvent
fields = ['active']
fields = ['active', 'event__live']
def ends_after_qs(self, queryset, name, value):
expr = Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=value))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=value))
)
return queryset.filter(expr)
def is_past_qs(self, queryset, name, value):
expr = Q(
Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__lt=now()))
)
if value:
return queryset.filter(expr)
else:
return queryset.exclude(expr)
def is_future_qs(self, queryset, name, value):
expr = Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=now()))
)
if value:
return queryset.filter(expr)
else:
return queryset.exclude(expr)
class SubEventViewSet(ConditionalListView, viewsets.ReadOnlyModelViewSet):
class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = SubEventSerializer
queryset = ItemCategory.objects.none()
write_permission = 'can_change_event_settings'
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
filter_class = SubEventFilter
filterset_class = SubEventFilter
def get_queryset(self):
return self.request.event.subevents.prefetch_related(
if getattr(self.request, 'event', None):
qs = self.request.event.subevents
elif isinstance(self.request.auth, (TeamAPIToken, Device)):
qs = SubEvent.objects.filter(
event__organizer=self.request.organizer,
event__in=self.request.auth.get_events_with_any_permission()
)
elif self.request.user.is_authenticated:
qs = SubEvent.objects.filter(
event__organizer=self.request.organizer,
event__in=self.request.user.get_events_with_any_permission()
)
return qs.prefetch_related(
'subeventitem_set', 'subeventitemvariation_set'
)
def perform_update(self, serializer):
super().perform_update(serializer)
serializer.instance.log_action(
'pretix.subevent.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
'pretix.subevent.added',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied('The sub-event can not be deleted as it has already been used in orders. Please set'
' \'active\' to false instead to hide it from users.')
try:
with transaction.atomic():
instance.log_action(
'pretix.subevent.deleted',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
CartPosition.objects.filter(addon_to__subevent=instance).delete()
instance.cartposition_set.all().delete()
super().perform_destroy(instance)
except ProtectedError:
raise PermissionDenied('The sub-event could not be deleted as some constraints (e.g. data created by '
'plug-ins) do not allow it.')
class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = TaxRuleSerializer

View File

@@ -1,22 +1,23 @@
import django_filters
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import viewsets
from rest_framework.decorators import detail_route
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response
from pretix.api.serializers.item import (
ItemAddOnSerializer, ItemCategorySerializer, ItemSerializer,
ItemVariationSerializer, QuestionOptionSerializer, QuestionSerializer,
QuotaSerializer,
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
ItemSerializer, ItemVariationSerializer, QuestionOptionSerializer,
QuestionSerializer, QuotaSerializer,
)
from pretix.api.views import ConditionalListView
from pretix.base.models import (
Item, ItemAddOn, ItemCategory, ItemVariation, Question, QuestionOption,
Quota,
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation,
Question, QuestionOption, Quota,
)
from pretix.helpers.dicts import merge_dicts
@@ -41,12 +42,12 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
filter_class = ItemFilter
permission = 'can_change_items'
filterset_class = ItemFilter
permission = None
write_permission = 'can_change_items'
def get_queryset(self):
return self.request.event.items.select_related('tax_rule').prefetch_related('variations', 'addons').all()
return self.request.event.items.select_related('tax_rule').prefetch_related('variations', 'addons', 'bundles').all()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
@@ -83,6 +84,8 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
user=self.request.user,
auth=self.request.auth,
)
CartPosition.objects.filter(addon_to__item=instance).delete()
instance.cartposition_set.all().delete()
super().perform_destroy(instance)
@@ -92,20 +95,23 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
filter_backends = (DjangoFilterBackend, OrderingFilter,)
ordering_fields = ('id', 'position')
ordering = ('id',)
permission = 'can_change_items'
permission = None
write_permission = 'can_change_items'
@cached_property
def item(self):
return get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
def get_queryset(self):
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
return item.variations.all()
return self.item.variations.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['item'] = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
ctx['item'] = self.item
return ctx
def perform_create(self, serializer):
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
item = self.item
if not item.has_variations:
raise PermissionDenied('This variation cannot be created because the item does not have variations. '
'Changing a product without variations to a product with variations is not allowed.')
@@ -148,27 +154,82 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
)
class ItemBundleViewSet(viewsets.ModelViewSet):
serializer_class = ItemBundleSerializer
queryset = ItemBundle.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter,)
ordering_fields = ('id',)
ordering = ('id',)
permission = None
write_permission = 'can_change_items'
@cached_property
def item(self):
return get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
def get_queryset(self):
return self.item.bundles.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['item'] = self.item
return ctx
def perform_create(self, serializer):
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
serializer.save(base_item=item)
item.log_action(
'pretix.event.item.bundles.added',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.base_item.log_action(
'pretix.event.item.bundles.changed',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
def perform_destroy(self, instance):
super().perform_destroy(instance)
instance.base_item.log_action(
'pretix.event.item.bundles.removed',
user=self.request.user,
auth=self.request.auth,
data={'bundled_item': instance.bundled_item.pk, 'bundled_variation': instance.bundled_variation.pk if instance.bundled_variation else None,
'count': instance.count, 'designated_price': instance.designated_price}
)
class ItemAddOnViewSet(viewsets.ModelViewSet):
serializer_class = ItemAddOnSerializer
queryset = ItemAddOn.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter,)
ordering_fields = ('id', 'position')
ordering = ('id',)
permission = 'can_change_items'
permission = None
write_permission = 'can_change_items'
@cached_property
def item(self):
return get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
def get_queryset(self):
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
return item.addons.all()
return self.item.addons.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['item'] = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
ctx['item'] = self.item
return ctx
def perform_create(self, serializer):
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
item = self.item
category = get_object_or_404(ItemCategory, pk=self.request.data['addon_category'])
serializer.save(base_item=item, addon_category=category)
item.log_action(
@@ -207,10 +268,10 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = ItemCategorySerializer
queryset = ItemCategory.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_class = ItemCategoryFilter
filterset_class = ItemCategoryFilter
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
permission = 'can_change_items'
permission = None
write_permission = 'can_change_items'
def get_queryset(self):
@@ -261,10 +322,11 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = QuestionSerializer
queryset = Question.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_class = QuestionFilter
filterset_class = QuestionFilter
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
permission = 'can_change_items'
permission = None
write_permission = 'can_change_items'
def get_queryset(self):
return self.request.event.questions.prefetch_related('options').all()
@@ -307,7 +369,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
filter_backends = (DjangoFilterBackend, OrderingFilter,)
ordering_fields = ('id', 'position')
ordering = ('position',)
permission = 'can_change_items'
permission = None
write_permission = 'can_change_items'
def get_queryset(self):
@@ -359,10 +421,10 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = QuotaSerializer
queryset = Quota.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter,)
filter_class = QuotaFilter
filterset_class = QuotaFilter
ordering_fields = ('id', 'size')
ordering = ('id',)
permission = 'can_change_items'
permission = None
write_permission = 'can_change_items'
def get_queryset(self):
@@ -437,7 +499,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
)
super().perform_destroy(instance)
@detail_route(methods=['get'])
@action(detail=True, methods=['get'])
def availability(self, request, *args, **kwargs):
quota = self.get_object()

View File

@@ -1,15 +1,18 @@
import datetime
from decimal import Decimal
import django_filters
import pytz
from django.db import transaction
from django.db.models import Q
from django.db.models.functions import Concat
from django.db.models import F, Prefetch, Q
from django.db.models.functions import Coalesce, Concat
from django.http import FileResponse
from django.shortcuts import get_object_or_404
from django.utils.timezone import make_aware, now
from django.utils.translation import ugettext as _
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import serializers, status, viewsets
from rest_framework.decorators import detail_route
from rest_framework import mixins, serializers, status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import (
APIException, NotFound, PermissionDenied, ValidationError,
)
@@ -19,45 +22,54 @@ from rest_framework.response import Response
from pretix.api.models import OAuthAccessToken
from pretix.api.serializers.order import (
InvoiceSerializer, OrderCreateSerializer, OrderPositionSerializer,
OrderSerializer,
InvoiceSerializer, OrderCreateSerializer, OrderPaymentSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer,
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer,
)
from pretix.base.i18n import language
from pretix.base.models import (
Invoice, Order, OrderPosition, Quota, TeamAPIToken,
CachedCombinedTicket, CachedTicket, Device, Event, Invoice, InvoiceAddress,
Order, OrderPayment, OrderPosition, OrderRefund, Quota, TeamAPIToken,
generate_position_secret, generate_secret,
)
from pretix.base.payment import PaymentException
from pretix.base.services import tickets
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
regenerate_invoice,
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import (
OrderError, cancel_order, extend_order, mark_order_expired,
mark_order_paid, mark_order_refunded,
OrderChangeManager, OrderError, approve_order, cancel_order, deny_order,
extend_order, mark_order_expired, mark_order_refunded,
)
from pretix.base.services.tickets import (
get_cachedticket_for_order, get_cachedticket_for_position,
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,
)
from pretix.base.signals import order_placed, register_ticket_outputs
from pretix.base.templatetags.money import money_filter
class OrderFilter(FilterSet):
email = django_filters.CharFilter(name='email', lookup_expr='iexact')
code = django_filters.CharFilter(name='code', lookup_expr='iexact')
status = django_filters.CharFilter(name='status', lookup_expr='iexact')
modified_since = django_filters.IsoDateTimeFilter(name='last_modified', lookup_expr='gte')
email = django_filters.CharFilter(field_name='email', lookup_expr='iexact')
code = django_filters.CharFilter(field_name='code', lookup_expr='iexact')
status = django_filters.CharFilter(field_name='status', lookup_expr='iexact')
modified_since = django_filters.IsoDateTimeFilter(field_name='last_modified', lookup_expr='gte')
created_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte')
class Meta:
model = Order
fields = ['code', 'status', 'email', 'locale']
fields = ['code', 'status', 'email', 'locale', 'testmode', 'require_approval']
class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
queryset = Order.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('datetime',)
ordering_fields = ('datetime', 'code', 'status')
filter_class = OrderFilter
ordering_fields = ('datetime', 'code', 'status', 'last_modified')
filterset_class = OrderFilter
lookup_field = 'code'
permission = 'can_view_orders'
write_permission = 'can_change_orders'
@@ -68,13 +80,35 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
return ctx
def get_queryset(self):
return self.request.event.orders.prefetch_related(
'positions', 'positions__checkins', 'positions__item', 'positions__answers', 'positions__answers__options',
'positions__answers__question', 'fees'
qs = self.request.event.orders.prefetch_related(
'fees', 'payments', 'refunds', 'refunds__payment'
).select_related(
'invoice_address'
)
if self.request.query_params.get('pdf_data', 'false') == 'true':
qs = qs.prefetch_related(
Prefetch(
'positions',
OrderPosition.objects.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
'item__category', 'addon_to',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
)
)
)
else:
qs = qs.prefetch_related(
Prefetch(
'positions',
OrderPosition.objects.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
)
return qs
def _get_output_provider(self, identifier):
responses = register_ticket_outputs.send(self.request.event)
for receiver, response in responses:
@@ -97,7 +131,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, headers={'X-Page-Generated': date})
@detail_route(url_name='download', url_path='download/(?P<output>[^/]+)')
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
def download(self, request, output, **kwargs):
provider = self._get_output_provider(output)
order = self.get_object()
@@ -105,9 +139,11 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
if order.status != Order.STATUS_PAID:
raise PermissionDenied("Downloads are not available for unpaid orders.")
ct = get_cachedticket_for_order(order, provider.identifier)
if not ct.file:
ct = CachedCombinedTicket.objects.filter(
order=order, provider=provider.identifier, file__isnull=False
).last()
if not ct or not ct.file:
generate.apply_async(args=('order', order.pk, provider.identifier))
raise RetryException()
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
@@ -117,19 +153,38 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
)
return resp
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def mark_paid(self, request, **kwargs):
order = self.get_object()
if order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED):
ps = order.pending_sum
try:
mark_order_paid(
order, manual=True,
user=request.user if request.user.is_authenticated else None,
auth=request.auth,
p = order.payments.get(
state__in=(OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED),
provider='manual',
amount=ps
)
except OrderPayment.DoesNotExist:
order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
OrderPayment.PAYMENT_STATE_CREATED)) \
.update(state=OrderPayment.PAYMENT_STATE_CANCELED)
p = order.payments.create(
state=OrderPayment.PAYMENT_STATE_CREATED,
provider='manual',
amount=ps,
fee=None
)
try:
p.confirm(auth=self.request.auth,
user=self.request.user if request.user.is_authenticated else None,
count_waitinglist=False)
except Quota.QuotaExceededException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except PaymentException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except SendMailException:
pass
@@ -139,9 +194,15 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
status=status.HTTP_400_BAD_REQUEST
)
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def mark_canceled(self, request, **kwargs):
send_mail = request.data.get('send_email', True)
cancellation_fee = request.data.get('cancellation_fee', None)
if cancellation_fee:
try:
cancellation_fee = float(Decimal(cancellation_fee))
except:
cancellation_fee = None
order = self.get_object()
if not order.cancel_allowed():
@@ -150,16 +211,60 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
status=status.HTTP_400_BAD_REQUEST
)
cancel_order(
order,
user=request.user if request.user.is_authenticated else None,
api_token=request.auth if isinstance(request.auth, TeamAPIToken) else None,
oauth_application=request.auth.application if isinstance(request.auth, OAuthAccessToken) else None,
send_mail=send_mail
)
try:
cancel_order(
order,
user=request.user if request.user.is_authenticated else None,
api_token=request.auth if isinstance(request.auth, TeamAPIToken) else None,
device=request.auth if isinstance(request.auth, Device) else None,
oauth_application=request.auth.application if isinstance(request.auth, OAuthAccessToken) else None,
send_mail=send_mail,
cancellation_fee=cancellation_fee
)
except OrderError as e:
return Response(
{'detail': str(e)},
status=status.HTTP_400_BAD_REQUEST
)
return self.retrieve(request, [], **kwargs)
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def approve(self, request, **kwargs):
send_mail = request.data.get('send_email', True)
order = self.get_object()
try:
approve_order(
order,
user=request.user if request.user.is_authenticated else None,
auth=request.auth if isinstance(request.auth, (Device, TeamAPIToken, OAuthAccessToken)) else None,
send_mail=send_mail,
)
except Quota.QuotaExceededException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except OrderError as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def deny(self, request, **kwargs):
send_mail = request.data.get('send_email', True)
comment = request.data.get('comment', '')
order = self.get_object()
try:
deny_order(
order,
user=request.user if request.user.is_authenticated else None,
auth=request.auth if isinstance(request.auth, (Device, TeamAPIToken, OAuthAccessToken)) else None,
send_mail=send_mail,
comment=comment,
)
except OrderError as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def mark_pending(self, request, **kwargs):
order = self.get_object()
@@ -170,8 +275,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
)
order.status = Order.STATUS_PENDING
order.payment_manual = True
order.save()
order.save(update_fields=['status'])
order.log_action(
'pretix.event.order.unpaid',
user=request.user if request.user.is_authenticated else None,
@@ -179,7 +283,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
)
return self.retrieve(request, [], **kwargs)
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def mark_expired(self, request, **kwargs):
order = self.get_object()
@@ -196,7 +300,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
)
return self.retrieve(request, [], **kwargs)
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def mark_refunded(self, request, **kwargs):
order = self.get_object()
@@ -209,11 +313,77 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
mark_order_refunded(
order,
user=request.user if request.user.is_authenticated else None,
api_token=(request.auth if isinstance(request.auth, TeamAPIToken) else None),
auth=(request.auth if isinstance(request.auth, (TeamAPIToken, OAuthAccessToken, Device)) else None),
)
return self.retrieve(request, [], **kwargs)
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def create_invoice(self, request, **kwargs):
order = self.get_object()
has_inv = order.invoices.exists() and not (
order.status in (Order.STATUS_PAID, Order.STATUS_PENDING)
and order.invoices.filter(is_cancellation=True).count() >= order.invoices.filter(is_cancellation=False).count()
)
if self.request.event.settings.get('invoice_generate') not in ('admin', 'user', 'paid', 'True') or not invoice_qualified(order):
return Response(
{'detail': _('You cannot generate an invoice for this order.')},
status=status.HTTP_400_BAD_REQUEST
)
elif has_inv:
return Response(
{'detail': _('An invoice for this order already exists.')},
status=status.HTTP_400_BAD_REQUEST
)
inv = generate_invoice(order)
order.log_action(
'pretix.event.order.invoice.generated',
user=self.request.user,
auth=self.request.auth,
data={
'invoice': inv.pk
}
)
return Response(
InvoiceSerializer(inv).data,
status=status.HTTP_201_CREATED
)
@action(detail=True, methods=['POST'])
def resend_link(self, request, **kwargs):
order = self.get_object()
if not order.email:
return Response({'detail': 'There is no email address associated with this order.'}, status=status.HTTP_400_BAD_REQUEST)
try:
order.resend_link(user=self.request.user, auth=self.request.auth)
except SendMailException:
return Response({'detail': _('There was an error sending the mail. Please try again later.')}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
return Response(
status=status.HTTP_204_NO_CONTENT
)
@action(detail=True, methods=['POST'])
@transaction.atomic
def regenerate_secrets(self, request, **kwargs):
order = self.get_object()
order.secret = generate_secret()
for op in order.all_positions.all():
op.secret = generate_position_secret()
op.save()
order.save(update_fields=['secret'])
CachedTicket.objects.filter(order_position__order=order).delete()
CachedCombinedTicket.objects.filter(order=order).delete()
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk,
'order': order.pk})
order.log_action(
'pretix.event.order.secret.changed',
user=self.request.user,
auth=self.request.auth,
)
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def extend(self, request, **kwargs):
new_date = request.data.get('expires', None)
force = request.data.get('force', False)
@@ -280,12 +450,88 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def update(self, request, *args, **kwargs):
partial = kwargs.get('partial', False)
if not partial:
return Response(
{"detail": "Method \"PUT\" not allowed."},
status=status.HTTP_405_METHOD_NOT_ALLOWED,
)
return super().update(request, *args, **kwargs)
def perform_update(self, serializer):
with transaction.atomic():
if 'comment' in self.request.data and serializer.instance.comment != self.request.data.get('comment'):
serializer.instance.log_action(
'pretix.event.order.comment',
user=self.request.user,
auth=self.request.auth,
data={
'new_comment': self.request.data.get('comment')
}
)
if 'checkin_attention' in self.request.data and serializer.instance.checkin_attention != self.request.data.get('checkin_attention'):
serializer.instance.log_action(
'pretix.event.order.checkin_attention',
user=self.request.user,
auth=self.request.auth,
data={
'new_value': self.request.data.get('checkin_attention')
}
)
if 'email' in self.request.data and serializer.instance.email != self.request.data.get('email'):
serializer.instance.log_action(
'pretix.event.order.contact.changed',
user=self.request.user,
auth=self.request.auth,
data={
'old_email': serializer.instance.email,
'new_email': self.request.data.get('email'),
}
)
if 'locale' in self.request.data and serializer.instance.locale != self.request.data.get('locale'):
serializer.instance.log_action(
'pretix.event.order.locale.changed',
user=self.request.user,
auth=self.request.auth,
data={
'old_locale': serializer.instance.locale,
'new_locale': self.request.data.get('locale'),
}
)
if 'invoice_address' in self.request.data:
serializer.instance.log_action(
'pretix.event.order.modified',
user=self.request.user,
auth=self.request.auth,
data={
'invoice_data': self.request.data.get('invoice_address'),
}
)
serializer.save()
tickets.invalidate_cache.apply_async(kwargs={'event': serializer.instance.event.pk, 'order': serializer.instance.pk})
if 'invoice_address' in self.request.data:
order_modified.send(sender=serializer.instance.event, order=serializer.instance)
def perform_create(self, serializer):
serializer.save()
def perform_destroy(self, instance):
if not instance.testmode:
raise PermissionDenied('Only test mode orders can be deleted.')
with transaction.atomic():
self.get_object().gracefully_delete(user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
class OrderPositionFilter(FilterSet):
order = django_filters.CharFilter(name='order', lookup_expr='code__iexact')
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs')
attendee_name = django_filters.CharFilter(method='attendee_name_qs')
search = django_filters.CharFilter(method='search_qs')
@@ -293,17 +539,20 @@ class OrderPositionFilter(FilterSet):
def search_qs(self, queryset, name, value):
return queryset.filter(
Q(secret__istartswith=value)
| Q(attendee_name__icontains=value)
| Q(addon_to__attendee_name__icontains=value)
| Q(attendee_name_cached__icontains=value)
| Q(addon_to__attendee_name_cached__icontains=value)
| Q(attendee_email__icontains=value)
| Q(addon_to__attendee_email__icontains=value)
| Q(order__code__istartswith=value)
| Q(order__invoice_address__name__icontains=value)
| Q(order__invoice_address__name_cached__icontains=value)
| Q(order__email__icontains=value)
)
def has_checkin_qs(self, queryset, name, value):
return queryset.filter(checkins__isnull=not value)
def attendee_name_qs(self, queryset, name, value):
return queryset.filter(Q(attendee_name__iexact=value) | Q(addon_to__attendee_name__iexact=value))
return queryset.filter(Q(attendee_name_cached__iexact=value) | Q(addon_to__attendee_name_cached__iexact=value))
class Meta:
model = OrderPosition
@@ -313,25 +562,61 @@ class OrderPositionFilter(FilterSet):
'secret': ['exact'],
'order__status': ['exact', 'in'],
'addon_to': ['exact', 'in'],
'subevent': ['exact', 'in']
'subevent': ['exact', 'in'],
'pseudonymization_id': ['exact'],
'voucher__code': ['exact'],
'voucher': ['exact'],
}
class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet):
class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPositionSerializer
queryset = OrderPosition.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('order__datetime', 'positionid')
ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',)
filter_class = OrderPositionFilter
filterset_class = OrderPositionFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
ordering_custom = {
'attendee_name': {
'_order': F('display_name').asc(nulls_first=True),
'display_name': Coalesce('attendee_name_cached', 'addon_to__attendee_name_cached')
},
'-attendee_name': {
'_order': F('display_name').asc(nulls_last=True),
'display_name': Coalesce('attendee_name_cached', 'addon_to__attendee_name_cached')
},
}
def get_queryset(self):
return OrderPosition.objects.filter(order__event=self.request.event).prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question'
).select_related(
'item', 'order', 'order__event', 'order__event__organizer'
)
qs = OrderPosition.objects.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('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
'event',
Event.objects.select_related('organizer')
),
Prefetch(
'positions',
OrderPosition.objects.prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
))
).select_related(
'item', 'variation', 'item__category', 'addon_to'
)
else:
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question'
).select_related(
'item', 'order', 'order__event', 'order__event__organizer'
)
return qs
def _get_output_provider(self, identifier):
responses = register_ticket_outputs.send(self.request.event)
@@ -341,21 +626,98 @@ class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet):
return prov
raise NotFound('Unknown output provider.')
@detail_route(url_name='download', url_path='download/(?P<output>[^/]+)')
@action(detail=True, methods=['POST'], url_name='price_calc')
def price_calc(self, request, *args, **kwargs):
"""
This calculates the price assuming a change of product or subevent. This endpoint
is deliberately not documented and considered a private API, only to be used by
pretix' web interface.
Sample input:
{
"item": 2,
"variation": null,
"subevent": 3
}
Sample output:
{
"gross": "2.34",
"gross_formatted": "2,34",
"net": "2.34",
"tax": "0.00",
"rate": "0.00",
"name": "VAT"
}
"""
serializer = PriceCalcSerializer(data=request.data, event=request.event)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
pos = self.get_object()
try:
ia = pos.order.invoice_address
except InvoiceAddress.DoesNotExist:
ia = InvoiceAddress()
kwargs = {
'item': pos.item,
'variation': pos.variation,
'voucher': pos.voucher,
'subevent': pos.subevent,
'addon_to': pos.addon_to,
'invoice_address': ia,
}
if data.get('item'):
item = data.get('item')
kwargs['item'] = item
if item.has_variations:
variation = data.get('variation') or pos.variation
if not variation:
raise ValidationError('No variation given')
if variation.item != item:
raise ValidationError('Variation does not belong to item')
kwargs['variation'] = variation
else:
variation = None
kwargs['variation'] = None
if pos.voucher and not pos.voucher.applies_to(item, variation):
kwargs['voucher'] = None
if data.get('subevent'):
kwargs['subevent'] = data.get('subevent')
price = get_price(**kwargs)
with language(data.get('locale') or self.request.event.settings.locale):
return Response({
'gross': price.gross,
'gross_formatted': money_filter(price.gross, self.request.event.currency, hide_currency=True),
'net': price.net,
'rate': price.rate,
'name': str(price.name),
'tax': price.tax,
})
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
def download(self, request, output, **kwargs):
provider = self._get_output_provider(output)
pos = self.get_object()
if pos.order.status != Order.STATUS_PAID:
raise PermissionDenied("Downloads are not available for unpaid orders.")
if pos.addon_to_id and not request.event.settings.ticket_download_addons:
raise PermissionDenied("Downloads are not enabled for add-on products.")
if not pos.item.admission and not request.event.settings.ticket_download_nonadm:
raise PermissionDenied("Downloads are not enabled for non-admission products.")
if not pos.generate_ticket:
raise PermissionDenied("Downloads are not enabled for this product.")
ct = get_cachedticket_for_position(pos, provider.identifier)
if not ct.file:
ct = CachedTicket.objects.filter(
order_position=pos, provider=provider.identifier, file__isnull=False
).last()
if not ct or not ct.file:
generate.apply_async(args=('orderposition', pos.pk, provider.identifier))
raise RetryException()
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
@@ -365,11 +727,242 @@ class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet):
)
return resp
def perform_destroy(self, instance):
try:
ocm = OrderChangeManager(
instance.order,
user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth,
notify=False
)
ocm.cancel(instance)
ocm.commit()
except OrderError as e:
raise ValidationError(str(e))
except Quota.QuotaExceededException as e:
raise ValidationError(str(e))
class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPaymentSerializer
queryset = OrderPayment.objects.none()
permission = 'can_view_orders'
write_permission = 'can_change_orders'
lookup_field = 'local_id'
def get_queryset(self):
order = get_object_or_404(Order, code=self.kwargs['order'], event=self.request.event)
return order.payments.all()
@action(detail=True, methods=['POST'])
def confirm(self, request, **kwargs):
payment = self.get_object()
force = request.data.get('force', False)
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)
try:
payment.confirm(user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth,
count_waitinglist=False,
force=force)
except Quota.QuotaExceededException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except PaymentException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except SendMailException:
pass
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def refund(self, request, **kwargs):
payment = self.get_object()
amount = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
request.data.get('amount', str(payment.amount))
)
if 'mark_refunded' in request.data:
mark_refunded = request.data.get('mark_refunded', False)
else:
mark_refunded = request.data.get('mark_canceled', False)
if payment.state != OrderPayment.PAYMENT_STATE_CONFIRMED:
return Response({'detail': 'Invalid state of payment.'}, status=status.HTTP_400_BAD_REQUEST)
full_refund_possible = payment.payment_provider.payment_refund_supported(payment)
partial_refund_possible = payment.payment_provider.payment_partial_refund_supported(payment)
available_amount = payment.amount - payment.refunded_amount
if amount <= 0:
return Response({'amount': ['Invalid refund amount.']}, status=status.HTTP_400_BAD_REQUEST)
if amount > available_amount:
return Response(
{'amount': ['Invalid refund amount, only {} are available to refund.'.format(available_amount)]},
status=status.HTTP_400_BAD_REQUEST)
if amount != payment.amount and not partial_refund_possible:
return Response({'amount': ['Partial refund not available for this payment method.']},
status=status.HTTP_400_BAD_REQUEST)
if amount == payment.amount and not full_refund_possible:
return Response({'amount': ['Full refund not available for this payment method.']},
status=status.HTTP_400_BAD_REQUEST)
r = payment.order.refunds.create(
payment=payment,
source=OrderRefund.REFUND_SOURCE_ADMIN,
state=OrderRefund.REFUND_STATE_CREATED,
amount=amount,
provider=payment.provider
)
try:
r.payment_provider.execute_refund(r)
except PaymentException as e:
r.state = OrderRefund.REFUND_STATE_FAILED
r.save()
return Response({'detail': 'External error: {}'.format(str(e))},
status=status.HTTP_400_BAD_REQUEST)
else:
payment.order.log_action('pretix.event.order.refund.created', {
'local_id': r.local_id,
'provider': r.provider,
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
if payment.order.pending_sum > 0:
if mark_refunded:
mark_order_refunded(payment.order,
user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth)
else:
payment.order.status = Order.STATUS_PENDING
payment.order.set_expires(
now(),
payment.order.event.subevents.filter(
id__in=payment.order.positions.values_list('subevent_id', flat=True))
)
payment.order.save(update_fields=['status', 'expires'])
return Response(OrderRefundSerializer(r).data, status=status.HTTP_200_OK)
@action(detail=True, methods=['POST'])
def cancel(self, request, **kwargs):
payment = self.get_object()
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)
return self.retrieve(request, [], **kwargs)
class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrderRefundSerializer
queryset = OrderRefund.objects.none()
permission = 'can_view_orders'
write_permission = 'can_change_orders'
lookup_field = 'local_id'
def get_queryset(self):
order = get_object_or_404(Order, code=self.kwargs['order'], event=self.request.event)
return order.refunds.all()
@action(detail=True, methods=['POST'])
def cancel(self, request, **kwargs):
refund = self.get_object()
if refund.state not in (OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_TRANSIT,
OrderRefund.REFUND_STATE_EXTERNAL):
return Response({'detail': 'Invalid state of refund'}, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
refund.state = OrderRefund.REFUND_STATE_CANCELED
refund.save()
refund.order.log_action('pretix.event.order.refund.canceled', {
'local_id': refund.local_id,
'provider': refund.provider,
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def process(self, request, **kwargs):
refund = self.get_object()
if refund.state != OrderRefund.REFUND_STATE_EXTERNAL:
return Response({'detail': 'Invalid state of refund'}, status=status.HTTP_400_BAD_REQUEST)
refund.done(user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
if 'mark_refunded' in request.data:
mark_refunded = request.data.get('mark_refunded', False)
else:
mark_refunded = request.data.get('mark_canceled', False)
if mark_refunded:
mark_order_refunded(refund.order, user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth)
elif not (refund.order.status == Order.STATUS_PAID and refund.order.pending_sum <= 0):
refund.order.status = Order.STATUS_PENDING
refund.order.set_expires(
now(),
refund.order.event.subevents.filter(
id__in=refund.order.positions.values_list('subevent_id', flat=True))
)
refund.order.save(update_fields=['status', 'expires'])
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def done(self, request, **kwargs):
refund = self.get_object()
if refund.state not in (OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_TRANSIT):
return Response({'detail': 'Invalid state of refund'}, status=status.HTTP_400_BAD_REQUEST)
refund.done(user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
return self.retrieve(request, [], **kwargs)
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 create(self, request, *args, **kwargs):
if 'mark_refunded' in request.data:
mark_refunded = request.data.pop('mark_refunded', False)
else:
mark_refunded = request.data.pop('mark_canceled', False)
serializer = OrderRefundCreateSerializer(data=request.data, context=self.get_serializer_context())
serializer.is_valid(raise_exception=True)
with transaction.atomic():
self.perform_create(serializer)
r = serializer.instance
serializer = OrderRefundSerializer(r, context=serializer.context)
r.order.log_action(
'pretix.event.order.refund.created', {
'local_id': r.local_id,
'provider': r.provider,
},
user=request.user if request.user.is_authenticated else None,
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),
)
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()
class InvoiceFilter(FilterSet):
refers = django_filters.CharFilter(method='refers_qs')
number = django_filters.CharFilter(method='nr_qs')
order = django_filters.CharFilter(name='order', lookup_expr='code__iexact')
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
def refers_qs(self, queryset, name, value):
return queryset.annotate(
@@ -396,7 +989,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('nr',)
ordering_fields = ('nr', 'date')
filter_class = InvoiceFilter
filterset_class = InvoiceFilter
permission = 'can_view_orders'
lookup_url_kwarg = 'number'
lookup_field = 'nr'
@@ -407,7 +1000,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
nr=Concat('prefix', 'invoice_no')
)
@detail_route()
@action(detail=True, )
def download(self, request, **kwargs):
invoice = self.get_object()
@@ -425,7 +1018,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
return resp
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def regenerate(self, request, **kwarts):
inv = self.get_object()
if inv.canceled:
@@ -444,7 +1037,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
)
return Response(status=204)
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def reissue(self, request, **kwarts):
inv = self.get_object()
if inv.canceled:
@@ -453,7 +1046,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
raise PermissionDenied('The invoice file is no longer stored on the server.')
else:
c = generate_cancellation(inv)
if inv.order.status not in (Order.STATUS_CANCELED, Order.STATUS_REFUNDED):
if inv.order.status != Order.STATUS_CANCELED:
inv = generate_invoice(inv.order)
else:
inv = c

View File

@@ -12,7 +12,7 @@ class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
lookup_url_kwarg = 'organizer'
def get_queryset(self):
if self.request.user.is_authenticated():
if self.request.user.is_authenticated:
if self.request.user.has_active_staff_session(self.request.session.session_key):
return Organizer.objects.all()
elif isinstance(self.request.auth, OAuthAccessToken):
@@ -23,5 +23,7 @@ class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
)
else:
return Organizer.objects.filter(pk__in=self.request.user.teams.values_list('organizer', flat=True))
elif hasattr(self.request.auth, 'organizer_id'):
return Organizer.objects.filter(pk=self.request.auth.organizer_id)
else:
return Organizer.objects.filter(pk=self.request.auth.team.organizer_id)

View File

@@ -0,0 +1,16 @@
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
from rest_framework.authentication import SessionAuthentication
from rest_framework.response import Response
from rest_framework.views import APIView
class MeView(APIView):
authentication_classes = (SessionAuthentication, OAuth2Authentication)
def get(self, request, format=None):
return Response({
'email': request.user.email,
'fullname': request.user.fullname,
'locale': request.user.locale,
'timezone': request.user.timezone
})

View File

@@ -1,11 +1,16 @@
import contextlib
from django.db import transaction
from django.db.models import F, Q
from django.utils.timezone import now
from django_filters.rest_framework import (
BooleanFilter, DjangoFilterBackend, FilterSet,
)
from rest_framework import viewsets
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response
from pretix.api.serializers.voucher import VoucherSerializer
from pretix.base.models import Voucher
@@ -34,15 +39,36 @@ class VoucherViewSet(viewsets.ModelViewSet):
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('id',)
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
filter_class = VoucherFilter
filterset_class = VoucherFilter
permission = 'can_view_vouchers'
write_permission = 'can_change_vouchers'
def get_queryset(self):
return self.request.event.vouchers.all()
def _predict_quota_check(self, data, instance):
# This method predicts if Voucher.clean_quota_needs_checking
# *migh* later require a quota check. It is only approximate
# and returns True a little too often. The point is to avoid
# locks when we know we won't need them.
if 'allow_ignore_quota' in data and data.get('allow_ignore_quota'):
return False
if instance and 'allow_ignore_quota' not in data and instance.allow_ignore_quota:
return False
if 'block_quota' in data and not data.get('block_quota'):
return False
if instance and 'block_quota' not in data and not instance.block_quota:
return False
return True
def create(self, request, *args, **kwargs):
with request.event.lock():
if self._predict_quota_check(request.data, None):
lockfn = request.event.lock
else:
lockfn = contextlib.suppress # noop context manager
with lockfn():
return super().create(request, *args, **kwargs)
def perform_create(self, serializer):
@@ -60,7 +86,11 @@ class VoucherViewSet(viewsets.ModelViewSet):
return ctx
def update(self, request, *args, **kwargs):
with request.event.lock():
if self._predict_quota_check(request.data, self.get_object()):
lockfn = request.event.lock
else:
lockfn = contextlib.suppress # noop context manager
with lockfn():
return super().update(request, *args, **kwargs)
def perform_update(self, serializer):
@@ -81,4 +111,28 @@ class VoucherViewSet(viewsets.ModelViewSet):
user=self.request.user,
auth=self.request.auth,
)
super().perform_destroy(instance)
with transaction.atomic():
instance.cartposition_set.filter(addon_to__isnull=False).delete()
instance.cartposition_set.all().delete()
super().perform_destroy(instance)
@action(detail=False, methods=['POST'])
def batch_create(self, request, *args, **kwargs):
if any(self._predict_quota_check(d, None) for d in request.data):
lockfn = request.event.lock
else:
lockfn = contextlib.suppress # noop context manager
with lockfn():
serializer = self.get_serializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True)
with transaction.atomic():
serializer.save(event=self.request.event)
for i, v in enumerate(serializer.instance):
v.log_action(
'pretix.voucher.added',
user=self.request.user,
auth=self.request.auth,
data=self.request.data[i]
)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

View File

@@ -1,7 +1,7 @@
import django_filters
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import viewsets
from rest_framework.decorators import detail_route
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response
@@ -28,7 +28,7 @@ class WaitingListViewSet(viewsets.ModelViewSet):
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('created',)
ordering_fields = ('id', 'created', 'email', 'item')
filter_class = WaitingListFilter
filterset_class = WaitingListFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
@@ -69,7 +69,7 @@ class WaitingListViewSet(viewsets.ModelViewSet):
)
super().perform_destroy(instance)
@detail_route(methods=['POST'])
@action(detail=True, methods=['POST'])
def send_voucher(self, *args, **kwargs):
try:
self.get_object().send_voucher(

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