Compare commits

...

226 Commits

Author SHA1 Message Date
Raphael Michel
5f607cc034 Bump version 2017-07-30 20:36:45 +02:00
Raphael Michel
3b9f508be9 Create squash migration 2017-07-30 20:11:23 +02:00
Raphael Michel
89e381b7ea Update translations 2017-07-30 19:44:11 +02:00
Tobias Kunze
57869b2145 Wording fixes (#577) 2017-07-30 19:29:17 +02:00
Raphael Michel
46976900d7 Add documentation on mail methods 2017-07-27 15:47:22 +02:00
Raphael Michel
a1535da117 Subevent creation: Layout issue 2017-07-27 15:18:25 +02:00
Raphael Michel
f43d782b5c Hide quota options during product creation for event series 2017-07-27 15:14:28 +02:00
Daniel
5c443e2f93 Ref #569 -- Include other email to history (#570)
Log all other order-specified emails and include them into
email history.
2017-07-27 15:09:06 +02:00
Raphael Michel
54f01f63f7 Update translation 2017-07-27 11:53:14 +02:00
Raphael Michel
c64b4473e4 Add organizer_info_text 2017-07-27 11:43:35 +02:00
Raphael Michel
d413a37c1f Translation improvements 2017-07-27 11:21:28 +02:00
Raphael Michel
202fb12008 Order payment providers by name 2017-07-27 11:17:29 +02:00
Raphael Michel
59dea63870 Add product description to ticket editor 2017-07-27 10:13:55 +02:00
Raphael Michel
9a18f2b553 Small JS improvements 2017-07-26 13:41:53 +02:00
Clint
4293ec3805 Fix #475 -- add to existing quota / add new quota from product form (#562)
* added fields and logic for adding quota in creation of product

* added section for selecting quota option

* logic for hiding quota selections when needed

* fixed logic for quota selection

* formatting, removed print statements

* styling

* tests for adding quotas in product form

* cleaned up

* added divs

* reworked to include translatable text, readable values for quota options

* quota_add_existing form assignment to str(q.pk)

* made changes for radio buttons, added in sliding animation

* moved string constants for quota options, changed quota options to RadioSelect from Select
2017-07-26 13:41:23 +02:00
Raphael Michel
f3b616e495 Add exporter to output all tickets 2017-07-26 13:18:35 +02:00
Raphael Michel
003ea24990 Fix Stripe test 2017-07-25 16:52:37 +02:00
Raphael Michel
92d4566a54 Make stripe refund call optional, add request parameter to order_control_refund_render 2017-07-25 16:11:42 +02:00
Raphael Michel
70a933edc1 Hide min_per_order=1 2017-07-24 09:54:33 +02:00
Raphael Michel
d2d77f28aa Fix wrongly displayed download buttons 2017-07-24 09:14:54 +02:00
Raphael Michel
39179971c5 Fix stripe tests 2017-07-23 13:33:43 +02:00
Raphael Michel
c06f36e8c2 Do not render time before JS locale file is loaded 2017-07-23 13:06:07 +02:00
Raphael Michel
1d2d9d8b99 PayPal: Event-independent webhooks 2017-07-23 12:38:41 +02:00
Raphael Michel
5f529817ef Stripe: Event-independent webhooks 2017-07-23 12:22:48 +02:00
Raphael Michel
7c91bc2f37 Respect primary browser language 2017-07-20 11:31:34 +02:00
Raphael Michel
ef022f5a6d Add new dependencies to setup.py 2017-07-19 17:42:07 +02:00
Raphael Michel
54ce00c8b9 Update translation 2017-07-19 14:17:23 +02:00
Raphael Michel
baabbfb1ea Check for required invoice information in other steps 2017-07-19 14:10:04 +02:00
Raphael Michel
28e676ac9a Allow to only require the name in the invoice address 2017-07-19 14:07:18 +02:00
Raphael Michel
21fac9ec7a Auto-toggle form fields for business/individual customers 2017-07-19 12:26:50 +02:00
Raphael Michel
335955820b Fix a counting bug 2017-07-19 12:10:35 +02:00
Raphael Michel
d2b0e7209f Use a choice field for invoice address countries 2017-07-19 12:08:18 +02:00
Johan von Forstner
f8ed21c819 Fix #130 -- Mark required question fields at checkout (#564)
* Fix #130: Mark required question fields at checkout

* Add legend
2017-07-19 11:02:07 +02:00
Tobias Kunze
9c2143effe Clarify mail subject prefix. (#520) 2017-07-19 10:42:34 +02:00
Raphael Michel
8a3fa6aff6 Show category descriptions if redeeming a voucher 2017-07-18 16:47:18 +02:00
Daniel
7e304bb231 Fix #565 -- OrderList ordering request handling (#566)
Fix missed out ordering get request handling.
2017-07-18 16:16:51 +02:00
Raphael Michel
99d614289e Link order from change pages, compat for old logs 2017-07-18 11:46:21 +02:00
Daniel
b90894c20f Fix #543 -- Allow send mail from order (#550)
- Add send email directly for order
- Add email history (from mass and custom) to each specific order
2017-07-18 11:45:30 +02:00
Johan von Forstner
921834c917 Fix os.chown call on Windows (#563) 2017-07-18 11:42:30 +02:00
Raphael Michel
83df4451e6 Fix #491 -- Search order by invoice number 2017-07-18 11:14:51 +02:00
Raphael Michel
2ad9e1bb43 Change API to accomodate for invoice numbers 2017-07-18 10:37:32 +02:00
Raphael Michel
c9990e5ca4 Update translation 2017-07-18 10:04:24 +02:00
Raphael Michel
c95e61db09 Add new external plugins to list 2017-07-17 23:32:41 +02:00
Raphael Michel
7bb12ff0ec Configurable invoice prefixes 2017-07-17 23:03:20 +02:00
Raphael Michel
670bfa18de Slug widget 2017-07-17 20:54:39 +02:00
Raphael Michel
130f619b05 Fix raven integration 2017-07-17 20:29:56 +02:00
Raphael Michel
f900c842cb Improve "copy from above" 2017-07-17 16:12:33 +02:00
Tobias Kunze
c2844a8f35 Add custom_header block to <head> (#561) 2017-07-17 14:52:20 +02:00
Raphael Michel
a864dabbaf Refs #552 -- Expand all panels that contain validation errors 2017-07-17 12:49:45 +02:00
Daniel
02786f4801 Fix #552 -- Add icon to expansible panels (#560)
* Ref #552 -- Add icon to expansible panels

Offer informative feedback to user by showing icons to indicate
expansible panels.

* Fix wrongly removed JavaScript line
2017-07-17 12:29:47 +02:00
Raphael Michel
5a4fe266c6 Fix usage of wrong settings key 2017-07-16 22:17:02 +02:00
Raphael Michel
b30a4db0b8 Update translation 2017-07-16 20:15:48 +02:00
Raphael Michel
2e76e07764 Show end date on front page 2017-07-16 20:11:59 +02:00
Raphael Michel
1be92f5078 Really fix 2050c0b7 issue 2017-07-16 19:37:18 +02:00
Raphael Michel
8afff29cd4 Make microdata XSS-safe and subevent-aware 2017-07-16 17:52:08 +02:00
Raphael Michel
9c6090a355 Fix translation sytax error 2017-07-16 17:49:31 +02:00
Joepriesto
27b73227ed Fix #30 -- Event microdata information (#546)
* Addition of all basics required for microdata

Microdata for event added to the head of the base event html template.

* Fixed flake8 errors & included settings checks

Fixed the flake8 too many blank line errors and changed the date and time section to refer to settings before including the time and/or the date_to.

* Typo in last commit

Fixed typo in last commit

* Fixed bracket mistake and flake8 error

Fixed tab between { and % in template tag. Removed extra lines. Removed unnecessary str() call.
Corrected binary & to [and] operator.

* Added testing for microdata

Testing added to the presale.test_event tests to cover the 4 branches and basic microdata functionality.
2017-07-16 17:42:46 +02:00
Raphael Michel
9582f8380f Fix translation syntax 2017-07-16 17:31:35 +02:00
Raphael Michel
bfeac7e70b Fix bogus translation 2017-07-16 17:21:07 +02:00
Raphael Michel
2050c0b7a8 Fix TypeError 2017-07-16 17:05:46 +02:00
Raphael Michel
144f9bed69 Fix URL in iCal test 2017-07-15 16:38:14 +02:00
Raphael Michel
f2b642d944 Fix iCal URL 2017-07-14 18:26:18 +02:00
Raphael Michel
bcdc75953e Add SubEvent.frontpage_text 2017-07-14 18:14:24 +02:00
Raphael Michel
1fabe5a7cf Disable button "add to cart" with no selection 2017-07-14 18:10:49 +02:00
Raphael Michel
fe9a4b7aa3 Allow word wrapping inside .dl-horizontal dt 2017-07-14 18:10:18 +02:00
Raphael Michel
56735dc1c6 Make PDF ticket output subevent-compatible 2017-07-14 18:09:48 +02:00
Raphael Michel
42287b92f1 Allow ticket output methods to opt-out from ZIP generation 2017-07-14 17:32:16 +02:00
Raphael Michel
7d9e642f24 Useful default date for admission field 2017-07-14 17:21:41 +02:00
Raphael Michel
b20e10585f Include Event.is_public in Event.copy_data_from 2017-07-14 17:11:52 +02:00
Raphael Michel
8438b211a6 Fix iCal test failing due to new UID format 2017-07-14 14:55:08 +02:00
Raphael Michel
f94314afec Generate organizer-level iCal files 2017-07-14 14:25:05 +02:00
Martin Gross
4584d23434 Fix #556 - Max. redemptions instead of amount of vouchers (#558) 2017-07-14 12:11:34 +02:00
Raphael Michel
2791501781 Fix #557 -- Drop unused CSS file 2017-07-14 12:11:20 +02:00
Raphael Michel
6ea798e55b Allow to exclude free products from invoices 2017-07-14 12:11:20 +02:00
Martin Gross
0ab6ac569e Fix #553 -- Order by position first in order_overview (#554) 2017-07-14 10:22:26 +02:00
Raphael Michel
f91d7352a4 Fix AttributeError in test 2017-07-13 22:04:34 +02:00
Raphael Michel
d644b8fe01 Fix detection of core modules after ae910eb7 2017-07-13 20:33:08 +02:00
Raphael Michel
5a8042cc10 Fix relative dates in payment method availability field 2017-07-13 17:37:07 +02:00
Raphael Michel
ae910eb731 Fix EventPluginSignal to recognize Django apps with custom app labels 2017-07-12 19:14:28 +02:00
Raphael Michel
c1158c3175 Allow to mark add-ons as "included" in price 2017-07-12 18:42:09 +02:00
Raphael Michel
79562e7ad9 Add logdisplay for charge.pending 2017-07-12 18:02:56 +02:00
Raphael Michel
e3388bea96 Fix Stripe webhooks 2017-07-12 17:37:05 +02:00
Daniel
a3b4a7ef1d Fix #516 -- Prevent gaps in plugin list (#551)
Use flexbox to fix misalignment when column height is imbalanced.
2017-07-12 17:30:01 +02:00
Raphael Michel
947b06cd61 Update translation 2017-07-12 17:20:09 +02:00
Raphael Michel
a3f3561f02 Fix import in stripe tests 2017-07-12 16:56:01 +02:00
Raphael Michel
48095d38be Fix #407 -- Integrate more Stripe payment methods 2017-07-12 16:42:44 +02:00
Raphael Michel
1c6858653a Avoid relative URLs in stripe plugin 2017-07-11 22:53:15 +02:00
Raphael Michel
648797325e Allow to copy across events with and w/o subevents 2017-07-11 22:45:41 +02:00
Raphael Michel
af3fa88d67 Typos and translation 2017-07-11 18:47:39 +02:00
Raphael Michel
8123effa65 Add sub-events and relative date settings (#503)
* Data model

* little crud

* SubEventItemForm etc

* Drop SubEventItem.active, quota editor

* Fix failing tests

* First frontend stuff

* Addons form stuff

* Quota calculation

* net price display on EventIndex

* Add tests, solve some bugs

* Correct quota selection in more places, consolidate pricing logic

* Fix failing quota tests

* Fix TypeError

* Add tests for checkout

* Fixed a bug in QuotaForm

* Prevent immutable cart if a quota was removed from an item

* Add tests for pricing

* Handle waiting list

* Filter in check-in list

* Fixed import lost in rebase

* Fix waiting list widget

* Voucher management

* Voucher redemption

* Fix broken tests

* Add subevents to OrderChangeManager

* Create a subevent during event creation

* Fix bulk voucher creation

* Introduce subevent.active

* Copy from for subevents

* Show active in list

* ICal download for subevents

* Check start and end of presale

* Failing tests / show cart logic

* Test

* Rebase migrations

* REST API integration of sub-events

* Integrate quota calculation into the traditional quota form

* Make subevent argument to add_position optional

* Log-display foo

* pretixdroid and subevents

* Filter by subevent

* Add more tests

* Some mor tests

* Rebase fixes

* More tests

* Relative dates

* Restrict selection in relative datetime widgets

* Filter subevent list

* Re-label has_subevents

* Rebase fixes, subevents in calendar view

* Performance and caching issues

* Refactor calendar templates

* Permission tests

* Calendar fixes and month selection

* subevent selection

* Rename subevents to dates

* Add tests for calendar views
2017-07-11 13:56:00 +02:00
Raphael Michel
554800c06f Add 3DS support to Stripe plugin 2017-07-10 21:36:59 +02:00
Raphael Michel
687ce29366 Fix #547 -- Switch to Stripe Elements 2017-07-10 20:00:03 +02:00
Raphael Michel
c70301572c Fix bug in invoice rendering 2017-07-10 14:43:37 +02:00
Raphael Michel
714f58e2c5 Update translation 2017-07-10 14:29:53 +02:00
Raphael Michel
9bf9dca88a Invoice exporter: open in binary mode 2017-07-10 14:25:26 +02:00
Daniel
0663f25208 Fix #541 -- Allow delete waitinglist entries (#544)
Add new action "delete" for each waiting list entries
2017-07-09 15:19:30 +02:00
Raphael Michel
275d162b81 List of events: Optional calendar view 2017-07-08 22:50:48 +02:00
Raphael Michel
675956a7c4 Update translations 2017-07-07 11:37:09 +02:00
Raphael Michel
cf1602c0af Add short form to event list 2017-07-07 11:32:53 +02:00
Raphael Michel
35979ed332 Add internal comment to events 2017-07-07 11:31:33 +02:00
Raphael Michel
6e65ae5306 Swappable invoice renderers 2017-07-07 11:16:07 +02:00
Raphael Michel
95e716b8ce Improve help texts 2017-07-07 10:11:39 +02:00
Raphael Michel
554284ac67 Link new plugins 2017-07-06 18:07:03 +02:00
Raphael Michel
fa6102bbad Allow to override sender for mail 2017-07-06 18:06:07 +02:00
Raphael Michel
f28d5f19a7 Ignore spaces in bank transfer references 2017-07-06 17:39:32 +02:00
Raphael Michel
34d59c7741 Add "Go to shop" in mobile view 2017-07-06 17:36:01 +02:00
Raphael Michel
21d432a3ca Make cancel notifications optional 2017-07-06 12:04:35 +02:00
Daniel
d444935140 Fix #391 -- Send email on cancelled order (#540)
Send a notification email to user when
order is cancelled.
2017-07-06 11:59:48 +02:00
Raphael Michel
9de9d96e35 Do not expose "Deutsch (Du)" in name-local 2017-07-06 09:26:26 +02:00
Michele Fattoruso
5932558ca2 Fix #300 - Feature/remove flag as language (#542)
* don't use flags to indicate languages #300

* cleaned code

* removed the filter and moved the logic to context.py

* cleaned code

* show the text in the local language

* cleaned code

* changed loop for list comprehension

* fix indentation

* ordered import
2017-07-06 09:13:57 +02:00
Raphael Michel
7b22adb72e Allow to manually generate invoice if invoice mode is "on payment" 2017-07-05 13:34:07 +02:00
Raphael Michel
0db5d062be Add a file upload type to questions (#534)
* Initial stuff

* More features
2017-07-03 14:22:31 +02:00
Sanket Dasgupta
678d510e29 config.rst: Include environment variable in list of paths (#539)
Closes https://github.com/pretix/pretix/issues/528
2017-07-03 12:52:36 +02:00
Raphael Michel
1fc3307d22 Add signal question_form_fields 2017-07-02 19:45:26 +02:00
Raphael Michel
45c17ba949 Fix syntax error in setup.py 2017-07-02 18:57:55 +02:00
Raphael Michel
15bd1d9006 Fix missing dependencies in setup.py 2017-07-02 18:44:25 +02:00
Tydon K
b4715f0931 Fix #517 -- Changed navpills to navtabs in email template editor (#533)
* Changed navpills to navtabs in email template editor

* Issue 517 - Added border radius to email template tabs and removed excess bottom margin.
2017-07-02 17:51:41 +02:00
Raphael Michel
6f24a2a88c Fix import in unit test 2017-07-02 17:36:12 +02:00
Raphael Michel
fcbecf895a Bump version to 1.6.0.dev0 2017-07-02 17:32:02 +02:00
Raphael Michel
5abe8a109c Bump release to 1.5.0 2017-07-02 17:29:55 +02:00
Raphael Michel
ee6d0af795 Update translations 2017-07-02 17:26:32 +02:00
Raphael Michel
0dc82b6a28 Add answers to REST API 2017-07-02 16:34:55 +02:00
Raphael Michel
a530eda9fc Add CSRF_TRUSTED_ORIGINS to settings.py 2017-06-30 11:55:59 +02:00
Raphael Michel
3fa39798e5 Fix test failures 2017-06-29 18:03:16 +02:00
Raphael Michel
b7f8832633 Fix test_order_list 2017-06-29 12:11:20 +02:00
Raphael Michel
2add8d671a Unify all calls to register_payment_providers 2017-06-29 12:09:48 +02:00
Raphael Michel
d5d1fcf331 Add PaymentProvider.public_name 2017-06-29 11:44:27 +02:00
Raphael Michel
9007501d81 Collappse payment provider settings by default 2017-06-29 11:40:17 +02:00
Raphael Michel
fc12a8f549 Relabel button. 2017-06-28 18:57:50 +02:00
Raphael Michel
86f03b3399 Fix #532: Ticket editor, moving element groups 2017-06-28 18:55:33 +02:00
Raphael Michel
80ad0e7f68 PDF editor: share source code 2017-06-28 18:36:45 +02:00
Raphael Michel
ea970be6f2 Allow to generate random slugs 2017-06-28 18:20:06 +02:00
Raphael Michel
4bd0b96a2d Order list of events descending 2017-06-28 17:59:07 +02:00
Raphael Michel
c7f0436ec0 Filter list of events 2017-06-28 17:57:15 +02:00
Raphael Michel
ecc788fd79 Invoice name in order list 2017-06-28 17:37:53 +02:00
Raphael Michel
8ea9d5685d Use FilterForm for list of orders 2017-06-28 17:28:30 +02:00
Raphael Michel
c2bd2f0672 Add filters to the order search 2017-06-28 12:52:48 +02:00
Raphael Michel
424b7964b4 Move some links to their own templates 2017-06-27 18:22:27 +02:00
Raphael Michel
98394fdc63 Fix test failing on MySQL 2017-06-27 16:56:10 +02:00
Raphael Michel
625e90518e Add banktransfer API 2017-06-27 12:31:56 +02:00
Raphael Michel
d446191cf4 Organizer-level bank import 2017-06-27 12:31:45 +02:00
Raphael Michel
ea2557274f Show "create team" button to everyone who can 2017-06-27 08:58:54 +02:00
Raphael Michel
1bcca566ab Remove SVG from supported files for invoice generation 2017-06-26 19:15:13 +02:00
Raphael Michel
cf365635ef Correct pluralization for addon help texts 2017-06-26 17:29:39 +02:00
Raphael Michel
df6a93da5f Update translation 2017-06-26 17:08:52 +02:00
Raphael Michel
433512a256 Optionally ask for email addresses twice 2017-06-26 17:06:20 +02:00
Raphael Michel
e0fe78b82e Fix typocheck problem 2017-06-26 17:00:16 +02:00
Raphael Michel
d3a84690f2 Add QuotaListExporter 2017-06-26 16:50:16 +02:00
Raphael Michel
3df4b4dc57 Update translations 2017-06-22 16:24:29 +02:00
Raphael Michel
b2801f3a40 EU legislation note 2017-06-22 15:53:30 +02:00
Raphael Michel
e1bdfd7e4e Allow headlines with markdown 2017-06-22 15:53:30 +02:00
Tydon K
5584c12d0a Fixed typo in Helptext (#530) 2017-06-22 10:35:56 +02:00
Raphael Michel
91bd02157c Avoid word wrap inside event date 2017-06-21 15:44:42 +02:00
Raphael Michel
02d2b88a54 Extend validator blacklist 2017-06-19 17:47:13 +02:00
Raphael Michel
6ae5c5e6ce Fix tests failing after last commit 2017-06-19 16:17:08 +02:00
Raphael Michel
123d2f6120 Allow to add positions to an existing order 2017-06-19 15:22:57 +02:00
Raphael Michel
3ada10c3f4 Fix broken ReST markup 2017-06-19 13:58:45 +02:00
Raphael Michel
b21a928028 Add API endpoint for Quota.availability 2017-06-19 13:57:58 +02:00
Raphael Michel
374cf08086 Mention offending item in QuotaExceededException message 2017-06-19 13:23:37 +02:00
Raphael Michel
15a85bb873 Commit style docs 2017-06-19 12:02:12 +02:00
Raphael Michel
4b12678fa4 Various docs improvements 2017-06-19 12:01:00 +02:00
Raphael Michel
b2d4bea1d0 Refs #314 -- Read-only REST API (#513)
* initial commit

* API auth

* Hierarchical URLs

* Add session auth

* Strong hierarchy

* Add filters

* Add i18n fields, questions

* More viewsets and serializers

* Ticket download

* Add OrderPosition serializer

* View-level permissions

* More tests

* More tests

* Add basic API docs

* Add REST API to docs frontpage

* Tests for order endpoints

* Add invoice tests

* Voucher and waitinglist tests

* Doc draft

* order docs

* Docs on all viewsets

* Disable DRF docs, style sphinx, style browsable API

* Fix tests

* deprecated imports

* Test foo

* Attendee names

* Fix migration problems

* Remove browsable API, plugin integration

* Doc fixes
2017-06-19 11:16:04 +02:00
Tobias Kunze
6df3a7d4b5 Order vouchers by code by default (#525) 2017-06-18 18:31:54 +02:00
Raphael Michel
6964bf7c8a Revert accidental deletion of translations 2017-06-18 18:31:09 +02:00
Tobias Kunze
965424799d Highlight voucher nav element when in detail view (#524) 2017-06-18 18:31:02 +02:00
Tobias Kunze
f7de2ead40 Allow to pass prefix to generate_code, too (#523) 2017-06-18 18:30:38 +02:00
Tobias Kunze
260ba19e44 Typo fix and translation update (#519) 2017-06-18 18:22:57 +02:00
Raphael Michel
5caff5d28e Remove validation error on valid input 2017-06-16 23:21:49 +02:00
Tobias Kunze
8fa490c938 Allow to add a prefix when generating voucher codes (#518) 2017-06-16 23:21:23 +02:00
Raphael Michel
8e3cc0df0c Fix readthedocs CSS issue 2017-06-16 22:37:51 +02:00
Raphael Michel
1a5a4cf72d Fix sphinx-related problems 2017-06-16 22:29:19 +02:00
Raphael Michel
6c5b7bbed0 Custom docs front page 2017-06-16 22:16:45 +02:00
Raphael Michel
f9eee6a864 Add custom Sphinx theme 2017-06-16 22:16:45 +02:00
Raphael Michel
4ad0fe5653 Voucher form validation order 2017-06-12 12:50:06 +02:00
Raphael Michel
a02823ca38 Add global order search 2017-06-05 20:50:06 +02:00
Raphael Michel
513f8e66f5 Display full name in event typeahead 2017-06-05 19:37:40 +02:00
Raphael Michel
0cecc168b6 Autocompletion in event selection 2017-06-05 19:28:52 +02:00
Raphael Michel
6f7281b0f5 Add organizer domain 2017-06-05 18:07:18 +02:00
Raphael Michel
c7022bd285 Truncate name of current event 2017-06-05 17:51:21 +02:00
Raphael Michel
e89e3d2e1b Fix Go to shop button for non-live events with subdomain 2017-06-02 17:58:44 +02:00
noelroy
574fb6804f Fix #445 - Added go to shop button (#512) 2017-06-02 17:48:11 +02:00
AV-room
caf75fafdf Fix #454 -- Email field is now required on user settings page (#511) 2017-06-02 17:42:42 +02:00
Raphael Michel
bc74fb63d1 Base Dockerfile on python:3.6 2017-06-01 22:28:24 +02:00
Raphael Michel
b576a87d61 mark mimetypes as stdlib 2017-06-01 16:20:52 +02:00
Raphael Michel
6e81d8acec isort show diff 2017-06-01 15:44:11 +02:00
Raphael Michel
c67a53d156 Travis: Check style on 3.6 2017-06-01 14:54:52 +02:00
Raphael Michel
bf5ea81b40 Ignore style in tests, change import order 2017-06-01 13:28:11 +02:00
Raphael Michel
1261b8670f Bump version to 1.5.0.dev0 2017-06-01 13:00:37 +02:00
Raphael Michel
17dad33f8b Bump version to 1.4.0 2017-06-01 12:57:29 +02:00
Raphael Michel
1d5c160e1d Document contact_form_fields 2017-06-01 12:57:11 +02:00
Raphael Michel
f04b7fa365 Update translations 2017-06-01 12:56:53 +02:00
Raphael Michel
fa011fbdce Update django-redis 2017-05-31 07:51:16 +02:00
Raphael Michel
759c5374d9 Fix chardet pinning in setup.py 2017-05-30 19:22:28 +02:00
Raphael Michel
f631acdf18 Force chardet update 2017-05-30 19:12:44 +02:00
Raphael Michel
b2dfd8ab11 Bankimport: Force re-annotation for badly parsed files 2017-05-30 18:43:53 +02:00
Raphael Michel
43f4803da7 Fix name being used instead of email in CSV files 2017-05-30 18:00:21 +02:00
Raphael Michel
019d8220b8 Fix wrong text with banktransfer orders manually marked as paid 2017-05-30 17:57:50 +02:00
Raphael Michel
b946010bdb Add event date range to ticket editor 2017-05-30 17:18:03 +02:00
Raphael Michel
c3097b12c3 Fix typo 2017-05-30 17:14:17 +02:00
Raphael Michel
12a53710c3 Fix test_csvparser case that changed with newer chardet 2017-05-27 14:28:39 +02:00
Raphael Michel
983326e610 Stop pinning chardet/setuptools versions 2017-05-27 12:50:18 +02:00
Claudio Luck
b4eb707b38 bump required mt940 to v4.7 2017-05-27 11:59:21 +02:00
Claudio Luck
e44f34f0a9 mt940: also consider non-SWIFT field NS 2017-05-27 11:59:21 +02:00
Claudio Luck
3c762adbf4 mt940: payer is not always available 2017-05-27 11:59:21 +02:00
Raphael Michel
ebabd20d09 [Django 1.11] Refs #481 -- Explicit sorting of NULLs 2017-05-26 09:44:11 +02:00
Raphael Michel
8694e1901a [Django 1.11] Port AddOnVariationField 2017-05-26 09:44:11 +02:00
Tobias Kunze
0f2875e89a [Django 1.11] Upgrade to Django 1.11 2017-05-26 09:44:11 +02:00
Raphael Michel
74d9921be1 Fix error 500 during building error 400 responses 2017-05-26 09:37:48 +02:00
Tobias Kunze
41e56adfdb Make checkin lists available by default (#508) 2017-05-25 16:47:09 +02:00
Raphael Michel
4ff1d302d9 Fix missing argument 2017-05-25 14:55:04 +02:00
Raphael Michel
d6e213d51a Disable inline pdfs again due to Safari issues 2017-05-25 14:43:31 +02:00
Raphael Michel
2a4deeba55 List commercial plugins 2017-05-24 14:34:20 +02:00
Raphael Michel
24b1d2afcb Disable CssAbsoluteFilter 2017-05-24 14:34:20 +02:00
Leah Oswald
5cea3d824a Fix some typos in the backup and monitoring documentation (#506)
* fix missing word typo in backup and monitoring documentation

* fix another typo in backup and monitoring documentation
2017-05-24 13:40:12 +02:00
Raphael Michel
78a8a7d744 Add Order.meta_info_data 2017-05-23 12:38:00 +02:00
Raphael Michel
a3bf85754a Add signal contact_form_fields 2017-05-23 11:43:05 +02:00
Raphael Michel
006b6fd5e8 Add support for image icons in navigation 2017-05-23 11:42:42 +02:00
Raphael Michel
8ff2c42070 Fix order changing with zero-priced products 2017-05-22 21:13:05 +02:00
Raphael Michel
c5d18c6884 Clear settings cache after migration 2017-05-22 14:17:52 +02:00
Heok Hong Low
48b3621f1e Fix #499 -- Refactor paymentinfo to payment_info (#501)
* Refactor paymentinfo to payment_info, resolve #499

* Fix calling of object attribute on tuple

* Minor update to setup documentation

* Do not use short words for typochecks

* Text clarification

* Refactor paymentinfo to payment_info, resolve #499

* Include data migration for existing event settings, resolve #499
2017-05-22 14:06:19 +02:00
Raphael Michel
fb716eb498 Add add-ons to ticket editor 2017-05-22 14:04:14 +02:00
Tobias Kunze
5d8e294350 Fix typo 2017-05-22 09:25:15 +02:00
Raphael Michel
8a96d8c24e Text clarification 2017-05-21 10:47:12 +02:00
Raphael Michel
6396d2f922 Do not use short words for typochecks 2017-05-21 10:47:12 +02:00
lhhong
ed7e90451b Minor update to setup documentation 2017-05-21 10:29:56 +02:00
lhhong
f5990dd5c4 Fix calling of object attribute on tuple 2017-05-21 10:26:58 +02:00
377 changed files with 32117 additions and 6263 deletions

View File

@@ -5,7 +5,6 @@ tests:
- virtualenv env
- source env/bin/activate
- pip install -U pip wheel setuptools
- XDG_CACHE_HOME=/cache bash .travis.sh style
- XDG_CACHE_HOME=/cache bash .travis.sh tests
tags:
- python3

View File

@@ -18,7 +18,7 @@ if [ "$1" == "style" ]; then
XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt -r src/requirements/py34.txt
cd src
flake8 .
isort -c -rc .
isort -c -rc -df .
fi
if [ "$1" == "doctests" ]; then
XDG_CACHE_HOME=/cache pip3 install -Ur doc/requirements.txt -r src/requirements/py34.txt

View File

@@ -30,11 +30,11 @@ matrix:
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.4
- python: 3.6
env: JOB=style
- python: 3.4
- python: 3.6
env: JOB=plugins
- python: 3.4
- python: 3.6
env: JOB=tests-cov
addons:
postgresql: "9.4"

View File

@@ -1,11 +1,9 @@
FROM debian:jessie
FROM python:3.6
RUN apt-get update && \
apt-get install -y python3 git python3-pip \
libxml2-dev libxslt1-dev python-dev python-virtualenv locales libffi-dev \
build-essential python3-dev zlib1g-dev libssl-dev gettext \
libpq-dev libmysqlclient-dev libmemcached-dev libjpeg-dev \
aqbanking-tools supervisor nginx sudo \
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 \
libmysqlclient-dev libmemcached-dev libjpeg-dev supervisor nginx sudo \
--no-install-recommends && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
@@ -33,8 +31,7 @@ RUN chmod +x /usr/local/bin/pretix && \
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 \
-r requirements/py34.txt gunicorn && \
-r requirements/memcached.txt -r requirements/redis.txt gunicorn && \
mkdir -p data && \
chown -R pretixuser:pretixuser /pretix /data data && \
sudo -u pretixuser make production

160
doc/_templates/index.html vendored Normal file
View File

@@ -0,0 +1,160 @@
{% extends "layout.html" %}
{% set title = 'Overview' %}
{% block body %}
<h1>Welcome to pretix' documentation!</h1>
<p>
We work hard to make this website contain all information that you need to use, run, understand, and improve
pretix. Of course, this documentation will never be perfect or complete, but if there is anything unclear
or anything specific that you miss here, that's a bug and we'd be happy if you'd
<a href="https://github.com/pretix/pretix/issues/new">let us know</a>.
</p>
<h2>Documentation structure</h2>
<div class="sectionbox">
<div class="icon">
<a href="user/index.html">
<span class="fa fa-user fa-fw"></span>
</a>
</div>
<div class="text">
<a href="user/index.html">
<strong>User Guide</strong>
</a>
<p>Go here to find information on how to configure and use pretix as an event organizer.</p>
</div>
</div>
<div class="sectionbox">
<div class="icon">
<a href="admin/index.html">
<span class="fa fa-server fa-fw"></span>
</a>
</div>
<div class="text">
<a href="admin/index.html">
<strong>Administrator docs</strong>
</a>
<p>Find out how to install pretix on your own server and how to maintain an installation of pretix.</p>
</div>
</div>
<div class="clearfix"></div>
<div class="sectionbox">
<div class="icon">
<a href="api/index.html">
<span class="fa fa-plug fa-fw"></span>
</a>
</div>
<div class="text">
<a href="api/index.html">
<strong>REST API</strong>
</a>
<p>
Documentation and reference of the RESTful API exposed by pretix for interaction with external
components.
</p>
</div>
</div>
<div class="sectionbox">
<div class="icon">
<a href="development/index.html">
<span class="fa fa-wrench fa-fw"></span>
</a>
</div>
<div class="text">
<a href="development/index.html">
<strong>Developer docs</strong>
</a>
<p>Get information on how to contribute to pretix itself and how to build plugins that interact with
pretix.</p>
</div>
</div>
<div class="clearfix"></div>
<div class="sectionbox">
<div class="icon">
<a href="plugins/index.html">
<span class="fa fa-puzzle-piece fa-fw"></span>
</a>
</div>
<div class="text">
<a href="plugins/index.html">
<strong>Plugin docs</strong>
</a>
<p>Documentation and details on plugins that ship with pretix or are officially supported.</p>
</div>
</div>
<div class="sectionbox">
<div class="icon">
<a href="contents.html">
<span class="fa fa-list fa-fw"></span>
</a>
</div>
<div class="text">
<a href="contents.html">
<strong>Table of contents</strong>
</a>
<p>Detailled overview of everything contained in this documentation.</p>
</div>
</div>
<div class="clearfix"></div>
<h2>Useful links</h2>
<div class="sectionbox">
<div class="icon">
<a href="https://pretix.eu" target="_blank">
<span class="fa fa-globe fa-fw"></span>
</a>
</div>
<div class="text">
<a href="https://pretix.eu" target="_blank">
<strong>Project website</strong>
</a>
<p>pretix.eu is the central entry-point to the pretix project and also the home of the commercial hosting
service available.</p>
</div>
</div>
<div class="sectionbox">
<div class="icon">
<a href="https://github.com/pretix/pretix" target="_blank">
<span class="fa fa-github fa-fw"></span>
</a>
</div>
<div class="text">
<a href="https://github.com/pretix/pretix" target="_blank">
<strong>GitHub repository</strong>
</a>
<p>Our main source code repository contains all code that is part of pretix as well as some plugins and the
source for this documentation.</p>
</div>
</div>
<div class="clearfix"></div>
<div class="sectionbox">
<div class="icon">
<a href="https://pretix.eu/about/en/blog/" target="_blank">
<span class="fa fa-newspaper-o fa-fw"></span>
</a>
</div>
<div class="text">
<a href="https://pretix.eu/about/en/blog/" target="_blank">
<strong>Project blog</strong>
</a>
<p>This important information source contains all release notes for all stable releases of pretix as well as
general news on pretix as a project.</p>
</div>
</div>
<div class="sectionbox">
<div class="icon">
<a href="https://twitter.com/pretixeu" target="_blank">
<span class="fa fa-twitter fa-fw"></span>
</a>
</div>
<div class="text">
<a href="https://twitter.com/pretixeu" target="_blank">
<strong>Twitter</strong>
</a>
<p>Keep in touch and stay up to date by following our project account on Twitter.</p>
</div>
</div>
<div class="clearfix"></div>
{% endblock %}

16
doc/_themes/pretix_theme/__init__.py vendored Normal file
View File

@@ -0,0 +1,16 @@
"""pretix sphinx theme.
Based on sphinx-rtd-theme
Based on https://github.com/ryan-roemer/sphinx-bootstrap-theme.
"""
import os
__version__ = '0.1.0'
__version_full__ = __version__
def get_html_theme_path():
"""Return list of HTML theme paths."""
cur_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
return cur_dir

View File

@@ -0,0 +1,82 @@
{# Support for Sphinx 1.3+ page_source_suffix, but don't break old builds. #}
{% if page_source_suffix %}
{% set suffix = page_source_suffix %}
{% else %}
{% set suffix = source_suffix %}
{% endif %}
{% if meta is defined and meta is not none %}
{% set check_meta = True %}
{% else %}
{% set check_meta = False %}
{% endif %}
{% if check_meta and 'github_url' in meta %}
{% set display_github = True %}
{% endif %}
{% if check_meta and 'bitbucket_url' in meta %}
{% set display_bitbucket = True %}
{% endif %}
{% if check_meta and 'gitlab_url' in meta %}
{% set display_gitlab = True %}
{% endif %}
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
{% block breadcrumbs %}
<li><a href="{{ pathto(master_doc) }}">{{ _('Docs') }}</a> &raquo;</li>
{% for doc in parents %}
<li><a href="{{ doc.link|e }}">{{ doc.title }}</a> &raquo;</li>
{% endfor %}
<li>{{ title }}</li>
{% endblock %}
{% block breadcrumbs_aside %}
<li class="wy-breadcrumbs-aside">
{% if pagename != "search" %}
{% if display_github %}
{% if check_meta and 'github_url' in meta %}
<!-- User defined GitHub URL -->
<a href="{{ meta['github_url'] }}" class="fa fa-github"> {{ _('Edit on GitHub') }}</a>
{% else %}
<a href="https://{{ github_host|default("github.com") }}/{{ github_user }}/{{ github_repo }}/blob/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-github"> {{ _('Edit on GitHub') }}</a>
{% endif %}
{% elif display_bitbucket %}
{% if check_meta and 'bitbucket_url' in meta %}
<!-- User defined Bitbucket URL -->
<a href="{{ meta['bitbucket_url'] }}" class="fa fa-bitbucket"> {{ _('Edit on Bitbucket') }}</a>
{% else %}
<a href="https://bitbucket.org/{{ bitbucket_user }}/{{ bitbucket_repo }}/src/{{ bitbucket_version}}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-bitbucket"> {{ _('Edit on Bitbucket') }}</a>
{% endif %}
{% elif display_gitlab %}
{% if check_meta and 'gitlab_url' in meta %}
<!-- User defined GitLab URL -->
<a href="{{ meta['gitlab_url'] }}" class="fa fa-gitlab"> {{ _('Edit on GitLab') }}</a>
{% else %}
<a href="https://{{ gitlab_host|default("gitlab.com") }}/{{ gitlab_user }}/{{ gitlab_repo }}/blob/{{ gitlab_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-gitlab"> {{ _('Edit on GitLab') }}</a>
{% endif %}
{% elif show_source and source_url_prefix %}
<a href="{{ source_url_prefix }}{{ pagename }}{{ suffix }}">{{ _('View page source') }}</a>
{% elif show_source and has_source and sourcename %}
<a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> {{ _('View page source') }}</a>
{% endif %}
{% endif %}
</li>
{% endblock %}
</ul>
{% if (theme_prev_next_buttons_location == 'top' or theme_prev_next_buttons_location == 'both') and (next or prev) %}
<div class="rst-breadcrumbs-buttons" role="navigation" aria-label="breadcrumb navigation">
{% if next %}
<a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n">Next <span class="fa fa-arrow-circle-right"></span></a>
{% endif %}
{% if prev %}
<a href="{{ prev.link|e }}" class="btn btn-neutral" title="{{ prev.title|striptags|e }}" accesskey="p"><span class="fa fa-arrow-circle-left"></span> Previous</a>
{% endif %}
</div>
{% endif %}
<hr/>
</div>

52
doc/_themes/pretix_theme/footer.html vendored Normal file
View File

@@ -0,0 +1,52 @@
<footer>
{% if (theme_prev_next_buttons_location == 'bottom' or theme_prev_next_buttons_location == 'both') and (next or prev) %}
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
{% if next %}
<a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n" rel="next">{{ _('Next') }} <span class="fa fa-arrow-circle-right"></span></a>
{% endif %}
{% if prev %}
<a href="{{ prev.link|e }}" class="btn btn-neutral" title="{{ prev.title|striptags|e }}" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> {{ _('Previous') }}</a>
{% endif %}
</div>
{% endif %}
<hr/>
<div role="contentinfo">
<p>
{%- if show_copyright %}
{%- if hasdoc('copyright') %}
{% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
{%- else %}
{% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
{%- endif %}
{%- endif %}
{%- if build_id and build_url %}
{% trans build_url=build_url, build_id=build_id %}
<span class="build">
Build
<a href="{{ build_url }}">{{ build_id }}</a>.
</span>
{% endtrans %}
{%- elif commit %}
{% trans commit=commit %}
<span class="commit">
Revision <code>{{ commit }}</code>.
</span>
{% endtrans %}
{%- elif last_updated %}
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
{%- endif %}
</p>
</div>
{%- if show_sphinx %}
<small>{% trans %}Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a theme that is based on a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>{% endtrans %}.</small>
{%- endif %}
{%- block extrafooter %} {% endblock %}
</footer>

211
doc/_themes/pretix_theme/layout.html vendored Normal file
View File

@@ -0,0 +1,211 @@
{# TEMPLATE VAR SETTINGS #}
{%- set url_root = pathto('', 1) %}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if not embedded and docstitle %}
{%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
{%- else %}
{%- set titlesuffix = "" %}
{%- endif %}
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
{{ metatags }}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block htmltitle %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
{% endblock %}
{# FAVICON #}
{% if favicon %}
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{% endif %}
{# CANONICAL URL #}
{% if theme_canonical_url %}
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
{% endif %}
{# CSS #}
{# OPENSEARCH #}
{% if not embedded %}
{% if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{% endif %}
{% endif %}
{# RTD hosts this file, so just load on non RTD builds #}
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
{% for cssfile in css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{% endfor %}
{% for cssfile in extra_css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{% endfor %}
{%- block linktags %}
{%- if hasdoc('about') %}
<link rel="author" title="{{ _('About these documents') }}"
href="{{ pathto('about') }}"/>
{%- endif %}
{%- if hasdoc('genindex') %}
<link rel="index" title="{{ _('Index') }}"
href="{{ pathto('genindex') }}"/>
{%- endif %}
{%- if hasdoc('search') %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/>
{%- endif %}
{%- if hasdoc('copyright') %}
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}"/>
{%- endif %}
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}"/>
{%- if parents %}
<link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}"/>
{%- endif %}
{%- if next %}
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}"/>
{%- endif %}
{%- if prev %}
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}"/>
{%- endif %}
{%- endblock %}
{%- block extrahead %} {% endblock %}
{# Keep modernizr in head - http://modernizr.com/docs/#installing #}
<script src="{{ pathto('_static/js/modernizr.min.js', 1) }}"></script>
</head>
<body class="wy-body-for-nav" role="document">
{% block extrabody %} {% endblock %}
<div class="wy-grid-for-nav">
{# SIDE NAV, TOGGLES ON MOBILE #}
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search">
{% block sidebartitle %}
{% if logo and theme_logo_only %}
<a href="{{ pathto('index') }}">
{% else %}
<a href="{{ pathto('index') }}" class="icon icon-home"> {{ project }}
{% endif %}
{% if logo %}
{# Not strictly valid HTML, but it's the only way to display/scale it properly, without weird scripting or heaps of work #}
<img src="{{ pathto('_static/' + logo, 1) }}" class="logo" />
{% endif %}
</a>
{% include "searchbox.html" %}
{% endblock %}
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
{% block menu %}
{#
The singlehtml builder doesn't handle this toctree call when the
toctree is empty. Skip building this for now.
#}
{% if 'singlehtml' not in builder %}
{% set global_toc = toctree(maxdepth=theme_navigation_depth|int, collapse=theme_collapse_navigation, includehidden=True) %}
{% endif %}
{% if global_toc %}
{{ global_toc }}
{% else %}
<!-- Local TOC -->
<div class="local-toc">{{ toc }}</div>
{% endif %}
{% endblock %}
</div>
{% if theme_display_version %}
{%- set nav_version = version %}
{% if READTHEDOCS and current_version %}
{%- set nav_version = current_version %}
{% endif %}
{% if nav_version %}
<div class="version">
{{ nav_version }}
</div>
{% endif %}
{% endif %}
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
{# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
<nav class="wy-nav-top" role="navigation" aria-label="top navigation">
{% block mobile_nav %}
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="{{ pathto('index') }}">{{ project }}</a>
{% endblock %}
</nav>
{# PAGE CONTENT #}
<div class="wy-nav-content">
<div class="rst-content">
{% include "breadcrumbs.html" %}
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
{% block body %}{% endblock %}
</div>
<div class="articleComments">
{% block comments %}{% endblock %}
</div>
</div>
{% include "footer.html" %}
</div>
</div>
</section>
</div>
{% include "versions.html" %}
{% if not embedded %}
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'{{ url_root }}',
VERSION:'{{ release|e }}',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'{{ '' if no_search_suffix else file_suffix }}',
HAS_SOURCE: {{ has_source|lower }},
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
};
</script>
{%- for scriptfile in script_files %}
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %}
{% endif %}
{# RTD hosts this file, so just load on non RTD builds #}
{% if not READTHEDOCS %}
<script type="text/javascript" src="{{ pathto('_static/js/theme.js', 1) }}"></script>
{% endif %}
{# STICKY NAVIGATION #}
{% if theme_sticky_navigation %}
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.StickyNav.enable();
});
</script>
{% endif %}
{%- block footer %} {% endblock %}
</body>
</html>

209
doc/_themes/pretix_theme/layout_old.html vendored Normal file
View File

@@ -0,0 +1,209 @@
{#
basic/layout.html
~~~~~~~~~~~~~~~~~
Master layout template for Sphinx themes.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
#}
{%- block doctype -%}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
{%- endblock %}
{%- set reldelim1 = reldelim1 is not defined and ' &raquo;' or reldelim1 %}
{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
(sidebars != []) %}
{%- set url_root = pathto('', 1) %}
{# XXX necessary? #}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if not embedded and docstitle %}
{%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
{%- else %}
{%- set titlesuffix = "" %}
{%- endif %}
{%- macro relbar() %}
<div class="related">
<h3>{{ _('Navigation') }}</h3>
<ul>
{%- for rellink in rellinks %}
<li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
<a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
{{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
{%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
{%- endfor %}
{%- block rootrellink %}
<li><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
{%- endblock %}
{%- for parent in parents %}
<li><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
{%- endfor %}
{%- block relbaritems %} {% endblock %}
</ul>
</div>
{%- endmacro %}
{%- macro sidebar() %}
{%- if render_sidebar %}
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
{%- block sidebarlogo %}
{%- if logo %}
<p class="logo"><a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
</a></p>
{%- endif %}
{%- endblock %}
{%- if sidebars != None %}
{#- new style sidebar: explicitly include/exclude templates #}
{%- for sidebartemplate in sidebars %}
{%- include sidebartemplate %}
{%- endfor %}
{%- else %}
{#- old style sidebars: using blocks -- should be deprecated #}
{%- block sidebartoc %}
{%- include "localtoc.html" %}
{%- endblock %}
{%- block sidebarrel %}
{%- include "relations.html" %}
{%- endblock %}
{%- block sidebarsourcelink %}
{%- include "sourcelink.html" %}
{%- endblock %}
{%- if customsidebar %}
{%- include customsidebar %}
{%- endif %}
{%- block sidebarsearch %}
{%- include "searchbox.html" %}
{%- endblock %}
{%- endif %}
</div>
</div>
{%- endif %}
{%- endmacro %}
{%- macro script() %}
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '{{ url_root }}',
VERSION: '{{ release|e }}',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
HAS_SOURCE: {{ has_source|lower }},
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
};
</script>
{%- for scriptfile in script_files %}
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %}
{%- endmacro %}
{%- macro css() %}
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
{%- for cssfile in css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{%- endfor %}
{%- endmacro %}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
{{ metatags }}
{%- block htmltitle %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
{%- endblock %}
{{ css() }}
{%- if not embedded %}
{{ script() }}
{%- if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml"
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{%- endif %}
{%- if favicon %}
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{%- endif %}
{%- if theme_canonical_url %}
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
{%- endif %}
{%- endif %}
{%- block linktags %}
{%- if hasdoc('about') %}
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
{%- endif %}
{%- if hasdoc('genindex') %}
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
{%- endif %}
{%- if hasdoc('search') %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
{%- endif %}
{%- if hasdoc('copyright') %}
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
{%- endif %}
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
{%- if parents %}
<link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
{%- endif %}
{%- if next %}
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
{%- endif %}
{%- if prev %}
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
{%- endif %}
{%- endblock %}
{%- block extrahead %} {% endblock %}
</head>
<body>
{%- block header %}{% endblock %}
{%- block relbar1 %}{{ relbar() }}{% endblock %}
{%- block content %}
{%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
<div class="document">
{%- block document %}
<div class="documentwrapper">
{%- if render_sidebar %}
<div class="bodywrapper">
{%- endif %}
<div class="body">
{% block body %} {% endblock %}
</div>
{%- if render_sidebar %}
</div>
{%- endif %}
</div>
{%- endblock %}
{%- block sidebar2 %}{{ sidebar() }}{% endblock %}
<div class="clearer"></div>
</div>
{%- endblock %}
{%- block relbar2 %}{{ relbar() }}{% endblock %}
{%- block footer %}
<div class="footer">
{%- if show_copyright %}
{%- if hasdoc('copyright') %}
{% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
{%- else %}
{% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
{%- endif %}
{%- endif %}
{%- if last_updated %}
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
{%- endif %}
{%- if show_sphinx %}
{% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
{%- endif %}
</div>
<p>asdf asdf asdf asdf 22</p>
{%- endblock %}
</body>
</html>

50
doc/_themes/pretix_theme/search.html vendored Normal file
View File

@@ -0,0 +1,50 @@
{#
basic/search.html
~~~~~~~~~~~~~~~~~
Template for the search page.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
#}
{%- extends "layout.html" %}
{% set title = _('Search') %}
{% set script_files = script_files + ['_static/searchtools.js'] %}
{% block footer %}
<script type="text/javascript">
jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
</script>
{# this is used when loading the search index using $.ajax fails,
such as on Chrome for documents on localhost #}
<script type="text/javascript" id="searchindexloader"></script>
{{ super() }}
{% endblock %}
{% block body %}
<noscript>
<div id="fallback" class="admonition warning">
<p class="last">
{% trans %}Please activate JavaScript to enable the search
functionality.{% endtrans %}
</p>
</div>
</noscript>
{% if search_performed %}
<h2>{{ _('Search Results') }}</h2>
{% if not search_results %}
<p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
{% endif %}
{% endif %}
<div id="search-results">
{% if search_results %}
<ul>
{% for href, caption, context in search_results %}
<li>
<a href="{{ pathto(item.href) }}">{{ caption }}</a>
<p class="context">{{ context|e }}</p>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,9 @@
{%- if builder != 'singlehtml' %}
<div role="search">
<form id="rtd-search-form" class="wy-form" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" placeholder="{{ _('Search docs') }}" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
{%- endif %}

View File

@@ -0,0 +1,2 @@
.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}
/*# sourceMappingURL=badge_only.css.map */

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,169 @@
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"sphinx-rtd-theme":[function(require,module,exports){
var jQuery = (typeof(window) != 'undefined') ? window.jQuery : require('jquery');
// Sphinx theme nav state
function ThemeNav () {
var nav = {
navBar: null,
win: null,
winScroll: false,
winResize: false,
linkScroll: false,
winPosition: 0,
winHeight: null,
docHeight: null,
isRunning: false
};
nav.enable = function () {
var self = this;
if (!self.isRunning) {
self.isRunning = true;
jQuery(function ($) {
self.init($);
self.reset();
self.win.on('hashchange', self.reset);
// Set scroll monitor
self.win.on('scroll', function () {
if (!self.linkScroll) {
self.winScroll = true;
}
});
setInterval(function () { if (self.winScroll) self.onScroll(); }, 25);
// Set resize monitor
self.win.on('resize', function () {
self.winResize = true;
});
setInterval(function () { if (self.winResize) self.onResize(); }, 25);
self.onResize();
});
};
};
nav.init = function ($) {
var doc = $(document),
self = this;
this.navBar = $('div.wy-side-scroll:first');
this.win = $(window);
// Set up javascript UX bits
$(document)
// Shift nav in mobile when clicking the menu.
.on('click', "[data-toggle='wy-nav-top']", function() {
$("[data-toggle='wy-nav-shift']").toggleClass("shift");
$("[data-toggle='rst-versions']").toggleClass("shift");
})
// Nav menu link click operations
.on('click', ".wy-menu-vertical .current ul li a", function() {
var target = $(this);
// Close menu when you click a link.
$("[data-toggle='wy-nav-shift']").removeClass("shift");
$("[data-toggle='rst-versions']").toggleClass("shift");
// Handle dynamic display of l3 and l4 nav lists
self.toggleCurrent(target);
self.hashChange();
})
.on('click', "[data-toggle='rst-current-version']", function() {
$("[data-toggle='rst-versions']").toggleClass("shift-up");
})
// Make tables responsive
$("table.docutils:not(.field-list)")
.wrap("<div class='wy-table-responsive'></div>");
// Add expand links to all parents of nested ul
$('.wy-menu-vertical ul').not('.simple').siblings('a').each(function () {
var link = $(this);
expand = $('<span class="toctree-expand"></span>');
expand.on('click', function (ev) {
self.toggleCurrent(link);
ev.stopPropagation();
return false;
});
link.prepend(expand);
});
};
nav.reset = function () {
// Get anchor from URL and open up nested nav
var anchor = encodeURI(window.location.hash);
if (anchor) {
try {
var link = $('.wy-menu-vertical')
.find('[href="' + anchor + '"]');
// If we didn't find a link, it may be because we clicked on
// something that is not in the sidebar (eg: when using
// sphinxcontrib.httpdomain it generates headerlinks but those
// aren't picked up and placed in the toctree). So let's find
// the closest header in the document and try with that one.
if (link.length === 0) {
var doc_link = $('.document a[href="' + anchor + '"]');
var closest_section = doc_link.closest('div.section');
// Try again with the closest section entry.
link = $('.wy-menu-vertical')
.find('[href="#' + closest_section.attr("id") + '"]');
}
$('.wy-menu-vertical li.toctree-l1 li.current')
.removeClass('current');
link.closest('li.toctree-l2').addClass('current');
link.closest('li.toctree-l3').addClass('current');
link.closest('li.toctree-l4').addClass('current');
}
catch (err) {
console.log("Error expanding nav for anchor", err);
}
}
};
nav.onScroll = function () {
this.winScroll = false;
var newWinPosition = this.win.scrollTop(),
winBottom = newWinPosition + this.winHeight,
navPosition = this.navBar.scrollTop(),
newNavPosition = navPosition + (newWinPosition - this.winPosition);
if (newWinPosition < 0 || winBottom > this.docHeight) {
return;
}
this.navBar.scrollTop(newNavPosition);
this.winPosition = newWinPosition;
};
nav.onResize = function () {
this.winResize = false;
this.winHeight = this.win.height();
this.docHeight = $(document).height();
};
nav.hashChange = function () {
this.linkScroll = true;
this.win.one('hashchange', function () {
this.linkScroll = false;
});
};
nav.toggleCurrent = function (elem) {
var parent_li = elem.closest('li');
parent_li.siblings('li.current').removeClass('current');
parent_li.siblings().find('li.current').removeClass('current');
parent_li.find('> ul li.current').removeClass('current');
parent_li.toggleClass('current');
}
return nav;
};
module.exports.ThemeNav = ThemeNav();
if (typeof(window) != 'undefined') {
window.SphinxRtdTheme = { StickyNav: module.exports.ThemeNav };
}
},{"jquery":"jquery"}]},{},["sphinx-rtd-theme"]);

14
doc/_themes/pretix_theme/theme.conf vendored Normal file
View File

@@ -0,0 +1,14 @@
[theme]
inherit = basic
stylesheet = css/pretix.css
[options]
typekit_id = hiw1hhg
analytics_id =
sticky_navigation = False
logo_only =
collapse_navigation = False
display_version = True
navigation_depth = 4
prev_next_buttons_location = bottom
canonical_url =

37
doc/_themes/pretix_theme/versions.html vendored Normal file
View File

@@ -0,0 +1,37 @@
{% if READTHEDOCS %}
{# Add rst-badge after rst-versions for small badge style. #}
<div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
<span class="rst-current-version" data-toggle="rst-current-version">
<span class="fa fa-book"> Read the Docs</span>
v: {{ current_version }}
<span class="fa fa-caret-down"></span>
</span>
<div class="rst-other-versions">
<dl>
<dt>{{ _('Versions') }}</dt>
{% for slug, url in versions %}
<dd><a href="{{ url }}">{{ slug }}</a></dd>
{% endfor %}
</dl>
<dl>
<dt>{{ _('Downloads') }}</dt>
{% for type, url in downloads %}
<dd><a href="{{ url }}">{{ type }}</a></dd>
{% endfor %}
</dl>
<dl>
<dt>{{ _('On Read the Docs') }}</dt>
<dd>
<a href="//{{ PRODUCTION_DOMAIN }}/projects/{{ slug }}/?fromdocs={{ slug }}">{{ _('Project Home') }}</a>
</dd>
<dd>
<a href="//{{ PRODUCTION_DOMAIN }}/builds/{{ slug }}/?fromdocs={{ slug }}">{{ _('Builds') }}</a>
</dd>
</dl>
<hr/>
{% trans %}Free document hosting provided by <a href="http://www.readthedocs.org">Read the Docs</a>.{% endtrans %}
</div>
</div>
{% endif %}

View File

@@ -10,9 +10,10 @@ at the following locations. It will try to read the file from the specified path
the following order. The file that is found *last* will override the settings from
the files found before.
1. ``/etc/pretix/pretix.cfg``
2. ``~/.pretix.cfg``
3. ``pretix.cfg`` in the current working directory
1. ``PREFIX_CONFIG_FILE`` environment variable
2. ``/etc/pretix/pretix.cfg``
3. ``~/.pretix.cfg``
4. ``pretix.cfg`` in the current working directory
The file is expected to be in the INI format as specified in the `Python documentation`_.

View File

@@ -1,10 +1,10 @@
.. _`admindocs`:
Administrator documentation
===========================
This documentation is for everyone who wants to install pretix on a server.
Contents:
.. toctree::
:maxdepth: 2

View File

@@ -51,7 +51,7 @@ If there is a problem, a status code in the ``5xx`` range will be returned.
Performance monitoring
----------------------
If you to generate detailled performance statistics of your pretix installation, there is an
If you want to generate detailed performance statistics of your pretix installation, there is an
endpoint at ``https://pretix.mydomain.com/metrics`` (no slash at the end) which returns a
number of values in the text format understood by monitoring tools like Prometheus_. This data
is only collected and exposed if you enable it in the :ref:`metrics-settings` section of your

145
doc/api/fundamentals.rst Normal file
View File

@@ -0,0 +1,145 @@
Basic concepts
==============
This page describes basic concepts and definition that you need to know to interact
with pretix' REST API, such as authentication, pagination and similar definitions.
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
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
Authentication
--------------
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`_.
Compatibility
-------------
We currently see pretix' API as a beta-stage feature. We therefore do not give any guarantees
for compatibility between feature releases of pretix (such as 1.5 and 1.6). However, as always,
we try not to break things when we don't need to. Any backwards-incompatible changes will be
prominently noted in the release notes.
We treat the following types of changes as *backwards-compatible* so we ask you to make sure
that your clients can deal with them properly:
* Support of new API endpoints
* Support of new HTTP methods for a given API endpoint
* Support of new query parameters for a given API endpoint
* New fields contained in API responses
We treat the following types of changes as *backwards-incompatible*:
* Type changes of fields in API responses
* New required input fields for an API endpoint
* New required type for input fields of an API endpoint
* Removal of endpoints, API methods or fields
Pagination
----------
Most lists of objects returned by pretix' API will be paginated. The response will take
the form of:
.. sourcecode:: javascript
{
"count": 117,
"next": "https://pretix.eu/api/v1/organizers/?page=2",
"previous": null,
"results": [],
}
As you can see, the response contains the total number of results in the field ``count``.
The fields ``next`` and ``previous`` contain links to the next and previous page of results,
respectively, or ``null`` if there is no such page. You can use those URLs to retrieve the
respective page.
The field ``results`` contains a list of objects representing the first results. For most
objects, every page contains 50 results.
Errors
------
Error responses (of type 400-499) are returned in one of the following forms, depending on
the type of error. General errors look like:
.. sourcecode:: http
HTTP/1.1 405 Method Not Allowed
Content-Type: application/json
Content-Length: 42
{"detail": "Method 'DELETE' not allowed."}
Field specific input errors include the name of the offending fields as keys in the response:
.. sourcecode:: http
HTTP/1.1 400 Bad Request
Content-Type: application/json
Content-Length: 94
{"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}
Data types
----------
All structured API responses are returned in JSON format using standard JSON data types such
as integers, floating point numbers, strings, lists, objects and booleans. Most fields can
be ``null`` as well.
The following table shows some data types that have no native JSON representation and how
we serialize them to JSON.
===================== ============================ ===================================
Internal pretix type JSON representation Examples
===================== ============================ ===================================
Datetime String in ISO 8601 format ``"2017-12-27T10:00:00Z"``
with timezone (normally UTC) ``"2017-12-27T10:00:00.596934Z"``,
``"2017-12-27T10:00:00+02:00"``
Date String in ISO 8601 format ``2017-12-27``
Multi-lingual string Object of strings ``{"en": "red", "de": "rot", "de_Informal": "rot"}``
Money String with decimal number ``"23.42"``
Currency String with ISO 4217 code ``"EUR"``, ``"USD"``
===================== ============================ ===================================
Query parameters
^^^^^^^^^^^^^^^^
Most list endpoints allow a filtering of the results using query parameters. In this case, booleans should be passed
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

BIN
doc/api/img/token_form.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

17
doc/api/index.rst Normal file
View File

@@ -0,0 +1,17 @@
.. _`rest-api`:
REST API
========
This part of the documentation contains information about the REST-style API
exposed by pretix since version 1.5 that can be used by third-party programs
to interact with pretix and its data structures.
Currently, the API provides mostly read-only capabilities, but it will be extended
in functionality over time.
.. toctree::
:maxdepth: 2
fundamentals
resources/index

View File

@@ -0,0 +1,108 @@
Item categories
===============
Resource description
--------------------
Categories provide grouping for items (better known as products).
The category resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the category
name multi-lingual string The category's visible name
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
on their own but the category provides a source for
defining add-ons for other products.
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/categories/
Returns a list of all categories within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/categories/ 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,
"name": {"en": "Tickets"},
"description": {"en": "Tickets are what you need to get in."},
"position": 1,
"is_addon": false
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query boolean is_addon: If set to ``true`` or ``false``, only categories with this value for the field ``is_addon`` will be
returned.
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
Default: ``position``
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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 this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/categories/(id)/
Returns information on one category, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/categories/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: text/javascript
{
"id": 1,
"name": {"en": "Tickets"},
"description": {"en": "Tickets are what you need to get in."},
"position": 1,
"is_addon": false
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the category 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.

View File

@@ -0,0 +1,122 @@
Events
======
Resource description
--------------------
The event resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
name multi-lingual string The event's full name
slug string A short form of the name, used e.g. in URLs.
live boolean If ``true``, the event ticket shop is publicly
available.
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``)
date_admission datetime The event's admission date (or ``null``)
is_public boolean If ``true``, the event shows up in places like the
organizer's public list of events
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
event
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/
Returns a list of all events within a given organizer the authenticated user/token has access to.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/ 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": [
{
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
"date_admission": null,
"is_public": null,
"presale_start": null,
"presale_end": null,
"location": null,
"has_subevents": false
}
]
}
: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
: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:get:: /api/v1/organizers/(organizer)/events/(event)/
Returns information on one event, identified by its slug.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/ 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
{
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
"date_admission": null,
"is_public": false,
"presale_start": null,
"presale_end": null,
"location": null,
"has_subevents": false
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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.

View File

@@ -0,0 +1,17 @@
Resources and endpoints
=======================
.. toctree::
:maxdepth: 2
organizers
events
subevents
categories
items
questions
quotas
orders
invoices
vouchers
waitinglist

View File

@@ -0,0 +1,194 @@
Invoices
========
Resource description
--------------------
The invoice resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
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
different invoice.
invoice_from string Sender address
invoice_to string Receiver address
date date Invoice date
refers string Invoice number of an invoice this invoice refers to
(for example a cancellation refers to the invoice it
cancels) or ``null``.
locale string Invoice locale
introductory_text string Text to be printed above the product list
additional_text string Text to be printed below the product list
payment_provider_text string Text to be printed below the product list with
payment information
footer_text string Text to be printed in the page footer area
lines list of objects The actual invoice contents
├ description string Text representing the invoice line (e.g. product name)
├ gross_value money (string) Price including VAT
├ tax_value money (string) VAT amount
└ tax_rate decimal (string) Used VAT rate
===================================== ========================== =======================================================
.. versionchanged:: 1.6
The attribute ``invoice_no`` has been dropped in favor of ``number`` which includes the number including the prefix,
since the prefix can now vary. Also, invoices now need to be identified by their ``number`` instead of the raw
number.
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/
Returns a list of all invoices within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/ 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": [
{
"number": "SAMPLECONF-00001",
"order": "ABC12",
"is_cancellation": false,
"invoice_from": "Big Events LLC\nDemo street 12\nDemo town",
"invoice_to": "Sample company\nJohn Doe\nTest street 12\n12345 Testington\nTestikistan\nVAT ID: EU123456789",
"date": "2017-12-01",
"refers": null,
"locale": "en",
"introductory_text": "thank you for your purchase of the following items:",
"additional_text": "We are looking forward to see you on our conference!",
"payment_provider_text": "Please transfer the money to our account ABC…",
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [
{
"description": "Budget Ticket",
"gross_value": "23.00",
"tax_value": "0.00",
"tax_rate": "0.00"
}
]
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query boolean is_cancellation: If set to ``true`` or ``false``, only invoices with this value for the field
``is_cancellation`` will be returned.
:query string order: If set, only invoices belonging to the order with the given order code will be returned.
:query string refers: If set, only invoices refering to the given invoice will be returned.
:query string locale: If set, only invoices with the given locale will be returned.
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``date`` and
``nr`` (equals to ``number``). Default: ``nr``
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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 this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/
Returns information on one invoice, identified by its invoice number.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/SAMPLECONF-00001/ 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
{
"number": "SAMPLECONF-00001",
"order": "ABC12",
"is_cancellation": false,
"invoice_from": "Big Events LLC\nDemo street 12\nDemo town",
"invoice_to": "Sample company\nJohn Doe\nTest street 12\n12345 Testington\nTestikistan\nVAT ID: EU123456789",
"date": "2017-12-01",
"refers": null,
"locale": "en",
"introductory_text": "thank you for your purchase of the following items:",
"additional_text": "We are looking forward to see you on our conference!",
"payment_provider_text": "Please transfer the money to our account ABC…",
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [
{
"description": "Budget Ticket",
"gross_value": "23.00",
"tax_value": "0.00",
"tax_rate": "0.00"
}
]
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice 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:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/download/
Download an invoice in PDF format.
Note that in some cases the PDF file might not yet have been created. In that case, you will receive a status
code :http:statuscode:`409` and you are expected to retry the request after a short period of waiting.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/00001/download/ 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/pdf
...
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param invoice_no: The ``invoice_no`` field of the invoice 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.
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting vor a few
seconds.

228
doc/api/resources/items.rst Normal file
View File

@@ -0,0 +1,228 @@
Items
=====
Resource description
--------------------
Items (better known as products) are the things that can be sold using pretix.
The item resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the item
name multi-lingual string The item's visible name
default_price money (string) The item price that is applied if the price is not
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
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
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.
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
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
voucher that is specifically assigned to this item.
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
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
limitation).
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).
has_variations boolean Shows whether or not this item has variations
(read-only).
variations list of objects A list with one object for each variation of this item.
Can be empty.
├ id integer Internal ID of the variation
├ default_price money (string) The price set directly for this variation or ``null``
├ 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.
├ 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
addons list of objects Definition of add-ons that can be chosen for this item
├ addon_category integer Internal ID of the item category the add-on can be
chosen from.
├ min_count integer The minimal number of add-ons that need to be chosen.
├ max_count integer The maxima number of add-ons that can be chosen.
└ position integer An integer, used for sorting
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/
Returns a list of all items within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/items/ 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,
"name": {"en": "Standard ticket"},
"default_price": "23.00",
"category": null,
"active": true,
"description": null,
"free_price": false,
"tax_rate": "0.00",
"admission": false,
"position": 0,
"picture": null,
"available_from": null,
"available_until": null,
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
"min_per_order": null,
"max_per_order": null,
"has_variations": false,
"variations": [
{
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"active": true,
"description": null,
"position": 0
},
{
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"active": true,
"description": null,
"position": 1
}
],
"addons": []
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query boolean active: If set to ``true`` or ``false``, only items with this value for the field ``active`` will be
returned.
:query integer category: If set to the ID of a category, only items within that category will be returned.
:query boolean admission: If set to ``true`` or ``false``, only items with this value for the field ``admission``
will be returned.
:query string tax_rate: If set to a decimal value, only items with this tax rate will be returned.
:query boolean free_price: If set to ``true`` or ``false``, only items with this value for the field ``free_price``
will be returned.
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
Default: ``position``
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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 this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(id)/
Returns information on one item, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/items/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: text/javascript
{
"id": 1,
"name": {"en": "Standard ticket"},
"default_price": "23.00",
"category": null,
"active": true,
"description": null,
"free_price": false,
"tax_rate": "0.00",
"admission": false,
"position": 0,
"picture": null,
"available_from": null,
"available_until": null,
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
"min_per_order": null,
"max_per_order": null,
"has_variations": false,
"variations": [
{
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"active": true,
"description": null,
"position": 0
},
{
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"active": true,
"description": null,
"position": 1
}
],
"addons": []
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the item 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.

View File

@@ -0,0 +1,530 @@
Orders
======
Order resource
--------------
The order resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
code string Order code
status string Order status, one of:
* ``n`` pending
* ``p`` paid
* ``e`` expired
* ``c`` canceled
* ``r`` refunded
secret string The secret contained in the link sent to the customer
email string The customer email address
locale string The locale used for communication with this customer
datetime datetime Time of order creation
expires datetime The order will expire, if it is still pending by this time
payment_date date Date of payment receival
payment_provider string Payment provider used for this order
payment_fee money (string) Payment fee included in this order's total
payment_fee_tax_rate decimal (string) VAT rate applied to the payment fee
payment_fee_tax_value money (string) VAT value included in the payment fee
total money (string) Total value of this order
comment string Internal comment on this order
invoice_address object Invoice address information (can be ``null``)
├ last_modified datetime Last modification date of the address
├ company string Customer company name
├ name string Customer name
├ street string Customer street
├ zipcode string Customer ZIP code
├ city string Customer city
├ country string Customer country
└ vat_id string Customer VAT ID
position list of objects List of order positions (see below)
downloads list of objects List of ticket download options for order-wise ticket
downloading. This might be a multi-page PDF or a ZIP
file of tickets for outputs that do not support
multiple tickets natively. See also order position
download options.
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
└ url string Download URL
===================================== ========================== =======================================================
.. versionchanged:: 1.6
The ``invoice_address.country`` attribute contains a two-letter country code for all new orders. For old orders,
a custom text might still be returned.
Order position resource
-----------------------
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the order positon
code string Order code of the order the position belongs to
positionid integer Number of the position within the order
item integer ID of the purchased item
variation integer ID of the purchased variation (or ``null``)
price money (string) Price of this position
attendee_name string Specified attendee name for this position (or ``null``)
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``)
tax_rate decimal (string) VAT rate applied for this position
tax_value money (string) VAT included in this position
secret string Secret code printed on the tickets for validation
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
subevent integer ID of the date inside an event series this position belongs to (or ``null``).
checkins list of objects List of check-ins with this ticket
└ datetime datetime Time of check-in
downloads list of objects List of ticket download options
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
└ url string Download URL
answers list of objects Answers to user-defined questions
├ question integer Internal ID of the answered question
├ answer string Text representation of the answer
└ options list of integers Internal IDs of selected option(s)s (only for choice types)
===================================== ========================== =======================================================
Order endpoints
---------------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/
Returns a list of all orders within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/orders/ 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": [
{
"code": "ABC12",
"status": "p",
"secret": "k24fiuwvu8kxz3y1",
"email": "tester@example.org",
"locale": "en",
"datetime": "2017-12-01T10:00:00Z",
"expires": "2017-12-10T10:00:00Z",
"payment_date": "2017-12-05",
"payment_provider": "banktransfer",
"payment_fee": "0.00",
"payment_fee_tax_rate": "0.00",
"payment_fee_tax_value": "0.00",
"total": "23.00",
"comment": "",
"invoice_address": {
"last_modified": "2017-12-01T10:00:00Z",
"company": "Sample company",
"name": "John Doe",
"street": "Test street 12",
"zipcode": "12345",
"city": "Testington",
"country": "Testikistan",
"vat_id": "EU123456789"
},
"positions": [
{
"id": 23442,
"order": "ABC12",
"positionid": 1,
"item": 1345,
"variation": null,
"price": "23.00",
"attendee_name": "Peter",
"attendee_email": null,
"voucher": null,
"tax_rate": "0.00",
"tax_value": "0.00",
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"checkins": [
{
"datetime": "2017-12-25T12:45:23Z"
}
],
"answers": [
{
"question": 12,
"answer": "Foo",
"options": []
}
],
"downloads": [
{
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
}
]
}
],
"downloads": [
{
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/download/pdf/"
}
]
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``code`` and
``status``. Default: ``datetime``
:query string code: Only return orders that match the given order code
:query string status: Only return orders in the given order status (see above)
:query string email: Only return orders created with the given email address
:query string locale: Only return orders with the given customer locale
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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 this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/
Returns information on one order, identified by its order code.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/ 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
{
"code": "ABC12",
"status": "p",
"secret": "k24fiuwvu8kxz3y1",
"email": "tester@example.org",
"locale": "en",
"datetime": "2017-12-01T10:00:00Z",
"expires": "2017-12-10T10:00:00Z",
"payment_date": "2017-12-05",
"payment_provider": "banktransfer",
"payment_fee": "0.00",
"payment_fee_tax_rate": "0.00",
"payment_fee_tax_value": "0.00",
"total": "23.00",
"comment": "",
"invoice_address": {
"last_modified": "2017-12-01T10:00:00Z",
"company": "Sample company",
"name": "John Doe",
"street": "Test street 12",
"zipcode": "12345",
"city": "Testington",
"country": "Testikistan",
"vat_id": "EU123456789"
},
"positions": [
{
"id": 23442,
"order": "ABC12",
"positionid": 1,
"item": 1345,
"variation": null,
"price": "23.00",
"attendee_name": "Peter",
"attendee_email": null,
"voucher": null,
"tax_rate": "0.00",
"tax_value": "0.00",
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"checkins": [
{
"datetime": "2017-12-25T12:45:23Z"
}
],
"answers": [
{
"question": 12,
"answer": "Foo",
"options": []
}
],
"downloads": [
{
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
}
]
}
],
"downloads": [
{
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/download/pdf/"
}
]
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param code: The ``code`` field of the order to fetch
: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:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/download/(output)/
Download tickets for an order, identified by its order code. Depending on the chosen output, the response might
be a ZIP file, PDF file or something else. The order details response contains a list of output options for this
partictular order.
Tickets can be only downloaded if the order is paid and if ticket downloads are active. Note that in some cases the
ticket file might not yet have been created. In that case, you will receive a status code :http:statuscode:`409` and
you are expected to retry the request after a short period of waiting.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/download/pdf/ 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/pdf
...
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param code: The ``code`` field of the order to fetch
:param output: The internal name of the output provider to use
: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
**or** downlodas are not available for this order at this time. The response content will
contain more details.
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting vor a few
seconds.
Order position endpoints
------------------------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/
Returns a list of all order positions within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/orderpositions/ 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": 23442,
"order": "ABC12",
"positionid": 1,
"item": 1345,
"variation": null,
"price": "23.00",
"attendee_name": "Peter",
"attendee_email": null,
"voucher": null,
"tax_rate": "0.00",
"tax_value": "0.00",
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"checkins": [
{
"datetime": "2017-12-25T12:45:23Z"
}
],
"answers": [
{
"question": 12,
"answer": "Foo",
"options": []
}
],
"downloads": [
{
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
}
]
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``order__code``,
``order__datetime``, ``positionid``, ``attendee_name``, and ``order__status``. Default:
``order__datetime,positionid``
:query string order: Only return positions of the order with the given order code
:query integer item: Only return positions with the purchased item matching the given ID.
:query integer variation: Only return positions with the purchased item variation matching the given ID.
:query string attendee_name: Only return positions with the given value in the attendee_name field. Also, add-on
products positions are shown if they refer to an attendee with the given name.
:query string secret: Only return positions with the given ticket secret.
:query string order__status: Only return positions with the given order status.
:query bollean has_checkin: If set to ``true`` or ``false``, only return positions that have or have not been
checked in already.
:query integer subevent: Only return positions of the sub-event with the given ID
:query integer addon_to: Only return positions that are add-ons to the position with the given ID.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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 this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/
Returns information on one order position, identified by its internal ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/ 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
{
"id": 23442,
"order": "ABC12",
"positionid": 1,
"item": 1345,
"variation": null,
"price": "23.00",
"attendee_name": "Peter",
"attendee_email": null,
"voucher": null,
"tax_rate": "0.00",
"tax_value": "0.00",
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"checkins": [
{
"datetime": "2017-12-25T12:45:23Z"
}
],
"answers": [
{
"question": 12,
"answer": "Foo",
"options": []
}
],
"downloads": [
{
"output": "pdf",
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
}
]
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the order position to fetch
: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:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/download/(output)/
Download tickets for one order position, identified by its internal ID.
Depending on the chosen output, the response might be a ZIP file, PDF file or something else. The order details
response contains a list of output options for this partictular order position.
Tickets can be only downloaded if the order is paid and if ticket downloads are active. Also, depending on event
configuration downloads might be only unavailable for add-on products or non-admission products.
Note that in some cases the ticket file might not yet have been created. In that case, you will receive a status
code :http:statuscode:`409` and you are expected to retry the request after a short period of waiting.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/ 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/pdf
...
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the order position to fetch
:param output: The internal name of the output provider to use
: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
**or** downlodas are not available for this order position at this time. The response content will
contain more details.
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting vor a few
seconds.

View File

@@ -0,0 +1,90 @@
Organizers
==========
Resource description
--------------------
An organizers is an entity running any number of events. In pretix, every event belongs to one
organizer and various settings, such as teams and permissions, are managed on organizer level.
The organizer resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
name string The organizer's full name, i.e. the name of an
organization or company.
slug string A short form of the name, used e.g. in URLs.
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/
Returns a list of all organizers the authenticated user/token has access to.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/ 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": [
{
"name": "Big Events LLC",
"slug": "Big Events",
}
]
}
:query page: The page number in case of a multi-page result set, default is 1
:statuscode 200: no error
:statuscode 401: Authentication failure
.. http:get:: /api/v1/organizers/(organizer)/
Returns information on one organizer account, identified by its slug.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/ 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
{
"name": "Big Events LLC",
"slug": "Big Events",
}
: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 it.

View File

@@ -0,0 +1,146 @@
Questions
=========
Resource description
--------------------
Questions define additional fields that need to be filled out by customers during checkout.
The question resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the question
question multi-lingual string The field label shown to the customer
type string The expected type of answer. Valid options:
* ``N`` number
* ``S`` one-line string
* ``T`` multi-line string
* ``B`` boolean
* ``C`` choice from a list
* ``M`` multiple choice from a list
* ``F`` file upload
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.
options list of objects In case of question type ``C`` or ``M``, this lists the
available objects.
├ id integer Internal ID of the option
└ answer multi-lingual string The displayed value of this option
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/questions/
Returns a list of all questions within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/questions/ 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,
"question": {"en": "T-Shirt size"},
"type": "C",
"required": false,
"items": [1, 2],
"position": 1,
"options": [
{
"id": 1,
"answer": {"en": "S"}
},
{
"id": 2,
"answer": {"en": "M"}
},
{
"id": 3,
"answer": {"en": "L"}
}
]
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
Default: ``position``
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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 this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/questions/(id)/
Returns information on one question, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/questions/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: text/javascript
{
"id": 1,
"question": {"en": "T-Shirt size"},
"type": "C",
"required": false,
"items": [1, 2],
"position": 1,
"options": [
{
"id": 1,
"answer": {"en": "S"}
},
{
"id": 2,
"answer": {"en": "M"}
},
{
"id": 3,
"answer": {"en": "L"}
}
]
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the question 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.

View File

@@ -0,0 +1,147 @@
Quotas
======
Resource description
--------------------
Questions define how many times an item can be sold.
The quota resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the quota
name string The internal name of the quota
size integer The size of the quota or ``null`` for unlimited
items list of integers List of item IDs this quota acts on.
variations list of integers List of item variation IDs this quota acts on.
subevent integer ID of the date inside an event series this quota belongs to (or ``null``).
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/quotas/
Returns a list of all quotas within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/quotas/ 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,
"name": "Ticket Quota",
"size": 200,
"items": [1, 2],
"variations": [1, 4, 5, 7],
"subevent": null
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
Default: ``position``
:query integer subevent: Only return quotas of the sub-event with the given ID
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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 this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/quotas/(id)/
Returns information on one quota, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/quotas/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: text/javascript
{
"id": 1,
"name": "Ticket Quota",
"size": 200,
"items": [1, 2],
"variations": [1, 4, 5, 7],
"subevent": null
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the quota to fetch
: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:get:: /api/v1/organizers/(organizer)/events/(event)/quotas/(id)/availability/
Returns availability information on one quota, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/quotas/1/availability/ 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
{
"available": true,
"available_number": 419,
"total_size": 1000,
"pending_orders": 25,
"paid_orders": 423,
"cart_positions": 7,
"blocking_vouchers": 126,
"waiting_list": 0
}
Note that ``total_size`` and ``available_number`` are ``null`` in case of unlimited quotas.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the quota to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.

View File

@@ -0,0 +1,137 @@
Event series dates / Sub-events
===============================
Resource description
--------------------
Events can represent whole event series if the ``has_subevents`` property of the event is active.
In this case, many other resources are additionally connected to an event date (also called sub-event).
The sub-event resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the sub-event
name multi-lingual string The sub-event's full name
active boolean If ``true``, the sub-event ticket shop is publicly
available.
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``)
presale_start datetime The sub-date at which the ticket shop opens (or ``null``)
presale_end datetime The sub-date at which the ticket shop closes (or ``null``)
location multi-lingual string The sub-event location (or ``null``)
item_price_overrides list of objects List of items for which this sub-event overrides the
default price
├ item integer The internal item ID
└ price money (string) The price or ``null`` for the default price
variation_price_overrides list of objects List of variations for which this sub-event overrides
the default price
├ variation integer The internal variation ID
└ price money (string) The price or ``null`` for the default price
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/subevents/
Returns a list of all sub-events of an event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/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: text/javascript
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": {"en": "First Sample Conference"},
"active": false,
"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": []
}
]
}
:query page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of the event to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/
Returns information on one sub-event, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /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 200 OK
Vary: Accept
Content-Type: text/javascript
{
"id": 1,
"name": {"en": "First Sample Conference"},
"active": false,
"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": []
}
: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
: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.

View File

@@ -0,0 +1,164 @@
Vouchers
========
Resource description
--------------------
The voucher resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the voucher
code string The voucher code that is required to redeem the voucher
max_usages integer The maximum number of times this voucher can be
redeemed (default: 1).
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
product is sold out and even if quota is not blocked
for this voucher.
price_mode string Determines how this voucher affects product prices.
Possible values:
* ``none`` No effect on price
* ``set`` The product price is set to the given ``value``
* ``subtract`` The product price is determined by the original price *minus* the given ``value``
* ``percent`` The product price is determined by the original price reduced by the percentage given in ``value``
value decimal (string) The value (see ``price_mode``)
item integer An ID of an item this voucher is restricted to (or ``null``)
variation integer An ID of a variation this voucher is restricted to (or ``null``)
quota integer An ID of a quota this voucher is restricted to (or
``null``). This is an exclusive alternative to
``item`` and ``variation``: A voucher can be
attached either to a specific product or to all
products within one quota or it can be available
for all items without restriction.
tag string A string that is used for grouping vouchers
comment string An internal comment on the voucher
subevent integer ID of the date inside an event series this voucher belongs to (or ``null``).
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/vouchers/
Returns a list of all vouchers within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/vouchers/ 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,
"code": "43K6LKM37FBVR2YG",
"max_usages": 1,
"redeemed": 0,
"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
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string code: Only show the voucher with the given voucher code.
:query integer max_usages: Only show vouchers with the given maximal number of usages.
:query integer redeemed: Only show vouchers with the given number of redemptions. Note that this doesn't tell you if
the voucher can still be redeemed, as this also depends on ``max_usages``. See the
``active`` query parameter as well.
:query boolean block_quota: If set to ``true`` or ``false``, only vouchers with this value in the field
``block_quota`` will be shown.
:query boolean allow_ignore_quota: If set to ``true`` or ``false``, only vouchers with this value in the field
``allow_ignore_quota`` will be shown.
:query string price_mode: If set, only vouchers with this value in the field ``price_mode`` will be shown (see
above).
:query string value: If set, only vouchers with this value in the field ``value`` will be shown.
:query integer item: If set, only vouchers attached to the item with the given ID will be shown.
:query integer variation: If set, only vouchers attached to the variation with the given ID will be shown.
:query integer quota: If set, only vouchers attached to the quota with the given ID will be shown.
:query string tag: If set, only vouchers with the given tag will be shown.
:query integer subevent: Only return vouchers of the sub-event with the given ID
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id``, ``code``,
``max_usages``, ``valid_until``, and ``value``. Default: ``id``
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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 this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/vouchers/(id)/
Returns information on one voucher, identified by its internal ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/vouchers/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: text/javascript
{
"id": 1,
"code": "43K6LKM37FBVR2YG",
"max_usages": 1,
"redeemed": 0,
"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
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the voucher 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.

View File

@@ -0,0 +1,123 @@
Waiting list entries
====================
Resource description
--------------------
The waiting list entry resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the waiting list entry
created datetime Creation date of the waiting list entry
email string Email address of the user on the waiting list
voucher integer Internal ID of the voucher sent to this user. If
this field is set, the user has been sent a voucher
and is no longer waiting. If it is ``null``, the
user is still waiting.
item integer An ID of an item the user is waiting to be available
again
variation integer An ID of a variation the user is waiting to be
available again (or ``null``)
locale string Locale of the waiting user
subevent integer ID of the date inside an event series this entry belongs to (or ``null``).
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/waitinglistentries/
Returns a list of all waiting list entries within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/waitinglistentries/ 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,
"created": "2017-12-01T10:00:00Z",
"email": "waiting@example.org",
"voucher": null,
"item": 2,
"variation": null,
"locale": "en",
"subevent": null
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string email: Only show waiting list entries created with the given email address.
:query string locale: Only show waiting list entries created with the given locale.
:query boolean has_voucher: If set to ``true`` or ``false``, only waiting list entries are returned that have or
have not been sent a voucher.
:query integer item: If set, only entries of users waiting for the item with the given ID will be shown.
:query integer variation: If set, only entries of users waiting for the variation with the given ID will be shown.
:query integer subevent: Only return entries of the sub-event with the given ID
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id``, ``created``,
``email``, ``item``. Default: ``created``
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the 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 this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/waitinglistentries/(id)/
Returns information on one waiting list entry, identified by its internal ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/waitinglistentries/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: text/javascript
{
"id": 1,
"created": "2017-12-01T10:00:00Z",
"email": "waiting@example.org",
"voucher": null,
"item": 2,
"variation": null,
"locale": "en",
"subevent": null
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the waiting list entry 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.

View File

@@ -19,10 +19,12 @@ import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
from datetime import date
sys.path.insert(0, os.path.abspath('../src'))
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.testutils.settings")
django.setup()
# -- General configuration ------------------------------------------------
@@ -51,11 +53,11 @@ source_suffix = '.rst'
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
master_doc = 'contents'
# General information about the project.
project = 'pretix'
copyright = '2014-2017, Raphael Michel'
copyright = '2014-{}, Raphael Michel'.format(date.today().year)
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -115,7 +117,9 @@ pygments_style = 'sphinx'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
html_theme_options = {
'logo_only': True,
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
@@ -129,7 +133,7 @@ pygments_style = 'sphinx'
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
html_logo = 'images/logo-white.svg'
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
@@ -139,7 +143,11 @@ pygments_style = 'sphinx'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = [
'_static',
os.path.abspath('../src/pretix/static/fonts/'),
os.path.abspath('../src/pretix/static/fontawesome/fonts/'),
]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
@@ -159,7 +167,9 @@ html_static_path = ['_static']
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
html_additional_pages = {
'index': 'index.html'
}
# If false, no module index is generated.
html_domain_indices = False
@@ -171,7 +181,7 @@ html_use_index = False
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
@@ -190,11 +200,8 @@ html_use_index = False
# Output file base name for HTML help builder.
htmlhelp_basename = 'pretixdoc'
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_theme = 'pretix_theme'
html_theme_path = [os.path.abspath('_themes')]
# -- Options for LaTeX output ---------------------------------------------
@@ -214,7 +221,7 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'pretix.tex', 'pretix Documentation',
('contents', 'pretix.tex', 'pretix Documentation',
'Raphael Michel', 'manual'),
]

12
doc/contents.rst Normal file
View File

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

View File

@@ -96,3 +96,52 @@ correctly ensure that:
* The ``request.event`` attribute contains the correct ``Event`` object
* The ``request.organizer`` attribute contains the correct ``Organizer`` object
* The locale is set correctly
REST API viewsets
-----------------
Our REST API is built upon `Django REST Framework`_ (DRF). DRF has two important concepts that are different from
standard Django request handling: There are `ViewSets`_ to group related views in a single class and `Routers`_ to
automatically build URL configurations from them.
To integrate a custom viewset with pretix' REST API, you can just register with one of our routers within the
``urls.py`` module of your plugin::
from pretix.api.urls import event_router, router, orga_router
router.register('global_viewset', MyViewSet)
orga_router.register('orga_level_viewset', MyViewSet)
event_router.register('event_level_viewset', MyViewSet)
Routes registered with ``router`` are inserted into the global API space at ``/api/v1/``. Routes registered with
``orga_router`` will be included at ``/api/v1/organizers/(organizer)/`` and routes registered with ``event_router``
will be included at ``/api/v1/organizers/(organizer)/events/(event)/``.
In case of ``orga_router`` and ``event_router``, permission checking is done for you similarly as with custom views
in the control panel. However, you need to make sure on your own only to return the correct subset of data! ``request
.event`` and ``request.organizer`` are available as usual.
To require a special permission like ``can_view_orders``, you do not need to inherit from a special ViewSet base
class, you can just set the ``permission`` attribute on your viewset::
class MyViewSet(ModelViewSet):
permission = 'can_view_orders'
...
If you want to check the permission only for some methods of your viewset, you have to do it yourself. Note here that
API authentications can be done via user sessions or API tokens and you should therefore check something like the
following::
perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken) else request.user)
if perm_holder.has_event_permission(request.event.organizer, request.event, 'can_view_orders'):
...
.. warning:: It is important that you do this in the ``yourplugin.urls`` module, otherwise pretix will not find your
routes early enough during system startup.
.. _Django REST Framework: http://www.django-rest-framework.org/
.. _ViewSets: http://www.django-rest-framework.org/api-guide/viewsets/
.. _Routers: http://www.django-rest-framework.org/api-guide/routers/

View File

@@ -25,7 +25,7 @@ Frontend
--------
.. automodule:: pretix.presale.signals
:members: html_head, html_footer, footer_links, front_page_top, front_page_bottom, checkout_confirm_messages
:members: html_head, html_footer, footer_links, front_page_top, front_page_bottom, contact_form_fields, question_form_fields, checkout_confirm_messages
.. automodule:: pretix.presale.signals

View File

@@ -1,5 +1,5 @@
Plugin hooks
============
Plugin development
==================
Contents:
@@ -10,5 +10,6 @@ Contents:
exporter
ticketoutput
payment
invoice
customview
general

View File

@@ -0,0 +1,95 @@
.. highlight:: python
:linenothreshold: 5
Writing an invoice renderer plugin
==================================
An invoice renderer controls how invoice files are 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 invoice renderer API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available ticket outputs. Your plugin
should listen for this signal and return the subclass of ``pretix.base.invoice.BaseInvoiceRenderer``
that we'll provide in this plugin::
from django.dispatch import receiver
from pretix.base.signals import register_invoice_renderers
@receiver(register_invoice_renderers, dispatch_uid="output_custom")
def register_infoice_renderers(sender, **kwargs):
from .invoice import MyInvoiceRenderer
return MyInvoiceRenderer
The renderer class
------------------
.. class:: pretix.base.invoice.BaseInvoiceRenderer
The central object of each invoice renderer is the subclass of ``BaseInvoiceRenderer``.
.. py:attribute:: BaseInvoiceRenderer.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!
.. automethod:: generate
Helper class for reportlab-base renderers
-----------------------------------------
All PDF rendering that ships with pretix is based on reportlab. We recommend to read the
`reportlab User Guide`_ to understand all the concepts used here.
If you want to implement a renderer that also uses report lab, this helper class might be
convenient to you:
.. class:: pretix.base.invoice.BaseReportlabInvoiceRenderer
.. py:attribute:: BaseReportlabInvoiceRenderer.pagesize
.. py:attribute:: BaseReportlabInvoiceRenderer.left_margin
.. py:attribute:: BaseReportlabInvoiceRenderer.right_margin
.. py:attribute:: BaseReportlabInvoiceRenderer.top_margin
.. py:attribute:: BaseReportlabInvoiceRenderer.bottom_margin
.. py:attribute:: BaseReportlabInvoiceRenderer.doc_template_class
.. py:attribute:: BaseReportlabInvoiceRenderer.invoice
.. automethod:: _init
.. automethod:: _get_stylesheet
.. automethod:: _register_fonts
.. automethod:: _on_first_page
.. automethod:: _on_other_page
.. automethod:: _get_first_page_frames
.. automethod:: _get_other_page_frames
.. automethod:: _build_doc
.. _reportlab User Guide: https://www.reportlab.com/docs/reportlab-userguide.pdf

View File

@@ -1,8 +1,10 @@
.. highlight:: python
:linenothreshold: 5
Plugin basics
=============
.. _`pluginsetup`:
Creating a plugin
=================
It is possible to extend pretix with custom Python code using the official plugin
API. Every plugin has to be implemented as an independent Django 'app' living
@@ -14,10 +16,15 @@ The communication between pretix and the plugins happens mostly using Django's
``pretix.control`` and ``pretix.presale`` expose a number of signals which are documented
on the next pages.
.. _`pluginsetup`:
To create a new plugin, create a new python package which must be a valid `Django app`_
and must contain plugin metadata, as described below.
There is some boilerplate that you will need for every plugin to get started. To save your
time, we created a `cookiecutter`_ template that you can use like this::
$ pip install cookiecutter
$ cookiecutter https://github.com/pretix/pretix-plugin-cookiecutter
This will ask you some questions and then create a project folder for your plugin.
The following pages go into detail about the several types of plugins currently
supported. While these instructions don't assume that you know a lot about pretix,
@@ -30,35 +37,29 @@ Plugin metadata
The plugin metadata lives inside a ``PretixPluginMeta`` class inside your app's
configuration class. The metadata class must define the following attributes:
``name`` (``str``):
The human-readable name of your plugin
.. rst-class:: rest-resource-table
``author`` (``str``):
Your name
``version`` (``str``):
A human-readable version code of your plugin
``description`` (``str``):
A more verbose description of what your plugin does.
``visible`` (``bool``):
``True`` by default, can hide a plugin so it cannot be normally activated.
``restricted`` (``bool``):
``False`` by default, restricts a plugin such that it can only be enabled for an event
by system administrators / superusers.
================== ==================== ===========================================================
Attribute Type Description
================== ==================== ===========================================================
name string The human-readable name of your plugin
author string Your name
version string A human-readable version code of your plugin
description string A more verbose description of what your plugin does.
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.
================== ==================== ===========================================================
A working example would be::
# file: pretix/plugins/timerestriction/__init__.py
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class PaypalApp(AppConfig):
name = 'pretix.plugins.paypal'
verbose_name = _("Stripe")
name = 'pretix_paypal'
verbose_name = _("PayPal")
class PretixPluginMeta:
name = _("PayPal")
@@ -69,8 +70,7 @@ A working example would be::
description = _("This plugin allows you to receive payments via PayPal")
default_app_config = 'pretix.plugins.paypal.PaypalApp'
default_app_config = 'pretix_paypal.PaypalApp'
The ``AppConfig`` class may implement a property ``compatiblity_errors``, that checks
whether the pretix installation meets all requirements of the plugin. If so,
@@ -87,11 +87,10 @@ make use of the `entry point`_ feature of setuptools. To register a plugin that
in a separate python package, your ``setup.py`` should contain something like this::
setup(
args...,
entry_points="""
[pretix.plugin]
sampleplugin=sampleplugin:PretixPluginMeta
pretix_paypal=pretix_paypal:PretixPluginMeta
"""
)
@@ -109,7 +108,7 @@ pages. We suggest that you put your signal receivers into a ``signals`` submodul
of your plugin. You should extend your ``AppConfig`` (see above) by the following
method to make your receivers available::
class TimeRestrictionApp(AppConfig):
class PaypalApp(AppConfig):
def ready(self):
@@ -131,3 +130,4 @@ your Django app label.
.. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/
.. _namespace packages: http://legacy.python.org/dev/peps/pep-0420/
.. _entry point: https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins
.. _cookiecutter: https://cookiecutter.readthedocs.io/en/latest/

View File

@@ -58,6 +58,8 @@ The output class
.. autoattribute:: is_enabled
.. autoattribute:: multi_download_enabled
.. autoattribute:: settings_form_fields
.. automethod:: settings_content_render

View File

@@ -1,18 +1,16 @@
Implementation concepts
=======================
Basic terminology
-----------------
Concepts and Terminology
========================
The components
^^^^^^^^^^^^^^
The project pretix is split into several components. The main three of them are:
The project pretix is split into several components. The main components are:
**base**
This is the foundation below all other components. It is primarily
responsible for the data structures and database communication. It also hosts
several utilities which are used by multiple other components.
several utilities which are used by multiple other components and important parts of
the business logic.
**control**
This is the web-based backend software which allows organizers to
@@ -20,7 +18,13 @@ The project pretix is split into several components. The main three of them are:
**presale**
This is the ticket-shop itself, containing all of the parts visible to the
end user.
end user. Also called "frontend" in parts of this documentation.
**api**
A RESTful API exposed to integrate with third-party software.
**plugins**
A set of pretix plugins that ship bundled with pretix.
Users and events
^^^^^^^^^^^^^^^^
@@ -61,6 +65,7 @@ limit:
* The number of orders placed for an item that are either already paid or within their granted payment period
* The number of non-expired items currently in the shopping cart of users
* The number of vouchers defined as "quota blocking" (see blow)
* The number of people on the waiting list
The quota system tries very hard to be as friendly as possible to your event attendees while still making sure
your limit is never exceeded. For example, when the payment period of an order expires without the order being

View File

@@ -9,7 +9,8 @@ constructive and friendly feedback on your changes.
First of all, you'll need pretix running locally on your machine. Head over to :ref:`devsetup` to learn how to do this.
If you run into any problems on your way, please do not hesitate to ask us anytime!
Please note that we have a :ref:`coc` in place that applies to all communication around the project.
Please note that we bound ourselves to a :ref:`coc` that applies to all communication around the project. You can be
assured that we will not tolerate any form of harassment.
Sending a patch
---------------

View File

@@ -1,5 +1,5 @@
Contribution guide
==================
Contributing to pretix
======================
.. toctree::
:maxdepth: 2

View File

@@ -1,39 +1,33 @@
Coding style
============
Coding style and quality
========================
Python code
-----------
* Basically, we want all python code to follow the `PEP 8`_ standard. There are a few exceptions where
we see things differently or just aren't that strict. The ``setup.cfg`` file in the project's source
folder contains definitions that allow `flake8`_ to check for violations automatically. See :ref:`checksandtests`
for more information. Use four spaces for indentation.
* Basically: Follow `PEP 8`_.
* We sort our imports by a certain schema, but you don't have to do this by hand. Again, ``setup.cfg`` contains
some definitions that allow the command ``isort -rc <directory>`` to automatically sort the imports in your source
files.
Use `flake8`_ to check for conformance problems. The project includes a setup.cfg file
with a default configuration for flake8 that excludes migrations and other non-relevant
code parts. It also silences a few checks, e.g. ``N802`` (function names should be lowercase)
and increases the maximum line length to more than 79 characters. **However** you should
still name all your functions lowercase [#f1]_ and keep your lines short when possible.
* For templates and models, please take a look at the `Django Coding Style`_. We like Django's `class-based views`_ and
kindly ask you to use them where appropriate.
* Our build server will reject all code violating other flake8 checks than the following:
* Please remember to always mark all strings ever displayed to any user for `translation`_.
* E123: closing bracket does not match indentation of opening brackets line
* F403: ``from module import *`` used; unable to detect undefined names
* F401: module imported but unused
* N802: function names should be lowercase
* We expect all new code to come with proper tests. When writing new tests, please write them using `pytest-style`_
test functions and raw ``assert`` statements. Use `fixtures`_ to prevent repetitive code. Some old parts of pretix'
test suite are in the style of Python's unit test module. If you extend those files, you might continue in this style,
but please use pytest style for any new test files.
So please make sure that you *always* follow all other rules and break these rules *only when
it makes sense*.
* Use ``isort -rc pretix`` in the source directory to order your imports.
* Indent your code with four spaces.
* For templates and models, follow the `Django Coding Style`_.
* Use Django's class-based views
* Always mark all strings ever displayed to any user for translation.
* Please keep the first line of your commit messages short. When referencing an issue, please phrase it like
``Fix #123 -- Problems with order creation`` or ``Refs #123 -- Fix this part of that bug``.
.. _PEP 8: http://legacy.python.org/dev/peps/pep-0008/
.. _flake8: https://pypi.python.org/pypi/flake8
.. _Django Coding Style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
.. [#f1] But Python's very own ``unittest`` module forces us to use ``setUp`` as a method name...
.. _translation: https://docs.djangoproject.com/en/1.11/topics/i18n/translation/
.. _class-based views: https://docs.djangoproject.com/en/1.11/topics/class-based-views/
.. _pytest-style: https://docs.pytest.org/en/latest/assert.html
.. _fixtures: https://docs.pytest.org/en/latest/fixture.html

View File

@@ -2,6 +2,9 @@ Sending Email
=============
pretix allows event organizers to configure how they want to send emails to their users in multiple ways.
Therefore, all emails should be sent through the following function:
Therefore, all emails should be sent through the following function.
If the email you send is related to an order, you should also take a look at the
:py:meth:`~pretix.base.models.Order.send_mail` of the order model.
.. autofunction:: pretix.base.services.mail.mail

View File

@@ -21,7 +21,10 @@ Organizers and events
:members:
.. autoclass:: pretix.base.models.Event
:members:
:members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running, get_cache, lock, get_plugins, get_mail_backend, payment_term_last, get_payment_providers, get_invoice_renderers, active_subevents, 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
.. autoclass:: pretix.base.models.Team
:members:
@@ -42,6 +45,15 @@ Items
.. autoclass:: pretix.base.models.ItemVariation
:members:
.. autoclass:: pretix.base.models.SubEventItem
:members:
.. autoclass:: pretix.base.models.SubEventItemVariation
:members:
.. autoclass:: pretix.base.models.ItemAddOn
:members:
.. autoclass:: pretix.base.models.Question
:members:

View File

@@ -1,17 +1,15 @@
Developer documentation
=======================
Contents:
.. toctree::
:maxdepth: 2
concepts
setup
structure
contribution/index
implementation/index
api/index
structure
.. TODO::
Document settings objects, ItemVariation objects, form fields.
Document settings objects, ItemVariation objects, form fields.

View File

@@ -1,7 +1,11 @@
.. _`devsetup`:
The development setup
=====================
Development setup
=================
This tutorial helps you to get started hacking with pretix on your own computer. You need this to
be able to contribute to pretix, but it might also be helpful if you want to write your own plugins.
If you want to install pretix on a server for actual usage, go to the :ref:`admindocs` instead.
Obtain a copy of the source code
--------------------------------
@@ -12,6 +16,8 @@ You can just clone our git repository::
External Dependencies
---------------------
Your should install the following on your system:
* Python 3.4 or newer
* ``pip`` for Python 3 (Debian package: ``python3-pip``)
* ``pyvenv`` for Python 3 (Debian package: ``python3-venv``)
@@ -40,7 +46,7 @@ automatically). If you are working on Ubuntu or Debian, we strongly recommend up
your pip and setuptools installation inside the virtual environment, otherwise some of
the dependencies might fail::
pip3 install -U pip setuptools==28.6.1
pip3 install -U pip setuptools
Working with the code
---------------------
@@ -49,7 +55,7 @@ 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)::
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
@@ -81,7 +87,7 @@ and head to http://localhost:8000/
As we did not implement an overall front page yet, you need to go directly to
http://localhost:8000/control/ for the admin view or, if you imported the test
data as suggested above, to the event page at http://localhost:8000/bigevents/2017/
data as suggested above, to the event page at http://localhost:8000/bigevents/2018/
.. note:: If you want the development server to listen on a different interface or
port (for example because you develop on `pretixdroid`_), you can check
@@ -91,11 +97,18 @@ data as suggested above, to the event page at http://localhost:8000/bigevents/20
Code checks and unit tests
^^^^^^^^^^^^^^^^^^^^^^^^^^
Before you check in your code into git, always run the static checkers and unit tests::
Before you check in your code into git, always run static checkers and linters. If any of these commands fail,
your pull request will not be merged into pretix. If you have trouble figuring out *why* they fail, create your
pull request nevertheless and ask us for help, we are happy to assist you.
Execute the following commands to check for code style errors::
flake8 .
isort -c -rc .
python manage.py check
Execute the following command to run pretix' test suite (might take a coumple of minutes)::
py.test
.. note:: If you have multiple CPU cores and want to speed up the test suite, you can install the python
@@ -107,9 +120,10 @@ for example::
#!/bin/sh
cd $GIT_DIR/../src
source ../env/bin/activate
flake8 --ignore=E123,E128,F403,F401,N802,W503 .
flake8 . || exit 1
isort -q -rc -c . || exit 1
This keeps you from accidentally creating commits violating the sdtyle guide.
Working with mails
^^^^^^^^^^^^^^^^^^
@@ -151,8 +165,14 @@ To build the documentation, run the following command from the ``doc/`` director
make html
You will now find the generated documentation in the ``doc/_build/html/`` subdirectory.
You will now find the generated documentation in the ``doc/_build/html/`` subdirectory. If you work
with the documentation a lot, you might find it useful to use sphinx-autobuild::
pip3 install sphinx-autobuild
sphinx-autobuild . _build/html -p 8081
Then, go to http://localhost:8081 for a version of the documentation that automatically re-builds
whenever you change a source file.
.. _Django's documentation: https://docs.djangoproject.com/en/1.11/ref/django-admin/#runserver
.. _pretixdroid: https://github.com/pretix/pretixdroid

View File

@@ -1,13 +1,10 @@
Project structure
=================
Python source code
------------------
Directory structure
===================
All the source code lives in ``src/``, which has several subdirectories.
pretix/
This directory contains nearly all source code.
This directory contains nearly all source code that belongs to pretix.
base/
This is the Django app containing all the models and methods which are
@@ -19,48 +16,31 @@ pretix/
presale/
This is the Django app containing the front end for users buying tickets.
api/
This is the Django app containing all views and serializers for pretix'
:ref:`rest-api`.
helpers/
Helpers contain a very few modules providing workarounds for low-level flaws in
Django or installed 3rd-party packages.
locale/
Contains translation file for pretix
multidomain/
Additional code implementing our customized :ref:`URL handling <urlconf>`.
static/
Contains all static files (CSS, JavaScript, images)
Contains all static files (CSS/SASS, JavaScript, images) of pretix' core
We use libsass as a preprocessor for CSS. Our own sass code is built in the same
step as Bootstrap and FontAwesome, so their mixins etc. are fully available.
static/
Contains some pretix plugins that ship with pretix itself
testutils/
Contains helper methods that are useful to write the test suite for pretix or test
suites for pretix plugins.
tests/
This is the root directory for all test codes. It includes subdirectories ``base``,
``control``, ``presale``, ``helpers`` and ``plugins`` to mirror the structure of the
``pretix`` source code as well as ``testdummy``, which is a pretix plugin used during
This is the root directory for all test codes. It includes subdirectories ``api``, ``base``,
``control``, ``presale``, ``helpers`, ``multidomain`` and ``plugins`` to mirror the structure
of the pretix source code as well as ``testdummy``, which is a pretix plugin used during
testing.
Language files
--------------
The language files live in ``pretix/locale/*/LC_MESSAGES/``.
Static files
------------
Sass source code
^^^^^^^^^^^^^^^^
We use libsass as a preprocessor for CSS. Our own sass code is built in the same
step as Bootstrap and FontAwesome, so their mixins etc. are fully available.
pretix.control
pretixcontrol has two main SCSS files, ``pretix/static/pretixcontrol/scss/main.scss`` and
``pretix/static/pretixcontrol/scss/auth.scss``, importing everything else.
pretix.presale
pretixpresale has one main SCSS files, ``pretix/pretixpresale/scss/main.scss``,
importing everything else.
3rd-party assets
^^^^^^^^^^^^^^^^
Most client-side 3rd-party assets are vendored in various subdirectories of ``pretix/static``.

69
doc/images/logo-white.svg Normal file
View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="254.15625"
height="109.59375"
viewBox="0 0 254.15625 109.59375"
version="1.1"
id="svg5"
sodipodi:docname="logo-white.svg"
inkscape:version="0.92.1 r"><metadata
id="metadata9">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1364"
inkscape:window-height="676"
id="namedview7"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="1"
inkscape:cx="56.462442"
inkscape:cy="54.796875"
inkscape:window-x="0"
inkscape:window-y="72"
inkscape:window-maximized="0"
inkscape:current-layer="svg5" />
id=&quot;svg2&quot;
version=&quot;1.1&quot;&gt;
<defs
id="defs4" />
<g
id="layer1"
transform="translate(-277.78125,-568.75)">
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;enable-background:accumulate"
d="m 20,20 v 34.09375 c 11.43679,0 20.71875,9.28196 20.71875,20.71875 C 40.71875,86.24928 31.43679,95.5 20,95.5 v 34.09375 h 146.6875 v -9.5 h 3 v 9.5 H 274.15625 V 95.5 c -0.0105,2e-5 -0.0208,0 -0.0312,0 -11.43678,0 -20.71875,-9.25072 -20.71875,-20.6875 0,-11.43679 9.28197,-20.71875 20.71875,-20.71875 0.0105,0 0.0208,-2e-5 0.0312,0 V 20 H 169.6875 v 9.09375 h -3 V 20 Z m 146.6875,16.09375 h 3 v 14 h -3 z m 41.44141,12.833984 c 2.79067,0 5.02343,1.92774 5.02343,4.3125 0,2.38476 -2.23276,4.363282 -5.02343,4.363282 -2.73994,0 -4.97266,-1.978522 -4.97266,-4.363282 0,-2.38476 2.23272,-4.3125 4.97266,-4.3125 z m -13.22852,4.210938 v 8.017578 h 3.95899 v 6.291016 h -3.95899 v 12.279296 c 0,2.02959 0.71015,2.791016 2.13086,2.791016 0.71035,0 1.06703,-0.10181 1.82813,-0.40625 v 5.935547 c -0.71036,0.40591 -2.38661,0.964844 -4.61915,0.964844 -6.13949,0 -8.98046,-3.753876 -8.98046,-8.472657 V 67.447266 h -2.8418 V 61.15625 h 2.8418 V 55.574219 Z M 166.6875,57.09375 h 3 v 14 h -3 z m -74.568359,3.554688 c 8.473509,0 14.207029,4.515688 14.207029,14.105468 0,8.62573 -5.02336,14.105469 -12.07617,14.105469 -1.72514,0 -3.147072,-0.20329 -3.857422,-0.40625 V 99.414062 H 80.751953 V 62.728516 c 2.58772,-1.21775 6.090268,-2.080081 11.367188,-2.080078 z m 49.863279,0 c 8.57499,0 12.63436,5.935363 12.12696,15.220703 l -15.93165,2.234375 c 0.60888,2.94289 2.18061,4.414062 5.68165,4.414062 3.24732,0 5.78445,-0.711556 7.30664,-1.472656 l 2.13086,5.886719 c -2.38476,1.16701 -5.58034,2.080078 -10.6543,2.080078 -8.93017,0 -13.64844,-6.037993 -13.64844,-14.257813 0,-8.21981 4.41329,-14.105468 12.98828,-14.105468 z m -17.92187,0.0059 c 0.8928,0.01358 1.82795,0.04496 2.80468,0.0957 l -1.67578,6.697266 c -1.77589,-0.86257 -3.50104,-0.913692 -4.76953,-0.457032 v 21.513672 h -9.64062 v -25.77539 c 2.79702,-1.376314 7.03166,-2.16926 13.28125,-2.074219 z m 79.24804,0.501953 h 9.64063 v 27.347656 h -9.64063 z m 13.23438,0 h 10.04687 l 3.29883,6.849609 h 0.10156 l 3.60157,-6.849609 h 8.98047 l -7.96485,12.632812 8.72656,14.714844 H 232.67969 L 229.17773,80.9434 h -0.10156 l -3.65234,7.560547 h -9.74219 l 8.57422,-14.105468 z m -74.9668,5.023438 c -2.84142,0 -4.41381,2.585948 -4.10937,7.355468 l 7.76367,-1.166015 c 0,-4.16064 -1.2188,-6.189454 -3.6543,-6.189453 z m -49.507811,0.09961 c -0.71035,0 -1.219131,0.101686 -1.675781,0.253906 v 16.439453 c 0.35517,0.15221 0.863828,0.253906 1.523438,0.253906 3.4503,0 4.871093,-2.840514 4.871093,-8.421874 0,-5.733571 -1.21772,-8.525391 -4.71875,-8.525391 z M 166.6875,78.09375 h 3 v 14 h -3 z m 0,21 h 3 v 14 h -3 z"
transform="translate(257.78125,548.75)"
id="rect3888"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -1,13 +0,0 @@
Welcome to pretix's documentation!
==================================
Contents:
.. toctree::
:maxdepth: 2
user/index
admin/index
development/index
plugins/index

View File

@@ -0,0 +1,201 @@
Bank transfer HTTP API
======================
The banktransfer plugin provides a HTTP API that `pretix-banktool`_ uses to send bank
transactions to the pretix server. This API is integrated with the regular :ref:`rest-api`
and therefore follows the conventions listed there.
Bank import job resource
^^^^^^^^^^^^^^^^^^^^^^^^
Resource description
--------------------
The bank import job resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal job ID
event string Slug of the event this job was uploaded for or ``null``
created datetime Job creation time
state string Job state, one of ``pending``, ``running``,
``error`` or ``completed``
transactions list of objects Transactions included in this job (will only appear
after the job has started processing).
├ state string Transaction state, one of ``imported``, ``nomatch``,
``invalid``, ``error``, ``valid``, ``discarded``,
``already`` (already paid)
├ message string Error message (if any)
├ checksum string Checksum computed from payer, reference, amount and
date
├ payer string Payment source
├ reference string Payment reference
├ amount string Payment amount
├ date string Payment date (in **user-inputted** format)
├ order string Associated order code (or ``null``)
└ comment string Internal comment
===================================== ========================== =======================================================
Note that the ``payer`` and ``reference`` fields are set to empty as soon as the payment is matched to an order or
discarded to avoid storing sensitive data when not necessary. The ``checksum`` persists to implement deduplication.
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/bankimportjobs/
Returns a list of all bank import jobs within a given organizer the authenticated user/token has access to.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/bankimportjobs/ 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,
"state": "completed",
"created": "2017-06-27T08:00:29Z",
"event": "sampleconf",
"transactions": [
{
"amount": "57.00",
"comment": "",
"date": "26.06.2017",
"payer": "John Doe",
"order": null,
"checksum": "5de03a601644dfa63420dacfd285565f8375a8f2",
"reference": "GUTSCHRIFT\r\nSAMPLECONF-NAB12 EREF: SAMPLECONF-NAB12\r\nIBAN: DE1234556…",
"state": "nomatch",
"message": ""
}
]
}
]
}
:query page: The page number in case of a multi-page result set, default is 1
:query event: Return only jobs for the event with the given slug
:query state: Return only jobs with the given state
:param organizer: The ``slug`` field of a valid organizer
: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:get:: /api/v1/organizers/(organizer)/bankimportjobs/(id)/
Returns information on one job, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/bankimportjobs/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: text/javascript
{
"id": 1,
"state": "completed",
"created": "2017-06-27T08:00:29Z",
"event": "sampleconf",
"transactions": [
{
"amount": "57.00",
"comment": "",
"date": "26.06.2017",
"payer": "John Doe",
"order": null,
"checksum": "5de03a601644dfa63420dacfd285565f8375a8f2",
"reference": "GUTSCHRIFT\r\nSAMPLECONF-NAB12 EREF: SAMPLECONF-NAB12\r\nIBAN: DE1234556…",
"state": "nomatch",
"message": ""
}
]
}
:param organizer: The ``slug`` field of the organizer 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:post:: /api/v1/organizers/(organizer)/bankimportjobs/
Upload a new job and execute it.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/bankimportjobs/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"event": "sampleconf",
"transactions": [
{
"payer": "Foo",
"reference": "SAMPLECONF-173AS",
"amount": "23.00",
"date": "2017-06-26"
}
]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: text/javascript
{
"id": 1,
"state": "pending",
"created": "2017-06-27T08:00:29Z",
"event": "sampleconf",
"transactions": []
}
.. note:: Depending on the server configuration, the job might be executed immediately, leading to a longer API
response time but a response with state ``completed`` or ``error``, or the job might be put into a
background queue, leading to an immediate response of state ``pending`` with an empty list of
transactions.
:param organizer: The ``slug`` field of a valid organizer
:statuscode 201: no error
:statuscode 400: Invalid input
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to perform this action.
.. _pretix-banktool: https://github.com/pretix/pretix-banktool

View File

@@ -11,3 +11,4 @@ If you want to **create** a plugin, please go to the
list
pretixdroid
banktransfer

View File

@@ -15,19 +15,33 @@ ways that pretix itself is:
* PDF ticket output
The following plugins are not shipped with pretix but are maintained by the
same team:
same team. We update them regularly to make them compatible with the latest
pretix releases:
* `SEPA direct debit`_
* `Wirecard payment`_
* `Pages`_
* `Passbook/Wallet ticket output`_
* `Cartshare`_
* `Fontpack Free fonts`_
* `Mailing list subscription`_
The following closed-source plugins are available to customers of the hosted pretix.eu platform.
Please get in touch with the pretix team if you want to have them for your self-hosted
pretix installation:
* Campaign tracking
* Integration with Google Analytics and Facebook Pixel
* Integration with Slack
* Integration with MailChimp
The following plugins are from independent third-party authors, so we can make
no statements about their stability:
no statements about their functionality, security, stability or compatibility:
* `esPass ticket output`_
* `IcePay integration`_
* `Average price chart`_
* `Pay in cash upon arrival`_
.. _SEPA direct debit: https://github.com/pretix/pretix-sepadebit
.. _Passbook/Wallet ticket output: https://github.com/pretix/pretix-passbook
@@ -36,3 +50,7 @@ no statements about their stability:
.. _esPass ticket output: https://github.com/esPass/pretix-espass
.. _IcePay integration: https://github.com/chotee/pretix-icepay
.. _Fontpack Free fonts: https://github.com/pretix/pretix-fontpack-free
.. _Wirecard payment: https://github.com/pretix/pretix-wirecard
.. _Mailing list subscription: https://github.com/pretix/pretix-newsletter-ml
.. _Average price chart: https://github.com/rixx/pretix-avgchart
.. _Pay in cash upon arrival: https://github.com/pc-coholic/pretix-cashpayment

View File

@@ -5,8 +5,9 @@ The pretixdroid plugin provides a HTTP API that the `pretixdroid Android app`_
uses to communicate with the pretix server.
.. warning:: This API is intended **only** to serve the pretixdroid Android app. There are no backwards compatibility
guarantees on this API. We will not add features that are not required for the Android App. There will be
a proper general-use API for pretix at a later point in time.
guarantees on this API. We will not add features that are not required for the Android App. There is a
general-purpose :ref:`rest-api` that not yet provides all features that this API provides, but will do
so in the future.
.. http:post:: /pretixdroid/api/(organizer)/(event)/redeem/

View File

@@ -1,8 +1,6 @@
User Guide
==========
Contents:
.. toctree::
:maxdepth: 2

View File

@@ -12,6 +12,12 @@ The choice totally depends on you and what your customers expect from you. Optio
offer different payment methods and want to encourage your customers to use the ones that come you cheaper, but you
might also decide to go for option one to make it easier for customers who don't have the option.
.. warning:: Please note that EU Directive 2015/2366 bans surcharging payment fees for most common payment
methods within the European Union. Depending on the payment method, this might affect
selling to consumers only or to business customers as well. Depending on your country, this
legislation might already be in place or become relevant from January 2018 the latest. This is not
legal advice. If in doubt, consult a lawyer or refrain from charging payment fees.
If you go for the second option, you can configure pretix to charge the payment method fees to your user. You can
define both an absolute fee as well as a percental fee based on the order total. If you do so, there are two
different ways in which pretix can calculate the fee. Normally, it is fine to just go with the default setting, but

View File

@@ -1,8 +1,6 @@
Accepting payments
==================
Contents:
.. toctree::
:maxdepth: 2

View File

@@ -36,7 +36,7 @@ t = Team.objects.get_or_create(
can_change_organizer_settings=True, can_change_event_settings=True, can_change_items=True,
can_view_orders=True, can_change_orders=True, can_view_vouchers=True, can_change_vouchers=True
)
t.members.add(user)
t[0].members.add(user)
cat_tickets = ItemCategory.objects.create(
event=event, name='Tickets'
)

View File

@@ -1 +1 @@
__version__ = "1.4.0-dev0"
__version__ = "1.6.0"

View File

View File

View File

@@ -0,0 +1,43 @@
from rest_framework.permissions import SAFE_METHODS, BasePermission
from pretix.base.models import Event
from pretix.base.models.organizer import Organizer, TeamAPIToken
class EventPermission(BasePermission):
model = TeamAPIToken
def has_permission(self, request, view):
if not request.user.is_authenticated and not isinstance(request.auth, TeamAPIToken):
if request.method in SAFE_METHODS and request.path.startswith('/api/v1/docs/'):
return True
return False
perm_holder = (request.auth if isinstance(request.auth, 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):
return False
request.organizer = request.event.organizer
request.eventpermset = perm_holder.get_event_permission_set(request.organizer, request.event)
if hasattr(view, 'permission'):
if view.permission and view.permission not in request.eventpermset:
return False
elif 'organizer' in request.resolver_match.kwargs:
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):
return False
request.orgapermset = perm_holder.get_organizer_permission_set(request.organizer)
if hasattr(view, 'permission'):
if view.permission and view.permission not in request.orgapermset:
return False
return True

View File

@@ -0,0 +1,21 @@
from django.contrib.auth.models import AnonymousUser
from rest_framework import exceptions
from rest_framework.authentication import TokenAuthentication
from pretix.base.models.organizer import TeamAPIToken
class TeamTokenAuthentication(TokenAuthentication):
model = TeamAPIToken
def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.select_related('team', 'team__organizer').get(token=key)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
if not token.active:
raise exceptions.AuthenticationFailed('Token inactive or deleted.')
return AnonymousUser(), token

View File

View File

@@ -0,0 +1,35 @@
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import Event
from pretix.base.models.event import SubEvent
from pretix.base.models.items import SubEventItem, SubEventItemVariation
class EventSerializer(I18nAwareModelSerializer):
class Meta:
model = Event
fields = ('name', 'slug', 'live', 'currency', 'date_from',
'date_to', 'date_admission', 'is_public', 'presale_start',
'presale_end', 'location', 'has_subevents')
class SubEventItemSerializer(I18nAwareModelSerializer):
class Meta:
model = SubEventItem
fields = ('item', 'price')
class SubEventItemVariationSerializer(I18nAwareModelSerializer):
class Meta:
model = SubEventItemVariation
fields = ('variation', 'price')
class SubEventSerializer(I18nAwareModelSerializer):
item_price_overrides = SubEventItemSerializer(source='subeventitem_set', many=True)
variation_price_overrides = SubEventItemVariationSerializer(source='subeventitemvariation_set', many=True)
class Meta:
model = SubEvent
fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission',
'presale_start', 'presale_end', 'location',
'item_price_overrides', 'variation_price_overrides')

View File

@@ -0,0 +1,31 @@
from django.conf import settings
from i18nfield.fields import I18nCharField, I18nTextField
from rest_framework.fields import Field
from rest_framework.serializers import ModelSerializer
class I18nField(Field):
def __init__(self, **kwargs):
self.allow_blank = kwargs.pop('allow_blank', False)
self.trim_whitespace = kwargs.pop('trim_whitespace', True)
self.max_length = kwargs.pop('max_length', None)
self.min_length = kwargs.pop('min_length', None)
super().__init__(**kwargs)
def to_representation(self, value):
if value is None or value.data is None:
return None
if isinstance(value.data, dict):
return value.data
else:
return {
settings.LANGUAGE_CODE: str(value.data)
}
class I18nAwareModelSerializer(ModelSerializer):
pass
I18nAwareModelSerializer.serializer_field_mapping[I18nCharField] = I18nField
I18nAwareModelSerializer.serializer_field_mapping[I18nTextField] = I18nField

View File

@@ -0,0 +1,64 @@
from rest_framework import serializers
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import (
Item, ItemAddOn, ItemCategory, ItemVariation, Question, QuestionOption,
Quota,
)
class InlineItemVariationSerializer(I18nAwareModelSerializer):
class Meta:
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price')
class InlineItemAddOnSerializer(serializers.ModelSerializer):
class Meta:
model = ItemAddOn
fields = ('addon_category', 'min_count', 'max_count',
'position')
class ItemSerializer(I18nAwareModelSerializer):
addons = InlineItemAddOnSerializer(many=True)
variations = InlineItemVariationSerializer(many=True)
class Meta:
model = Item
fields = ('id', 'category', 'name', 'active', 'description',
'default_price', 'free_price', 'tax_rate', 'admission',
'position', 'picture', 'available_from', 'available_until',
'require_voucher', 'hide_without_voucher', 'allow_cancel',
'min_per_order', 'max_per_order', 'has_variations',
'variations', 'addons')
class ItemCategorySerializer(I18nAwareModelSerializer):
class Meta:
model = ItemCategory
fields = ('id', 'name', 'description', 'position', 'is_addon')
class InlineQuestionOptionSerializer(I18nAwareModelSerializer):
class Meta:
model = QuestionOption
fields = ('id', 'answer')
class QuestionSerializer(I18nAwareModelSerializer):
options = InlineQuestionOptionSerializer(many=True)
class Meta:
model = Question
fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position')
class QuotaSerializer(I18nAwareModelSerializer):
class Meta:
model = Quota
fields = ('id', 'name', 'size', 'items', 'variations', 'subevent')

View File

@@ -0,0 +1,129 @@
from rest_framework import serializers
from rest_framework.reverse import reverse
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import (
Checkin, Invoice, InvoiceAddress, InvoiceLine, Order, OrderPosition,
QuestionAnswer,
)
from pretix.base.signals import register_ticket_outputs
class CompatibleCountryField(serializers.Field):
def to_representation(self, instance: InvoiceAddress):
if instance.country:
return str(instance.country)
else:
return instance.country_old
class InvoiceAdddressSerializer(I18nAwareModelSerializer):
country = CompatibleCountryField(source='*')
class Meta:
model = InvoiceAddress
fields = ('last_modified', 'company', 'name', 'street', 'zipcode', 'city', 'country', 'vat_id')
class AnswerSerializer(I18nAwareModelSerializer):
class Meta:
model = QuestionAnswer
fields = ('question', 'answer', 'options')
class CheckinSerializer(I18nAwareModelSerializer):
class Meta:
model = Checkin
fields = ('datetime',)
class OrderDownloadsField(serializers.Field):
def to_representation(self, instance: Order):
if instance.status != Order.STATUS_PAID:
return []
request = self.context['request']
res = []
responses = register_ticket_outputs.send(instance.event)
for receiver, response in responses:
provider = response(instance.event)
if provider.is_enabled:
res.append({
'output': provider.identifier,
'url': reverse('api-v1:order-download', kwargs={
'organizer': instance.event.organizer.slug,
'event': instance.event.slug,
'code': instance.code,
'output': provider.identifier,
}, request=request)
})
return res
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:
return []
request = self.context['request']
res = []
responses = register_ticket_outputs.send(instance.order.event)
for receiver, response in responses:
provider = response(instance.order.event)
if provider.is_enabled:
res.append({
'output': provider.identifier,
'url': reverse('api-v1:orderposition-download', kwargs={
'organizer': instance.order.event.organizer.slug,
'event': instance.order.event.slug,
'pk': instance.pk,
'output': provider.identifier,
}, request=request)
})
return res
class OrderPositionSerializer(I18nAwareModelSerializer):
checkins = CheckinSerializer(many=True)
answers = AnswerSerializer(many=True)
downloads = PositionDownloadsField(source='*')
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
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')
class OrderSerializer(I18nAwareModelSerializer):
invoice_address = InvoiceAdddressSerializer()
positions = OrderPositionSerializer(many=True)
downloads = OrderDownloadsField(source='*')
class Meta:
model = Order
fields = ('code', 'status', 'secret', 'email', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'payment_fee', 'payment_fee_tax_rate', 'payment_fee_tax_value',
'total', 'comment', 'invoice_address', 'positions', 'downloads')
class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
class Meta:
model = InvoiceLine
fields = ('description', 'gross_value', 'tax_value', 'tax_rate')
class InvoiceSerializer(I18nAwareModelSerializer):
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
refers = serializers.SlugRelatedField(slug_field='invoice_no', read_only=True)
lines = InlineInvoiceLineSerializer(many=True)
class Meta:
model = Invoice
fields = ('order', 'number', 'is_cancellation', 'invoice_from', 'invoice_to', 'date', 'refers', 'locale',
'introductory_text', 'additional_text', 'payment_provider_text', 'footer_text', 'lines')

View File

@@ -0,0 +1,8 @@
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import Organizer
class OrganizerSerializer(I18nAwareModelSerializer):
class Meta:
model = Organizer
fields = ('name', 'slug')

View File

@@ -0,0 +1,10 @@
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import Voucher
class VoucherSerializer(I18nAwareModelSerializer):
class Meta:
model = Voucher
fields = ('id', 'code', 'max_usages', 'redeemed', 'valid_until', 'block_quota',
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
'tag', 'comment', 'subevent')

View File

@@ -0,0 +1,9 @@
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import WaitingListEntry
class WaitingListSerializer(I18nAwareModelSerializer):
class Meta:
model = WaitingListEntry
fields = ('id', 'created', 'email', 'voucher', 'item', 'variation', 'locale', 'subevent')

View File

View File

@@ -0,0 +1,19 @@
{% extends "rest_framework/base.html" %}
{% load staticfiles %}
{% load compress %}
{% block bootstrap_theme %}
{% compress css %}
<link rel="stylesheet" type="text/x-scss" href="{% static "rest_framework/scss/main.scss" %}" />
{% endcompress %}
{% endblock %}
{% block branding %}
<a class="navbar-brand" href="/api/v1/">pretix REST API</a>
{% endblock %}
{% block description %}
<div class="alert alert-info alert-docs-link">
<a href="https://docs.pretix.eu/en/latest/api/index.html">
You can find documentation on our REST API on docs.pretix.eu.
</a>
</div>
{% endblock %}

37
src/pretix/api/urls.py Normal file
View File

@@ -0,0 +1,37 @@
import importlib
from django.apps import apps
from django.conf.urls import include, url
from rest_framework import routers
from .views import event, item, order, organizer, voucher, waitinglist
router = routers.DefaultRouter()
router.register(r'organizers', organizer.OrganizerViewSet)
orga_router = routers.DefaultRouter()
orga_router.register(r'events', event.EventViewSet)
event_router = routers.DefaultRouter()
event_router.register(r'subevents', event.SubEventViewSet)
event_router.register(r'items', item.ItemViewSet)
event_router.register(r'categories', item.ItemCategoryViewSet)
event_router.register(r'questions', item.QuestionViewSet)
event_router.register(r'quotas', item.QuotaViewSet)
event_router.register(r'vouchers', voucher.VoucherViewSet)
event_router.register(r'orders', order.OrderViewSet)
event_router.register(r'orderpositions', order.OrderPositionViewSet)
event_router.register(r'invoices', order.InvoiceViewSet)
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
# Force import of all plugins to give them a chance to register URLs with the router
for app in apps.get_app_configs():
if hasattr(app, 'PretixPluginMeta'):
if importlib.util.find_spec(app.name + '.urls'):
importlib.import_module(app.name + '.urls')
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/', include(orga_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/', include(event_router.urls)),
]

View File

View File

@@ -0,0 +1,34 @@
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import filters, viewsets
from pretix.api.serializers.event import EventSerializer, SubEventSerializer
from pretix.base.models import Event, ItemCategory
from pretix.base.models.event import SubEvent
class EventViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = EventSerializer
queryset = Event.objects.none()
lookup_field = 'slug'
lookup_url_kwarg = 'event'
def get_queryset(self):
return self.request.organizer.events.all()
class SubEventFilter(FilterSet):
class Meta:
model = SubEvent
fields = ['active']
class SubEventViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = SubEventSerializer
queryset = ItemCategory.objects.none()
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
filter_class = SubEventFilter
def get_queryset(self):
return self.request.event.subevents.prefetch_related(
'subeventitem_set', 'subeventitemvariation_set'
)

View File

@@ -0,0 +1,94 @@
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import viewsets
from rest_framework.decorators import detail_route
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response
from pretix.api.serializers.item import (
ItemCategorySerializer, ItemSerializer, QuestionSerializer,
QuotaSerializer,
)
from pretix.base.models import Item, ItemCategory, Question, Quota
class ItemFilter(FilterSet):
class Meta:
model = Item
fields = ['active', 'category', 'admission', 'tax_rate', 'free_price']
class ItemViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = ItemSerializer
queryset = Item.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
filter_class = ItemFilter
def get_queryset(self):
return self.request.event.items.prefetch_related('variations', 'addons').all()
class ItemCategoryFilter(FilterSet):
class Meta:
model = ItemCategory
fields = ['is_addon']
class ItemCategoryViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = ItemCategorySerializer
queryset = ItemCategory.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_class = ItemCategoryFilter
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
def get_queryset(self):
return self.request.event.categories.all()
class QuestionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = QuestionSerializer
queryset = Question.objects.none()
filter_backends = (OrderingFilter,)
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
def get_queryset(self):
return self.request.event.questions.prefetch_related('options').all()
class QuotaFilter(FilterSet):
class Meta:
model = Quota
fields = ['subevent']
class QuotaViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = QuotaSerializer
queryset = Quota.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter,)
filter_class = QuotaFilter
ordering_fields = ('id', 'size')
ordering = ('id',)
def get_queryset(self):
return self.request.event.quotas.all()
@detail_route(methods=['get'])
def availability(self, request, *args, **kwargs):
quota = self.get_object()
avail = quota.availability()
data = {
'paid_orders': quota.count_paid_orders(),
'pending_orders': quota.count_pending_orders(),
'blocking_vouchers': quota.count_blocking_vouchers(),
'cart_positions': quota.count_in_cart(),
'waiting_list': quota.count_waiting_list_pending(),
'available_number': avail[1],
'available': avail[0] == Quota.AVAILABILITY_OK,
'total_size': quota.size,
}
return Response(data)

View File

@@ -0,0 +1,193 @@
import django_filters
from django.db.models import Q
from django.db.models.functions import Concat
from django.http import FileResponse
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import viewsets
from rest_framework.decorators import detail_route
from rest_framework.exceptions import APIException, NotFound, PermissionDenied
from rest_framework.filters import OrderingFilter
from pretix.api.serializers.order import (
InvoiceSerializer, OrderPositionSerializer, OrderSerializer,
)
from pretix.base.models import Invoice, Order, OrderPosition
from pretix.base.services.invoices import invoice_pdf
from pretix.base.services.tickets import (
get_cachedticket_for_order, get_cachedticket_for_position,
)
from pretix.base.signals import register_ticket_outputs
class OrderFilter(FilterSet):
class Meta:
model = Order
fields = ['code', 'status', 'email', 'locale']
class OrderViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = OrderSerializer
queryset = Order.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('datetime',)
ordering_fields = ('datetime', 'code', 'status')
filter_class = OrderFilter
lookup_field = 'code'
permission = 'can_view_orders'
def get_queryset(self):
return self.request.event.orders.prefetch_related(
'positions', 'positions__checkins', 'positions__item', 'positions__answers', 'positions__answers__options'
).select_related(
'invoice_address'
)
def _get_output_provider(self, identifier):
responses = register_ticket_outputs.send(self.request.event)
for receiver, response in responses:
prov = response(self.request.event)
if prov.identifier == identifier:
return prov
raise NotFound('Unknown output provider.')
@detail_route(url_name='download', url_path='download/(?P<output>[^/]+)')
def download(self, request, output, **kwargs):
provider = self._get_output_provider(output)
order = self.get_object()
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:
raise RetryException()
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
self.request.event.slug.upper(), order.code,
provider.identifier, ct.extension
)
return resp
class OrderPositionFilter(FilterSet):
order = django_filters.CharFilter(name='order', lookup_expr='code')
has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs')
attendee_name = django_filters.CharFilter(method='attendee_name_qs')
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=value) | Q(addon_to__attendee_name=value))
class Meta:
model = OrderPosition
fields = ['item', 'variation', 'attendee_name', 'secret', 'order', 'order__status', 'has_checkin',
'addon_to', 'subevent']
class OrderPositionViewSet(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
permission = 'can_view_orders'
def get_queryset(self):
return OrderPosition.objects.filter(order__event=self.request.event).prefetch_related(
'checkins', 'answers', 'answers__options'
).select_related(
'item', 'order', 'order__event', 'order__event__organizer'
)
def _get_output_provider(self, identifier):
responses = register_ticket_outputs.send(self.request.event)
for receiver, response in responses:
prov = response(self.request.event)
if prov.identifier == identifier:
return prov
raise NotFound('Unknown output provider.')
@detail_route(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.")
ct = get_cachedticket_for_position(pos, provider.identifier)
if not ct.file:
raise RetryException()
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
self.request.event.slug.upper(), pos.order.code, pos.positionid,
provider.identifier, ct.extension
)
return resp
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')
def refers_qs(self, queryset, name, value):
return queryset.annotate(
refers_nr=Concat('refers__prefix', 'refers__invoice_no')
).filter(refers_nr__iexact=value)
def nr_qs(self, queryset, name, value):
return queryset.filter(nr__iexact=value)
class Meta:
model = Invoice
fields = ['order', 'number', 'is_cancellation', 'refers', 'locale']
class RetryException(APIException):
status_code = 409
default_detail = 'The requested resource is not ready, please retry later.'
default_code = 'retry_later'
class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = InvoiceSerializer
queryset = Invoice.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('nr',)
ordering_fields = ('nr', 'date')
filter_class = InvoiceFilter
permission = 'can_view_orders'
lookup_url_kwarg = 'number'
lookup_field = 'nr'
def get_queryset(self):
return self.request.event.invoices.prefetch_related('lines').select_related('order', 'refers').annotate(
nr=Concat('prefix', 'invoice_no')
)
@detail_route()
def download(self, request, **kwargs):
invoice = self.get_object()
if not invoice.file:
invoice_pdf(invoice.pk)
invoice.refresh_from_db()
if not invoice.file:
raise RetryException()
resp = FileResponse(invoice.file.file, content_type='application/pdf')
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
return resp

View File

@@ -0,0 +1,20 @@
from rest_framework import viewsets
from pretix.api.serializers.organizer import OrganizerSerializer
from pretix.base.models import Organizer
class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = OrganizerSerializer
queryset = Organizer.objects.none()
lookup_field = 'slug'
lookup_url_kwarg = 'organizer'
def get_queryset(self):
if self.request.user.is_authenticated():
if self.request.user.is_superuser:
return Organizer.objects.all()
else:
return Organizer.objects.filter(pk__in=self.request.user.teams.values_list('organizer', flat=True))
else:
return Organizer.objects.filter(pk=self.request.auth.team.organizer_id)

View File

@@ -0,0 +1,40 @@
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.filters import OrderingFilter
from pretix.api.serializers.voucher import VoucherSerializer
from pretix.base.models import Voucher
class VoucherFilter(FilterSet):
active = BooleanFilter(method='filter_active')
class Meta:
model = Voucher
fields = ['code', 'max_usages', 'redeemed', 'block_quota', 'allow_ignore_quota',
'price_mode', 'value', 'item', 'variation', 'quota', 'tag', 'subevent']
def filter_active(self, queryset, name, value):
if value:
return queryset.filter(Q(redeemed__lt=F('max_usages')) &
(Q(valid_until__isnull=True) | Q(valid_until__gt=now())))
else:
return queryset.filter(Q(redeemed__gte=F('max_usages')) |
(Q(valid_until__isnull=False) & Q(valid_until__lte=now())))
class VoucherViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = VoucherSerializer
queryset = Voucher.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('id',)
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
filter_class = VoucherFilter
permission = 'can_view_vouchers'
def get_queryset(self):
return self.request.event.vouchers.all()

View File

@@ -0,0 +1,31 @@
import django_filters
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import viewsets
from rest_framework.filters import OrderingFilter
from pretix.api.serializers.waitinglist import WaitingListSerializer
from pretix.base.models import WaitingListEntry
class WaitingListFilter(FilterSet):
has_voucher = django_filters.rest_framework.BooleanFilter(method='has_voucher_qs')
def has_voucher_qs(self, queryset, name, value):
return queryset.filter(voucher__isnull=not value)
class Meta:
model = WaitingListEntry
fields = ['item', 'variation', 'email', 'locale', 'has_voucher', 'subevent']
class WaitingListViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = WaitingListSerializer
queryset = WaitingListEntry.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('created',)
ordering_fields = ('id', 'created', 'email', 'item')
filter_class = WaitingListFilter
permission = 'can_view_orders'
def get_queryset(self):
return self.request.event.waitinglistentries.all()

View File

@@ -1,4 +1,5 @@
from django.apps import AppConfig
from django.conf import settings
class PretixBaseConfig(AppConfig):
@@ -9,6 +10,7 @@ class PretixBaseConfig(AppConfig):
from . import exporter # NOQA
from . import payment # NOQA
from . import exporters # NOQA
from . import invoice # NOQA
from .services import export, mail, tickets, cart, orders, cleanup, update_check # NOQA
try:
@@ -16,6 +18,10 @@ class PretixBaseConfig(AppConfig):
except ImportError:
pass
if hasattr(settings, 'RAVEN_CONFIG'):
from ..sentry import initialize
initialize()
default_app_config = 'pretix.base.PretixBaseConfig'
try:

View File

@@ -1,3 +1,4 @@
from .answers import * # noqa
from .invoices import * # noqa
from .json import * # noqa
from .mail import * # noqa

View File

@@ -0,0 +1,61 @@
import os
import tempfile
from collections import OrderedDict
from zipfile import ZipFile
from django import forms
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import QuestionAnswer
from ..exporter import BaseExporter
from ..signals import register_data_exporters
class AnswerFilesExporter(BaseExporter):
identifier = 'answerfiles'
verbose_name = _('Answers to file upload questions')
@property
def export_form_fields(self):
return OrderedDict(
[
('questions',
forms.ModelMultipleChoiceField(
queryset=self.event.questions.filter(type='F'),
label=_('Questions'),
widget=forms.CheckboxSelectMultiple,
required=False
)),
]
)
def render(self, form_data: dict):
qs = QuestionAnswer.objects.filter(
orderposition__order__event=self.event,
).select_related('orderposition', 'orderposition__order', 'question')
if form_data.get('questions'):
qs = qs.filter(question__in=form_data['questions'])
with tempfile.TemporaryDirectory() as d:
with ZipFile(os.path.join(d, 'tmp.zip'), 'w') as zipf:
for i in qs:
if i.file:
i.file.open('r')
fname = '{}-{}-{}-q{}-{}'.format(
self.event.slug.upper(),
i.orderposition.order.code,
i.orderposition.positionid,
i.question.pk,
os.path.basename(i.file.name).split('.', 1)[1]
)
zipf.writestr(fname, i.file.read())
i.file.close()
with open(os.path.join(d, 'tmp.zip'), 'rb') as zipf:
return 'answers.zip', 'application/zip', zipf.read()
@receiver(register_data_exporters, dispatch_uid="exporter_answers")
def register_anwers_export(sender, **kwargs):
return AnswerFilesExporter

View File

@@ -21,7 +21,7 @@ class InvoiceExporter(BaseExporter):
if not i.file:
invoice_pdf_task.apply(args=(i.pk,))
i.refresh_from_db()
i.file.open('r')
i.file.open('rb')
zipf.writestr('{}.pdf'.format(i.number), i.file.read())
i.file.close()

View File

@@ -13,7 +13,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from pretix.base.models import InvoiceAddress, Order, OrderPosition
from ..exporter import BaseExporter
from ..signals import register_data_exporters, register_payment_providers
from ..signals import register_data_exporters
class OrderListExporter(BaseExporter):
@@ -35,13 +35,13 @@ class OrderListExporter(BaseExporter):
def _get_all_tax_rates(self, qs):
tax_rates = set(
qs.exclude(payment_fee=0).values_list('payment_fee_tax_rate', flat=True)
.distinct().order_by()
qs.exclude(payment_fee=0).values_list('payment_fee_tax_rate', flat=True).distinct().order_by()
)
tax_rates |= set(
a for a
in OrderPosition.objects.filter(order__event=self.event)
.values_list('tax_rate', flat=True).distinct().order_by()
in OrderPosition.objects.filter(
order__event=self.event
).values_list('tax_rate', flat=True).distinct().order_by()
)
tax_rates = sorted(tax_rates)
return tax_rates
@@ -73,11 +73,10 @@ class OrderListExporter(BaseExporter):
writer.writerow(headers)
provider_names = {}
responses = register_payment_providers.send(self.event)
for rec, response in responses:
provider = response(self.event)
provider_names[provider.identifier] = provider.verbose_name
provider_names = {
k: v.verbose_name
for k, v in self.event.get_payment_providers().items()
}
sum_cache = {
(o['order__id'], o['tax_rate']): o for o in
@@ -101,7 +100,7 @@ class OrderListExporter(BaseExporter):
order.invoice_address.street,
order.invoice_address.zipcode,
order.invoice_address.city,
order.invoice_address.country,
order.invoice_address.country if order.invoice_address.country else order.invoice_address.country_old,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:
@@ -131,6 +130,42 @@ class OrderListExporter(BaseExporter):
return 'orders.csv', 'text/csv', output.getvalue().encode("utf-8")
class QuotaListExporter(BaseExporter):
identifier = 'quotalistcsv'
verbose_name = ugettext_lazy('Quota availabilities (CSV)')
def render(self, form_data: dict):
output = io.StringIO()
writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=",")
headers = [
_('Quota name'), _('Total quota'), _('Paid orders'), _('Pending orders'), _('Blocking vouchers'),
_('Current user\'s carts'), _('Waiting list'), _('Current availability')
]
writer.writerow(headers)
for quota in self.event.quotas.all():
avail = quota.availability()
row = [
quota.name,
_('Infinite') if quota.size is None else quota.size,
quota.count_paid_orders(),
quota.count_pending_orders(),
quota.count_blocking_vouchers(),
quota.count_in_cart(),
quota.count_waiting_list_pending(),
_('Infinite') if avail[1] is None else avail[1]
]
writer.writerow(row)
return 'quotas.csv', 'text/csv', output.getvalue().encode("utf-8")
@receiver(register_data_exporters, dispatch_uid="exporter_orderlist")
def register_orderlist_exporter(sender, **kwargs):
return OrderListExporter
@receiver(register_data_exporters, dispatch_uid="exporter_quotalist")
def register_quotalist_exporter(sender, **kwargs):
return QuotaListExporter

View File

@@ -7,6 +7,7 @@ from django.utils.crypto import get_random_string
from hierarkey.forms import HierarkeyForm
from pretix.base.models import Event
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
from .validators import PlaceholderValidator # NOQA
@@ -56,6 +57,9 @@ class SettingsForm(i18nfield.forms.I18nFormMixin, HierarkeyForm):
kwargs['locales'] = self.locales
kwargs['initial'] = self.obj.settings.freeze()
super().__init__(*args, **kwargs)
for f in self.fields.values():
if isinstance(f, (RelativeDateTimeField, RelativeDateField)):
f.set_event(self.obj)
def get_new_filename(self, name: str) -> str:
nonce = get_random_string(length=8)

View File

@@ -48,6 +48,7 @@ class UserSettingsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['email'].required = True
def clean_old_pw(self):
old_pw = self.cleaned_data.get('old_pw')

411
src/pretix/base/invoice.py Normal file
View File

@@ -0,0 +1,411 @@
from collections import defaultdict
from decimal import Decimal
from io import BytesIO
from typing import Tuple
from django.contrib.staticfiles import finders
from django.dispatch import receiver
from django.utils.formats import date_format, localize
from django.utils.translation import pgettext
from reportlab.lib import pagesizes
from reportlab.lib.styles import ParagraphStyle, StyleSheet1
from reportlab.lib.units import mm
from reportlab.lib.utils import ImageReader
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import (
BaseDocTemplate, Frame, NextPageTemplate, PageTemplate, Paragraph, Spacer,
Table, TableStyle,
)
from pretix.base.models import Event, Invoice
from pretix.base.signals import register_invoice_renderers
class BaseInvoiceRenderer:
"""
This is the base class for all invoice renderers.
"""
def __init__(self, event: Event):
self.event = event
def __str__(self):
return self.identifier
def generate(self, invoice: Invoice) -> Tuple[str, str, str]:
"""
This method should generate the invoice file and return a tuple consisting of a
filename, a file type and file content. The extension will be taken from the filename
which is otherwise ignored.
"""
raise NotImplementedError()
@property
def verbose_name(self) -> str:
"""
A human-readable name for this renderer. This should be short but
self-explanatory. Good examples include 'German DIN 5008' or 'Italian invoice'.
"""
raise NotImplementedError() # NOQA
@property
def identifier(self) -> str:
"""
A short and unique identifier for this renderer.
This should only contain lowercase letters and in most
cases will be the same as your package name.
"""
raise NotImplementedError() # NOQA
class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
"""
This is a convenience class to avoid duplicate code when implementing invoice renderers
that are based on reportlab.
"""
pagesize = pagesizes.A4
left_margin = 25 * mm
right_margin = 20 * mm
top_margin = 20 * mm
bottom_margin = 15 * mm
doc_template_class = BaseDocTemplate
def _init(self):
"""
Initialize the renderer. By default, this registers fonts and sets ``self.stylesheet``.
"""
self.stylesheet = self._get_stylesheet()
self._register_fonts()
def _get_stylesheet(self):
"""
Get a stylesheet. By default, this contains the "Normal" and "Heading1" styles.
"""
stylesheet = StyleSheet1()
stylesheet.add(ParagraphStyle(name='Normal', fontName='OpenSans', fontSize=10, leading=12))
stylesheet.add(ParagraphStyle(name='Heading1', fontName='OpenSansBd', fontSize=15, leading=15 * 1.2))
return stylesheet
def _register_fonts(self):
"""
Register fonts with reportlab. By default, this registers the OpenSans font family
"""
pdfmetrics.registerFont(TTFont('OpenSans', finders.find('fonts/OpenSans-Regular.ttf')))
pdfmetrics.registerFont(TTFont('OpenSansIt', finders.find('fonts/OpenSans-Italic.ttf')))
pdfmetrics.registerFont(TTFont('OpenSansBd', finders.find('fonts/OpenSans-Bold.ttf')))
pdfmetrics.registerFont(TTFont('OpenSansBI', finders.find('fonts/OpenSans-BoldItalic.ttf')))
pdfmetrics.registerFontFamily('OpenSans', normal='OpenSans', bold='OpenSansBd',
italic='OpenSansIt', boldItalic='OpenSansBI')
def _on_other_page(self, canvas: Canvas, doc):
"""
Called when a new page is rendered that is *not* the first page.
"""
pass
def _on_first_page(self, canvas: Canvas, doc):
"""
Called when a new page is rendered that is the first page.
"""
pass
def _get_story(self, doc):
"""
Called to create the story to be inserted into the main frames.
"""
raise NotImplementedError()
def _get_first_page_frames(self, doc):
"""
Called to create a list of frames for the first page.
"""
return [
Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height - 75 * mm,
leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0,
id='normal')
]
def _get_other_page_frames(self, doc):
"""
Called to create a list of frames for the other pages.
"""
return [
Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height,
leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0,
id='normal')
]
def _build_doc(self, fhandle):
"""
Build a PDF document in a given file handle
"""
self._init()
doc = self.doc_template_class(fhandle, pagesize=self.pagesize,
leftMargin=self.left_margin, rightMargin=self.right_margin,
topMargin=self.top_margin, bottomMargin=self.bottom_margin)
doc.addPageTemplates([
PageTemplate(
id='FirstPage',
frames=self._get_first_page_frames(doc),
onPage=self._on_first_page,
pagesize=self.pagesize
),
PageTemplate(
id='OtherPages',
frames=self._get_other_page_frames(doc),
onPage=self._on_other_page,
pagesize=self.pagesize
)
])
story = self._get_story(doc)
doc.build(story)
return doc
def generate(self, invoice: Invoice):
self.invoice = invoice
buffer = BytesIO()
self._build_doc(buffer)
buffer.seek(0)
return 'invoice.pdf', 'application/pdf', buffer.read()
class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
identifier = 'classic'
verbose_name = pgettext('invoice', 'Classic renderer (pretix 1.0)')
def _on_other_page(self, canvas: Canvas, doc):
canvas.saveState()
canvas.setFont('OpenSans', 8)
canvas.drawRightString(self.pagesize[0] - 20 * mm, 10 * mm, pgettext("invoice", "Page %d") % (doc.page,))
for i, line in enumerate(self.invoice.footer_text.split('\n')[::-1]):
canvas.drawCentredString(self.pagesize[0] / 2, 25 + (3.5 * i) * mm, line.strip())
canvas.restoreState()
def _on_first_page(self, canvas: Canvas, doc):
canvas.setCreator('pretix.eu')
canvas.setTitle(pgettext('invoice', 'Invoice {num}').format(num=self.invoice.number))
canvas.saveState()
canvas.setFont('OpenSans', 8)
canvas.drawRightString(self.pagesize[0] - 20 * mm, 10 * mm, pgettext("invoice", "Page %d") % (doc.page,))
for i, line in enumerate(self.invoice.footer_text.split('\n')[::-1]):
canvas.drawCentredString(self.pagesize[0] / 2, 25 + (3.5 * i) * mm, line.strip())
textobject = canvas.beginText(25 * mm, (297 - 15) * mm)
textobject.setFont('OpenSansBd', 8)
textobject.textLine(pgettext('invoice', 'Invoice from').upper())
canvas.drawText(textobject)
p = Paragraph(self.invoice.invoice_from.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p.wrapOn(canvas, 70 * mm, 50 * mm)
p_size = p.wrap(70 * mm, 50 * mm)
p.drawOn(canvas, 25 * mm, (297 - 17) * mm - p_size[1])
textobject = canvas.beginText(25 * mm, (297 - 50) * mm)
textobject.setFont('OpenSansBd', 8)
textobject.textLine(pgettext('invoice', 'Invoice to').upper())
canvas.drawText(textobject)
p = Paragraph(self.invoice.invoice_to.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p.wrapOn(canvas, 85 * mm, 50 * mm)
p_size = p.wrap(85 * mm, 50 * mm)
p.drawOn(canvas, 25 * mm, (297 - 52) * mm - p_size[1])
textobject = canvas.beginText(125 * mm, (297 - 38) * mm)
textobject.setFont('OpenSansBd', 8)
textobject.textLine(pgettext('invoice', 'Order code').upper())
textobject.moveCursor(0, 5)
textobject.setFont('OpenSans', 10)
textobject.textLine(self.invoice.order.full_code)
canvas.drawText(textobject)
textobject = canvas.beginText(125 * mm, (297 - 50) * mm)
textobject.setFont('OpenSansBd', 8)
if self.invoice.is_cancellation:
textobject.textLine(pgettext('invoice', 'Cancellation number').upper())
textobject.moveCursor(0, 5)
textobject.setFont('OpenSans', 10)
textobject.textLine(self.invoice.number)
textobject.moveCursor(0, 5)
textobject.setFont('OpenSansBd', 8)
textobject.textLine(pgettext('invoice', 'Original invoice').upper())
textobject.moveCursor(0, 5)
textobject.setFont('OpenSans', 10)
textobject.textLine(self.invoice.refers.number)
else:
textobject.textLine(pgettext('invoice', 'Invoice number').upper())
textobject.moveCursor(0, 5)
textobject.setFont('OpenSans', 10)
textobject.textLine(self.invoice.number)
textobject.moveCursor(0, 5)
if self.invoice.is_cancellation:
textobject.setFont('OpenSansBd', 8)
textobject.textLine(pgettext('invoice', 'Cancellation date').upper())
textobject.moveCursor(0, 5)
textobject.setFont('OpenSans', 10)
textobject.textLine(date_format(self.invoice.date, "DATE_FORMAT"))
textobject.moveCursor(0, 5)
textobject.setFont('OpenSansBd', 8)
textobject.textLine(pgettext('invoice', 'Original invoice date').upper())
textobject.moveCursor(0, 5)
textobject.setFont('OpenSans', 10)
textobject.textLine(date_format(self.invoice.refers.date, "DATE_FORMAT"))
textobject.moveCursor(0, 5)
else:
textobject.setFont('OpenSansBd', 8)
textobject.textLine(pgettext('invoice', 'Invoice date').upper())
textobject.moveCursor(0, 5)
textobject.setFont('OpenSans', 10)
textobject.textLine(date_format(self.invoice.date, "DATE_FORMAT"))
textobject.moveCursor(0, 5)
canvas.drawText(textobject)
if self.invoice.event.settings.invoice_logo_image:
logo_file = self.invoice.event.settings.get('invoice_logo_image', binary_file=True)
canvas.drawImage(ImageReader(logo_file),
95 * mm, (297 - 38) * mm,
width=25 * mm, height=25 * mm,
preserveAspectRatio=True, anchor='n',
mask='auto')
if self.invoice.event.settings.show_date_to:
p_str = (
str(self.invoice.event.name) + '\n' + pgettext('invoice', '{from_date}\nuntil {to_date}').format(
from_date=self.invoice.event.get_date_from_display(),
to_date=self.invoice.event.get_date_to_display())
)
else:
p_str = (
str(self.invoice.event.name) + '\n' + self.invoice.event.get_date_from_display()
)
p = Paragraph(p_str.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p.wrapOn(canvas, 65 * mm, 50 * mm)
p_size = p.wrap(65 * mm, 50 * mm)
p.drawOn(canvas, 125 * mm, (297 - 17) * mm - p_size[1])
textobject = canvas.beginText(125 * mm, (297 - 15) * mm)
textobject.setFont('OpenSansBd', 8)
textobject.textLine(pgettext('invoice', 'Event').upper())
canvas.drawText(textobject)
canvas.restoreState()
def _get_first_page_frames(self, doc):
footer_length = 3.5 * len(self.invoice.footer_text.split('\n')) * mm
return [
Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height - 75 * mm,
leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=footer_length,
id='normal')
]
def _get_other_page_frames(self, doc):
footer_length = 3.5 * len(self.invoice.footer_text.split('\n')) * mm
return [
Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height,
leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=footer_length,
id='normal')
]
def _get_story(self, doc):
story = [
NextPageTemplate('FirstPage'),
Paragraph(pgettext('invoice', 'Invoice')
if not self.invoice.is_cancellation
else pgettext('invoice', 'Cancellation'),
self.stylesheet['Heading1']),
Spacer(1, 5 * mm),
NextPageTemplate('OtherPages'),
]
if self.invoice.introductory_text:
story.append(Paragraph(self.invoice.introductory_text, self.stylesheet['Normal']))
story.append(Spacer(1, 10 * mm))
taxvalue_map = defaultdict(Decimal)
grossvalue_map = defaultdict(Decimal)
tstyledata = [
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('FONTNAME', (0, 0), (-1, 0), 'OpenSansBd'),
('FONTNAME', (0, -1), (-1, -1), 'OpenSansBd'),
('LEFTPADDING', (0, 0), (0, -1), 0),
('RIGHTPADDING', (-1, 0), (-1, -1), 0),
]
tdata = [(
pgettext('invoice', 'Description'),
pgettext('invoice', 'Tax rate'),
pgettext('invoice', 'Net'),
pgettext('invoice', 'Gross'),
)]
total = Decimal('0.00')
for line in self.invoice.lines.all():
tdata.append((
Paragraph(line.description, self.stylesheet['Normal']),
localize(line.tax_rate) + " %",
localize(line.net_value) + " " + self.invoice.event.currency,
localize(line.gross_value) + " " + self.invoice.event.currency,
))
taxvalue_map[line.tax_rate] += line.tax_value
grossvalue_map[line.tax_rate] += line.gross_value
total += line.gross_value
tdata.append(
[pgettext('invoice', 'Invoice total'), '', '', localize(total) + " " + self.invoice.event.currency])
colwidths = [a * doc.width for a in (.55, .15, .15, .15)]
table = Table(tdata, colWidths=colwidths, repeatRows=1)
table.setStyle(TableStyle(tstyledata))
story.append(table)
story.append(Spacer(1, 15 * mm))
if self.invoice.payment_provider_text:
story.append(Paragraph(self.invoice.payment_provider_text, self.stylesheet['Normal']))
if self.invoice.additional_text:
story.append(Paragraph(self.invoice.additional_text, self.stylesheet['Normal']))
story.append(Spacer(1, 15 * mm))
tstyledata = [
('SPAN', (1, 0), (-1, 0)),
('ALIGN', (2, 1), (-1, -1), 'RIGHT'),
('LEFTPADDING', (0, 0), (0, -1), 0),
('RIGHTPADDING', (-1, 0), (-1, -1), 0),
('FONTSIZE', (0, 0), (-1, -1), 8),
]
tdata = [('', pgettext('invoice', 'Included taxes'), '', '', ''),
('', pgettext('invoice', 'Tax rate'),
pgettext('invoice', 'Net value'), pgettext('invoice', 'Gross value'), pgettext('invoice', 'Tax'))]
for rate, gross in grossvalue_map.items():
if rate == 0:
continue
tax = taxvalue_map[rate]
tdata.append((
'',
localize(rate) + " %",
localize((gross - tax)) + " " + self.invoice.event.currency,
localize(gross) + " " + self.invoice.event.currency,
localize(tax) + " " + self.invoice.event.currency,
))
if len(tdata) > 2:
colwidths = [a * doc.width for a in (.45, .10, .15, .15, .15)]
table = Table(tdata, colWidths=colwidths, repeatRows=2)
table.setStyle(TableStyle(tstyledata))
story.append(table)
return story
@receiver(register_invoice_renderers, dispatch_uid="invoice_renderer_classic")
def recv_classic(sender, **kwargs):
return ClassicInvoiceRenderer

View File

@@ -1,4 +1,5 @@
from collections import OrderedDict
from urllib.parse import urlsplit
import pytz
from django.conf import settings
@@ -13,6 +14,8 @@ from django.utils.translation.trans_real import (
parse_accept_lang_header,
)
from pretix.multidomain.urlreverse import get_domain
_supported = None
@@ -129,8 +132,8 @@ def get_language_from_request(request: HttpRequest) -> str:
return (
get_language_from_user_settings(request)
or get_language_from_session_or_cookie(request)
or get_language_from_event(request)
or get_language_from_browser(request)
or get_language_from_event(request)
or get_default_language()
)
@@ -158,6 +161,9 @@ def _merge_csp(a, b):
class SecurityMiddleware(MiddlewareMixin):
CSP_EXEMPT = (
'/api/v1/docs/',
)
def process_response(self, request, resp):
if settings.DEBUG and resp.status_code >= 400:
@@ -196,5 +202,15 @@ class SecurityMiddleware(MiddlewareMixin):
else:
staticdomain += " " + settings.SITE_URL
dynamicdomain += " " + settings.SITE_URL
resp['Content-Security-Policy'] = _render_csp(h).format(static=staticdomain, dynamic=dynamicdomain)
if hasattr(request, 'organizer') and request.organizer:
domain = get_domain(request.organizer)
if domain:
siteurlsplit = urlsplit(settings.SITE_URL)
if siteurlsplit.port and siteurlsplit.port not in (80, 443):
domain = '%s:%d' % (domain, siteurlsplit.port)
dynamicdomain += " " + domain
if request.path not in self.CSP_EXEMPT:
resp['Content-Security-Policy'] = _render_csp(h).format(static=staticdomain, dynamic=dynamicdomain)
return resp

View File

@@ -0,0 +1,573 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-30 17:47
from __future__ import unicode_literals
import django.core.validators
import django.db.migrations.operations.special
import django.db.models.deletion
import django_countries.fields
import i18nfield.fields
from django.conf import settings
from django.core.cache import cache
from django.db import migrations, models
import pretix.base.models.base
import pretix.base.models.event
import pretix.base.models.invoices
import pretix.base.models.orders
import pretix.base.models.organizer
import pretix.base.validators
def create_teams(apps, schema_editor):
Organizer = apps.get_model('pretixbase', 'Organizer')
Team = apps.get_model('pretixbase', 'Team')
for o in Organizer.objects.prefetch_related('events'):
for e in o.events.all():
teams = {}
for p in e.user_perms.all():
pkey = (p.can_change_settings, p.can_change_items, p.can_view_orders,
p.can_change_permissions, p.can_change_orders, p.can_view_vouchers,
p.can_change_vouchers)
if pkey not in teams:
team = Team()
team.can_change_event_settings = p.can_change_settings
team.can_change_items = p.can_change_items
team.can_view_orders = p.can_view_orders
team.can_change_orders = p.can_change_orders
team.can_view_vouchers = p.can_view_vouchers
team.can_change_vouchers = p.can_change_vouchers
team.organizer = o
team.name = '{} Team {}'.format(
str(e.name), len(teams) + 1
)
team.save()
team.limit_events.add(e)
teams[pkey] = team
if p.user:
teams[pkey].members.add(p.user)
else:
teams[pkey].invites.create(email=p.invite_email, token=p.invite_token)
teams = {}
for p in o.user_perms.all():
pkey = (p.can_create_events, p.can_change_permissions)
if pkey not in teams:
team = Team()
team.can_change_organizer_settings = True
team.can_create_events = p.can_create_events
team.can_change_teams = p.can_change_permissions
team.organizer = o
team.name = '{} Team {}'.format(
str(o.name), len(teams) + 1
)
team.save()
teams[pkey] = team
if p.user:
teams[pkey].members.add(p.user)
else:
teams[pkey].invites.create(email=p.invite_email, token=p.invite_token)
def rename_placeholder(app, schema_editor):
EventSettingsStore = app.get_model('pretixbase', 'Event_SettingsStore')
for setting in EventSettingsStore.objects.all():
if setting.key == 'mail_text_order_placed':
new_value = setting.value.replace('{paymentinfo}', '{payment_info}')
setting.value = new_value
cache.delete('hierarkey_{}_{}'.format('event', setting.object_id))
setting.save()
def fwd69(app, schema_editor):
Event = app.get_model('pretixbase', 'Event')
for e in Event.objects.select_related('organizer').all():
e.invoices.all().update(prefix=e.slug.upper() + '-', organizer=e.organizer)
def fwd70(app, schema_editor):
InvoiceAddress = app.get_model('pretixbase', 'InvoiceAddress')
for ia in InvoiceAddress.objects.all():
if ia.company or ia.vat_id:
ia.is_business = True
ia.save()
class Migration(migrations.Migration):
replaces = [('pretixbase', '0052_team_teaminvite'), ('pretixbase', '0058_auto_20170429_1020'),
('pretixbase', '0059_checkin_nonce'), ('pretixbase', '0060_auto_20170510_1027'),
('pretixbase', '0061_auto_20170521_0942'), ('pretixbase', '0062_auto_20170602_0948'),
('pretixbase', '0063_auto_20170702_1711'), ('pretixbase', '0064_auto_20170703_0912'),
('pretixbase', '0065_auto_20170707_0920'), ('pretixbase', '0066_auto_20170708_2102'),
('pretixbase', '0067_auto_20170712_1610'), ('pretixbase', '0068_subevent_frontpage_text'),
('pretixbase', '0069_invoice_prefix'), ('pretixbase', '0070_auto_20170719_0910')]
dependencies = [
('pretixbase', '0051_auto_20170206_2027_squashed_0057_auto_20170501_2116'),
]
operations = [
migrations.CreateModel(
name='Team',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=190, verbose_name='Team name')),
('all_events',
models.BooleanField(default=False, verbose_name='All events (including newly created ones)')),
('can_create_events', models.BooleanField(default=False, verbose_name='Can create events')),
('can_change_teams', models.BooleanField(default=False, verbose_name='Can change permissions')),
('can_change_organizer_settings',
models.BooleanField(default=False, verbose_name='Can change organizer settings')),
('can_change_event_settings',
models.BooleanField(default=False, verbose_name='Can change event settings')),
('can_change_items', models.BooleanField(default=False, verbose_name='Can change product settings')),
('can_view_orders', models.BooleanField(default=False, verbose_name='Can view orders')),
('can_change_orders', models.BooleanField(default=False, verbose_name='Can change orders')),
('can_view_vouchers', models.BooleanField(default=False, verbose_name='Can view vouchers')),
('can_change_vouchers', models.BooleanField(default=False, verbose_name='Can change vouchers')),
('limit_events', models.ManyToManyField(to='pretixbase.Event', verbose_name='Limit to events')),
('members', models.ManyToManyField(related_name='teams', to=settings.AUTH_USER_MODEL,
verbose_name='Team members')),
('organizer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teams',
to='pretixbase.Organizer')),
],
options={
'verbose_name_plural': 'Teams',
'verbose_name': 'Team',
},
),
migrations.CreateModel(
name='TeamInvite',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('token',
models.CharField(blank=True, default=pretix.base.models.organizer.generate_invite_token, max_length=64,
null=True)),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites',
to='pretixbase.Team')),
],
),
migrations.RunPython(
code=create_teams,
reverse_code=django.db.migrations.operations.special.RunPython.noop,
),
migrations.RemoveField(
model_name='eventpermission',
name='event',
),
migrations.RemoveField(
model_name='eventpermission',
name='user',
),
migrations.RemoveField(
model_name='organizerpermission',
name='organizer',
),
migrations.RemoveField(
model_name='organizerpermission',
name='user',
),
migrations.RemoveField(
model_name='event',
name='permitted',
),
migrations.RemoveField(
model_name='organizer',
name='permitted',
),
migrations.AlterField(
model_name='team',
name='can_change_teams',
field=models.BooleanField(default=False, verbose_name='Can change teams and permissions'),
),
migrations.AlterField(
model_name='team',
name='limit_events',
field=models.ManyToManyField(blank=True, to='pretixbase.Event', verbose_name='Limit to events'),
),
migrations.DeleteModel(
name='EventPermission',
),
migrations.DeleteModel(
name='OrganizerPermission',
),
migrations.AddField(
model_name='checkin',
name='nonce',
field=models.CharField(blank=True, max_length=190, null=True),
),
migrations.AddField(
model_name='event',
name='date_admission',
field=models.DateTimeField(blank=True, null=True, verbose_name='Admission time'),
),
migrations.AlterField(
model_name='event',
name='location',
field=i18nfield.fields.I18nTextField(blank=True, max_length=200, null=True, verbose_name='Location'),
),
migrations.RunPython(
code=rename_placeholder,
reverse_code=django.db.migrations.operations.special.RunPython.noop,
),
migrations.CreateModel(
name='TeamAPIToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=190)),
('active', models.BooleanField(default=True)),
('token', models.CharField(default=pretix.base.models.organizer.generate_api_token, max_length=64)),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens',
to='pretixbase.Team')),
],
),
migrations.AlterModelOptions(
name='voucher',
options={'ordering': ('code',), 'verbose_name': 'Voucher', 'verbose_name_plural': 'Vouchers'},
),
migrations.AddField(
model_name='cartposition',
name='meta_info',
field=models.TextField(blank=True, null=True, verbose_name='Meta information'),
),
migrations.AddField(
model_name='orderposition',
name='meta_info',
field=models.TextField(blank=True, null=True, verbose_name='Meta information'),
),
migrations.AlterField(
model_name='event',
name='currency',
field=models.CharField(choices=[('AED', 'AED - UAE Dirham'), ('AFN', 'AFN - Afghani'), ('ALL', 'ALL - Lek'),
('AMD', 'AMD - Armenian Dram'),
('ANG', 'ANG - Netherlands Antillean Guilder'),
('AOA', 'AOA - Kwanza'), ('ARS', 'ARS - Argentine Peso'),
('AUD', 'AUD - Australian Dollar'), ('AWG', 'AWG - Aruban Florin'),
('AZN', 'AZN - Azerbaijanian Manat'), ('BAM', 'BAM - Convertible Mark'),
('BBD', 'BBD - Barbados Dollar'), ('BDT', 'BDT - Taka'),
('BGN', 'BGN - Bulgarian Lev'), ('BHD', 'BHD - Bahraini Dinar'),
('BIF', 'BIF - Burundi Franc'), ('BMD', 'BMD - Bermudian Dollar'),
('BND', 'BND - Brunei Dollar'), ('BOB', 'BOB - Boliviano'),
('BRL', 'BRL - Brazilian Real'), ('BSD', 'BSD - Bahamian Dollar'),
('BTN', 'BTN - Ngultrum'), ('BWP', 'BWP - Pula'),
('BYN', 'BYN - Belarusian Ruble'), ('BZD', 'BZD - Belize Dollar'),
('CAD', 'CAD - Canadian Dollar'), ('CDF', 'CDF - Congolese Franc'),
('CHF', 'CHF - Swiss Franc'), ('CLP', 'CLP - Chilean Peso'),
('CNY', 'CNY - Yuan Renminbi'), ('COP', 'COP - Colombian Peso'),
('CRC', 'CRC - Costa Rican Colon'), ('CUC', 'CUC - Peso Convertible'),
('CUP', 'CUP - Cuban Peso'), ('CVE', 'CVE - Cabo Verde Escudo'),
('CZK', 'CZK - Czech Koruna'), ('DJF', 'DJF - Djibouti Franc'),
('DKK', 'DKK - Danish Krone'), ('DOP', 'DOP - Dominican Peso'),
('DZD', 'DZD - Algerian Dinar'), ('EGP', 'EGP - Egyptian Pound'),
('ERN', 'ERN - Nakfa'), ('ETB', 'ETB - Ethiopian Birr'),
('EUR', 'EUR - Euro'),
('FJD', 'FJD - Fiji Dollar'), ('FKP', 'FKP - Falkland Islands Pound'),
('GBP', 'GBP - Pound Sterling'), ('GEL', 'GEL - Lari'),
('GHS', 'GHS - Ghana Cedi'), ('GIP', 'GIP - Gibraltar Pound'),
('GMD', 'GMD - Dalasi'), ('GNF', 'GNF - Guinea Franc'),
('GTQ', 'GTQ - Quetzal'), ('GYD', 'GYD - Guyana Dollar'),
('HKD', 'HKD - Hong Kong Dollar'), ('HNL', 'HNL - Lempira'),
('HRK', 'HRK - Kuna'), ('HTG', 'HTG - Gourde'), ('HUF', 'HUF - Forint'),
('IDR', 'IDR - Rupiah'), ('ILS', 'ILS - New Israeli Sheqel'),
('INR', 'INR - Indian Rupee'), ('IQD', 'IQD - Iraqi Dinar'),
('IRR', 'IRR - Iranian Rial'), ('ISK', 'ISK - Iceland Krona'),
('JMD', 'JMD - Jamaican Dollar'), ('JOD', 'JOD - Jordanian Dinar'),
('JPY', 'JPY - Yen'), ('KES', 'KES - Kenyan Shilling'),
('KGS', 'KGS - Som'),
('KHR', 'KHR - Riel'), ('KMF', 'KMF - Comoro Franc'),
('KPW', 'KPW - North Korean Won'), ('KRW', 'KRW - Won'),
('KWD', 'KWD - Kuwaiti Dinar'), ('KYD', 'KYD - Cayman Islands Dollar'),
('KZT', 'KZT - Tenge'), ('LAK', 'LAK - Kip'),
('LBP', 'LBP - Lebanese Pound'),
('LKR', 'LKR - Sri Lanka Rupee'), ('LRD', 'LRD - Liberian Dollar'),
('LSL', 'LSL - Loti'), ('LYD', 'LYD - Libyan Dinar'),
('MAD', 'MAD - Moroccan Dirham'), ('MDL', 'MDL - Moldovan Leu'),
('MGA', 'MGA - Malagasy Ariary'), ('MKD', 'MKD - Denar'),
('MMK', 'MMK - Kyat'),
('MNT', 'MNT - Tugrik'), ('MOP', 'MOP - Pataca'), ('MRO', 'MRO - Ouguiya'),
('MUR', 'MUR - Mauritius Rupee'), ('MVR', 'MVR - Rufiyaa'),
('MWK', 'MWK - Malawi Kwacha'), ('MXN', 'MXN - Mexican Peso'),
('MYR', 'MYR - Malaysian Ringgit'), ('MZN', 'MZN - Mozambique Metical'),
('NAD', 'NAD - Namibia Dollar'), ('NGN', 'NGN - Naira'),
('NIO', 'NIO - Cordoba Oro'), ('NOK', 'NOK - Norwegian Krone'),
('NPR', 'NPR - Nepalese Rupee'), ('NZD', 'NZD - New Zealand Dollar'),
('OMR', 'OMR - Rial Omani'), ('PAB', 'PAB - Balboa'), ('PEN', 'PEN - Sol'),
('PGK', 'PGK - Kina'), ('PHP', 'PHP - Philippine Peso'),
('PKR', 'PKR - Pakistan Rupee'), ('PLN', 'PLN - Zloty'),
('PYG', 'PYG - Guarani'), ('QAR', 'QAR - Qatari Rial'),
('RON', 'RON - Romanian Leu'), ('RSD', 'RSD - Serbian Dinar'),
('RUB', 'RUB - Russian Ruble'), ('RWF', 'RWF - Rwanda Franc'),
('SAR', 'SAR - Saudi Riyal'), ('SBD', 'SBD - Solomon Islands Dollar'),
('SCR', 'SCR - Seychelles Rupee'), ('SDG', 'SDG - Sudanese Pound'),
('SEK', 'SEK - Swedish Krona'), ('SGD', 'SGD - Singapore Dollar'),
('SHP', 'SHP - Saint Helena Pound'), ('SLL', 'SLL - Leone'),
('SOS', 'SOS - Somali Shilling'), ('SRD', 'SRD - Surinam Dollar'),
('SSP', 'SSP - South Sudanese Pound'), ('STD', 'STD - Dobra'),
('SVC', 'SVC - El Salvador Colon'), ('SYP', 'SYP - Syrian Pound'),
('SZL', 'SZL - Lilangeni'), ('THB', 'THB - Baht'), ('TJS', 'TJS - Somoni'),
('TMT', 'TMT - Turkmenistan New Manat'), ('TND', 'TND - Tunisian Dinar'),
('TOP', 'TOP - Paanga'), ('TRY', 'TRY - Turkish Lira'),
('TTD', 'TTD - Trinidad and Tobago Dollar'),
('TWD', 'TWD - New Taiwan Dollar'),
('TZS', 'TZS - Tanzanian Shilling'), ('UAH', 'UAH - Hryvnia'),
('UGX', 'UGX - Uganda Shilling'), ('USD', 'USD - US Dollar'),
('UYU', 'UYU - Peso Uruguayo'), ('UZS', 'UZS - Uzbekistan Sum'),
('VEF', 'VEF - Bolívar'), ('VND', 'VND - Dong'), ('VUV', 'VUV - Vatu'),
('WST', 'WST - Tala'), ('XAF', 'XAF - CFA Franc BEAC'),
('XAG', 'XAG - Silver'),
('XAU', 'XAU - Gold'),
('XBA', 'XBA - Bond Markets Unit European Composite Unit (EURCO)'),
('XBB', 'XBB - Bond Markets Unit European Monetary Unit (E.M.U.-6)'),
('XBC', 'XBC - Bond Markets Unit European Unit of Account 9 (E.U.A.-9)'),
('XBD', 'XBD - Bond Markets Unit European Unit of Account 17 (E.U.A.-17)'),
('XCD', 'XCD - East Caribbean Dollar'),
('XDR', 'XDR - SDR (Special Drawing Right)'),
('XOF', 'XOF - CFA Franc BCEAO'),
('XPD', 'XPD - Palladium'), ('XPF', 'XPF - CFP Franc'),
('XPT', 'XPT - Platinum'), ('XSU', 'XSU - Sucre'),
('XTS', 'XTS - Codes specifically reserved for testing purposes'),
('XUA', 'XUA - ADB Unit of Account'), ('XXX',
'XXX - The codes assigned for transactions where no currency is involved'),
('YER', 'YER - Yemeni Rial'), ('ZAR', 'ZAR - Rand'),
('ZMW', 'ZMW - Zambian Kwacha'), ('ZWL', 'ZWL - Zimbabwe Dollar')],
default='EUR', max_length=10, verbose_name='Default currency'),
),
migrations.AlterField(
model_name='event',
name='slug',
field=models.SlugField(
help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. We recommend some kind of abbreviation or a date with less than 10 characters that can be easily remembered, but you can also choose to use a random value. This will be used in URLs, order codes, invoice numbers, and bank transfer references.',
validators=[django.core.validators.RegexValidator(
message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'),
pretix.base.validators.EventSlugBlacklistValidator()], verbose_name='Short form'),
),
migrations.AlterField(
model_name='invoice',
name='date',
field=models.DateField(default=pretix.base.models.invoices.today),
),
migrations.AlterField(
model_name='item',
name='allow_cancel',
field=models.BooleanField(default=True,
help_text='If this is active and the general event settings allow it, orders containing this product can be canceled by the user until the order is paid for. Users cannot cancel paid orders on their own and you can cancel orders at all times, regardless of this setting',
verbose_name='Allow product to be canceled'),
),
migrations.AddField(
model_name='questionanswer',
name='file',
field=models.FileField(blank=True, null=True, upload_to=pretix.base.models.orders.answerfile_name),
),
migrations.AlterField(
model_name='question',
name='type',
field=models.CharField(
choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No'),
('C', 'Choose one from a list'), ('M', 'Choose multiple from a list'), ('F', 'File upload')],
max_length=5, verbose_name='Question type'),
),
migrations.AddField(
model_name='event',
name='comment',
field=models.TextField(blank=True, null=True, verbose_name='Internal comment'),
),
migrations.AlterField(
model_name='event',
name='presale_end',
field=models.DateTimeField(blank=True, help_text='Optional. No products will be sold after this date.',
null=True, verbose_name='End of presale'),
),
migrations.AlterField(
model_name='event',
name='presale_start',
field=models.DateTimeField(blank=True, help_text='Optional. No products will be sold before this date.',
null=True, verbose_name='Start of presale'),
),
migrations.CreateModel(
name='SubEvent',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('active', models.BooleanField(default=False,
help_text='Only with this checkbox enabled, this sub-event is visible in the frontend to users.',
verbose_name='Active')),
('name', i18nfield.fields.I18nCharField(max_length=200, verbose_name='Name')),
('date_from', models.DateTimeField(verbose_name='Event start time')),
('date_to', models.DateTimeField(blank=True, null=True, verbose_name='Event end time')),
('date_admission', models.DateTimeField(blank=True, null=True, verbose_name='Admission time')),
('presale_end',
models.DateTimeField(blank=True, help_text='No products will be sold after this date.', null=True,
verbose_name='End of presale')),
('presale_start',
models.DateTimeField(blank=True, help_text='No products will be sold before this date.', null=True,
verbose_name='Start of presale')),
(
'location',
i18nfield.fields.I18nTextField(blank=True, max_length=200, null=True, verbose_name='Location')),
],
options={
'verbose_name': 'Sub-Event',
'verbose_name_plural': 'Sub-Events',
'ordering': ('date_from', 'name'),
},
bases=(pretix.base.models.event.EventMixin, models.Model, pretix.base.models.base.LoggingMixin),
),
migrations.CreateModel(
name='SubEventItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Item')),
('subevent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.SubEvent')),
],
),
migrations.CreateModel(
name='SubEventItemVariation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)),
('subevent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.SubEvent')),
(
'variation',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.ItemVariation')),
],
),
migrations.AddField(
model_name='event',
name='has_subevents',
field=models.BooleanField(default=False, verbose_name='Event series'),
),
migrations.AddField(
model_name='subevent',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='subevents',
to='pretixbase.Event'),
),
migrations.AddField(
model_name='subevent',
name='items',
field=models.ManyToManyField(through='pretixbase.SubEventItem', to='pretixbase.Item'),
),
migrations.AddField(
model_name='subevent',
name='variations',
field=models.ManyToManyField(through='pretixbase.SubEventItemVariation', to='pretixbase.ItemVariation'),
),
migrations.AddField(
model_name='cartposition',
name='subevent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
to='pretixbase.SubEvent', verbose_name='Date'),
),
migrations.AddField(
model_name='orderposition',
name='subevent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
to='pretixbase.SubEvent', verbose_name='Date'),
),
migrations.AddField(
model_name='quota',
name='subevent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
related_name='quotas', to='pretixbase.SubEvent', verbose_name='Date'),
),
migrations.AddField(
model_name='voucher',
name='subevent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
to='pretixbase.SubEvent', verbose_name='Date'),
),
migrations.AddField(
model_name='waitinglistentry',
name='subevent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
to='pretixbase.SubEvent', verbose_name='Date'),
),
migrations.AlterModelOptions(
name='subevent',
options={'ordering': ('date_from', 'name'), 'verbose_name': 'Date in event series',
'verbose_name_plural': 'Dates in event series'},
),
migrations.AddField(
model_name='itemaddon',
name='price_included',
field=models.BooleanField(default=False,
help_text='If selected, adding add-ons to this ticket is free, even if the add-ons would normally cost money individually.',
verbose_name='Add-Ons are included in the price'),
),
migrations.AlterField(
model_name='subevent',
name='active',
field=models.BooleanField(default=False,
help_text='Only with this checkbox enabled, this date is visible in the frontend to users.',
verbose_name='Active'),
),
migrations.AlterField(
model_name='subevent',
name='presale_end',
field=models.DateTimeField(blank=True, help_text='Optional. No products will be sold after this date.',
null=True, verbose_name='End of presale'),
),
migrations.AlterField(
model_name='subevent',
name='presale_start',
field=models.DateTimeField(blank=True, help_text='Optional. No products will be sold before this date.',
null=True, verbose_name='Start of presale'),
),
migrations.AddField(
model_name='subevent',
name='frontpage_text',
field=i18nfield.fields.I18nTextField(blank=True, null=True, verbose_name='Frontpage text'),
),
migrations.AddField(
model_name='invoice',
name='prefix',
field=models.CharField(db_index=True, default='', max_length=160),
preserve_default=False,
),
migrations.AddField(
model_name='invoice',
name='organizer',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='invoices',
to='pretixbase.Organizer'),
preserve_default=False,
),
migrations.RunPython(
code=fwd69, reverse_code=django.db.migrations.operations.special.RunPython.noop,
),
migrations.AlterUniqueTogether(
name='invoice',
unique_together=set([('organizer', 'prefix', 'invoice_no')]),
),
migrations.AlterField(
model_name='invoice',
name='organizer',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='invoices',
to='pretixbase.Organizer'),
),
migrations.RenameField(
model_name='invoiceaddress',
old_name='country',
new_name='country_old',
),
migrations.AddField(
model_name='invoiceaddress',
name='country',
field=django_countries.fields.CountryField(default='', max_length=2, verbose_name='Country'),
preserve_default=False,
),
migrations.AddField(
model_name='invoiceaddress',
name='is_business',
field=models.BooleanField(default=False, verbose_name='Business customer'),
),
migrations.RunPython(
code=fwd70, reverse_code=django.db.migrations.operations.special.RunPython.noop,
),
]

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-05-21 09:42
from __future__ import unicode_literals
from django.core.cache import cache
from django.db import migrations
def rename_placeholder(app, schema_editor):
EventSettingsStore = app.get_model('pretixbase', 'Event_SettingsStore')
for setting in EventSettingsStore.objects.all():
if setting.key == 'mail_text_order_placed':
new_value = setting.value.replace('{paymentinfo}', '{payment_info}')
setting.value = new_value
cache.delete('hierarkey_{}_{}'.format('event', setting.object_id))
setting.save()
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0060_auto_20170510_1027'),
]
operations = [
migrations.RunPython(rename_placeholder, migrations.RunPython.noop)
]

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-02 09:48
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
import pretix.base.models.organizer
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0061_auto_20170521_0942'),
]
operations = [
migrations.CreateModel(
name='TeamAPIToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=190)),
('active', models.BooleanField(default=True)),
('token', models.CharField(default=pretix.base.models.organizer.generate_api_token, max_length=64)),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to='pretixbase.Team')),
],
),
]

File diff suppressed because one or more lines are too long

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